refactor(devops): 重构流水线相关代码

- 新增 WebSocket 消息解析工具类 WsMessageUtil
- 添加流水线实例节点明细查询功能
- 优化流水线重试逻辑,支持指定节点重试
- 新增多个流水线节点处理服务类
- 调整流水线模板获取方式
- 优化流水线状态检查逻辑
This commit is contained in:
2025-07-25 23:16:49 +08:00
parent 0497f13b89
commit d9a622e5bd
37 changed files with 535 additions and 206 deletions
@@ -8,6 +8,14 @@ export function startPipeline(data) {
}) })
} }
export function retryPipeline(data) {
return request({
url: 'api/devops/pipelineInstance/retry',
method: 'post',
data
})
}
export function restartPipeline(data) { export function restartPipeline(data) {
return request({ return request({
url: 'api/devops/pipelineInstance/restart', url: 'api/devops/pipelineInstance/restart',
+6 -1
View File
@@ -1,3 +1,7 @@
/**
* 在modules中定义后,需要到这注册
* @type {any}
*/
const getters = { const getters = {
deployUploadApi: state => state.api.deployUploadApi, deployUploadApi: state => state.api.deployUploadApi,
databaseUploadApi: state => state.api.databaseUploadApi, databaseUploadApi: state => state.api.databaseUploadApi,
@@ -19,6 +23,7 @@ const getters = {
druidSqlApi: state => state.api.druidSqlApi, druidSqlApi: state => state.api.druidSqlApi,
swaggerApi: state => state.api.swaggerApi, swaggerApi: state => state.api.swaggerApi,
websocketApi: state => state.api.websocketApi, websocketApi: state => state.api.websocketApi,
sidebarRouters: state => state.permission.sidebarRouters sidebarRouters: state => state.permission.sidebarRouters,
statusConst: state => state.pipeline.statusConst
} }
export default getters export default getters
@@ -0,0 +1,15 @@
const pipeline = {
/**
* 全局常量
*/
state: {
statusConst: {
pending: { code: 'pending', label: '未开始', color: '#C0C4CC', icon: 'el-icon-time' },
running: { code: 'running', label: '运行中', color: '#409EFF', icon: 'el-icon-loading' },
success: { code: 'success', label: '执行成功', color: '#67C23A', icon: 'el-icon-success' },
fail: { code: 'fail', label: '执行失败', color: '#F56C6C', icon: 'el-icon-error' }
}
}
}
export default pipeline
View File
+4 -2
View File
@@ -30,7 +30,9 @@ service.interceptors.request.use(
// response 拦截器 // response 拦截器
service.interceptors.response.use( service.interceptors.response.use(
response => { response => {
return response.data const data = response.data
// console.error('data', data)
return data
}, },
error => { error => {
// 兼容blob下载出错json提示 // 兼容blob下载出错json提示
@@ -57,7 +59,7 @@ service.interceptors.response.use(
return Promise.reject(error) return Promise.reject(error)
} }
} }
console.log(code) // console.log(code)
if (code) { if (code) {
if (code === 401) { if (code === 401) {
store.dispatch('LogOut').then(() => { store.dispatch('LogOut').then(() => {
@@ -1,118 +1,60 @@
<template> <template>
<div <div
class="box-card" class="box-card"
:style="{ :style="cardStyle"
borderTopWidth: '5px',
borderTopStyle: 'solid',
borderTopColor: statusColorConst[pipelineData.currentNodeStatus ? pipelineData.currentNodeStatus : statusColorConst.pending.code].color
}"
> >
<div> <div>
<div style="float: left;"> <div style="float: left;">
<i <i :class="statusIconClass" :style="statusIconStyle" />
v-if="pipelineData.currentNodeStatus && pipelineData.currentNodeStatus === statusColorConst.pending.code"
class="el-icon-time"
:style="{
fontSize: '22px',
color: statusColorConst.pending.color
}"
/>
<i
v-else-if="pipelineData.currentNodeStatus && pipelineData.currentNodeStatus === statusColorConst.running.code"
class="el-icon-loading"
:style="{
fontSize: '22px',
color: statusColorConst.running.color
}"
/>
<i
v-else-if="pipelineData.currentNodeStatus && pipelineData.currentNodeStatus === statusColorConst.success.code"
class="el-icon-success"
:style="{
fontSize: '22px',
color: statusColorConst.success.color
}"
/>
<i
v-else-if="pipelineData.currentNodeStatus && pipelineData.currentNodeStatus === statusColorConst.fail.code"
class="el-icon-error"
:style="{
fontSize: '22px',
color: statusColorConst.fail.color
}"
/>
<i
v-else
class="el-icon-time"
:style="{
fontSize: '22px',
color: statusColorConst.pending.color
}"
/>
</div> </div>
<div class="box-name">{{ pipelineData.name }}</div> <div class="box-name">{{ currentTemplateData.name }}</div>
<div style="clear: both" /> <div style="clear: both" />
</div> </div>
<div class="box-current-node"> <div class="box-current-node">
<a @click="onCurrentNodeClick"> <a @click="onCurrentNodeClick">
{{ pipelineData.currentNodeMsg ? pipelineData.currentNodeMsg : statusColorConst.pending.label }} {{ currentNodeDisplayText }}
</a> </a>
</div> </div>
<el-row> <el-row>
<el-col :span="12" style="text-align: left"> <el-col :span="12" style="text-align: left">
<!-- 流水线状态为fail,且节点支持重试 --> <!-- 流水线节点状态为fail,且节点支持重试 -->
<div v-if="pipelineData.currentNodeStatus && pipelineData.currentNodeStatus === 'fail' && pipelineData.retry === true"> <div v-if="showRetryButton">
<div class="box-buttons"> <div class="box-buttons">
{{ '1' }}
<el-button <el-button
v-prevent-re-click="5000"
type="text" type="text"
size="medium" size="medium"
:style="{ :style="getButtonStyle()"
padding: 0,
margin: '0 10px 0 10px',
color: statusColorConst.running.color
}"
@click="onNodeRetryClick" @click="onNodeRetryClick"
> >
重试 重试
</el-button> </el-button>
</div> </div>
</div> </div>
<!-- 流水线状态为success,且有需要满足条件的操作按钮 --> <!-- 流水线节点状态为success,且有操作按钮 -->
<div v-else-if="pipelineData.currentNodeStatus && pipelineData.currentNodeStatus === statusColorConst.success.code && pipelineData.buttons && pipelineData.buttons.length > 0"> <div v-else-if="showOperationButtons">
<div class="box-buttons"> <div class="box-buttons">
{{ '2' }}
<el-button <el-button
v-for="buttonItem in pipelineData.buttons" v-for="buttonItem in currentTemplateData.buttons"
:key="buttonItem.service" :key="buttonItem.service"
size="medium" size="medium"
type="text" type="text"
:style="{ :style="getButtonStyle(buttonItem)"
padding: 0,
margin: '0 10px 0 10px',
color: ['apply','agree','ok','success'].includes(buttonItem.code) ? statusColorConst.running.color : statusColorConst.fail.color,
display: ['success', 'fail'].includes(buttonItem.code) && pipelineData.status === buttonItem.code ? '' : 'none'
}"
@click="onNodeOperationClick(buttonItem)" @click="onNodeOperationClick(buttonItem)"
> >
{{ buttonItem.title }} {{ buttonItem.title }}
</el-button> </el-button>
</div> </div>
</div> </div>
<!-- 流水线状态非pending,且有操作按钮 --> <!-- 流水线节点状态为running,且有操作按钮 -->
<div v-else-if="pipelineData.currentNodeStatus && pipelineData.currentNodeStatus !== statusColorConst.pending.code && pipelineData.buttons && pipelineData.buttons.length > 0"> <div v-else-if="showOperationButtons">
<div class="box-buttons"> <div class="box-buttons">
{{ '3' }}
<el-button <el-button
v-for="buttonItem in pipelineData.buttons" v-for="buttonItem in currentTemplateData.buttons"
:key="buttonItem.service" :key="buttonItem.service"
size="medium" size="medium"
type="text" type="text"
:style="{ :style="getButtonStyle(buttonItem)"
padding: 0,
margin: '0 10px 0 10px',
color: ['apply','agree','ok','success'].includes(buttonItem.code) ? statusColorConst.running.color : statusColorConst.fail.color
}"
@click="onNodeOperationClick(buttonItem)" @click="onNodeOperationClick(buttonItem)"
> >
{{ buttonItem.title }} {{ buttonItem.title }}
@@ -131,10 +73,27 @@
</el-col> </el-col>
<div style="clear: both" /> <div style="clear: both" />
</el-row> </el-row>
<cute-preview-drawer ref="detailDrawer" :title="pipelineData.name"> <cute-preview-drawer ref="detailDrawer" :title="currentTemplateData.name">
<div style="padding: 20px"> <el-timeline>
这里是明细 <el-timeline-item timestamp="2018/4/12" placement="top">
</div> <el-card>
<h4>更新 Github 模板</h4>
<p>王小虎 提交于 2018/4/12 20:46</p>
</el-card>
</el-timeline-item>
<el-timeline-item timestamp="2018/4/3" placement="top">
<el-card>
<h4>更新 Github 模板</h4>
<p>王小虎 提交于 2018/4/3 20:46</p>
</el-card>
</el-timeline-item>
<el-timeline-item timestamp="2018/4/2" placement="top">
<el-card>
<h4>更新 Github 模板</h4>
<p>王小虎 提交于 2018/4/2 20:46</p>
</el-card>
</el-timeline-item>
</el-timeline>
</cute-preview-drawer> </cute-preview-drawer>
</div> </div>
</template> </template>
@@ -142,7 +101,9 @@
<script> <script>
import dayjs from 'dayjs' import dayjs from 'dayjs'
import CutePreviewDrawer from '@/views/components/dev/CutePreviewDrawer' import CutePreviewDrawer from '@/views/components/dev/CutePreviewDrawer'
import { mapGetters } from 'vuex'
import CsMessage from '@/utils/elementui/CsMessage'
import { retryPipeline } from '@/api/devops/pipelineInstance'
export default { export default {
name: 'CutePipelineNode', name: 'CutePipelineNode',
components: { CutePreviewDrawer }, components: { CutePreviewDrawer },
@@ -153,34 +114,137 @@ export default {
default: function() { default: function() {
return {} return {}
} }
},
instanceData: {
type: Object,
required: false,
default: function() {
return {}
}
} }
}, },
data() { data() {
return { return {
statusColorConst: { currentTemplateData: {},
pending: { code: 'pending', label: '未开始', color: '#C0C4CC' }, currentInstanceData: {}
running: { code: 'running', label: '运行中', color: '#409EFF' }, }
success: { code: 'success', label: '执行成功', color: '#67C23A' }, },
fail: { code: 'fail', label: '执行失败', color: '#F56C6C' } // 动态计算
}, computed: {
executeTimeStr: '', ...mapGetters([
pipelineData: {} 'statusConst'
]),
cardStyle() {
const statusKey = this.currentTemplateData.currentNodeStatus || this.statusConst.pending.code
const statusConfig = this.statusConst[statusKey] || this.statusConst.pending
return {
borderTop: `5px solid ${statusConfig.color}`
}
},
statusIconClass() {
const status = this.currentTemplateData.currentNodeStatus || this.statusConst.pending.code
return this.statusConst[status].icon || 'el-icon-time'
},
statusIconStyle() {
return {
fontSize: '22px',
color: this.getStatusColor()
}
},
currentNodeDisplayText() {
return this.currentTemplateData.currentNodeMsg || this.statusConst.pending.label
},
showRetryButton() {
return this.currentTemplateData.currentNodeStatus === this.statusConst.fail.code &&
this.currentTemplateData.retry === true
},
showOperationButtons() {
const status = this.currentTemplateData.currentNodeStatus
const hasButtons = this.currentTemplateData.buttons && this.currentTemplateData.buttons.length > 0
return hasButtons && (
status === this.statusConst.success.code ||
status === this.statusConst.running.code
)
},
executeTimeStr() {
return this.renderDateTimeStr(this.currentTemplateData)
} }
}, },
watch: { watch: {
templateData: { templateData: {
handler(newVal, oldVal) { handler(newVal, oldVal) {
this.pipelineData = { ...newVal } this.currentTemplateData = { ...newVal }
this.executeTimeStr = this.renderDateTimeStr(newVal) },
deep: true
},
instanceData: {
handler(newVal, oldVal) {
this.currentInstanceData = { ...newVal }
}, },
deep: true deep: true
} }
}, },
mounted() { mounted() {
this.pipelineData = this.templateData // 初始化时,设置流水线模板
this.executeTimeStr = this.renderDateTimeStr(this.templateData) this.currentTemplateData = this.templateData
this.currentInstanceData = this.instanceData
}, },
methods: { methods: {
getStatusColor() {
const status = this.currentTemplateData.currentNodeStatus || this.statusConst.pending.code
const statusConfig = this.statusConst[status] || this.statusConst.pending
return statusConfig.color
},
getButtonStyle(buttonItem) {
if (buttonItem) {
const isPositiveAction = ['apply', 'agree', 'ok', 'success'].includes(buttonItem.code)
const status = this.currentTemplateData.currentNodeStatus
const color = isPositiveAction
? this.statusConst.running.color
: this.statusConst.fail.color
let display = ''
if (status === this.statusConst.running.code) {
display = this.getRunningNodeButtonVisibleStatus(buttonItem)
} else if (status === this.statusConst.success.code) {
display = this.getSuccessNodeButtonVisibleStatus(buttonItem)
}
return {
padding: 0,
margin: '0 10px 0 10px',
color,
display
}
}
return {
padding: 0,
margin: '0 10px 0 10px',
color: this.statusConst.running.color
}
},
/**
* 运行中节点的按钮显示
* @param buttonItem
* @returns {string}
*/
getRunningNodeButtonVisibleStatus(buttonItem) {
if (buttonItem.type === 'qrCodeDialog') {
return 'none'
}
return ''
},
/**
* 成功节点的按钮显示
* @param buttonItem
* @returns {string}
*/
getSuccessNodeButtonVisibleStatus(buttonItem) {
const that = this
// 根据按钮编码
if (buttonItem.code === that.currentTemplateData.currentNodeStatus) {
return ''
}
return 'none'
},
renderDateTimeStr(newVal) { renderDateTimeStr(newVal) {
let executeTimeStr = '' let executeTimeStr = ''
switch (newVal.status) { switch (newVal.status) {
@@ -190,7 +254,7 @@ export default {
case 'running': case 'running':
case 'success': case 'success':
case 'fail': case 'fail':
if (newVal.currentNodeStatus !== this.statusColorConst.pending.code) { if (newVal.currentNodeStatus !== this.statusConst.pending.code) {
if (newVal.startTime && newVal.finishTime) { if (newVal.startTime && newVal.finishTime) {
executeTimeStr = this.formatTimeDifference(newVal.startTime, newVal.finishTime) executeTimeStr = this.formatTimeDifference(newVal.startTime, newVal.finishTime)
return executeTimeStr return executeTimeStr
@@ -240,21 +304,27 @@ export default {
return `${diffInMilliseconds} 毫秒` return `${diffInMilliseconds} 毫秒`
}, },
onCurrentNodeClick() { onCurrentNodeClick() {
const pipelineData = this.pipelineData || { click: false } const currentTemplateData = this.currentTemplateData || { click: false }
if (!pipelineData.click) { if (!currentTemplateData.click) {
console.warn('当前流水线节点不支持查看明细') CsMessage.Warning('当前流水线节点不支持查看明细')
return return
} }
console.error('templateData', this.templateData) console.error('templateData', this.templateData)
this.$refs.detailDrawer.show() this.$refs.detailDrawer.show()
}, },
onNodeRetryClick() { onNodeRetryClick() {
const pipelineData = this.pipelineData || { retry: false } const currentTemplateData = this.currentTemplateData || { retry: false }
if (!pipelineData.retry) { if (!currentTemplateData.retry) {
console.warn('当前流水线节点不支持重试') CsMessage.Warning('当前流水线节点不支持重试')
return return
} }
console.error('pipelineData', this.pipelineData) const data = {
...this.currentInstanceData,
code: this.currentTemplateData.code
}
// console.error('data', data)
retryPipeline(data)
this.$emit('retry', data)
}, },
onNodeOperationClick(buttonInfo) { onNodeOperationClick(buttonInfo) {
console.error('buttonInfo', buttonInfo) console.error('buttonInfo', buttonInfo)
@@ -17,6 +17,8 @@
v-for="(template, index) in dynamicInstance.nodes" v-for="(template, index) in dynamicInstance.nodes"
:key="template.code" :key="template.code"
:template-data.sync="dynamicInstance.nodes[index]" :template-data.sync="dynamicInstance.nodes[index]"
:instance-data.sync="dynamicInstance"
@retry="onPipelineRetrySuccess"
/> />
</div> </div>
<div v-else class="box-pipeline-content"> <div v-else class="box-pipeline-content">
@@ -24,6 +26,7 @@
v-for="(template, index) in dynamicTemplateList" v-for="(template, index) in dynamicTemplateList"
:key="template.code" :key="template.code"
:template-data.sync="dynamicTemplateList[index]" :template-data.sync="dynamicTemplateList[index]"
:instance-data.sync="dynamicInstance"
/> />
</div> </div>
</div> </div>
@@ -462,7 +465,7 @@ export default {
dynamicStartButtonLoading: false, dynamicStartButtonLoading: false,
// 当前模板 // 当前模板
dynamicTemplate: { dynamicTemplate: {
id: 6 id: 21
}, },
// 当前流水线节点模板 // 当前流水线节点模板
dynamicTemplateList: [], dynamicTemplateList: [],
@@ -553,6 +556,7 @@ export default {
// 所有节点执行成功 // 所有节点执行成功
if (that.dynamicWsClient) { if (that.dynamicWsClient) {
that.dynamicWsClient.close() that.dynamicWsClient.close()
sessionStorage.removeItem('pipelineInstanceId')
} }
that.dynamicStartupStatus = that.dynamicStartupStatusMap.start.code that.dynamicStartupStatus = that.dynamicStartupStatusMap.start.code
} else { } else {
@@ -597,7 +601,7 @@ export default {
* 启动流水线 * 启动流水线
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async startPipelineTest(data) { async startPipelineTest() {
const that = this const that = this
const args = { const args = {
id: that.dynamicTemplate.id id: that.dynamicTemplate.id
@@ -625,6 +629,10 @@ export default {
console.error('error', e) console.error('error', e)
that.dynamicStartButtonLoading = false that.dynamicStartButtonLoading = false
} }
},
onPipelineRetrySuccess(data) {
sessionStorage.setItem('pipelineInstanceId', data.instanceId)
this.connectWebSocketServer(data.instanceId)
} }
} }
} }
@@ -0,0 +1,13 @@
package cn.odboy.framework.websocket.model;
import cn.odboy.base.CsObject;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class WsSidVo extends CsObject {
private String username;
private String bizCode;
private String param;
}
@@ -0,0 +1,27 @@
package cn.odboy.framework.websocket.util;
import cn.odboy.framework.exception.BadRequestException;
import cn.odboy.framework.websocket.model.WsSidVo;
import lombok.experimental.UtilityClass;
/**
* WebSocket消息解析工具
*
* @author odboy
* @date 2025-07-25
*/
@UtilityClass
public class WsMessageUtil {
public static WsSidVo parseSid(String sid) {
// sid {username}_{bizCode}_{param}
String[] sids = sid.split("_");
if (sids.length != 3) {
throw new BadRequestException("sid格式异常");
}
WsSidVo sidVo = new WsSidVo();
sidVo.setUsername(sids[0]);
sidVo.setBizCode(sids[1]);
sidVo.setParam(sids[2]);
return sidVo;
}
}
@@ -3,6 +3,7 @@ package cn.odboy.devops.controller;
import cn.odboy.devops.dal.dataobject.PipelineInstanceTb; import cn.odboy.devops.dal.dataobject.PipelineInstanceTb;
import cn.odboy.devops.dal.dataobject.PipelineTemplateTb; import cn.odboy.devops.dal.dataobject.PipelineTemplateTb;
import cn.odboy.devops.dal.model.DevOpsQueryLastPipelineDetailArgs; import cn.odboy.devops.dal.model.DevOpsQueryLastPipelineDetailArgs;
import cn.odboy.devops.framework.pipeline.model.PipelineInstanceVo;
import cn.odboy.devops.service.core.PipelineInstanceService; import cn.odboy.devops.service.core.PipelineInstanceService;
import cn.odboy.devops.service.core.PipelineTemplateService; import cn.odboy.devops.service.core.PipelineTemplateService;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
@@ -68,21 +69,8 @@ public class DevopsPipelineInstanceController {
@ApiOperation("流水线节点重试") @ApiOperation("流水线节点重试")
@PostMapping(value = "/retry") @PostMapping(value = "/retry")
@PreAuthorize("@el.check()") @PreAuthorize("@el.check()")
public ResponseEntity<?> retryPipelineNode(@RequestBody PipelineTemplateTb args) { public ResponseEntity<?> retryPipelineNode(@RequestBody PipelineInstanceVo args) {
String appName = "cuteops"; return ResponseEntity.ok(pipelineInstanceService.retryPipelineNode(args));
String envCode = "daily";
PipelineTemplateTb pipelineTemplateTb = pipelineTemplateService.getPipelineTemplateById(args.getId());
PipelineInstanceTb pipelineInstanceTb = new PipelineInstanceTb();
pipelineInstanceTb.setTemplateId(pipelineTemplateTb.getId());
pipelineInstanceTb.setInstanceName("流水线测试");
pipelineInstanceTb.setTemplateType(pipelineTemplateTb.getType());
pipelineInstanceTb.setContextName(appName);
pipelineInstanceTb.setEnv(envCode);
pipelineInstanceTb.setTemplateContent(pipelineTemplateTb.getTemplate());
pipelineInstanceTb.setInstanceId(1947604330727079936L);
String retryNodeCode = "node_build_java";
pipelineInstanceService.restartPipeline(pipelineInstanceTb, retryNodeCode);
return ResponseEntity.ok(pipelineInstanceService.restartPipeline(pipelineInstanceTb, retryNodeCode));
} }
@ApiOperation("查询流水线明细") @ApiOperation("查询流水线明细")
@@ -18,7 +18,7 @@ public abstract class AbstractPipelineNodeJobService {
return pipelineInstanceNodeService.getPipelineInstanceNodeByArgs(instanceId, code); return pipelineInstanceNodeService.getPipelineInstanceNodeByArgs(instanceId, code);
} }
public void addLog(PipelineInstanceNodeTb pipelineInstanceNode, String stepName, PipelineStatusEnum stepStatus, String stepMsg, Date finishTime) { public void addPipelineInstanceNodeDetailLog(PipelineInstanceNodeTb pipelineInstanceNode, String stepName, PipelineStatusEnum stepStatus, String stepMsg, Date finishTime) {
if (pipelineInstanceNode != null) { if (pipelineInstanceNode != null) {
pipelineInstanceNodeDetailService.addLog(pipelineInstanceNode.getId(), pipelineInstanceNode.getCode(), stepName, stepStatus, stepMsg, finishTime); pipelineInstanceNodeDetailService.addLog(pipelineInstanceNode.getId(), pipelineInstanceNode.getCode(), stepName, stepStatus, stepMsg, finishTime);
} }
@@ -45,16 +45,6 @@ public class PipelineJobBean implements InterruptableJob {
PipelineInstanceTb pipelineInstanceTb = (PipelineInstanceTb) jobDataMap.get(PipelineConst.INSTANCE); PipelineInstanceTb pipelineInstanceTb = (PipelineInstanceTb) jobDataMap.get(PipelineConst.INSTANCE);
pipelineInstanceTb.setStatus(PipelineStatusEnum.PENDING.getCode()); pipelineInstanceTb.setStatus(PipelineStatusEnum.PENDING.getCode());
// // 模拟终止流水线
// ThreadUtil.execAsync(() -> {
// ThreadUtil.safeSleep(6000);
// try {
// pipelineJobManage.interruptJob(pipelineInstanceTb.getInstanceId());
// } catch (SchedulerException e) {
// log.error("终止流水线异常", e);
// }
// });
if (StrUtil.isBlank(retryNodeCode)) { if (StrUtil.isBlank(retryNodeCode)) {
// 创建实例 // 创建实例
pipelineInstanceMapper.insert(pipelineInstanceTb); pipelineInstanceMapper.insert(pipelineInstanceTb);
@@ -11,5 +11,5 @@ import org.quartz.JobDataMap;
* @date 2025-07-21 * @date 2025-07-21
*/ */
public interface PipelineNodeJobExecutor { public interface PipelineNodeJobExecutor {
PipelineNodeJobExecuteResult execute(JobDataMap contextArgs) throws BadRequestException; PipelineNodeJobExecuteResult execute(JobDataMap contextArgs) throws Exception;
} }
@@ -46,17 +46,17 @@ public class PipelineNodeStepLogAspect extends AbstractPipelineNodeJobService {
} }
PipelineNodeStepLog pipelineNodeStepLog = method.getAnnotation(PipelineNodeStepLog.class); PipelineNodeStepLog pipelineNodeStepLog = method.getAnnotation(PipelineNodeStepLog.class);
try { try {
addLog(currentNodeInfo, pipelineNodeStepLog.value(), PipelineStatusEnum.RUNNING, PipelineStatusEnum.RUNNING.getDesc(), null); addPipelineInstanceNodeDetailLog(currentNodeInfo, pipelineNodeStepLog.value(), PipelineStatusEnum.RUNNING, PipelineStatusEnum.RUNNING.getDesc(), null);
Object result = joinPoint.proceed(); Object result = joinPoint.proceed();
addLog(currentNodeInfo, pipelineNodeStepLog.value(), PipelineStatusEnum.SUCCESS, PipelineStatusEnum.SUCCESS.getDesc(), new Date()); addPipelineInstanceNodeDetailLog(currentNodeInfo, pipelineNodeStepLog.value(), PipelineStatusEnum.SUCCESS, PipelineStatusEnum.SUCCESS.getDesc(), new Date());
return result; return result;
} catch (BadRequestException e) { } catch (BadRequestException e) {
log.error("执行失败", e); log.error("执行失败", e);
addLog(currentNodeInfo, pipelineNodeStepLog.value(), PipelineStatusEnum.FAIL, e.getMessage(), new Date()); addPipelineInstanceNodeDetailLog(currentNodeInfo, pipelineNodeStepLog.value(), PipelineStatusEnum.FAIL, e.getMessage(), new Date());
throw e; throw e;
} catch (Throwable e) { } catch (Throwable e) {
log.error("执行失败", e); log.error("执行失败", e);
addLog(currentNodeInfo, pipelineNodeStepLog.value(), PipelineStatusEnum.FAIL, PipelineStatusEnum.FAIL.getDesc(), new Date()); addPipelineInstanceNodeDetailLog(currentNodeInfo, pipelineNodeStepLog.value(), PipelineStatusEnum.FAIL, PipelineStatusEnum.FAIL.getDesc(), new Date());
throw e; throw e;
} }
} }
@@ -1,9 +1,12 @@
package cn.odboy.devops.framework.pipeline.model; package cn.odboy.devops.framework.pipeline.model;
import cn.odboy.devops.dal.dataobject.PipelineInstanceNodeDetailTb;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List;
/** /**
* 流水线节点数据 * 流水线节点数据
@@ -12,7 +15,7 @@ import java.util.Date;
*/ */
@Getter @Getter
@Setter @Setter
public class PipelineNodeDataVo extends PipelineNodeTemplateVo { public class PipelineInstanceNodeVo extends PipelineNodeTemplateVo {
/** /**
* 节点创建时间 * 节点创建时间
*/ */
@@ -41,4 +44,8 @@ public class PipelineNodeDataVo extends PipelineNodeTemplateVo {
* 流水线实例状态 * 流水线实例状态
*/ */
private String status; private String status;
/**
* 节点步骤明细记录
*/
private List<PipelineInstanceNodeDetailTb> historys = new ArrayList<>();
} }
@@ -1,6 +1,8 @@
package cn.odboy.devops.framework.pipeline.model; package cn.odboy.devops.framework.pipeline.model;
import cn.odboy.base.CsObject; import cn.odboy.base.CsObject;
import com.alibaba.fastjson2.annotation.JSONField;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@@ -14,10 +16,11 @@ import java.util.List;
@Getter @Getter
@Setter @Setter
public class PipelineInstanceVo extends CsObject { public class PipelineInstanceVo extends CsObject {
private Long instanceId; private String instanceId;
private String instanceName; private String instanceName;
private String env; private String env;
private String code;
private String contextName; private String contextName;
private String contextParams; private String contextParams;
private List<PipelineNodeDataVo> nodes; private List<PipelineInstanceNodeVo> nodes;
} }
@@ -42,7 +42,9 @@ public class PipelineNodeTemplateVo extends CsObject {
*/ */
protected String detailType = ""; protected String detailType = "";
/** /**
* 默认参数 * 默认参数<br/>
* execute: 执行调用
* describe: 明细调用
*/ */
protected Map<String, Object> parameters = new HashMap<>(); protected Map<String, Object> parameters = new HashMap<>();
/** /**
@@ -23,5 +23,7 @@ public interface PipelineInstanceNodeDetailService extends IService<PipelineInst
void removeByNodeIds(List<Long> nodeIds); void removeByNodeIds(List<Long> nodeIds);
PipelineInstanceNodeDetailTb getLastPipelineInstanceNodeDetailByArgs(PipelineInstanceNodeTb instanceNode, Long nodeId, String nodeCode); PipelineInstanceNodeDetailTb getLastPipelineInstanceNodeDetailByArgs(PipelineInstanceNodeTb instanceNode);
List<PipelineInstanceNodeDetailTb> queryPipelineInstanceNodeDetailByArgs(PipelineInstanceNodeTb instanceNodeTb);
} }
@@ -15,9 +15,9 @@ import cn.odboy.devops.framework.pipeline.model.PipelineInstanceVo;
public interface PipelineInstanceService { public interface PipelineInstanceService {
StartPipelineResultVo startPipeline(PipelineInstanceTb pipelineInstanceTb); StartPipelineResultVo startPipeline(PipelineInstanceTb pipelineInstanceTb);
StartPipelineResultVo restartPipeline(PipelineInstanceTb pipelineInstanceTb, String retryNodeCode);
PipelineInstanceVo queryLastPipelineDetail(String instanceId); PipelineInstanceVo queryLastPipelineDetail(String instanceId);
void queryLastPipelineDetailWs(String sid); void queryLastPipelineDetailWs(String sid);
StartPipelineResultVo retryPipelineNode(PipelineInstanceVo args);
} }
@@ -26,9 +26,4 @@ public class PipelineTemplateService {
} }
return pipelineTemplateTb; return pipelineTemplateTb;
} }
public String getPipelineTemplateContentById(Long id) {
PipelineTemplateTb pipelineTemplateTb = getPipelineTemplateById(id);
return pipelineTemplateTb.getTemplate();
}
} }
@@ -11,6 +11,7 @@ import com.anwen.mongo.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
@@ -40,22 +41,27 @@ public class PipelineInstanceNodeDetailServiceImpl extends ServiceImpl<PipelineI
@Override @Override
public PipelineInstanceNodeDetailTb getPipelineInstanceNodeDetailByArgs(Long nodeId, String nodeCode, String stepName) { public PipelineInstanceNodeDetailTb getPipelineInstanceNodeDetailByArgs(Long nodeId, String nodeCode, String stepName) {
return one(new LambdaQueryChainWrapper<>(getBaseMapper(), PipelineInstanceNodeDetailTb.class).eq(PipelineInstanceNodeDetailTb::getNodeId, nodeId).eq(PipelineInstanceNodeDetailTb::getNodeCode, nodeCode).eq(PipelineInstanceNodeDetailTb::getStepName, stepName)); return one(new LambdaQueryChainWrapper<>(getBaseMapper(), PipelineInstanceNodeDetailTb.class)
.eq(PipelineInstanceNodeDetailTb::getNodeId, nodeId).eq(PipelineInstanceNodeDetailTb::getNodeCode, nodeCode)
.eq(PipelineInstanceNodeDetailTb::getStepName, stepName)
);
} }
@Override @Override
public void removeByNodeIds(List<Long> nodeIds) { public void removeByNodeIds(List<Long> nodeIds) {
if (CollUtil.isNotEmpty(nodeIds)) { if (CollUtil.isNotEmpty(nodeIds)) {
remove(new LambdaUpdateChainWrapper<>(getBaseMapper(), PipelineInstanceNodeDetailTb.class).in(PipelineInstanceNodeDetailTb::getNodeId, nodeIds)); remove(new LambdaUpdateChainWrapper<>(getBaseMapper(), PipelineInstanceNodeDetailTb.class)
.in(PipelineInstanceNodeDetailTb::getNodeId, nodeIds)
);
} }
} }
@Override @Override
public PipelineInstanceNodeDetailTb getLastPipelineInstanceNodeDetailByArgs(PipelineInstanceNodeTb instanceNode, Long nodeId, String nodeCode) { public PipelineInstanceNodeDetailTb getLastPipelineInstanceNodeDetailByArgs(PipelineInstanceNodeTb instanceNode) {
List<PipelineInstanceNodeDetailTb> pipelineInstanceNodeDetailList = list(new LambdaQueryChainWrapper<>(getBaseMapper(), PipelineInstanceNodeDetailTb.class) List<PipelineInstanceNodeDetailTb> pipelineInstanceNodeDetailList = list(new LambdaQueryChainWrapper<>(getBaseMapper(), PipelineInstanceNodeDetailTb.class)
.eq(PipelineInstanceNodeDetailTb::getNodeId, nodeId) .eq(PipelineInstanceNodeDetailTb::getNodeId, instanceNode.getId())
.eq(PipelineInstanceNodeDetailTb::getNodeCode, nodeCode) .eq(PipelineInstanceNodeDetailTb::getNodeCode, instanceNode.getCode())
.orderByDesc(PipelineInstanceNodeDetailTb::getId) .orderByDesc(PipelineInstanceNodeDetailTb::getStartTime)
); );
// 明细为空,返回节点状态 // 明细为空,返回节点状态
if (pipelineInstanceNodeDetailList.isEmpty()) { if (pipelineInstanceNodeDetailList.isEmpty()) {
@@ -87,4 +93,13 @@ public class PipelineInstanceNodeDetailServiceImpl extends ServiceImpl<PipelineI
pipelineInstanceNodeDetailTb.setStepMsg(pipelineInstanceNodeDetailTb.getStepName()); pipelineInstanceNodeDetailTb.setStepMsg(pipelineInstanceNodeDetailTb.getStepName());
return pipelineInstanceNodeDetailTb; return pipelineInstanceNodeDetailTb;
} }
@Override
public List<PipelineInstanceNodeDetailTb> queryPipelineInstanceNodeDetailByArgs(PipelineInstanceNodeTb instanceNode) {
return list(new LambdaQueryChainWrapper<>(getBaseMapper(), PipelineInstanceNodeDetailTb.class)
.eq(PipelineInstanceNodeDetailTb::getNodeId, instanceNode.getId())
.eq(PipelineInstanceNodeDetailTb::getNodeCode, instanceNode.getCode())
.orderByAsc(PipelineInstanceNodeDetailTb::getStartTime)
);
}
} }
@@ -2,6 +2,7 @@ package cn.odboy.devops.service.core.impl;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.thread.ThreadUtil; import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.StrUtil;
import cn.odboy.devops.constant.pipeline.PipelineConst; import cn.odboy.devops.constant.pipeline.PipelineConst;
import cn.odboy.devops.constant.pipeline.PipelineStatusEnum; import cn.odboy.devops.constant.pipeline.PipelineStatusEnum;
import cn.odboy.devops.dal.dataobject.PipelineInstanceNodeDetailTb; import cn.odboy.devops.dal.dataobject.PipelineInstanceNodeDetailTb;
@@ -11,15 +12,17 @@ import cn.odboy.devops.dal.model.StartPipelineResultVo;
import cn.odboy.devops.dal.mysql.PipelineInstanceMapper; import cn.odboy.devops.dal.mysql.PipelineInstanceMapper;
import cn.odboy.devops.dal.redis.PipelineInstanceDAO; import cn.odboy.devops.dal.redis.PipelineInstanceDAO;
import cn.odboy.devops.framework.pipeline.core.PipelineJobManage; import cn.odboy.devops.framework.pipeline.core.PipelineJobManage;
import cn.odboy.devops.framework.pipeline.model.PipelineInstanceNodeVo;
import cn.odboy.devops.framework.pipeline.model.PipelineInstanceVo; import cn.odboy.devops.framework.pipeline.model.PipelineInstanceVo;
import cn.odboy.devops.framework.pipeline.model.PipelineNodeDataVo;
import cn.odboy.devops.service.core.PipelineInstanceNodeDetailService; import cn.odboy.devops.service.core.PipelineInstanceNodeDetailService;
import cn.odboy.devops.service.core.PipelineInstanceNodeService; import cn.odboy.devops.service.core.PipelineInstanceNodeService;
import cn.odboy.devops.service.core.PipelineInstanceService; import cn.odboy.devops.service.core.PipelineInstanceService;
import cn.odboy.framework.exception.BadRequestException; import cn.odboy.framework.exception.BadRequestException;
import cn.odboy.framework.websocket.model.WsSidVo;
import cn.odboy.framework.websocket.context.CsWsClientManager; import cn.odboy.framework.websocket.context.CsWsClientManager;
import cn.odboy.framework.websocket.context.CsWsMessage; import cn.odboy.framework.websocket.context.CsWsMessage;
import cn.odboy.framework.websocket.context.CsWsServer; import cn.odboy.framework.websocket.context.CsWsServer;
import cn.odboy.framework.websocket.util.WsMessageUtil;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -51,28 +54,23 @@ public class PipelineInstanceServiceImpl implements PipelineInstanceService {
} }
@Override @Override
public StartPipelineResultVo restartPipeline(PipelineInstanceTb pipelineInstanceTb, String retryNodeCode) { public StartPipelineResultVo retryPipelineNode(PipelineInstanceVo args) {
if (pipelineInstanceTb.getInstanceId() == null) { if (args.getInstanceId() == null) {
throw new BadRequestException("流水线实例Id必填"); throw new BadRequestException("流水线实例Id必填");
} }
PipelineInstanceTb currentInstance = pipelineInstanceMapper.selectById(pipelineInstanceTb.getInstanceId()); PipelineInstanceTb currentInstance = pipelineInstanceMapper.selectById(args.getInstanceId());
if (currentInstance == null) { if (currentInstance == null) {
throw new BadRequestException("无效流水线,请刷新页面后再试"); throw new BadRequestException("无效流水线,请刷新页面后再试");
} }
if (pipelineInstanceDAO.lock(currentInstance)) { if (StrUtil.isBlank(args.getCode())) {
throw new BadRequestException("流水线运行中,无法重复执行"); throw new BadRequestException("重试节点Code必填");
} }
// pending、running、success、fail if (!PipelineStatusEnum.FAIL.getCode().equals(currentInstance.getCurrentNodeStatus())) {
List<String> canRestartStatus = new ArrayList<>() {{ throw new BadRequestException("流水线节点无异常,无法重试");
add(PipelineStatusEnum.SUCCESS.getCode());
add(PipelineStatusEnum.FAIL.getCode());
}};
if (!canRestartStatus.contains(currentInstance.getStatus())) {
throw new BadRequestException("流水线运行中,无法重复执行");
} }
log.info("解锁流水线, {}", JSON.toJSONString(currentInstance));
pipelineInstanceDAO.unLock(currentInstance); pipelineInstanceDAO.unLock(currentInstance);
PipelineInstanceTb record = pipelineJobManage.startJobByNodeCode(currentInstance, retryNodeCode); log.info("解锁流水线, {}", JSON.toJSONString(currentInstance));
PipelineInstanceTb record = pipelineJobManage.startJobByNodeCode(currentInstance, args.getCode());
StartPipelineResultVo resultVo = new StartPipelineResultVo(); StartPipelineResultVo resultVo = new StartPipelineResultVo();
resultVo.setInstanceId(record.getInstanceId()); resultVo.setInstanceId(record.getInstanceId());
resultVo.setTemplateContent(record.getTemplateContent()); resultVo.setTemplateContent(record.getTemplateContent());
@@ -83,45 +81,39 @@ public class PipelineInstanceServiceImpl implements PipelineInstanceService {
public PipelineInstanceVo queryLastPipelineDetail(String instanceIdStr) { public PipelineInstanceVo queryLastPipelineDetail(String instanceIdStr) {
String realInstanceIdStr = instanceIdStr.replace(PipelineConst.INSTANCE_ID, ""); String realInstanceIdStr = instanceIdStr.replace(PipelineConst.INSTANCE_ID, "");
Long instanceId = Long.valueOf(realInstanceIdStr); Long instanceId = Long.valueOf(realInstanceIdStr);
List<PipelineNodeDataVo> records = new ArrayList<>(); List<PipelineInstanceNodeVo> records = new ArrayList<>();
PipelineInstanceTb pipelineInstanceTb = pipelineInstanceMapper.selectById(instanceId); PipelineInstanceTb pipelineInstanceTb = pipelineInstanceMapper.selectById(instanceId);
if (pipelineInstanceTb == null) { if (pipelineInstanceTb == null) {
throw new BadRequestException("流水线实例不存在"); throw new BadRequestException("流水线实例不存在");
} }
PipelineInstanceVo pipelineInstanceVo = BeanUtil.copyProperties(pipelineInstanceTb, PipelineInstanceVo.class);
String status = pipelineInstanceTb.getStatus(); String status = pipelineInstanceTb.getStatus();
List<PipelineInstanceNodeTb> instanceNodeTbs = pipelineInstanceNodeService.queryPipelineInstanceNodeListByInstanceId(instanceId); List<PipelineInstanceNodeTb> instanceNodeTbs = pipelineInstanceNodeService.queryPipelineInstanceNodeListByInstanceId(instanceId);
for (PipelineInstanceNodeTb instanceNodeTb : instanceNodeTbs) { for (PipelineInstanceNodeTb instanceNodeTb : instanceNodeTbs) {
PipelineNodeDataVo dataVo = BeanUtil.copyProperties(instanceNodeTb, PipelineNodeDataVo.class); PipelineInstanceNodeVo dataVo = BeanUtil.copyProperties(instanceNodeTb, PipelineInstanceNodeVo.class);
dataVo.setStatus(status); dataVo.setStatus(status);
// 取流水线节点最后一步的信息 // 取流水线节点最后一步的信息
PipelineInstanceNodeDetailTb pipelineInstanceNodeDetail = pipelineInstanceNodeDetailService.getLastPipelineInstanceNodeDetailByArgs(instanceNodeTb, instanceNodeTb.getId(), instanceNodeTb.getCode()); PipelineInstanceNodeDetailTb pipelineInstanceNodeDetail = pipelineInstanceNodeDetailService.getLastPipelineInstanceNodeDetailByArgs(instanceNodeTb);
dataVo.setCurrentNodeMsg(pipelineInstanceNodeDetail.getStepMsg()); dataVo.setCurrentNodeMsg(pipelineInstanceNodeDetail.getStepMsg());
dataVo.setCurrentNodeStatus(pipelineInstanceNodeDetail.getStepStatus()); // 取执行历史
dataVo.setHistorys(pipelineInstanceNodeDetailService.queryPipelineInstanceNodeDetailByArgs(instanceNodeTb));
records.add(dataVo); records.add(dataVo);
} }
PipelineInstanceVo pipelineInstanceVo = BeanUtil.copyProperties(pipelineInstanceTb, PipelineInstanceVo.class);
pipelineInstanceVo.setNodes(records); pipelineInstanceVo.setNodes(records);
return pipelineInstanceVo; return pipelineInstanceVo;
} }
@Override @Override
public void queryLastPipelineDetailWs(String sid) { public void queryLastPipelineDetailWs(String sid) {
// sid {username}_{bizCode}_{contextParams} WsSidVo sidVo = WsMessageUtil.parseSid(sid);
String[] sids = sid.split("_");
if (sids.length != 3) {
throw new BadRequestException("sid格式异常");
}
CsWsServer wsServer = CsWsClientManager.getClientBySid(sid); CsWsServer wsServer = CsWsClientManager.getClientBySid(sid);
wsServer.restartTask(() -> { wsServer.restartTask(() -> {
// String username = sids[0];
String bizCode = sids[1];
String instanceId = sids[2];
boolean loop = true; boolean loop = true;
while (loop) { while (loop) {
ThreadUtil.safeSleep(1000); ThreadUtil.safeSleep(1000);
try { try {
PipelineInstanceVo pipelineInstanceVo = queryLastPipelineDetail(instanceId); PipelineInstanceVo pipelineInstanceVo = queryLastPipelineDetail(sidVo.getParam());
CsWsMessage message = new CsWsMessage(bizCode, JSON.toJSONString(pipelineInstanceVo)); CsWsMessage message = new CsWsMessage(sidVo.getBizCode(), JSON.toJSONString(pipelineInstanceVo));
wsServer.sendMessage(message, sid); wsServer.sendMessage(message, sid);
} catch (Exception e) { } catch (Exception e) {
log.error("推送流水线最新数据失败", e); log.error("推送流水线最新数据失败", e);
@@ -22,7 +22,7 @@ import java.util.List;
* @date 2025-07-24 * @date 2025-07-24
*/ */
@RequiredArgsConstructor @RequiredArgsConstructor
@Service(value = PipelineConst.EXECUTOR_PREFIX + "node_apk_sign_android") @Service(value = PipelineConst.EXECUTOR_PREFIX + "node_sign_android_apk")
public class PipelineNodeApkSignAndroidService extends AbstractPipelineNodeJobService implements PipelineNodeJobExecutor { public class PipelineNodeApkSignAndroidService extends AbstractPipelineNodeJobService implements PipelineNodeJobExecutor {
private final PipelineNodeDemoBiz pipelineNodeDemoBiz; private final PipelineNodeDemoBiz pipelineNodeDemoBiz;
@@ -16,14 +16,14 @@ import org.springframework.stereotype.Service;
import java.util.List; import java.util.List;
/** /**
* 流水线节点任务:Post调用插件 代码上传扫描 * 流水线节点任务:人工审批C++构建结果
* *
* @author odboy * @author odboy
* @date 2025-07-24 * @date 2025-07-24
*/ */
@RequiredArgsConstructor @RequiredArgsConstructor
@Service(value = PipelineConst.EXECUTOR_PREFIX + "node_http_post_plugin-stc_upload") @Service(value = PipelineConst.EXECUTOR_PREFIX + "node_approve_cplus")
public class PipelineNodeHttpPostPluginStcUploadService extends AbstractPipelineNodeJobService implements PipelineNodeJobExecutor { public class PipelineNodeApproveCPlusService extends AbstractPipelineNodeJobService implements PipelineNodeJobExecutor {
private final PipelineNodeDemoBiz pipelineNodeDemoBiz; private final PipelineNodeDemoBiz pipelineNodeDemoBiz;
@Override @Override
@@ -16,14 +16,14 @@ import org.springframework.stereotype.Service;
import java.util.List; import java.util.List;
/** /**
* 流水线节点任务:上传安卓发布包 * 流水线节点任务:人工确认Android测试通过
* *
* @author odboy * @author odboy
* @date 2025-07-24 * @date 2025-07-24
*/ */
@RequiredArgsConstructor @RequiredArgsConstructor
@Service(value = PipelineConst.EXECUTOR_PREFIX + "node_mpaas_upload_android") @Service(value = PipelineConst.EXECUTOR_PREFIX + "node_approve_test_android")
public class PipelineNodeMPassUploadAndroidService extends AbstractPipelineNodeJobService implements PipelineNodeJobExecutor { public class PipelineNodeApproveTestAndroidService extends AbstractPipelineNodeJobService implements PipelineNodeJobExecutor {
private final PipelineNodeDemoBiz pipelineNodeDemoBiz; private final PipelineNodeDemoBiz pipelineNodeDemoBiz;
@Override @Override
@@ -0,0 +1,44 @@
package cn.odboy.devops.service.pipeline;
import cn.odboy.devops.constant.pipeline.PipelineConst;
import cn.odboy.devops.dal.dataobject.PipelineInstanceNodeTb;
import cn.odboy.devops.framework.pipeline.AbstractPipelineNodeJobService;
import cn.odboy.devops.framework.pipeline.core.PipelineNodeJobExecutor;
import cn.odboy.devops.framework.pipeline.model.PipelineNodeJobExecuteResult;
import cn.odboy.devops.framework.pipeline.model.PipelineNodeTemplateVo;
import cn.odboy.devops.service.pipeline.node.PipelineNodeDemoBiz;
import cn.odboy.framework.exception.BadRequestException;
import com.alibaba.fastjson2.JSON;
import lombok.RequiredArgsConstructor;
import org.quartz.JobDataMap;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 流水线节点任务:C++构建
*
* @author odboy
* @date 2025-07-24
*/
@RequiredArgsConstructor
@Service(value = PipelineConst.EXECUTOR_PREFIX + "node_build_cplus")
public class PipelineNodeBuildCPlusService extends AbstractPipelineNodeJobService implements PipelineNodeJobExecutor {
private final PipelineNodeDemoBiz pipelineNodeDemoBiz;
@Override
public PipelineNodeJobExecuteResult execute(JobDataMap jobDataMap) throws BadRequestException {
// 参数列表
long instanceId = jobDataMap.getLong(PipelineConst.INSTANCE_ID);
PipelineNodeTemplateVo currentNodeTemplate = (PipelineNodeTemplateVo) jobDataMap.get(PipelineConst.CURRENT_NODE_TEMPLATE);
PipelineInstanceNodeTb pipelineInstanceNode = getCurrentNodeInfo(instanceId, currentNodeTemplate.getCode());
String contextName = jobDataMap.getString(PipelineConst.CONTEXT_NAME);
String env = jobDataMap.getString(PipelineConst.ENV);
List<PipelineNodeTemplateVo> templateList = JSON.parseArray(jobDataMap.getString(PipelineConst.TEMPLATE), PipelineNodeTemplateVo.class);
PipelineNodeJobExecuteResult lastNodeResult = (PipelineNodeJobExecuteResult) jobDataMap.get(PipelineConst.LAST_NODE_RESULT);
// 步骤执行
pipelineNodeDemoBiz.start(pipelineInstanceNode, contextName, env, templateList, lastNodeResult);
pipelineNodeDemoBiz.finish(pipelineInstanceNode, contextName, env, templateList, lastNodeResult);
return PipelineNodeJobExecuteResult.success();
}
}
@@ -0,0 +1,44 @@
package cn.odboy.devops.service.pipeline;
import cn.odboy.devops.constant.pipeline.PipelineConst;
import cn.odboy.devops.dal.dataobject.PipelineInstanceNodeTb;
import cn.odboy.devops.framework.pipeline.AbstractPipelineNodeJobService;
import cn.odboy.devops.framework.pipeline.core.PipelineNodeJobExecutor;
import cn.odboy.devops.framework.pipeline.model.PipelineNodeJobExecuteResult;
import cn.odboy.devops.framework.pipeline.model.PipelineNodeTemplateVo;
import cn.odboy.devops.service.pipeline.node.PipelineNodeDemoBiz;
import cn.odboy.framework.exception.BadRequestException;
import com.alibaba.fastjson2.JSON;
import lombok.RequiredArgsConstructor;
import org.quartz.JobDataMap;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 流水线节点任务:IOS构建
*
* @author odboy
* @date 2025-07-24
*/
@RequiredArgsConstructor
@Service(value = PipelineConst.EXECUTOR_PREFIX + "node_build_ios")
public class PipelineNodeBuildIOSService extends AbstractPipelineNodeJobService implements PipelineNodeJobExecutor {
private final PipelineNodeDemoBiz pipelineNodeDemoBiz;
@Override
public PipelineNodeJobExecuteResult execute(JobDataMap jobDataMap) throws BadRequestException {
// 参数列表
long instanceId = jobDataMap.getLong(PipelineConst.INSTANCE_ID);
PipelineNodeTemplateVo currentNodeTemplate = (PipelineNodeTemplateVo) jobDataMap.get(PipelineConst.CURRENT_NODE_TEMPLATE);
PipelineInstanceNodeTb pipelineInstanceNode = getCurrentNodeInfo(instanceId, currentNodeTemplate.getCode());
String contextName = jobDataMap.getString(PipelineConst.CONTEXT_NAME);
String env = jobDataMap.getString(PipelineConst.ENV);
List<PipelineNodeTemplateVo> templateList = JSON.parseArray(jobDataMap.getString(PipelineConst.TEMPLATE), PipelineNodeTemplateVo.class);
PipelineNodeJobExecuteResult lastNodeResult = (PipelineNodeJobExecuteResult) jobDataMap.get(PipelineConst.LAST_NODE_RESULT);
// 步骤执行
pipelineNodeDemoBiz.start(pipelineInstanceNode, contextName, env, templateList, lastNodeResult);
pipelineNodeDemoBiz.finish(pipelineInstanceNode, contextName, env, templateList, lastNodeResult);
return PipelineNodeJobExecuteResult.success();
}
}
@@ -0,0 +1,44 @@
package cn.odboy.devops.service.pipeline;
import cn.odboy.devops.constant.pipeline.PipelineConst;
import cn.odboy.devops.dal.dataobject.PipelineInstanceNodeTb;
import cn.odboy.devops.framework.pipeline.AbstractPipelineNodeJobService;
import cn.odboy.devops.framework.pipeline.core.PipelineNodeJobExecutor;
import cn.odboy.devops.framework.pipeline.model.PipelineNodeJobExecuteResult;
import cn.odboy.devops.framework.pipeline.model.PipelineNodeTemplateVo;
import cn.odboy.devops.service.pipeline.node.PipelineNodeDemoBiz;
import cn.odboy.framework.exception.BadRequestException;
import com.alibaba.fastjson2.JSON;
import lombok.RequiredArgsConstructor;
import org.quartz.JobDataMap;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 流水线节点任务:检查Flutter版本
*
* @author odboy
* @date 2025-07-24
*/
@RequiredArgsConstructor
@Service(value = PipelineConst.EXECUTOR_PREFIX + "node_check_flutter_version")
public class PipelineNodeCheckFlutterVersionService extends AbstractPipelineNodeJobService implements PipelineNodeJobExecutor {
private final PipelineNodeDemoBiz pipelineNodeDemoBiz;
@Override
public PipelineNodeJobExecuteResult execute(JobDataMap jobDataMap) throws BadRequestException {
// 参数列表
long instanceId = jobDataMap.getLong(PipelineConst.INSTANCE_ID);
PipelineNodeTemplateVo currentNodeTemplate = (PipelineNodeTemplateVo) jobDataMap.get(PipelineConst.CURRENT_NODE_TEMPLATE);
PipelineInstanceNodeTb pipelineInstanceNode = getCurrentNodeInfo(instanceId, currentNodeTemplate.getCode());
String contextName = jobDataMap.getString(PipelineConst.CONTEXT_NAME);
String env = jobDataMap.getString(PipelineConst.ENV);
List<PipelineNodeTemplateVo> templateList = JSON.parseArray(jobDataMap.getString(PipelineConst.TEMPLATE), PipelineNodeTemplateVo.class);
PipelineNodeJobExecuteResult lastNodeResult = (PipelineNodeJobExecuteResult) jobDataMap.get(PipelineConst.LAST_NODE_RESULT);
// 步骤执行
pipelineNodeDemoBiz.start(pipelineInstanceNode, contextName, env, templateList, lastNodeResult);
pipelineNodeDemoBiz.finish(pipelineInstanceNode, contextName, env, templateList, lastNodeResult);
return PipelineNodeJobExecuteResult.success();
}
}
@@ -22,7 +22,7 @@ import java.util.List;
* @date 2025-07-24 * @date 2025-07-24
*/ */
@RequiredArgsConstructor @RequiredArgsConstructor
@Service(value = PipelineConst.EXECUTOR_PREFIX + "node_deploy_python") @Service(value = PipelineConst.EXECUTOR_PREFIX + "node_deploy_node")
public class PipelineNodeDeployNodeService extends AbstractPipelineNodeJobService implements PipelineNodeJobExecutor { public class PipelineNodeDeployNodeService extends AbstractPipelineNodeJobService implements PipelineNodeJobExecutor {
private final PipelineNodeDemoBiz pipelineNodeDemoBiz; private final PipelineNodeDemoBiz pipelineNodeDemoBiz;
@@ -2,11 +2,13 @@ package cn.odboy.devops.service.pipeline;
import cn.odboy.devops.constant.pipeline.PipelineConst; import cn.odboy.devops.constant.pipeline.PipelineConst;
import cn.odboy.devops.dal.dataobject.PipelineInstanceNodeTb; import cn.odboy.devops.dal.dataobject.PipelineInstanceNodeTb;
import cn.odboy.devops.dal.dataobject.PipelineTemplateTb;
import cn.odboy.devops.framework.pipeline.AbstractPipelineNodeJobService; import cn.odboy.devops.framework.pipeline.AbstractPipelineNodeJobService;
import cn.odboy.devops.framework.pipeline.core.PipelineNodeJobExecutor; import cn.odboy.devops.framework.pipeline.core.PipelineNodeJobExecutor;
import cn.odboy.devops.framework.pipeline.model.PipelineNodeJobExecuteResult; import cn.odboy.devops.framework.pipeline.model.PipelineNodeJobExecuteResult;
import cn.odboy.devops.framework.pipeline.model.PipelineNodeTemplateVo; import cn.odboy.devops.framework.pipeline.model.PipelineNodeTemplateVo;
import cn.odboy.devops.service.pipeline.node.PipelineNodeDemoBiz; import cn.odboy.devops.service.core.PipelineTemplateService;
import cn.odboy.devops.service.pipeline.node.PipelineNodeDeployJavaBiz;
import cn.odboy.framework.exception.BadRequestException; import cn.odboy.framework.exception.BadRequestException;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@@ -16,20 +18,23 @@ import org.springframework.stereotype.Service;
import java.util.List; import java.util.List;
/** /**
* 流水线节点任务:Post调用插件 代码上传扫描结果 * 流水线节点任务:Python部署
* *
* @author odboy * @author odboy
* @date 2025-07-24 * @date 2025-07-24
*/ */
@RequiredArgsConstructor @RequiredArgsConstructor
@Service(value = PipelineConst.EXECUTOR_PREFIX + "node_http_post_plugin-stc_result") @Service(value = PipelineConst.EXECUTOR_PREFIX + "node_deploy_python")
public class PipelineNodeHttpPostPluginStcResultService extends AbstractPipelineNodeJobService implements PipelineNodeJobExecutor { public class PipelineNodeDeployPythonService extends AbstractPipelineNodeJobService implements PipelineNodeJobExecutor {
private final PipelineNodeDemoBiz pipelineNodeDemoBiz; private final PipelineNodeDeployJavaBiz pipelineNodeDeployJavaBiz;
private final PipelineTemplateService pipelineTemplateService;
@Override @Override
public PipelineNodeJobExecuteResult execute(JobDataMap jobDataMap) throws BadRequestException { public PipelineNodeJobExecuteResult execute(JobDataMap jobDataMap) throws BadRequestException {
// 参数列表 // 参数列表
long instanceId = jobDataMap.getLong(PipelineConst.INSTANCE_ID); long instanceId = jobDataMap.getLong(PipelineConst.INSTANCE_ID);
long templateId = jobDataMap.getLong(PipelineConst.TEMPLATE_ID);
PipelineTemplateTb pipelineTemplate = pipelineTemplateService.getPipelineTemplateById(templateId);
PipelineNodeTemplateVo currentNodeTemplate = (PipelineNodeTemplateVo) jobDataMap.get(PipelineConst.CURRENT_NODE_TEMPLATE); PipelineNodeTemplateVo currentNodeTemplate = (PipelineNodeTemplateVo) jobDataMap.get(PipelineConst.CURRENT_NODE_TEMPLATE);
PipelineInstanceNodeTb pipelineInstanceNode = getCurrentNodeInfo(instanceId, currentNodeTemplate.getCode()); PipelineInstanceNodeTb pipelineInstanceNode = getCurrentNodeInfo(instanceId, currentNodeTemplate.getCode());
String contextName = jobDataMap.getString(PipelineConst.CONTEXT_NAME); String contextName = jobDataMap.getString(PipelineConst.CONTEXT_NAME);
@@ -37,8 +42,9 @@ public class PipelineNodeHttpPostPluginStcResultService extends AbstractPipeline
List<PipelineNodeTemplateVo> templateList = JSON.parseArray(jobDataMap.getString(PipelineConst.TEMPLATE), PipelineNodeTemplateVo.class); List<PipelineNodeTemplateVo> templateList = JSON.parseArray(jobDataMap.getString(PipelineConst.TEMPLATE), PipelineNodeTemplateVo.class);
PipelineNodeJobExecuteResult lastNodeResult = (PipelineNodeJobExecuteResult) jobDataMap.get(PipelineConst.LAST_NODE_RESULT); PipelineNodeJobExecuteResult lastNodeResult = (PipelineNodeJobExecuteResult) jobDataMap.get(PipelineConst.LAST_NODE_RESULT);
// 步骤执行 // 步骤执行
pipelineNodeDemoBiz.start(pipelineInstanceNode, contextName, env, templateList, lastNodeResult); pipelineNodeDeployJavaBiz.deployStart(pipelineInstanceNode, contextName, env, templateList, lastNodeResult);
pipelineNodeDemoBiz.finish(pipelineInstanceNode, contextName, env, templateList, lastNodeResult); pipelineNodeDeployJavaBiz.deployJavaByWithContextName(pipelineInstanceNode, contextName, env, templateList, lastNodeResult, pipelineTemplate);
pipelineNodeDeployJavaBiz.deployFinish(pipelineInstanceNode, contextName, env, templateList, lastNodeResult);
return PipelineNodeJobExecuteResult.success(); return PipelineNodeJobExecuteResult.success();
} }
} }
@@ -16,14 +16,14 @@ import org.springframework.stereotype.Service;
import java.util.List; import java.util.List;
/** /**
* 流水线节点任务:特殊合并到release分支 * 流水线节点任务:Post调用插件
* *
* @author odboy * @author odboy
* @date 2025-07-24 * @date 2025-07-24
*/ */
@RequiredArgsConstructor @RequiredArgsConstructor
@Service(value = PipelineConst.EXECUTOR_PREFIX + "node_release_android") @Service(value = PipelineConst.EXECUTOR_PREFIX + "node_http_post_plugin")
public class PipelineNodeReleaseAndroidService extends AbstractPipelineNodeJobService implements PipelineNodeJobExecutor { public class PipelineNodeHttpPostPluginService extends AbstractPipelineNodeJobService implements PipelineNodeJobExecutor {
private final PipelineNodeDemoBiz pipelineNodeDemoBiz; private final PipelineNodeDemoBiz pipelineNodeDemoBiz;
@Override @Override
@@ -27,7 +27,7 @@ public class PipelineNodeMergeBranchService extends AbstractPipelineNodeJobServi
private final PipelineNodeMergeBranchBiz pipelineNodeMergeBranchBiz; private final PipelineNodeMergeBranchBiz pipelineNodeMergeBranchBiz;
@Override @Override
public PipelineNodeJobExecuteResult execute(JobDataMap jobDataMap) throws BadRequestException { public PipelineNodeJobExecuteResult execute(JobDataMap jobDataMap) throws Exception {
// 参数列表 // 参数列表
long instanceId = jobDataMap.getLong(PipelineConst.INSTANCE_ID); long instanceId = jobDataMap.getLong(PipelineConst.INSTANCE_ID);
PipelineNodeTemplateVo currentNodeTemplate = (PipelineNodeTemplateVo) jobDataMap.get(PipelineConst.CURRENT_NODE_TEMPLATE); PipelineNodeTemplateVo currentNodeTemplate = (PipelineNodeTemplateVo) jobDataMap.get(PipelineConst.CURRENT_NODE_TEMPLATE);
@@ -22,8 +22,8 @@ import java.util.List;
* @date 2025-07-24 * @date 2025-07-24
*/ */
@RequiredArgsConstructor @RequiredArgsConstructor
@Service(value = PipelineConst.EXECUTOR_PREFIX + "node_mpaas_start_android") @Service(value = PipelineConst.EXECUTOR_PREFIX + "node_reinforce_android_apk")
public class PipelineNodeMPassStartAndroidService extends AbstractPipelineNodeJobService implements PipelineNodeJobExecutor { public class PipelineNodeReinforceAndroidApkService extends AbstractPipelineNodeJobService implements PipelineNodeJobExecutor {
private final PipelineNodeDemoBiz pipelineNodeDemoBiz; private final PipelineNodeDemoBiz pipelineNodeDemoBiz;
@Override @Override
@@ -0,0 +1,44 @@
package cn.odboy.devops.service.pipeline;
import cn.odboy.devops.constant.pipeline.PipelineConst;
import cn.odboy.devops.dal.dataobject.PipelineInstanceNodeTb;
import cn.odboy.devops.framework.pipeline.AbstractPipelineNodeJobService;
import cn.odboy.devops.framework.pipeline.core.PipelineNodeJobExecutor;
import cn.odboy.devops.framework.pipeline.model.PipelineNodeJobExecuteResult;
import cn.odboy.devops.framework.pipeline.model.PipelineNodeTemplateVo;
import cn.odboy.devops.service.pipeline.node.PipelineNodeDemoBiz;
import cn.odboy.framework.exception.BadRequestException;
import com.alibaba.fastjson2.JSON;
import lombok.RequiredArgsConstructor;
import org.quartz.JobDataMap;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 流水线节点任务:上传安卓发布包
*
* @author odboy
* @date 2025-07-24
*/
@RequiredArgsConstructor
@Service(value = PipelineConst.EXECUTOR_PREFIX + "node_upload_android_apk")
public class PipelineNodeUploadAndroidApkService extends AbstractPipelineNodeJobService implements PipelineNodeJobExecutor {
private final PipelineNodeDemoBiz pipelineNodeDemoBiz;
@Override
public PipelineNodeJobExecuteResult execute(JobDataMap jobDataMap) throws BadRequestException {
// 参数列表
long instanceId = jobDataMap.getLong(PipelineConst.INSTANCE_ID);
PipelineNodeTemplateVo currentNodeTemplate = (PipelineNodeTemplateVo) jobDataMap.get(PipelineConst.CURRENT_NODE_TEMPLATE);
PipelineInstanceNodeTb pipelineInstanceNode = getCurrentNodeInfo(instanceId, currentNodeTemplate.getCode());
String contextName = jobDataMap.getString(PipelineConst.CONTEXT_NAME);
String env = jobDataMap.getString(PipelineConst.ENV);
List<PipelineNodeTemplateVo> templateList = JSON.parseArray(jobDataMap.getString(PipelineConst.TEMPLATE), PipelineNodeTemplateVo.class);
PipelineNodeJobExecuteResult lastNodeResult = (PipelineNodeJobExecuteResult) jobDataMap.get(PipelineConst.LAST_NODE_RESULT);
// 步骤执行
pipelineNodeDemoBiz.start(pipelineInstanceNode, contextName, env, templateList, lastNodeResult);
pipelineNodeDemoBiz.finish(pipelineInstanceNode, contextName, env, templateList, lastNodeResult);
return PipelineNodeJobExecuteResult.success();
}
}
@@ -16,11 +16,11 @@ public class PipelineNodeDemoBiz {
@PipelineNodeStepLog("开始") @PipelineNodeStepLog("开始")
public void start(PipelineInstanceNodeTb pipelineInstanceNode, String contextName, String env, List<PipelineNodeTemplateVo> templateList, PipelineNodeJobExecuteResult lastNodeResult) { public void start(PipelineInstanceNodeTb pipelineInstanceNode, String contextName, String env, List<PipelineNodeTemplateVo> templateList, PipelineNodeJobExecuteResult lastNodeResult) {
ThreadUtil.safeSleep(1000); ThreadUtil.safeSleep(2000);
} }
@PipelineNodeStepLog("完成") @PipelineNodeStepLog("完成")
public void finish(PipelineInstanceNodeTb pipelineInstanceNode, String contextName, String env, List<PipelineNodeTemplateVo> templateList, PipelineNodeJobExecuteResult lastNodeResult) { public void finish(PipelineInstanceNodeTb pipelineInstanceNode, String contextName, String env, List<PipelineNodeTemplateVo> templateList, PipelineNodeJobExecuteResult lastNodeResult) {
ThreadUtil.safeSleep(1000); ThreadUtil.safeSleep(2000);
} }
} }
@@ -5,6 +5,7 @@ import cn.odboy.devops.dal.dataobject.PipelineInstanceNodeTb;
import cn.odboy.devops.framework.pipeline.log.PipelineNodeStepLog; import cn.odboy.devops.framework.pipeline.log.PipelineNodeStepLog;
import cn.odboy.devops.framework.pipeline.model.PipelineNodeJobExecuteResult; import cn.odboy.devops.framework.pipeline.model.PipelineNodeJobExecuteResult;
import cn.odboy.devops.framework.pipeline.model.PipelineNodeTemplateVo; import cn.odboy.devops.framework.pipeline.model.PipelineNodeTemplateVo;
import cn.odboy.framework.exception.BadRequestException;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -21,6 +22,7 @@ public class PipelineNodeMergeBranchBiz {
@PipelineNodeStepLog("集成区分支合并到release分支") @PipelineNodeStepLog("集成区分支合并到release分支")
public void integrationAreaBranchMergeRelease(PipelineInstanceNodeTb pipelineInstanceNode, String contextName, String env, List<PipelineNodeTemplateVo> templateList, PipelineNodeJobExecuteResult lastNodeResult) { public void integrationAreaBranchMergeRelease(PipelineInstanceNodeTb pipelineInstanceNode, String contextName, String env, List<PipelineNodeTemplateVo> templateList, PipelineNodeJobExecuteResult lastNodeResult) {
ThreadUtil.safeSleep(2000); ThreadUtil.safeSleep(2000);
throw new BadRequestException("合并失败");
} }
@PipelineNodeStepLog("分支合并完成") @PipelineNodeStepLog("分支合并完成")
+3
View File
@@ -0,0 +1,3 @@
### 注意事项
long、bigdecimal放回前端时,字段尽量设置为String,否则会出现精度丢失问题