feat: 2.6.0

1. 新增表达式校验接口
2. 修复前端任务项刷新参数错误
This commit is contained in:
byteblogs168 2024-01-17 23:49:46 +08:00
parent bd2e5b5633
commit ac0003ae1d
10 changed files with 345 additions and 51 deletions

View File

@ -1,5 +1,6 @@
package com.aizuda.easy.retry.server.web.controller; package com.aizuda.easy.retry.server.web.controller;
import com.aizuda.easy.retry.server.common.dto.DecisionConfig;
import com.aizuda.easy.retry.server.web.annotation.LoginRequired; import com.aizuda.easy.retry.server.web.annotation.LoginRequired;
import com.aizuda.easy.retry.server.web.annotation.RoleEnum; import com.aizuda.easy.retry.server.web.annotation.RoleEnum;
import com.aizuda.easy.retry.server.web.model.base.PageResult; import com.aizuda.easy.retry.server.web.model.base.PageResult;
@ -78,4 +79,10 @@ public class WorkflowController {
return workflowService.getWorkflowNameList(keywords, workflowId); return workflowService.getWorkflowNameList(keywords, workflowId);
} }
@PostMapping("/check-node-expression")
@LoginRequired(role = RoleEnum.ADMIN)
public void checkNodeExpression(@RequestBody DecisionConfig decisionConfig) {
workflowService.checkNodeExpression(decisionConfig);
}
} }

View File

@ -1,5 +1,6 @@
package com.aizuda.easy.retry.server.web.service; package com.aizuda.easy.retry.server.web.service;
import com.aizuda.easy.retry.server.common.dto.DecisionConfig;
import com.aizuda.easy.retry.server.web.model.base.PageResult; import com.aizuda.easy.retry.server.web.model.base.PageResult;
import com.aizuda.easy.retry.server.web.model.request.WorkflowQueryVO; import com.aizuda.easy.retry.server.web.model.request.WorkflowQueryVO;
import com.aizuda.easy.retry.server.web.model.request.WorkflowRequestVO; import com.aizuda.easy.retry.server.web.model.request.WorkflowRequestVO;
@ -31,4 +32,6 @@ public interface WorkflowService {
Boolean trigger(Long id); Boolean trigger(Long id);
List<WorkflowResponseVO> getWorkflowNameList(String keywords, Long workflowId); List<WorkflowResponseVO> getWorkflowNameList(String keywords, Long workflowId);
void checkNodeExpression(DecisionConfig decisionConfig);
} }

View File

@ -147,6 +147,8 @@ public class WorkflowHandler {
DecisionConfig decision = nodeInfo.getDecision(); DecisionConfig decision = nodeInfo.getDecision();
Assert.notNull(decision, () -> new EasyRetryServerException("【{}】配置信息不能为空", nodeInfo.getNodeName())); Assert.notNull(decision, () -> new EasyRetryServerException("【{}】配置信息不能为空", nodeInfo.getNodeName()));
Assert.notBlank(decision.getNodeExpression(), ()-> new EasyRetryServerException("【{}】表达式不能为空", nodeInfo.getNodeName())); Assert.notBlank(decision.getNodeExpression(), ()-> new EasyRetryServerException("【{}】表达式不能为空", nodeInfo.getNodeName()));
Assert.notNull(decision.getDefaultDecision(), () -> new EasyRetryServerException("【{}】默认决策不能为空", nodeInfo.getNodeName()));
Assert.notNull(decision.getExpressionType(), () -> new EasyRetryServerException("【{}】表达式类型不能为空", nodeInfo.getNodeName()));
workflowNode.setNodeInfo(JsonUtil.toJsonString(decision)); workflowNode.setNodeInfo(JsonUtil.toJsonString(decision));
} }

View File

@ -5,10 +5,14 @@ import cn.hutool.core.util.HashUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.aizuda.easy.retry.common.core.constant.SystemConstants; import com.aizuda.easy.retry.common.core.constant.SystemConstants;
import com.aizuda.easy.retry.common.core.enums.StatusEnum; import com.aizuda.easy.retry.common.core.enums.StatusEnum;
import com.aizuda.easy.retry.common.core.expression.ExpressionEngine;
import com.aizuda.easy.retry.common.core.expression.ExpressionFactory;
import com.aizuda.easy.retry.common.core.util.JsonUtil; import com.aizuda.easy.retry.common.core.util.JsonUtil;
import com.aizuda.easy.retry.server.common.WaitStrategy; import com.aizuda.easy.retry.server.common.WaitStrategy;
import com.aizuda.easy.retry.server.common.config.SystemProperties; import com.aizuda.easy.retry.server.common.config.SystemProperties;
import com.aizuda.easy.retry.server.common.dto.DecisionConfig;
import com.aizuda.easy.retry.server.common.dto.JobTaskConfig; import com.aizuda.easy.retry.server.common.dto.JobTaskConfig;
import com.aizuda.easy.retry.server.common.enums.ExpressionTypeEnum;
import com.aizuda.easy.retry.server.common.enums.JobTaskExecutorSceneEnum; import com.aizuda.easy.retry.server.common.enums.JobTaskExecutorSceneEnum;
import com.aizuda.easy.retry.server.common.exception.EasyRetryServerException; import com.aizuda.easy.retry.server.common.exception.EasyRetryServerException;
import com.aizuda.easy.retry.server.common.strategy.WaitStrategies; import com.aizuda.easy.retry.server.common.strategy.WaitStrategies;
@ -19,6 +23,7 @@ import com.aizuda.easy.retry.server.job.task.dto.WorkflowPartitionTaskDTO;
import com.aizuda.easy.retry.server.job.task.dto.WorkflowTaskPrepareDTO; import com.aizuda.easy.retry.server.job.task.dto.WorkflowTaskPrepareDTO;
import com.aizuda.easy.retry.server.job.task.support.WorkflowPrePareHandler; import com.aizuda.easy.retry.server.job.task.support.WorkflowPrePareHandler;
import com.aizuda.easy.retry.server.job.task.support.WorkflowTaskConverter; import com.aizuda.easy.retry.server.job.task.support.WorkflowTaskConverter;
import com.aizuda.easy.retry.server.job.task.support.expression.ExpressionInvocationHandler;
import com.aizuda.easy.retry.server.web.model.base.PageResult; import com.aizuda.easy.retry.server.web.model.base.PageResult;
import com.aizuda.easy.retry.server.web.model.request.SceneConfigRequestVO; import com.aizuda.easy.retry.server.web.model.request.SceneConfigRequestVO;
import com.aizuda.easy.retry.server.web.model.request.UserSessionVO; import com.aizuda.easy.retry.server.web.model.request.UserSessionVO;
@ -320,4 +325,13 @@ public class WorkflowServiceImpl implements WorkflowService {
return WorkflowConverter.INSTANCE.toWorkflowResponseVO(selectPage.getRecords()); return WorkflowConverter.INSTANCE.toWorkflowResponseVO(selectPage.getRecords());
} }
@Override
public void checkNodeExpression(DecisionConfig decisionConfig) {
ExpressionEngine realExpressionEngine = ExpressionTypeEnum.valueOf(decisionConfig.getExpressionType());
Assert.notNull(realExpressionEngine, () -> new EasyRetryServerException("表达式引擎不存在"));
ExpressionInvocationHandler invocationHandler = new ExpressionInvocationHandler(realExpressionEngine);
ExpressionEngine expressionEngine = ExpressionFactory.getExpressionEngine(invocationHandler);
expressionEngine.eval(decisionConfig.getNodeExpression(), new HashMap<>());
}
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -5,8 +5,8 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Easy Retry</title> <title>Easy Retry</title>
<script type="module" crossorigin src="./assets/1l1Q8kYs.js"></script> <script type="module" crossorigin src="./assets/O1TCl_bu.js"></script>
<link rel="stylesheet" crossorigin href="./assets/qOmWCRIC.css"> <link rel="stylesheet" crossorigin href="./assets/gKrsqr4E.css">
</head> </head>
<body> <body>

View File

@ -0,0 +1,264 @@
<template>
<a-modal
:visible="visible"
width="100%"
wrap-class-name="full-modal"
:footer="null"
title="日志详情"
@cancel="onCancel">
<div class="log">
<div class="scroller">
<div class="gutters">
<div style="margin-top: 4px"></div>
<div v-for="(log, index) in logList" :key="index">
<div class="gutter-element">{{ index + 1 }}</div>
<div style="height: 25px"></div>
</div>
</div>
<div class="content">
<div class="line" v-for="log in logList" :key="log.time_stamp">
<div class="flex">
<div class="text" style="color: #2db7f5">{{ timestampToDate(log.time_stamp) }}</div>
<div class="text" :style="{ color: LevelEnum[log.level].color }">
{{ log.level.length === 4 ? log.level + '\t' : log.level }}
</div>
<div class="text" style="color: #00a3a3">[{{ log.thread }}]</div>
<div class="text" style="color: #a771bf; font-weight: 500">{{ log.location }}</div>
<div class="text">:</div>
</div>
<div class="text" style="font-size: 16px">{{ log.message }}</div>
</div>
<div style="text-align: center; width: 100vw">
<a-spin :indicator="indicator" :spinning="!finished"/>
</div>
</div>
</div>
</div>
</a-modal>
</template>
<script>
import request from '@/utils/request'
export default {
name: 'JobBatchLog',
components: {},
props: {
open: {
type: Boolean,
default: false
},
record: {
type: Object,
default: () => {}
},
value: {
type: Array,
default: () => []
}
},
watch: {
value: {
deep: true,
immediate: true,
handler (val) {
this.logList = val
}
},
open: {
deep: true,
immediate: true,
handler (val) {
this.visible = val
}
}
},
data () {
return {
visible: false,
finished: false,
logList: [],
interval: null,
startId: 0,
fromIndex: 0,
indicator: <a-icon type="loading" style="font-size: 24px; color: '#d9d9d9'" spin/>,
LevelEnum: {
DEBUG: {
name: 'DEBUG',
color: '#2647cc'
},
INFO: {
name: 'INFO',
color: '#5c962c'
},
WARN: {
name: 'WARN',
color: '#da9816'
},
ERROR: {
name: 'ERROR',
color: '#dc3f41'
}
}
}
},
mounted () {
this.getLogList()
this.interval = setInterval(() => {
this.getLogList()
}, 1000)
},
methods: {
onCancel () {
clearInterval(this.interval)
this.$emit('update:open', false)
},
getLogList () {
if (!this.finished) {
request(
{
url: '/job/log/list',
method: 'get',
params: {
taskBatchId: this.record.taskBatchId,
jobId: this.record.jobId,
taskId: this.record.id,
startId: this.startId,
fromIndex: this.fromIndex,
size: 50
}
}
)
.then((res) => {
this.finished = res.data.finished
this.startId = res.data.nextStartId
this.fromIndex = res.data.fromIndex
if (res.data.message) {
this.logList.push(...res.data.message)
}
})
.catch(() => {
this.finished = true
})
} else {
clearInterval(this.interval)
}
},
timestampToDate (timestamp) {
const date = new Date(Number.parseInt(timestamp.toString()))
const year = date.getFullYear()
const month =
(date.getMonth() + 1).toString().length === 1 ? '0' + (date.getMonth() + 1) : (date.getMonth() + 1).toString()
const day = date.getDate()
const hours = date.getHours()
const minutes = date.getMinutes().toString().length === 1 ? '0' + date.getMinutes() : date.getMinutes().toString()
const seconds = date.getSeconds().toString().length === 1 ? '0' + date.getSeconds() : date.getSeconds().toString()
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
}
}
</script>
<style scoped lang="less">
.log {
height: calc(100vh - 56px);
color: #abb2bf;
background-color: #282c34;
position: relative !important;
box-sizing: border-box;
display: flex !important;
flex-direction: column;
.scroller {
height: 100%;
overflow: auto;
display: flex !important;
align-items: flex-start !important;
font-family: monospace;
line-height: 1.4;
position: relative;
z-index: 0;
.gutters {
min-height: 108px;
position: sticky;
background-color: #1e1f22;
color: #7d8799;
border: none;
flex-shrink: 0;
display: flex;
flex-direction: column;
height: 100%;
box-sizing: border-box;
inset-inline-start: 0;
z-index: 200;
.gutter-element {
height: 25px;
font-size: 14px;
padding: 0 8px 0 5px;
min-width: 20px;
text-align: right;
white-space: nowrap;
box-sizing: border-box;
color: #7d8799;
display: flex;
align-items: center;
justify-content: flex-end;
}
}
.content {
tab-size: 4;
caret-color: transparent !important;
margin: 0;
flex-grow: 2;
flex-shrink: 0;
// display: block;
white-space: pre;
word-wrap: normal;
box-sizing: border-box;
min-height: 100%;
padding: 4px 8px;
outline: none;
color: #bcbec4;
.line {
height: 25px;
caret-color: transparent !important;
font-size: 16px;
// background-color: #6699ff0b;
display: contents;
padding: 0 2px 0 6px;
.flex{
display: flex;
align-items: center;
gap: 5px;
}
.text {
font-size: 16px;
height: 25px;
}
}
}
}
}
/deep/ .ant-modal {
max-width: 100%;
top: 0;
padding-bottom: 0;
margin: 0;
}
/deep/ .ant-modal-content {
display: flex;
flex-direction: column;
height: calc(100vh);
}
/deep/ .ant-modal-body {
flex: 1;
padding: 0;
}
</style>

View File

@ -80,6 +80,9 @@
rowKey="id" rowKey="id"
@expand="getRows" @expand="getRows"
> >
<span slot="log" slot-scope="text, record">
<a @click="getLogRows(record)">点击查看</a>
</span>
<span slot="serial" slot-scope="text, record"> <span slot="serial" slot-scope="text, record">
{{ record.id }} {{ record.id }}
</span> </span>
@ -91,20 +94,8 @@
<span slot="clientInfo" slot-scope="text"> <span slot="clientInfo" slot-scope="text">
{{ text !== '' ? text.split('@')[1] : '' }} {{ text !== '' ? text.split('@')[1] : '' }}
</span> </span>
<a-table
slot="expandedRowRender"
slot-scope="record"
:columns="logColumns"
:data-source="record.logData"
:pagination="false"
rowKey="id"
>
<span slot="serial" slot-scope="text, record">
{{ record.id }}
</span>
</a-table>
</a-table> </a-table>
<job-batch-log v-if="logOpen && record" :open.sync="logOpen" :record="record" />
</a-card> </a-card>
</template> </template>
@ -115,10 +106,12 @@ import { STable } from '@/components'
import { jobLogList, jobTaskList } from '@/api/jobApi' import { jobLogList, jobTaskList } from '@/api/jobApi'
import enums from '@/utils/jobEnum' import enums from '@/utils/jobEnum'
import moment from 'moment/moment' import moment from 'moment/moment'
import JobBatchLog from './JobBatchLog'
export default { export default {
name: 'JobTaskList', name: 'JobTaskList',
components: { components: {
JobBatchLog,
AInput, AInput,
ATextarea, ATextarea,
STable STable
@ -129,6 +122,8 @@ export default {
visible: false, visible: false,
// / // /
advanced: false, advanced: false,
logOpen: false,
record: {},
// //
queryParam: { queryParam: {
startId: 0, startId: 0,
@ -139,6 +134,11 @@ export default {
taskStatus: enums.taskStatus, taskStatus: enums.taskStatus,
// //
columns: [ columns: [
{
title: '日志',
scopedSlots: { customRender: 'log' },
width: '5%'
},
{ {
title: 'ID', title: 'ID',
scopedSlots: { customRender: 'serial' }, scopedSlots: { customRender: 'serial' },
@ -275,6 +275,10 @@ export default {
onSelectChange (selectedRowKeys, selectedRows) { onSelectChange (selectedRowKeys, selectedRows) {
this.selectedRowKeys = selectedRowKeys this.selectedRowKeys = selectedRowKeys
this.selectedRows = selectedRows this.selectedRows = selectedRows
},
getLogRows (task) {
this.record = task
this.logOpen = true
} }
} }
} }