接口测试 shcedule 业务逻辑

This commit is contained in:
chenjianxing 2020-06-22 12:49:20 +08:00
parent 8c09d052f3
commit f8cb40cb10
11 changed files with 195 additions and 102 deletions

View File

@ -18,6 +18,7 @@
<shiro.version>1.5.1</shiro.version> <shiro.version>1.5.1</shiro.version>
<java.version>1.8</java.version> <java.version>1.8</java.version>
<jmeter.version>5.2.1</jmeter.version> <jmeter.version>5.2.1</jmeter.version>
<quartz-starter.version>0.0.4</quartz-starter.version>
</properties> </properties>
<dependencies> <dependencies>
@ -60,6 +61,7 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId> <artifactId>spring-boot-starter-mail</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
@ -75,6 +77,14 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId> <artifactId>spring-boot-starter-websocket</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.fit2cloud</groupId>
<artifactId>quartz-spring-boot-starter</artifactId>
<version>${quartz-starter.version}</version>
</dependency>
<!-- flyway --> <!-- flyway -->
<dependency> <dependency>
<groupId>org.flywaydb</groupId> <groupId>org.flywaydb</groupId>

View File

@ -50,6 +50,11 @@ public class APITestController {
return apiTestService.getApiTestByProjectId(projectId); return apiTestService.getApiTestByProjectId(projectId);
} }
@PostMapping(value = "/schedule/update")
public void updateSchedule(@RequestBody SaveAPITestRequest request) {
apiTestService.updateSchedule(request);
}
@PostMapping(value = "/create", consumes = {"multipart/form-data"}) @PostMapping(value = "/create", consumes = {"multipart/form-data"})
public void create(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "files") List<MultipartFile> files) { public void create(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "files") List<MultipartFile> files) {
apiTestService.create(request, files); apiTestService.create(request, files);

View File

@ -1,6 +1,7 @@
package io.metersphere.api.dto; package io.metersphere.api.dto;
import io.metersphere.api.dto.scenario.Scenario; import io.metersphere.api.dto.scenario.Scenario;
import io.metersphere.dto.ScheduleDTO;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@ -17,4 +18,6 @@ public class SaveAPITestRequest {
private String name; private String name;
private List<Scenario> scenarioDefinition; private List<Scenario> scenarioDefinition;
private ScheduleDTO schedule;
} }

View File

@ -207,4 +207,14 @@ public class APITestService {
} }
} }
public void updateSchedule(SaveAPITestRequest request) {
// todo 开启调度线程
ApiTestWithBLOBs apiTest = new ApiTestWithBLOBs();
apiTest.setId(request.getId());
apiTest.setSchedule(JSONObject.toJSONString(request.getSchedule()));
apiTest.setUpdateTime(System.currentTimeMillis());
apiTestMapper.updateByPrimaryKeySelective(apiTest);
}
} }

View File

@ -0,0 +1,11 @@
package io.metersphere.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class ScheduleDTO {
private Boolean enable;
private String cronExpression;
}

View File

@ -47,7 +47,7 @@
<ms-api-report-dialog :test-id="id" ref="reportDialog"/> <ms-api-report-dialog :test-id="id" ref="reportDialog"/>
<ms-scheduler-config/> <ms-schedule-config :schedule="test.schedule" :save="saveCronExpression" @scheduleChange="saveSchedule" :check-open="checkScheduleEdit"/>
</el-row> </el-row>
</el-header> </el-header>
<ms-api-scenario-config :is-read-only="isReadOnly" :scenarios="test.scenarioDefinition" ref="config"/> <ms-api-scenario-config :is-read-only="isReadOnly" :scenarios="test.scenarioDefinition" ref="config"/>
@ -63,12 +63,12 @@
import MsApiReportStatus from "../report/ApiReportStatus"; import MsApiReportStatus from "../report/ApiReportStatus";
import MsApiReportDialog from "./ApiReportDialog"; import MsApiReportDialog from "./ApiReportDialog";
import {checkoutTestManagerOrTestUser, downloadFile} from "../../../../common/js/utils"; import {checkoutTestManagerOrTestUser, downloadFile} from "../../../../common/js/utils";
import MsSchedulerConfig from "../../common/components/MsSchedulerConfig"; import MsScheduleConfig from "../../common/components/MsScheduleConfig";
export default { export default {
name: "MsApiTestConfig", name: "MsApiTestConfig",
components: {MsSchedulerConfig, MsApiReportDialog, MsApiReportStatus, MsApiScenarioConfig}, components: {MsScheduleConfig, MsApiReportDialog, MsApiReportStatus, MsApiScenarioConfig},
props: ["id"], props: ["id"],
@ -129,6 +129,7 @@
name: item.name, name: item.name,
status: item.status, status: item.status,
scenarioDefinition: JSON.parse(item.scenarioDefinition), scenarioDefinition: JSON.parse(item.scenarioDefinition),
schedule: item.schedule ? JSON.parse(item.schedule) : {},
}); });
this.$refs.config.reset(); this.$refs.config.reset();
} }
@ -211,6 +212,31 @@
downloadFile(this.test.name + ".json", this.test.export()); downloadFile(this.test.name + ".json", this.test.export());
break; break;
} }
},
saveCronExpression(cronExpression) {
this.test.schedule.enable = true;
this.test.schedule.cronExpression = cronExpression;
this.saveSchedule();
},
saveSchedule() {
if (this.create) {
this.$message('请先保存测试,在设置定时任务');
return;
}
let param = {};
param.id = this.test.id;
param.schedule = this.test.schedule;
this.$post('/api/schedule/update', param, response => {
this.$success('保存成功');
this.getTest(this.test.id);
});
},
checkScheduleEdit() {
if (this.create) {
this.$message('请先保存测试');
return false;
}
return true;
} }
}, },
@ -249,7 +275,7 @@
margin-left: 10px; margin-left: 10px;
} }
.scheduler-config { .schedule-config {
float: right; float: right;
} }
</style> </style>

View File

@ -100,6 +100,7 @@ export class Test extends BaseConfig {
this.name = undefined; this.name = undefined;
this.projectId = undefined; this.projectId = undefined;
this.scenarioDefinition = []; this.scenarioDefinition = [];
this.schedule = {};
this.set(options); this.set(options);
this.sets({scenarioDefinition: Scenario}, options); this.sets({scenarioDefinition: Scenario}, options);

View File

@ -0,0 +1,101 @@
<template>
<div class="schedule-config">
<div>
<span class="cron-ico">
<i class="el-icon-date" size="small"></i>
<span class="character" @click="scheduleEdit">SCHEDULER</span>
</span>
<el-switch :disabled="!schedule.cronExpression" v-model="schedule.enable" @change="scheduleChange"/>
<ms-schedule-edit :schedule="schedule" :save="save" ref="scheduleEdit"/>
<crontab-result v-show="false" :ex="schedule.cronExpression" ref="crontabResult" @resultListChange="recentListChange"/>
</div>
<div>
<span :class="{'disable-character': !schedule.enable}"> 下次执行时间{{this.recentList.length > 0 ? this.recentList[0] : ''}} </span>
</div>
</div>
</template>
<script>
import MsScheduleEdit from "./MsScheduleEdit";
import CrontabResult from "../cron/CrontabResult";
export default {
name: "MsScheduleConfig",
components: {CrontabResult, MsScheduleEdit},
data() {
return {
recentList: [],
}
},
props: {
save: Function,
schedule: {},
checkOpen: {
type: Function,
default() {
return new Function()
}
},
},
// mounted() {
// console.log(this.schedule);
// // this.recentList = this.$refs.crontabResult.resultList;
// // console.log(this.recentList);
// console.log(this.recentList+ "====");
// },
// watch: {
// 'schedule.cronExpression'() {
// console.log(this.schedule);
// this.$refs.crontabResult.expressionChange();
// this.recentList = this.$refs.crontabResult.resultList;
// console.log(this.recentList);
// }
// },
methods: {
scheduleEdit() {
if (!this.checkOpen()) {
return;
}
this.$refs.scheduleEdit.open();
},
scheduleChange() {
this.$emit('scheduleChange');
},
recentListChange(resultList) {
this.recentList = resultList;
}
}
}
</script>
<style scoped>
.schedule-config {
float: right;
width: 250px;
height: 15px;
line-height: 25px;
}
.el-icon-date {
font-size: 20px;
margin-left: 5px;
}
.character {
font-weight: bold;
margin: 0 5px;
}
.disable-character {
color: #cccccc;
}
.el-switch {
margin: 0 5px;
}
.cron-ico {
cursor: pointer;
}
</style>

View File

@ -1,5 +1,5 @@
<template> <template>
<el-dialog width="30%" class="scheduler-edit" :title="'编辑定时任务'" :visible.sync="dialogVisible" @close="close"> <el-dialog width="30%" class="schedule-edit" :title="'编辑定时任务'" :visible.sync="dialogVisible" @close="close">
<div id="app"> <div id="app">
<el-form :model="form" :rules="rules" ref="from"> <el-form :model="form" :rules="rules" ref="from">
<el-form-item <el-form-item
@ -7,12 +7,12 @@
prop="cronValue"> prop="cronValue">
<el-input v-model="form.cronValue" placeholder class="inp"/> <el-input v-model="form.cronValue" placeholder class="inp"/>
<el-button type="primary" @click="showCronDialog">生成 Cron</el-button> <el-button type="primary" @click="showCronDialog">生成 Cron</el-button>
<el-button type="primary" @click="save">保存</el-button> <el-button type="primary" @click="saveCron">保存</el-button>
</el-form-item> </el-form-item>
<crontab-result :ex="cronExpression"/> <crontab-result :ex="schedule.cronExpression" ref="crontabResult"/>
</el-form> </el-form>
<el-dialog title="生成 cron" :visible.sync="showCron" :modal="false"> <el-dialog title="生成 cron" :visible.sync="showCron" :modal="false">
<crontab @hide="showCron=false" @fill="crontabFill" :expression="cronExpression"/> <crontab @hide="showCron=false" @fill="crontabFill" :expression="schedule.cronExpression"/>
</el-dialog> </el-dialog>
</div> </div>
</el-dialog> </el-dialog>
@ -25,20 +25,28 @@
import {cronValidate} from "../../../../common/js/cron"; import {cronValidate} from "../../../../common/js/cron";
export default { export default {
name: "MsSchedulerEdit", name: "MsScheduleEdit",
components: {CrontabResult, Crontab}, components: {CrontabResult, Crontab},
props: {
save: Function,
schedule: {},
},
watch: {
'schedule.cronExpression'() {
this.form.cronValue = this.schedule.cronExpression;
}
},
data() { data() {
const validateCron = (rule, cronValue, callback) => { const validateCron = (rule, cronValue, callback) => {
if (!cronValidate(cronValue)) { if (!cronValidate(cronValue)) {
callback(new Error('Cron 表达式格式错误')); callback(new Error('Cron 表达式格式错误'));
} else { } else {
this.cronExpression = cronValue; this.schedule.cronExpression = cronValue;
callback(); callback();
} }
}; };
return { return {
dialogVisible: false, dialogVisible: false,
cronExpression: null,
showCron: false, showCron: false,
form: { form: {
cronValue: "" cronValue: ""
@ -54,29 +62,26 @@
}, },
crontabFill(value) { crontabFill(value) {
// //
this.cronExpression = value; this.schedule.cronExpression = value;
this.form.cronValue = value; this.form.cronValue = value;
this.$refs['from'].validate(); this.$refs['from'].validate();
}, },
showCronDialog() { showCronDialog() {
this.showCron = true; this.showCron = true;
}, },
save () { saveCron () {
if (!this.formValidate()) {
return;
}
console.log("save");
},
formValidate() {
this.$refs['from'].validate((valid) => { this.$refs['from'].validate((valid) => {
if (!valid) { if (valid) {
this.save(this.form.cronValue);
this.dialogVisible = false;
} else {
return false; return false;
} }
}); });
}, },
close() { close() {
this.cronExpression = null;
this.$refs['from'].resetFields(); this.$refs['from'].resetFields();
this.$refs.crontabResult.resultList = [];
} }
} }
} }

View File

@ -1,80 +0,0 @@
<template>
<div class="scheduler-config">
<div>
<span class="cron-ico">
<i class="el-icon-date" size="small"></i>
<span class="character" @click="schedulerEdit">SCHEDULER</span>
</span>
<el-switch v-model="isActive"/>
<!-- <el-button class="add-button" type="primary" size="mini" icon="el-icon-plus" plain @click="schedulerEdit"/>-->
<ms-scheduler-edit ref="schedulerEdit"/>
</div>
<div>
<span :class="{'disable-character': !isActive}"> 下次执行时间2020-06-19 12:07:09 </span>
</div>
</div>
</template>
<script>
import MsSchedulerEdit from "./MsSchedulerEdit";
export default {
name: "MsSchedulerConfig",
components: {MsSchedulerEdit},
data() {
return {
isActive: false
}
},
methods: {
schedulerEdit() {
this.$refs.schedulerEdit.open();
}
}
}
</script>
<style scoped>
.scheduler-config {
float: right;
width: 250px;
height: 15px;
line-height: 25px;
}
.el-icon-date {
font-size: 20px;
margin-left: 5px;
}
.character {
font-weight: bold;
margin: 0 5px;
}
.disable-character {
color: #cccccc;
}
/*.scheduler-config >>> .el-switch__core::after {*/
/* height: 12px;*/
/* width: 12px;*/
/*}*/
/*.scheduler-config >>> .el-switch__core {*/
/* height: 15px;*/
/*}*/
/*.scheduler-config >>> .el-switch.is-checked .el-switch__core::after {*/
/* margin-left: -13px;*/
/*}*/
.el-switch {
margin: 0 5px;
}
.cron-ico {
cursor: pointer;
}
</style>

View File

@ -344,10 +344,11 @@ export default {
this.resultList.push('最近100年内只有上面' + resultArr.length + '条结果!') this.resultList.push('最近100年内只有上面' + resultArr.length + '条结果!')
} }
} }
this.$emit("resultListChange", this.resultList);
// - // -
this.isShow = true; this.isShow = true;
}, },
// //
getIndex(arr, value) { getIndex(arr, value) {