FasterRunner (httptunner)搭建以及功能补充(邮件、修改定时任务)
配置
下载地址
https://github.com/httprunner/FasterRunner
后端配置
https://www.jianshu.com/p/e26ccc21ddf2
前端配置
https://www.cnblogs.com/luopan/p/10250485.html
mac下安装RabbitMq
下载brew: https://blog.csdn.net/iteapoy/article/details/134701525
下载RabbitMq:https://www.cnblogs.com/yihuihui/p/9095130.html
网络慢,本地下载RabbitMq:https://blog.csdn.net/a116385895/article/details/111938495
入口
http://localhost:8080/fastrunner/login
问题:
1. pycharm 创建的虚拟环境不能pip
https://www.jianshu.com/p/e46e36addf8d
2. pymysql/mysqlclient caching_sha2_password,
https://blog.csdn.net/weekdawn/article/details/81039382
django 1.8.2 mysql 加密方式 caching_sha2_password
django 2.0.3 mysql 加密方式 mysql_native_password
部分功能配置及补充
定时任务配置
1. setting.py
djcelery.setup_loader() CELERY_ENABLE_UTC = True CELERY_TIMEZONE = 'Asia/Shanghai' # BROKER_URL = 'amqp://username:password@IP:5672//' BROKER_URL = 'amqp://guest:guest@127.0.0.1:5672//' CELERYBEAT_SCHEDULER = 'djcelery.schedulers.DatabaseScheduler' CELERY_RESULT_BACKEND = 'djcelery.backends.database:DatabaseBackend' CELERY_ACCEPT_CONTENT = ['application/json'] CELERY_TASK_SERIALIZER = 'json' CELERY_RESULT_SERIALIZER = 'json' CELERY_TASK_RESULT_EXPIRES = 7200 CELERYD_CONCURRENCY = 1 if DEBUG else 5 CELERYD_MAX_TASKS_PER_CHILD = 40
2.定时服务
cd /home/conan/conan-ta/FasterRunner/ nohup python3 manage.py celery beat -l info >> /Users/zd/Documents/FasterRunner/logs/beat.log 2>&1 & cd /home/conan/conan-ta/FasterRunner/ celery multi start w1 -A FasterRunner -l info --logfile=/Users/zd/Documents/FasterRunner/logs/worker.log 2>&1 &
3.调试定时任务
更改定时相关逻辑时,需要关掉并重新启动celery beat,celery multi 才会生效,打印出来的调试信息在logs/worker.log下查看。
举例:定时任务,增加发送邮件的标题:
fastrunner/utils/task.py
fastrunner/task.py
发送邮件
setting.py
# 发邮件 EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' EMAIL_SEND_USERNAME = 'no-reply@fenbi.com' # 定时任务报告发送邮箱,支持163,qq,sina,企业qq邮箱等,注意需要开通smtp服务 EMAIL_SEND_PASSWORD = '' # 邮箱密码 EMAIL_PORT = 25 EMAIL_USE_TLS = True
fastrunner/utils/email.py
import smtplib from email.mime.text import MIMEText from email.header import Header from FasterRunner.settings import EMAIL_SEND_USERNAME, EMAIL_SEND_PASSWORD def send_email_reports(receiver,save_summary,Cc=None,title=None): receiver = receiver.rstrip(';') all_receivers = receiver.split(';') if '@sina.com' in EMAIL_SEND_USERNAME: smtpserver = 'smtp.sina.com' elif '@163.com' in EMAIL_SEND_USERNAME: smtpserver = 'smtp.163.com' else: smtpserver = 'smtp.exmail.qq.com' if title: subject = "【%s】接口自动化测试报告"%title else: subject = "接口自动化测试报告" smtp = smtplib.SMTP_SSL(smtpserver, 465) smtp.login(EMAIL_SEND_USERNAME, EMAIL_SEND_PASSWORD) msg = MIMEText(save_summary, "html", "utf-8") msg["Subject"] = Header(subject, "utf-8") msg['From'] = Header('no-reply', 'utf-8') msg['To'] = receiver # 处理抄送 if Cc: Cc = Cc.rstrip(';') msg['Cc'] = Cc all_receivers = receiver + ';' + Cc all_receivers = all_receivers.split(';') smtp.sendmail(EMAIL_SEND_USERNAME, all_receivers, msg.as_string())
fastrunner/tasks.py
1 from fastrunner.utils.emails import send_email_reports 2 import time 3 4 @shared_task 5 def schedule_debug_suite(*args, **kwargs): 6 """定时任务 7 """ 8 print("定时任务start.....") 9 project = kwargs["project"] 10 receiver = kwargs["receiver"] 11 Cc = kwargs["copy"] 12 title = kwargs["name"] 13 14 print("receiver****:%s"%receiver) 15 print("args****:%s"%args) 16 print("kwargs****:%s"%kwargs) 17 print("定时任务end.....") 18 receiver = receiver.strip() 19 Cc = Cc.strip() 20 21 suite = [] 22 test_sets = [] 23 config_list = [] 24 for pk in args: 25 try: 26 name = models.Case.objects.get(id=pk).name 27 suite.append({ 28 "name": name, 29 "id": pk 30 }) 31 except ObjectDoesNotExist: 32 pass 33 34 for content in suite: 35 test_list = models.CaseStep.objects. \ 36 filter(case__id=content["id"]).order_by("step").values("body") 37 38 testcase_list = [] 39 config = None 40 for content in test_list: 41 body = eval(content["body"]) 42 if "base_url" in body["request"].keys(): 43 config = eval(models.Config.objects.get(name=body["name"], project__id=project).body) 44 continue 45 testcase_list.append(body) 46 config_list.append(config) 47 test_sets.append(testcase_list) 48 49 summary = debug_suite(test_sets, project, suite, config_list, save=False) 50 save_summary("", summary, project, type=3) 51 52 # 整理数据 53 testTime = summary['time']['start_at'] 54 testTime = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(testTime)) 55 durTime = str(summary['time']['duration'])[:8] 56 57 totalApi = summary['stat']['testsRun'] 58 successApi = summary['stat']['successes'] 59 FailApi = summary['stat']['failures'] 60 errorApi = summary['stat']['errors'] 61 skipApi = summary['stat']['skipped'] 62 63 htmll = """ 64 <table border="1" cellpadding="0" cellspacing="0" width="700px"> 65 <tr style="background-color: #f8f8fa"> 66 <th>测试时间</th> 67 <th>持续时间</th> 68 <th>Total</th> 69 <th>Success</th> 70 <th>Failed</th> 71 <th>Error</th> 72 <th>Skipped</th> 73 </tr> 74 <tr> 75 <td>%s</td> 76 <td>%s 秒</td> 77 <td>%s</td> 78 <td>%s</td> 79 <td>%s</td> 80 <td>%s</td> 81 <td>%s</td> 82 </tr> 83 </table> 84 <div style="height: 30px"></div> 85 <table border="1" cellpadding="0" cellspacing="0" width="700px"> 86 <tr style="background-color: #f8f8fa"> 87 <th>名称</th> 88 <th>请求地址</th> 89 <th>请求方法</th> 90 <th>响应时间(ms)</th> 91 <th>测试结果</th> 92 </tr> 93 """ % ( 94 testTime, durTime, totalApi, successApi, FailApi, errorApi, skipApi 95 ) 96 97 # 名称/请求地址/请求方法/响应时间/测试结果 98 for i in summary['details']: # [{},{}] 99 detail = i['records'] # 列表 100 for d in detail: 101 name = d['name'] 102 uurl = d['meta_data']['request']['url'] 103 method = d['meta_data']['request']['method'] 104 responseTime = d['meta_data']['response']['response_time_ms'] 105 iresult = d['status'] 106 107 htmll += """ 108 <tr> 109 <td>%s</td> 110 <td>%s</td> 111 <td>%s</td> 112 <td>%s (ms)</td> 113 <td>%s</td> 114 </tr> 115 """ % (name, uurl, method, responseTime, iresult) 116 117 htmll = htmll + '</table>' 118 119 120 121 if Cc: 122 send_email_reports(receiver, htmll, Cc=Cc,title=title) 123 else: 124 send_email_reports(receiver, htmll,title=title)
编辑定时任务
前端 Task.vue
1、父传子,将编辑页面的数据,跨页面传给AddTask.vue
1 <template> 2 3 <el-container> 4 <el-header style="background: #fff; padding: 0; height: 50px"> 5 <div class="nav-api-header"> 6 <div style="padding-top: 10px; margin-left: 20px"> 7 <el-button 8 type="primary" 9 size="small" 10 icon="el-icon-circle-plus-outline" 11 @click="addTasks2()" 12 >添加任务 13 </el-button> 14 15 <el-button 16 :disabled="!addTasks" 17 type="text" 18 style="position: absolute; right: 30px;" 19 @click="addTasks=false" 20 21 >返回列表 22 </el-button> 23 24 </div> 25 </div> 26 </el-header> 27 28 <el-container> 29 <el-header v-if="!addTasks" style="padding: 0; height: 50px; margin-top: 10px"> 30 <div style="padding-top: 8px; padding-left: 30px;"> 31 <el-pagination 32 :page-size="11" 33 v-show="tasksData.count !== 0 " 34 background 35 @current-change="handleCurrentChange" 36 :current-page.sync="currentPage" 37 layout="total, prev, pager, next, jumper" 38 :total="tasksData.count" 39 > 40 </el-pagination> 41 </div> 42 </el-header> 43 <el-main style="padding: 0; margin-left: 10px; margin-top: 10px;"> 44 <div style="position: fixed; bottom: 0; right:0; left: 230px; top: 160px"> 45 <el-table 46 v-if="!addTasks" 47 :data="tasksData.results" 48 :show-header="tasksData.results.length !== 0 " 49 stripe 50 highlight-current-row 51 height="calc(100%)" 52 @cell-mouse-enter="cellMouseEnter" 53 @cell-mouse-leave="cellMouseLeave" 54 > 55 56 <el-table-column 57 label="任务名称" 58 width="240" 59 > 60 <template slot-scope="scope"> 61 <div>{{scope.row.name}}</div> 62 </template> 63 </el-table-column> 64 65 66 <el-table-column 67 width="120" 68 label="时间配置" 69 > 70 <template slot-scope="scope"> 71 <div>{{scope.row.kwargs.corntab}}</div> 72 </template> 73 </el-table-column> 74 75 <el-table-column 76 width="100" 77 label="邮件策略" 78 > 79 <template slot-scope="scope"> 80 <div>{{scope.row.kwargs.strategy}}</div> 81 82 </template> 83 </el-table-column> 84 85 86 <el-table-column 87 width="80" 88 label="状态" 89 > 90 <template slot-scope="scope"> 91 <el-switch 92 disabled 93 v-model="scope.row.enabled" 94 active-color="#13ce66" 95 inactive-color="#ff4949"> 96 </el-switch> 97 </template> 98 </el-table-column> 99 <el-table-column 100 width="320" 101 label="接收人" 102 > 103 <template slot-scope="scope"> 104 <div>{{scope.row.kwargs.receiver}}</div> 105 </template> 106 </el-table-column> 107 <el-table-column 108 width="320" 109 label="抄送人" 110 > 111 <template slot-scope="scope"> 112 <div>{{scope.row.kwargs.copy}}</div> 113 </template> 114 </el-table-column> 115 116 <el-table-column 117 width="200" 118 > 119 <template slot-scope="scope"> 120 <el-row v-show="currentRow === scope.row"> 121 <el-button 122 type="info" 123 icon="el-icon-edit" 124 circle size="mini" 125 @click="editTask(scope.row)" 126 ></el-button> 127 <el-button 128 type="danger" 129 icon="el-icon-delete" 130 circle size="mini" 131 @click="delTasks(scope.row.id)" 132 > 133 </el-button> 134 </el-row> 135 </template> 136 137 </el-table-column> 138 139 </el-table> 140 </div> 141 </el-main> 142 <!-- 将本页面的编辑数据,传给AddTask.vue --> 143 <AddTasks 144 v-if="addTasks" 145 v-on:changeStatus="changeStatus" 146 :editValue=this.editValue 147 > 148 </AddTasks> 149 150 </el-container> 151 </el-container> 152 153 </template> 154 155 <script> 156 import AddTasks from './AddTasks' 157 158 export default { 159 components: { 160 AddTasks 161 }, 162 data() { 163 return { 164 editValue:[], 165 addTasks: false, 166 currentPage: 1, 167 currentRow: '', 168 tasksData: { 169 count: 0, 170 results: [] 171 }, 172 } 173 }, 174 methods: { 175 // 添加任务,要把编辑的内容置空 176 addTasks2(){ 177 this.addTasks = true 178 this.editValue = [] 179 }, 180 editTask(val){ 181 console.log(val) 182 this.addTasks = true 183 this.editValue = val // 父传子 184 // this.$store.commit("dispalyTask", val); 缓存方式 185 // this.addTasks.ruleForm.name = this.$store.state.task.name; 186 187 }, 188 189 delTasks(id) { 190 this.$confirm('此操作将永久删除该定时任务,是否继续?', '提示', { 191 confirmButtonText: '确定', 192 cancelButtonText: '取消', 193 type: 'warning', 194 }).then(() => { 195 this.$api.deleteTasks(id).then(resp => { 196 if (resp.success) { 197 this.getTaskList(); 198 } 199 }) 200 }) 201 }, 202 handleCurrentChange(val) { 203 this.$api.getTaskPaginationBypage({ 204 params: { 205 page: this.currentPage, 206 project: this.$route.params.id 207 } 208 }).then(resp => { 209 this.tasksData = resp; 210 }) 211 }, 212 213 changeStatus(value) { 214 this.getTaskList(); 215 this.addTasks = value; 216 }, 217 getTaskList() { 218 this.$api.taskList({params: {project: this.$route.params.id}}).then(resp => { 219 this.tasksData = resp 220 }) 221 }, 222 cellMouseEnter(row) { 223 this.currentRow = row; 224 }, 225 226 cellMouseLeave(row) { 227 this.currentRow = ''; 228 }, 229 }, 230 name: "Tasks", 231 mounted() { 232 this.getTaskList(); 233 } 234 } 235 </script> 236 237 <style> 238 239 240 </style>
前端 AddTasks.vue
1、编辑数据回显
2、在原来保存任务的接口上延伸出更新,将修改的数据传后端
1 <template> 2 <el-container> 3 <template v-if="!next"> 4 <el-main style="padding-top: 0"> 5 <div style="margin-top: 10px;"> 6 <el-col :span="12"> 7 <el-form 8 :model="ruleForm" 9 :rules="rules" 10 ref="ruleForm" 11 label-width="100px" 12 > 13 <el-form-item label="任务名称" prop="name" > 14 15 <el-input v-model="ruleForm.name" placeholder="请输入任务名称" clearable=""></el-input> 16 </el-form-item> 17 18 <el-form-item label="时间配置" prop="corntab"> 19 <el-input clearable v-model="ruleForm.corntab" placeholder="请输入cortab表达式,例如 2 12 * * *"></el-input> 20 </el-form-item> 21 22 <el-form-item label="任务状态" prop="switch"> 23 <el-switch v-model="ruleForm.switch"></el-switch> 24 </el-form-item> 25 26 <el-form-item label="邮件策略" prop="strategy"> 27 <el-radio-group v-model="ruleForm.strategy"> 28 <el-radio label="始终发送"></el-radio> 29 <el-radio label="仅失败发送"></el-radio> 30 <el-radio label="从不发送"></el-radio> 31 </el-radio-group> 32 </el-form-item> 33 34 <el-form-item label="邮件接收人列表" prop="receiver"> 35 <el-input type="textarea" v-model="ruleForm.receiver" 36 placeholder="多个接收人以;分隔" clearable></el-input> 37 </el-form-item> 38 39 <el-form-item label="邮件抄送人列表" prop="copy"> 40 <el-input type="textarea" v-model="ruleForm.copy" 41 placeholder="多个抄送人以;分隔" clearable></el-input> 42 </el-form-item> 43 44 <el-form-item> 45 <el-button type="primary" @click="submitForm('ruleForm')">下一步</el-button> 46 <el-button @click="resetForm('ruleForm')">重置</el-button> 47 </el-form-item> 48 </el-form> 49 50 </el-col> 51 52 </div> 53 </el-main> 54 </template> 55 <template v-if="next"> 56 <el-aside style="margin-top: 10px;"> 57 <div class="nav-api-side"> 58 <div class="api-tree"> 59 <el-input 60 placeholder="输入关键字进行过滤" 61 v-model="filterText" 62 size="medium" 63 clearable 64 prefix-icon="el-icon-search" 65 > 66 </el-input> 67 <el-tree 68 @node-click="handleNodeClick" 69 :data="dataTree" 70 node-key="id" 71 :default-expand-all="false" 72 :expand-on-click-node="false" 73 highlight-current 74 :filter-node-method="filterNode" 75 ref="tree2" 76 > 77 <span class="custom-tree-node" 78 slot-scope="{ node, data }" 79 > 80 <span><i class="iconfont" v-html="expand"></i> {{ node.label }}</span> 81 </span> 82 </el-tree> 83 </div> 84 85 </div> 86 87 </el-aside> 88 <el-main style="padding-top: 0px"> 89 <div> 90 <el-row :gutter="20"> 91 <el-col :span="12"> 92 <el-pagination 93 :page-size="11" 94 v-show="suiteData.count !== 0" 95 background 96 @current-change="handlePageChange" 97 :current-page.sync="currentPage" 98 layout="total, prev, pager, next, jumper" 99 :total="suiteData.count" 100 style="text-align: center" 101 > 102 </el-pagination> 103 </el-col> 104 <el-col :span="12"> 105 <el-button type="primary" v-if="testData.length > 0" @click="saveTask">保存</el-button> 106 <el-button v-if="testData.length > 0" @click="next=false">上一步</el-button> 107 </el-col> 108 109 110 </el-row> 111 </div> 112 113 <div> 114 <el-row :gutter="20"> 115 <el-col :span="12"> 116 <div 117 v-for="(item,index) in suiteData.results" 118 draggable='true' 119 @dragstart="currentSuite = JSON.parse(JSON.stringify(item))" 120 style="cursor: pointer; margin-top: 10px; overflow: auto;" 121 :key="index" 122 123 124 > 125 126 127 <div class="block block_options" > 128 <span class="block-method block_method_options block_method_color">Case</span> 129 <span class="block_name">{{item.name}}</span> 130 </div> 131 </div> 132 </el-col> 133 <el-col :span="12"> 134 135 <div style="max-height: 600px; overflow: auto" 136 @drop='drop($event)' 137 @dragover='allowDrop($event)' 138 > 139 <span 140 v-if="testData.length ===0" 141 style="color: red">温馨提示:<br/>选择左侧相应用例节点显示可拖拽的用例<br/>从左边拖拽用例至此区域组成任务列表<br/> 142 上下拖动此区域任务调整监控调用顺序 143 </span> 144 145 146 147 <div class='test-list'> 148 <draggable 149 v-model="testData" 150 @end="dragEnd" 151 @start="length = testData.length" 152 :options="{animation:200}" 153 > 154 <div 155 v-for="(test, index) in testData" 156 :key="index" 157 class="block block_test" 158 @mousemove="currentTest = index" 159 > 160 <span 161 class="block-method block_method_test block_method_color">Tasks</span> 162 <span class="block-test-name">{{test.name}}</span> 163 164 <el-button 165 style="position: absolute; right: 12px; top: 8px" 166 v-show="currentTest === index" 167 type="danger" 168 icon="el-icon-delete" 169 circle size="mini" 170 @click="testData.splice(index, 1)" 171 > 172 173 </el-button> 174 </div> 175 </draggable> 176 </div> 177 178 </div> 179 </el-col> 180 </el-row> 181 </div> 182 </el-main> 183 </template> 184 </el-container> 185 186 187 </template> 188 189 <script> 190 import draggable from 'vuedraggable' 191 192 export default { 193 194 name: "AddTasks", 195 watch: { 196 filterText(val) { 197 this.$refs.tree2.filter(val); 198 }, 199 }, 200 components: { 201 draggable 202 }, 203 props:['editValue'], 204 205 data() { 206 return { 207 208 currentTest: '', 209 length: 0, 210 testData: [], 211 currentSuite: '', 212 search: '', 213 next: false, 214 node: '', 215 currentPage: 1, 216 filterText: '', 217 expand: '', 218 dataTree: [], 219 suiteData: { 220 count: 0, 221 results: [] 222 }, 223 ruleForm: { 224 name: '', 225 switch: true, 226 corntab: '', 227 strategy: '始终发送', 228 receiver: '', 229 copy: '' 230 }, 231 rules: { 232 name: [ 233 {required: true, message: '请输入任务名称', trigger: 'blur'}, 234 {min: 1, max: 50, message: '长度在 1 到 50 个字符', trigger: 'blur'} 235 ], 236 corntab: [ 237 {required: true, message: '请输入正确的corntab表达式', trigger: 'blur'} 238 ] 239 240 } 241 } 242 }, 243 methods: { 244 // 将之前的保存任务扩展成保存+修改。判断逻辑,如果拿到了父传子的数据(编辑数据),点击保存就走更新接口。 245 saveTask(){ 246 // var task = []; 247 // for(let value of this.testData){ 248 // task.push(value.id); 249 // } 250 // var form = this.ruleForm; 251 // form["data"] = task ; 252 // form["project"] = this.$route.params.id; 253 254 if(this.editValue.kwargs){ 255 var task = []; 256 for(let value of this.testData){ 257 task.push(value.id); 258 } 259 var form = this.ruleForm; 260 form["data"] = task ; 261 form["project"] = this.$route.params.id; 262 form["taskId"] = this.editValue.id; 263 264 265 266 console.log("更新任务。。。") 267 this.$api.updateTask11(form).then(resp => { 268 if(!resp.success){ 269 this.$message.error(resp.msg) 270 }else{ 271 this.$emit("changeStatus", false); 272 } 273 }) 274 }else{ 275 var task2 = []; 276 for(let value2 of this.testData){ 277 task2.push(value2.id); 278 } 279 var form2 = this.ruleForm; 280 form2["data"] = task2 ; 281 form2["project"] = this.$route.params.id; 282 283 this.$api.addTask(form2).then(resp => { 284 if(!resp.success){ 285 this.$message.error(resp.msg) 286 }else{ 287 this.$emit("changeStatus", false); 288 } 289 }) 290 } 291 292 293 }, 294 dragEnd(event) { 295 if (this.testData.length > this.length) { 296 this.testData.splice(this.length, 1) 297 } 298 }, 299 drop(event) { 300 event.preventDefault(); 301 this.testData.push(this.currentSuite); 302 303 }, 304 allowDrop(event) { 305 event.preventDefault(); 306 }, 307 handlePageChange(val) { 308 this.$api.getTestPaginationBypage({ 309 params: { 310 page: this.currentPage, 311 node: this.node, 312 project: this.$route.params.id, 313 search: '' 314 } 315 }).then(res => { 316 this.suiteData = res; 317 }) 318 }, 319 handleCurrentChange(val) { 320 this.$api.getTestPaginationBypage({ 321 params: { 322 page: this.currentPage, 323 project: this.$route.params.id, 324 node: this.node, 325 search: this.search 326 } 327 }).then(resp => { 328 this.suiteData = resp; 329 }) 330 }, 331 // 点击下一步,将testData 回显出来 332 submitForm(formName) { 333 this.$refs[formName].validate((valid) => { 334 if (valid) { 335 this.next = true; 336 337 // var a = [] 338 // var b = [] 339 console.log("带出之前编辑的项目2"); 340 console.log(this.suiteData); 341 console.log(this.testData); 342 for(var i=0;i<this.suiteData.results.length;i++){ 343 console.log("ok1"); 344 // a.push(this.suiteData.results[i]) 345 for(var i2=0;i2<this.editValue.args.length;i2++){ 346 347 if(this.suiteData.results[i].id==this.editValue.args[i2]){ 348 this.testData.push(this.suiteData.results[i]) 349 // b.push(this.suiteData.results[i]) 350 } 351 352 } 353 354 }; 355 356 357 // 差集,把用过的case去掉,展示在左侧 358 // var minus = a.filter(v => !b.includes(v)); 359 // this.suiteData.results=minus 360 361 362 } else { 363 return false; 364 } 365 }); 366 }, 367 resetForm(formName) { 368 this.$refs[formName].resetFields(); 369 }, 370 filterNode(value, data) { 371 if (!value) return true; 372 return data.label.indexOf(value) !== -1; 373 }, 374 getTree() { 375 this.$api.getTree(this.$route.params.id, {params: {type: 2}}).then(resp => { 376 this.dataTree = resp.tree; 377 }) 378 }, 379 380 handleNodeClick(node) { 381 this.node = node.id; 382 this.getTestList(); 383 384 }, 385 getTestList() { 386 this.$api.testList({ 387 params: { 388 project: this.$route.params.id, 389 node: this.node, 390 search: this.search 391 } 392 }).then(resp => { 393 this.suiteData = resp; 394 }) 395 }, 396 397 // 父传子,从Task.vue 传来的数据 398 getEditData(){ 399 console.log("****") 400 console.log(this.editValue) 401 // console.log(this.editValue.name) 402 console.log(this.editValue.args) 403 if(this.editValue.kwargs){ 404 this.ruleForm.name=this.editValue.name; 405 this.ruleForm.corntab=this.editValue.kwargs.corntab; 406 this.ruleForm.receiver=this.editValue.kwargs.receiver; 407 this.ruleForm.copy=this.editValue.kwargs.copy; 408 this.ruleForm.strategy=this.editValue.kwargs.strategy; 409 } 410 411 412 }, 413 }, 414 415 mounted() { 416 this.getTree(); 417 this.getTestList(); 418 this.getEditData(); //自动加载编辑数据 419 420 } 421 } 422 </script> 423 424 <style scoped> 425 426 .test-list { 427 height: 590px; 428 } 429 430 .block_test { 431 margin-top: 10px; 432 border: 1px solid #49cc90; 433 background-color: rgba(236, 248, 238, .4) 434 } 435 436 .block_method_test { 437 background-color: #304056; 438 } 439 440 .block-test-name { 441 width: 700px; 442 padding-left: 10px; 443 -webkit-box-flex: 1; 444 -ms-flex: 1; 445 font-family: Open Sans, sans-serif; 446 color: #3b4151; 447 border: none; 448 outline: none; 449 background: rgba(236, 248, 238, .4) 450 451 } 452 453 454 </style>
前端 api.js
更新接口
1 export const updateTask11 = params => { 2 return axios.patch('/api/fastrunner/schedule/', params).then(res => res.data) 3 };
后端 schedule.py
1、更新定时任务,涉及到了三只表
2、修改了删除,把关联的时间也删除
1 @method_decorator(request_log(level='INFO')) 2 def update(self, request): 3 """ 4 编辑项目 5 """ 6 print("**编辑定时任务") 7 project = request.data["project"] 8 9 try: 10 project2 = models2.Project.objects.get(id=project) 11 12 except (KeyError, ObjectDoesNotExist): 13 return Response(response.SYSTEM_ERROR) 14 15 16 project2.name = request.data['name'] 17 project2.save() 18 print("项目名称保存完成!") 19 print("-------") 20 print("这是一堆什么东西?", request.data) 21 22 task2 = Task(**request.data) 23 resp = task2.update_task() 24 print("**5: ", resp) 25 print("**6: ", request.data) 26 27 return Response(response.PROJECT_UPDATE_SUCCESS) 28 29 30 31 @method_decorator(request_log(level='INFO')) 32 def delete(self, request, **kwargs): 33 """删除任务 34 """ 35 print("**删除") 36 task = models.PeriodicTask.objects.get(id=kwargs["pk"]) 37 task.enabled = False 38 task.delete() 39 schedule = models.CrontabSchedule.objects.get(id=task.crontab_id) 40 schedule.delete() 41 return Response(response.TASK_DEL_SUCCESS)
后端 task.py
1 import json 2 import logging 3 from djcelery import models as celery_models 4 5 from fastrunner.utils import response 6 from fastrunner.utils.parser import format_json 7 8 logger = logging.getLogger('FasterRunner') 9 10 11 class Task(object): 12 """ 13 定时任务操作 14 """ 15 16 def __init__(self, **kwargs): 17 logger.info("before process task data:\n {kwargs}".format(kwargs=format_json(kwargs))) 18 self.__name = kwargs["name"] 19 self.__data = kwargs["data"] 20 self.__corntab = kwargs["corntab"] 21 self.__switch = kwargs["switch"] 22 self.__task = "fastrunner.tasks.schedule_debug_suite" 23 self.__project = kwargs["project"] 24 self.__email = { 25 "strategy": kwargs["strategy"], 26 "copy": kwargs["copy"], 27 "receiver": kwargs["receiver"], 28 "corntab": self.__corntab, 29 "project": self.__project 30 } 31 32 self.__corntab_time = None 33 self.__taskId = "" 34 # 编辑的时候需要 35 for i in kwargs.keys(): 36 if "taskId" in i: 37 self.__taskId = kwargs["taskId"] 38 print("邮件**", self.__email) 39 40 def format_corntab(self): 41 """ 42 格式化时间 43 """ 44 corntab = self.__corntab.split(' ') 45 if len(corntab) > 5: 46 return response.TASK_TIME_ILLEGAL 47 try: 48 self.__corntab_time = { 49 'day_of_week': corntab[4], 50 'month_of_year': corntab[3], 51 'day_of_month': corntab[2], 52 'hour': corntab[1], 53 'minute': corntab[0] 54 } 55 except Exception: 56 return response.TASK_TIME_ILLEGAL 57 58 return response.TASK_ADD_SUCCESS 59 60 def add_task(self): 61 """ 62 add tasks 63 """ 64 if celery_models.PeriodicTask.objects.filter(name__exact=self.__name).count() > 0: 65 logger.info("{name} tasks exist".format(name=self.__name)) 66 return response.TASK_HAS_EXISTS 67 68 if self.__email["strategy"] == '始终发送' or self.__email["strategy"] == '仅失败发送': 69 if self.__email["receiver"] == '': 70 return response.TASK_EMAIL_ILLEGAL 71 72 resp = self.format_corntab() 73 if resp["success"]: 74 task, created = celery_models.PeriodicTask.objects.get_or_create(name=self.__name, task=self.__task) 75 crontab = celery_models.CrontabSchedule.objects.filter(**self.__corntab_time).first() 76 if crontab is None: 77 crontab = celery_models.CrontabSchedule.objects.create(**self.__corntab_time) 78 task.crontab = crontab 79 task.enabled = self.__switch 80 task.args = json.dumps(self.__data, ensure_ascii=False) 81 task.kwargs = json.dumps(self.__email, ensure_ascii=False) 82 task.description = self.__project 83 task.save() 84 logger.info("{name} tasks save success".format(name=self.__name)) 85 return response.TASK_ADD_SUCCESS 86 else: 87 return resp 88 89 def update_task(self): 90 """ 91 update tasks 92 """ 93 print("update tasks......") 94 print(self) 95 try: 96 if self.__email["strategy"] == '始终发送' or self.__email["strategy"] == '仅失败发送': 97 if self.__email["receiver"] == '': 98 return response.TASK_EMAIL_ILLEGAL 99 100 obj = celery_models.PeriodicTask.objects.filter(id=self.__taskId).first() 101 print("--8",obj,self.__name) 102 obj.name = self.__name 103 obj.enabled = self.__switch 104 obj.args = json.dumps(self.__data, ensure_ascii=False) 105 obj.kwargs = json.dumps(self.__email, ensure_ascii=False) 106 obj.description = self.__project 107 108 obj.save() 109 print("--9",obj.crontab_id) 110 obj2 = celery_models.CrontabSchedule.objects.filter(id=obj.crontab_id).first() 111 112 timeSplit = self.__corntab.split(' ') 113 114 obj2.minute = timeSplit[0] 115 obj2.hour = timeSplit[1] 116 obj2.day_of_month = timeSplit[2] 117 obj2.month_of_year = timeSplit[3] 118 obj2.day_of_week = timeSplit[4] 119 obj2.save() 120 121 return response.TASK_UPDATE_SUCCESS 122 except Exception as e: 123 return "定时任务更新失败,原因:{%s}"%e
启动/关闭 shell
start.sh
#!/bin/bash #启动 FasterWeb echo -e "启动 FasterWeb" cd /home/conan/conan-ta/FasterWeb/ nohup npm run build >> /home/shared/log/npm.log 2>&1 & # 启动 FasterRunner echo -e "启动 FasterRunner" cd /home/conan/conan-ta/FasterRunner/ nohup python3 manage.py runserver 0.0.0.0:9000 >> /home/shared/log/django.log 2>&1 & # 使用默认的celery.py启动 echo -e "启动celery beat" cd /home/conan/conan-ta/FasterRunner/ nohup python3 manage.py celery beat -l info >> /Users/zd/Documents/FasterRunner/logs/beat.log 2>&1 & # 使用默认的celery.py启动 echo -e "启动 celery work" cd /home/conan/conan-ta/FasterRunner/ celery multi start w1 -A FasterRunner -l info --logfile=/Users/zd/Documents/FasterRunner/logs/worker.log 2>&1 &
stop.sh
#!/bin/bash # kill django pid echo -e "shutting down django pid" pids=$(ps aux | grep "python" | grep "runserver" | awk '{print $2}') for pid in $pids do kill -9 $pid done # kill celery beat pid echo -e "shutting down celery beat pid" pids=$(ps aux | grep "celery" | grep "FasterRunner" | awk '{print $2}') for pid in $pids do kill -9 $pid done