feat(接口定义): 生成随机数据

This commit is contained in:
fit2-zhao 2021-09-07 14:31:19 +08:00 committed by fit2-zhao
parent 4dcd789ac5
commit 3f6e67bb26
10 changed files with 349 additions and 301 deletions

View File

@ -440,13 +440,23 @@
<artifactId>xmindjbehaveplugin</artifactId> <artifactId>xmindjbehaveplugin</artifactId>
<version>0.8</version> <version>0.8</version>
</dependency> </dependency>
<!-- 基础包 -->
<dependency> <dependency>
<groupId>io.metersphere</groupId> <groupId>io.metersphere</groupId>
<artifactId>metersphere-plugin-core</artifactId> <artifactId>metersphere-plugin-core</artifactId>
<version>1.0</version> <version>1.0</version>
</dependency> </dependency>
<!--随机数据生成API-->
<dependency>
<groupId>com.apifan.common</groupId>
<artifactId>common-random</artifactId>
<version>1.0.7</version>
</dependency>
<dependency>
<groupId>com.github.mifmif</groupId>
<artifactId>generex</artifactId>
<version>1.0.2</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -16,17 +16,15 @@ public class JSONSchemaGenerator {
} }
private static void analyzeSchema(String json, JSONObject rootObj) { private static void analyzeSchema(String json, JSONObject rootObj) {
// Let's start with the root element of the file
JsonObject rootElement = null;
try { try {
JsonParser jsonParser = new JsonParser(); Gson gson = new Gson();
JsonElement inputElement = jsonParser.parse(json); JsonElement element = gson.fromJson(json, JsonElement.class);
rootElement = inputElement.getAsJsonObject(); JsonObject rootElement = element.getAsJsonObject();
analyzeRootSchemaElement(rootElement, rootObj);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
analyzeRootSchemaElement(rootElement, rootObj);
} }
private static void analyzeRootSchemaElement(JsonObject rootElement, JSONObject rootObj) { private static void analyzeRootSchemaElement(JsonObject rootElement, JSONObject rootObj) {
@ -232,7 +230,7 @@ public class JSONSchemaGenerator {
String value = ScriptEngineUtils.buildFunctionCallString(itemsObject.get("mock").getAsJsonObject().get("mock").getAsString()); String value = ScriptEngineUtils.buildFunctionCallString(itemsObject.get("mock").getAsJsonObject().get("mock").getAsString());
array.add(value); array.add(value);
} else { } else {
array.add(null); array.add("");
} }
} else if (itemsObject.has("type") && itemsObject.get("type").getAsString().equals("number")) { } else if (itemsObject.has("type") && itemsObject.get("type").getAsString().equals("number")) {
if (itemsObject.has("default")) { if (itemsObject.has("default")) {

View File

@ -126,6 +126,18 @@ export default {
codeEditActive: true codeEditActive: true
}; };
}, },
watch:{
'body.raw'(){
if(this.body.format !== 'JSON-SCHEMA' && this.body.raw){
try {
const MsConvert = new Convert();
this.body.jsonSchema = MsConvert.format(JSON.parse(this.body.raw));
}catch (ex){
this.body.jsonSchema = "";
}
}
}
},
methods: { methods: {
reloadCodeEdit() { reloadCodeEdit() {
this.codeEditActive = false; this.codeEditActive = false;
@ -136,7 +148,7 @@ export default {
formatChange() { formatChange() {
const MsConvert = new Convert(); const MsConvert = new Convert();
if (this.body.format === 'JSON-SCHEMA') { if (this.body.format === 'JSON-SCHEMA') {
if (this.body.raw) { if (this.body.raw && !this.body.jsonSchema) {
this.body.jsonSchema = MsConvert.format(JSON.parse(this.body.raw)); this.body.jsonSchema = MsConvert.format(JSON.parse(this.body.raw));
} }
} else { } else {

View File

@ -8,14 +8,14 @@
<!-- 请求头--> <!-- 请求头-->
<el-tab-pane :label="$t('api_test.request.headers')" name="headers"> <el-tab-pane :label="$t('api_test.request.headers')" name="headers">
<el-tooltip class="item-tabs" effect="dark" :content="$t('api_test.request.headers')" placement="top-start" slot="label"> <el-tooltip class="item-tabs" effect="dark" :content="$t('api_test.request.headers')" placement="top-start" slot="label">
<span>{{$t('api_test.request.headers')}} <span>{{ $t('api_test.request.headers') }}
<div class="el-step__icon is-text ms-api-col ms-header" v-if="headers.length>1"> <div class="el-step__icon is-text ms-api-col ms-header" v-if="headers.length>1">
<div class="el-step__icon-inner">{{headers.length-1}}</div> <div class="el-step__icon-inner">{{ headers.length - 1 }}</div>
</div> </div>
</span> </span>
</el-tooltip> </el-tooltip>
<el-row> <el-row>
<el-link class="ms-el-link" @click="batchAdd" style="color: #783887"> {{$t("commons.batch_add")}}</el-link> <el-link class="ms-el-link" @click="batchAdd" style="color: #783887"> {{ $t("commons.batch_add") }}</el-link>
</el-row> </el-row>
<ms-api-key-value :is-read-only="isReadOnly" :isShowEnable="isShowEnable" :suggestions="headerSuggestions" :items="headers" :need-mock="true"/> <ms-api-key-value :is-read-only="isReadOnly" :isShowEnable="isShowEnable" :suggestions="headerSuggestions" :items="headers" :need-mock="true"/>
</el-tab-pane> </el-tab-pane>
@ -23,13 +23,13 @@
<!--query 参数--> <!--query 参数-->
<el-tab-pane :label="$t('api_test.definition.request.query_param')" name="parameters"> <el-tab-pane :label="$t('api_test.definition.request.query_param')" name="parameters">
<el-tooltip class="item-tabs" effect="dark" :content="$t('api_test.definition.request.query_info')" placement="top-start" slot="label"> <el-tooltip class="item-tabs" effect="dark" :content="$t('api_test.definition.request.query_info')" placement="top-start" slot="label">
<span>{{$t('api_test.definition.request.query_param')}} <span>{{ $t('api_test.definition.request.query_param') }}
<div class="el-step__icon is-text ms-api-col ms-header" v-if="request.arguments.length>1"> <div class="el-step__icon is-text ms-api-col ms-header" v-if="request.arguments.length>1">
<div class="el-step__icon-inner">{{request.arguments.length-1}}</div> <div class="el-step__icon-inner">{{ request.arguments.length - 1 }}</div>
</div></span> </div></span>
</el-tooltip> </el-tooltip>
<el-row> <el-row>
<el-link class="ms-el-link" @click="batchAdd" style="color: #783887"> {{$t("commons.batch_add")}}</el-link> <el-link class="ms-el-link" @click="batchAdd" style="color: #783887"> {{ $t("commons.batch_add") }}</el-link>
</el-row> </el-row>
<ms-api-variable :with-mor-setting="true" :is-read-only="isReadOnly" :isShowEnable="isShowEnable" :parameters="request.arguments"/> <ms-api-variable :with-mor-setting="true" :is-read-only="isReadOnly" :isShowEnable="isShowEnable" :parameters="request.arguments"/>
</el-tab-pane> </el-tab-pane>
@ -38,14 +38,14 @@
<el-tab-pane :label="$t('api_test.definition.request.rest_param')" name="rest"> <el-tab-pane :label="$t('api_test.definition.request.rest_param')" name="rest">
<el-tooltip class="item-tabs" effect="dark" :content="$t('api_test.definition.request.rest_info')" placement="top-start" slot="label"> <el-tooltip class="item-tabs" effect="dark" :content="$t('api_test.definition.request.rest_info')" placement="top-start" slot="label">
<span> <span>
{{$t('api_test.definition.request.rest_param')}} {{ $t('api_test.definition.request.rest_param') }}
<div class="el-step__icon is-text ms-api-col ms-header" v-if="request.rest.length>1"> <div class="el-step__icon is-text ms-api-col ms-header" v-if="request.rest.length>1">
<div class="el-step__icon-inner">{{request.rest.length-1}}</div> <div class="el-step__icon-inner">{{ request.rest.length - 1 }}</div>
</div> </div>
</span> </span>
</el-tooltip> </el-tooltip>
<el-row> <el-row>
<el-link class="ms-el-link" @click="batchAdd" style="color: #783887"> {{$t("commons.batch_add")}}</el-link> <el-link class="ms-el-link" @click="batchAdd" style="color: #783887"> {{ $t("commons.batch_add") }}</el-link>
</el-row> </el-row>
<ms-api-variable :with-mor-setting="true" :is-read-only="isReadOnly" :isShowEnable="isShowEnable" :parameters="request.rest"/> <ms-api-variable :with-mor-setting="true" :is-read-only="isReadOnly" :isShowEnable="isShowEnable" :parameters="request.rest"/>
</el-tab-pane> </el-tab-pane>
@ -58,7 +58,7 @@
<!-- 认证配置 --> <!-- 认证配置 -->
<el-tab-pane :label="$t('api_test.definition.request.auth_config')" name="authConfig"> <el-tab-pane :label="$t('api_test.definition.request.auth_config')" name="authConfig">
<el-tooltip class="item-tabs" effect="dark" :content="$t('api_test.definition.request.auth_config_info')" placement="top-start" slot="label"> <el-tooltip class="item-tabs" effect="dark" :content="$t('api_test.definition.request.auth_config_info')" placement="top-start" slot="label">
<span>{{$t('api_test.definition.request.auth_config')}}</span> <span>{{ $t('api_test.definition.request.auth_config') }}</span>
</el-tooltip> </el-tooltip>
<ms-api-auth-config :is-read-only="isReadOnly" :request="request"/> <ms-api-auth-config :is-read-only="isReadOnly" :request="request"/>
@ -67,11 +67,11 @@
<el-tab-pane :label="$t('api_test.definition.request.other_config')" name="advancedConfig"> <el-tab-pane :label="$t('api_test.definition.request.other_config')" name="advancedConfig">
<ms-api-advanced-config :is-read-only="isReadOnly" :request="request"/> <ms-api-advanced-config :is-read-only="isReadOnly" :request="request"/>
</el-tab-pane> </el-tab-pane>
<!-- <el-tab-pane name="create" v-if="hasPermission('PROJECT_API_DEFINITION:READ+CREATE_API')">--> <el-tab-pane name="create" v-if="hasPermission('PROJECT_API_DEFINITION:READ+CREATE_API')">
<!-- <template v-slot:label>--> <template v-slot:label>
<!-- <el-button size="mini" type="primary" @click.stop @click="createTestData">生成测试数据</el-button>--> <el-button size="mini" type="primary" @click.stop @click="createTestData">生成测试数据</el-button>
<!-- </template>--> </template>
<!-- </el-tab-pane>--> </el-tab-pane>
</el-tabs> </el-tabs>
</div> </div>
@ -84,23 +84,23 @@
</template> </template>
<script> <script>
import MsApiKeyValue from "../../ApiKeyValue"; import MsApiKeyValue from "../../ApiKeyValue";
import MsApiBody from "../../body/ApiBody"; import MsApiBody from "../../body/ApiBody";
import MsApiAuthConfig from "../../auth/ApiAuthConfig"; import MsApiAuthConfig from "../../auth/ApiAuthConfig";
import ApiRequestMethodSelect from "../../collapse/ApiRequestMethodSelect"; import ApiRequestMethodSelect from "../../collapse/ApiRequestMethodSelect";
import {REQUEST_HEADERS} from "@/common/js/constants"; import {REQUEST_HEADERS} from "@/common/js/constants";
import MsApiVariable from "../../ApiVariable"; import MsApiVariable from "../../ApiVariable";
import MsApiAssertions from "../../assertion/ApiAssertions"; import MsApiAssertions from "../../assertion/ApiAssertions";
import MsApiExtract from "../../extract/ApiExtract"; import MsApiExtract from "../../extract/ApiExtract";
import {Body, KeyValue} from "../../../model/ApiTestModel"; import {Body, KeyValue} from "../../../model/ApiTestModel";
import {getUUID} from "@/common/js/utils"; import {getCurrentProjectID, getUUID} from "@/common/js/utils";
import BatchAddParameter from "../../basis/BatchAddParameter"; import BatchAddParameter from "../../basis/BatchAddParameter";
import MsApiAdvancedConfig from "./ApiAdvancedConfig"; import MsApiAdvancedConfig from "./ApiAdvancedConfig";
import MsJsr233Processor from "../../../../automation/scenario/component/Jsr233Processor"; import MsJsr233Processor from "../../../../automation/scenario/component/Jsr233Processor";
import ApiDefinitionStepButton from "../components/ApiDefinitionStepButton"; import ApiDefinitionStepButton from "../components/ApiDefinitionStepButton";
import {hasPermission} from '@/common/js/utils'; import {hasPermission} from '@/common/js/utils';
export default { export default {
name: "MsApiHttpRequestForm", name: "MsApiHttpRequestForm",
components: { components: {
ApiDefinitionStepButton, ApiDefinitionStepButton,
@ -168,12 +168,12 @@
headerSuggestions: REQUEST_HEADERS, headerSuggestions: REQUEST_HEADERS,
isReloadData: false, isReloadData: false,
isBodyShow: true, isBodyShow: true,
dialogVisible: false, dialogVisible: false
} }
}, },
created() { created() {
if(!this.referenced && this.showScript){ if (!this.referenced && this.showScript) {
this.spanCount = 21; this.spanCount = 21;
} else { } else {
this.spanCount = 24; this.spanCount = 24;
@ -183,8 +183,13 @@
methods: { methods: {
hasPermission, hasPermission,
createTestData(){ createTestData() {
this.$post('/api/test/data/generator', this.request.body.jsonSchema, response => {
if (this.request.body.format !== 'JSON-SCHEMA') {
this.request.body.raw = response.data;
this.reloadBody();
}
});
}, },
remove(row) { remove(row) {
let index = this.request.hashTree.indexOf(row); let index = this.request.hashTree.indexOf(row);
@ -258,32 +263,32 @@
} }
} }
} }
} }
</script> </script>
<style scoped> <style scoped>
.ms-query { .ms-query {
background: #783887; background: #783887;
color: white; color: white;
height: 18px; height: 18px;
border-radius: 42%; border-radius: 42%;
} }
.ms-header { .ms-header {
background: #783887; background: #783887;
color: white; color: white;
height: 18px; height: 18px;
border-radius: 42%; border-radius: 42%;
} }
.request-tabs { .request-tabs {
margin: 20px; margin: 20px;
min-height: 200px; min-height: 200px;
} }
.ms-el-link { .ms-el-link {
float: right; float: right;
margin-right: 45px; margin-right: 45px;
} }
</style> </style>

View File

@ -50,11 +50,10 @@
<el-dialog append-to-body :close-on-click-modal="false" :title="$t('schema.adv_setting')" :visible.sync="modalVisible" :destroy-on-close="true" <el-dialog append-to-body :close-on-click-modal="false" :title="$t('schema.adv_setting')" :visible.sync="modalVisible" :destroy-on-close="true"
@close="handleClose"> @close="handleClose">
<p class="tip">基础设置 </p> <p class="tip">基础设置 </p>
<el-form :inline="true" v-model="advancedValue" class="ms-advanced-search-form">
<el-row :gutter="6"> <el-form label-position="left" label-width="100px" v-model="advancedValue" class="ms-advanced-search-form">
<el-col :span="8" v-for="(item,key) in advancedValue" :key="key" style="float: right"> <div :span="8" v-for="(item,key) in advancedValue" :key="key">
<el-form-item> <el-form-item :label="$t('schema.'+key)">
<div>{{ $t('schema.'+key)}}</div>
<el-input-number v-model="advancedValue[key]" v-if="advancedAttr[key].type === 'integer'" style="width:100%" :placeholder="key" size="small"/> <el-input-number v-model="advancedValue[key]" v-if="advancedAttr[key].type === 'integer'" style="width:100%" :placeholder="key" size="small"/>
<el-input-number v-model="advancedValue[key]" v-else-if="advancedAttr[key].type === 'number'" style="width:100%" :placeholder="key" size="small"/> <el-input-number v-model="advancedValue[key]" v-else-if="advancedAttr[key].type === 'number'" style="width:100%" :placeholder="key" size="small"/>
<span v-else-if="advancedAttr[key].type === 'boolean'" style="display:inline-block;width:100%"> <span v-else-if="advancedAttr[key].type === 'boolean'" style="display:inline-block;width:100%">
@ -64,10 +63,14 @@
<el-option value="" :label="$t('schema.nothing')"></el-option> <el-option value="" :label="$t('schema.nothing')"></el-option>
<el-option :key="t" :value="t" :label="t" v-for="t in advancedAttr[key].enums"/> <el-option :key="t" :value="t" :label="t" v-for="t in advancedAttr[key].enums"/>
</el-select> </el-select>
<el-input v-else-if="advancedAttr[key].type === 'textarea'" :placeholder="advancedAttr[key].description" v-model="advancedValue[key]"
type="textarea"
:autosize="{ minRows: 2, maxRows: 10}"
:rows="2"></el-input>
<el-input v-model="advancedValue[key]" v-else style="width:100%;" :placeholder="key" size="small"/> <el-input v-model="advancedValue[key]" v-else style="width:100%;" :placeholder="key" size="small"/>
</el-form-item> </el-form-item>
</el-col> </div>
</el-row>
</el-form> </el-form>
<!--<h3 v-text="$t('schema.add_custom')" v-show="custom">添加自定义属性</h3> <!--<h3 v-text="$t('schema.add_custom')" v-show="custom">添加自定义属性</h3>
<el-form class="ms-advanced-search-form" v-show="custom"> <el-form class="ms-advanced-search-form" v-show="custom">

View File

@ -1,8 +1,7 @@
const value = { const value = {
description: null,
minItems:null, minItems:null,
maxItems:null, maxItems:null,
uniqueItems:false description: null
} }
const attr = { const attr = {
description: { description: {

View File

@ -1,31 +1,40 @@
const value = { const value = {
description: null,
maximum: null, maximum: null,
minimum: null, minimum: null,
exclusiveMaximum:null, default: null,
exclusiveMinimum:null enum: null,
description: null,
} }
const attr = { const attr = {
description: { description: {
name: '描述', name: '描述',
type: 'string', type: 'string',
}, },
maximum:{ maximum: {
name:'最大值', name: '最大值',
type:'integer' type: 'integer'
}, },
minimum:{ minimum: {
name:'最小值', name: '最小值',
type:'integer' type: 'integer'
}, },
exclusiveMaximum:{ exclusiveMaximum: {
name:'不包含最大值', name: '不包含最大值',
type:'boolean' type: 'boolean'
},
exclusiveMinimum: {
name: '不包含最小值',
type: 'boolean'
},
default: {
name: '默认值',
type: 'integer',
},
enum: {
name: '枚举值',
type: 'textarea',
description: "一行一个枚举值"
}, },
exclusiveMinimum:{
name:'不包含最小值',
type:'boolean'
}
} }
const wrapper = {value, attr} const wrapper = {value, attr}
export default wrapper export default wrapper

View File

@ -1,31 +1,32 @@
const value = { const value = {
description: null,
maximum: null, maximum: null,
minimum: null, minimum: null,
exclusiveMaximum:null, default: null,
exclusiveMinimum:null enum: null,
description: null,
} }
const attr = { const attr = {
description: { description: {
name: '描述', name: '描述',
type: 'string', type: 'string',
}, },
maximum:{ maximum: {
name:'最大值', name: '最大值',
type:'number' type: 'number'
}, },
minimum:{ minimum: {
name:'最小值', name: '最小值',
type:'number' type: 'number'
}, },
exclusiveMaximum:{ default: {
name:'不包含最大值', name: '默认值',
type:'boolean' type: 'string',
},
enum: {
name: '枚举值',
type: 'textarea',
description: "一行一个枚举值"
}, },
exclusiveMinimum:{
name:'不包含最小值',
type:'boolean'
}
} }
const wrapper = {value, attr} const wrapper = {value, attr}
export default wrapper export default wrapper

View File

@ -1,31 +1,42 @@
const value = { const value = {
description: null,
maxLength: null, maxLength: null,
minLength: null, minLength: null,
default: null,
enum: null,
pattern: null, pattern: null,
format:null format: null,
description: null,
} }
const attr = { const attr = {
description: { maxLength: {
name: '描述', name: '最大字符数',
type: 'integer'
},
minLength: {
name: '最小字符数',
type: 'integer'
},
default: {
name: '默认值',
type: 'string', type: 'string',
}, },
maxLength:{ enum: {
name:'最大字符数', name: '枚举值',
type:'integer' type: 'textarea',
}, description:"一行一个枚举值"
minLength:{
name:'最小字符数',
type:'integer'
}, },
pattern: { pattern: {
name: '正则表达式', name: '正则表达式',
type:'string' type: 'string'
}, },
format: { format: {
name:'格式', name: '格式',
type:'array', type: 'array',
enums:['date','date-time','email','hostname','ipv4','ipv6','uri'] enums: ['date', 'date-time', 'email', 'hostname', 'ipv4', 'ipv6', 'uri']
},
description: {
name: '描述',
type: 'string',
} }
} }
const wrapper = {value, attr} const wrapper = {value, attr}

View File

@ -1929,9 +1929,9 @@ export default {
cancel: "取消", cancel: "取消",
minLength: "最小长度", minLength: "最小长度",
maxLength: "最大长度", maxLength: "最大长度",
pattern: "正则表达式约束字符串", pattern: "正则表达式",
exclusiveMinimum: "开启后,数据必须大于最小值", exclusiveMinimum: "数据必须大于最小值",
exclusiveMaximum: "开启后,数据必须小于最大值", exclusiveMaximum: "数据必须小于最大值",
minimum: "最小值", minimum: "最小值",
maximum: "最大值", maximum: "最大值",
uniqueItems: "开启后,每个元素都不相同", uniqueItems: "开启后,每个元素都不相同",