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>
<version>0.8</version>
</dependency>
<!-- 基础包 -->
<dependency>
<groupId>io.metersphere</groupId>
<artifactId>metersphere-plugin-core</artifactId>
<version>1.0</version>
</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>
<build>

View File

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

View File

@ -126,6 +126,18 @@ export default {
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: {
reloadCodeEdit() {
this.codeEditActive = false;
@ -136,7 +148,7 @@ export default {
formatChange() {
const MsConvert = new Convert();
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));
}
} else {

View File

@ -8,14 +8,14 @@
<!-- 请求头-->
<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">
<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-inner">{{headers.length-1}}</div>
<div class="el-step__icon-inner">{{ headers.length - 1 }}</div>
</div>
</span>
</el-tooltip>
<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>
<ms-api-key-value :is-read-only="isReadOnly" :isShowEnable="isShowEnable" :suggestions="headerSuggestions" :items="headers" :need-mock="true"/>
</el-tab-pane>
@ -23,13 +23,13 @@
<!--query 参数-->
<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">
<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-inner">{{request.arguments.length-1}}</div>
<div class="el-step__icon-inner">{{ request.arguments.length - 1 }}</div>
</div></span>
</el-tooltip>
<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>
<ms-api-variable :with-mor-setting="true" :is-read-only="isReadOnly" :isShowEnable="isShowEnable" :parameters="request.arguments"/>
</el-tab-pane>
@ -38,14 +38,14 @@
<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">
<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-inner">{{request.rest.length-1}}</div>
<div class="el-step__icon-inner">{{ request.rest.length - 1 }}</div>
</div>
</span>
</el-tooltip>
<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>
<ms-api-variable :with-mor-setting="true" :is-read-only="isReadOnly" :isShowEnable="isShowEnable" :parameters="request.rest"/>
</el-tab-pane>
@ -58,7 +58,7 @@
<!-- 认证配置 -->
<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">
<span>{{$t('api_test.definition.request.auth_config')}}</span>
<span>{{ $t('api_test.definition.request.auth_config') }}</span>
</el-tooltip>
<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">
<ms-api-advanced-config :is-read-only="isReadOnly" :request="request"/>
</el-tab-pane>
<!-- <el-tab-pane name="create" v-if="hasPermission('PROJECT_API_DEFINITION:READ+CREATE_API')">-->
<!-- <template v-slot:label>-->
<!-- <el-button size="mini" type="primary" @click.stop @click="createTestData">生成测试数据</el-button>-->
<!-- </template>-->
<!-- </el-tab-pane>-->
<el-tab-pane name="create" v-if="hasPermission('PROJECT_API_DEFINITION:READ+CREATE_API')">
<template v-slot:label>
<el-button size="mini" type="primary" @click.stop @click="createTestData">生成测试数据</el-button>
</template>
</el-tab-pane>
</el-tabs>
</div>
@ -84,206 +84,211 @@
</template>
<script>
import MsApiKeyValue from "../../ApiKeyValue";
import MsApiBody from "../../body/ApiBody";
import MsApiAuthConfig from "../../auth/ApiAuthConfig";
import ApiRequestMethodSelect from "../../collapse/ApiRequestMethodSelect";
import {REQUEST_HEADERS} from "@/common/js/constants";
import MsApiVariable from "../../ApiVariable";
import MsApiAssertions from "../../assertion/ApiAssertions";
import MsApiExtract from "../../extract/ApiExtract";
import {Body, KeyValue} from "../../../model/ApiTestModel";
import {getUUID} from "@/common/js/utils";
import BatchAddParameter from "../../basis/BatchAddParameter";
import MsApiAdvancedConfig from "./ApiAdvancedConfig";
import MsJsr233Processor from "../../../../automation/scenario/component/Jsr233Processor";
import ApiDefinitionStepButton from "../components/ApiDefinitionStepButton";
import {hasPermission} from '@/common/js/utils';
import MsApiKeyValue from "../../ApiKeyValue";
import MsApiBody from "../../body/ApiBody";
import MsApiAuthConfig from "../../auth/ApiAuthConfig";
import ApiRequestMethodSelect from "../../collapse/ApiRequestMethodSelect";
import {REQUEST_HEADERS} from "@/common/js/constants";
import MsApiVariable from "../../ApiVariable";
import MsApiAssertions from "../../assertion/ApiAssertions";
import MsApiExtract from "../../extract/ApiExtract";
import {Body, KeyValue} from "../../../model/ApiTestModel";
import {getCurrentProjectID, getUUID} from "@/common/js/utils";
import BatchAddParameter from "../../basis/BatchAddParameter";
import MsApiAdvancedConfig from "./ApiAdvancedConfig";
import MsJsr233Processor from "../../../../automation/scenario/component/Jsr233Processor";
import ApiDefinitionStepButton from "../components/ApiDefinitionStepButton";
import {hasPermission} from '@/common/js/utils';
export default {
name: "MsApiHttpRequestForm",
components: {
ApiDefinitionStepButton,
MsJsr233Processor,
MsApiAdvancedConfig,
BatchAddParameter,
MsApiVariable,
ApiRequestMethodSelect,
MsApiExtract,
MsApiAuthConfig,
MsApiBody,
MsApiKeyValue,
MsApiAssertions
export default {
name: "MsApiHttpRequestForm",
components: {
ApiDefinitionStepButton,
MsJsr233Processor,
MsApiAdvancedConfig,
BatchAddParameter,
MsApiVariable,
ApiRequestMethodSelect,
MsApiExtract,
MsApiAuthConfig,
MsApiBody,
MsApiKeyValue,
MsApiAssertions
},
props: {
method: String,
request: {},
response: {},
showScript: {
type: Boolean,
default: true,
},
props: {
method: String,
request: {},
response: {},
showScript: {
type: Boolean,
default: true,
},
headers: {
type: Array,
default() {
return [];
}
},
referenced: {
type: Boolean,
default: false,
},
isShowEnable: Boolean,
jsonPathList: Array,
isReadOnly: {
type: Boolean,
default: false
},
type: String,
},
data() {
let validateURL = (rule, value, callback) => {
try {
new URL(this.addProtocol(this.request.url));
} catch (e) {
callback(this.$t('api_test.request.url_invalid'));
}
};
return {
activeName: this.request.method === "POST" ? "body" : "parameters",
rules: {
name: [
{max: 300, message: this.$t('commons.input_limit', [1, 300]), trigger: 'blur'}
],
url: [
{max: 500, required: true, message: this.$t('commons.input_limit', [1, 500]), trigger: 'blur'},
{validator: validateURL, trigger: 'blur'}
],
path: [
{max: 500, message: this.$t('commons.input_limit', [0, 500]), trigger: 'blur'},
]
},
spanCount: 21,
headerSuggestions: REQUEST_HEADERS,
isReloadData: false,
isBodyShow: true,
dialogVisible: false,
headers: {
type: Array,
default() {
return [];
}
},
created() {
if(!this.referenced && this.showScript){
this.spanCount = 21;
} else {
this.spanCount = 24;
}
this.init();
referenced: {
type: Boolean,
default: false,
},
isShowEnable: Boolean,
jsonPathList: Array,
isReadOnly: {
type: Boolean,
default: false
},
type: String,
},
methods: {
hasPermission,
createTestData(){
data() {
let validateURL = (rule, value, callback) => {
try {
new URL(this.addProtocol(this.request.url));
} catch (e) {
callback(this.$t('api_test.request.url_invalid'));
}
};
return {
activeName: this.request.method === "POST" ? "body" : "parameters",
rules: {
name: [
{max: 300, message: this.$t('commons.input_limit', [1, 300]), trigger: 'blur'}
],
url: [
{max: 500, required: true, message: this.$t('commons.input_limit', [1, 500]), trigger: 'blur'},
{validator: validateURL, trigger: 'blur'}
],
path: [
{max: 500, message: this.$t('commons.input_limit', [0, 500]), trigger: 'blur'},
]
},
spanCount: 21,
headerSuggestions: REQUEST_HEADERS,
isReloadData: false,
isBodyShow: true,
dialogVisible: false
}
},
},
remove(row) {
let index = this.request.hashTree.indexOf(row);
this.request.hashTree.splice(index, 1);
this.reload();
},
copyRow(row) {
let obj = JSON.parse(JSON.stringify(row));
obj.id = getUUID();
this.request.hashTree.push(obj);
this.reload();
},
reload() {
this.isReloadData = true
this.$nextTick(() => {
this.isReloadData = false
created() {
if (!this.referenced && this.showScript) {
this.spanCount = 21;
} else {
this.spanCount = 24;
}
this.init();
},
methods: {
hasPermission,
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) {
let index = this.request.hashTree.indexOf(row);
this.request.hashTree.splice(index, 1);
this.reload();
},
copyRow(row) {
let obj = JSON.parse(JSON.stringify(row));
obj.id = getUUID();
this.request.hashTree.push(obj);
this.reload();
},
reload() {
this.isReloadData = true
this.$nextTick(() => {
this.isReloadData = false
})
},
init() {
if (!this.request.body) {
this.request.body = new Body();
}
if (!this.request.body.kvs) {
this.request.body.kvs = [];
}
if (!this.request.rest) {
this.request.rest = [];
}
if (!this.request.arguments) {
this.request.arguments = [];
}
},
// body
reloadBody() {
this.isBodyShow = false;
this.$nextTick(() => {
this.isBodyShow = true;
});
},
batchAdd() {
this.$refs.batchAddParameter.open();
},
batchSave(data) {
if (data) {
let params = data.split("\n");
let keyValues = [];
params.forEach(item => {
let line = item.split(/|,/);
let required = false;
if (line[1] === '必填' || line[1] === 'Required' || line[1] === 'true') {
required = true;
}
keyValues.push(new KeyValue({name: line[0], required: required, value: line[2], description: line[3], type: "text", valid: false, file: false, encode: true, enable: true, contentType: "text/plain"}));
})
keyValues.forEach(item => {
switch (this.activeName) {
case "parameters":
this.request.arguments.unshift(item);
break;
case "rest":
this.request.rest.unshift(item);
break;
case "headers":
this.request.headers.unshift(item);
break;
default:
break;
}
})
},
init() {
if (!this.request.body) {
this.request.body = new Body();
}
if (!this.request.body.kvs) {
this.request.body.kvs = [];
}
if (!this.request.rest) {
this.request.rest = [];
}
if (!this.request.arguments) {
this.request.arguments = [];
}
},
// body
reloadBody() {
this.isBodyShow = false;
this.$nextTick(() => {
this.isBodyShow = true;
});
},
batchAdd() {
this.$refs.batchAddParameter.open();
},
batchSave(data) {
if (data) {
let params = data.split("\n");
let keyValues = [];
params.forEach(item => {
let line = item.split(/|,/);
let required = false;
if (line[1] === '必填' || line[1] === 'Required' || line[1] === 'true') {
required = true;
}
keyValues.push(new KeyValue({name: line[0], required: required, value: line[2], description: line[3], type: "text", valid: false, file: false, encode: true, enable: true, contentType: "text/plain"}));
})
keyValues.forEach(item => {
switch (this.activeName) {
case "parameters":
this.request.arguments.unshift(item);
break;
case "rest":
this.request.rest.unshift(item);
break;
case "headers":
this.request.headers.unshift(item);
break;
default:
break;
}
})
}
}
}
}
}
</script>
<style scoped>
.ms-query {
background: #783887;
color: white;
height: 18px;
border-radius: 42%;
}
.ms-query {
background: #783887;
color: white;
height: 18px;
border-radius: 42%;
}
.ms-header {
background: #783887;
color: white;
height: 18px;
border-radius: 42%;
}
.ms-header {
background: #783887;
color: white;
height: 18px;
border-radius: 42%;
}
.request-tabs {
margin: 20px;
min-height: 200px;
}
.request-tabs {
margin: 20px;
min-height: 200px;
}
.ms-el-link {
float: right;
margin-right: 45px;
}
.ms-el-link {
float: right;
margin-right: 45px;
}
</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"
@close="handleClose">
<p class="tip">基础设置 </p>
<el-form :inline="true" v-model="advancedValue" class="ms-advanced-search-form">
<el-row :gutter="6">
<el-col :span="8" v-for="(item,key) in advancedValue" :key="key" style="float: right">
<el-form-item>
<div>{{ $t('schema.'+key)}}</div>
<el-form label-position="left" label-width="100px" v-model="advancedValue" class="ms-advanced-search-form">
<div :span="8" v-for="(item,key) in advancedValue" :key="key">
<el-form-item :label="$t('schema.'+key)">
<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"/>
<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 :key="t" :value="t" :label="t" v-for="t in advancedAttr[key].enums"/>
</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-form-item>
</el-col>
</el-row>
</div>
</el-form>
<!--<h3 v-text="$t('schema.add_custom')" v-show="custom">添加自定义属性</h3>
<el-form class="ms-advanced-search-form" v-show="custom">

View File

@ -1,8 +1,7 @@
const value = {
description: null,
minItems:null,
maxItems:null,
uniqueItems:false
description: null
}
const attr = {
description: {
@ -23,4 +22,4 @@ const attr = {
}
}
const wrapper = {value, attr}
export default wrapper
export default wrapper

View File

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

View File

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

View File

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

View File

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