diff --git a/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/test/TeFlowController.java b/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/test/TeFlowController.java index 444f5c2..bd1bbbb 100644 --- a/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/test/TeFlowController.java +++ b/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/test/TeFlowController.java @@ -11,13 +11,16 @@ import com.cmvr.test.flow.context.TaskContextManager; import com.cmvr.test.flow.control.FlowControlService; import com.cmvr.test.flow.runtime.engine.FlowTaskRuntimeService; import com.cmvr.test.flow.runtime.operator.edge.ti.TiTouchOperateService; -import com.cmvr.test.model.domain.TeDetectionItem; import com.cmvr.test.model.vo.FlowActionRequestVO; +import com.cmvr.test.model.vo.TeDetectItemDeployFlowVO; +import com.cmvr.test.model.vo.TeFlowPublishVO; +import com.cmvr.test.model.vo.TeFlowVersionActionVO; import com.cmvr.test.model.vo.TeFlowViewVO; import com.cmvr.test.model.vo.TeTaskExecuteNormalVO; import com.cmvr.test.model.vo.TeTaskExecuteTrailVO; import com.cmvr.test.service.FlowActionExecutorService; import com.cmvr.test.service.ITeDetectionItemService; +import com.cmvr.test.service.ITeDetectionItemVersionService; import com.cmvr.test.service.ITeNodeInstService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -29,6 +32,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.PutMapping; import jakarta.annotation.PreDestroy; import jakarta.validation.Valid; @@ -62,6 +66,7 @@ public class TeFlowController extends BaseController { private static final String AI_AGENT_API_KEY = "d9etum54shheenol3apg"; private final ITeDetectionItemService teDetectionItemService; + private final ITeDetectionItemVersionService detectionItemVersionService; private final ITeNodeInstService nodeInstService; private final FlowTaskRuntimeService flowTaskRuntimeService; private final TaskContextManager taskContextManager; @@ -87,8 +92,42 @@ public class TeFlowController extends BaseController { @ApiOperation("流程发布") @PostMapping("/publish") - public AjaxResult publish(@RequestBody TeDetectionItem detectionItem) { - return toAjax(teDetectionItemService.publish(detectionItem.getId())); + public AjaxResult publish(@Valid @RequestBody TeFlowPublishVO request) { + return success(detectionItemVersionService.publish(request, getUsername())); + } + + @ApiOperation("保存工作流草稿") + @PutMapping("/draft") + public AjaxResult saveDraft(@Valid @RequestBody TeDetectItemDeployFlowVO request) { + return success(teDetectionItemService.saveDraft(request, getUsername())); + } + + @ApiOperation("查询工作流发布记录") + @GetMapping("/versions/{itemId}") + public AjaxResult versions(@PathVariable String itemId) { + return success(detectionItemVersionService.listVersions(itemId)); + } + + @ApiOperation("查看工作流发布版本") + @GetMapping("/version/{versionId}") + public AjaxResult version(@PathVariable String versionId) { + return success(detectionItemVersionService.getVersion(versionId)); + } + + @ApiOperation("将发布版本恢复为草稿") + @PostMapping("/version/{versionId}/restore-draft") + public AjaxResult restoreDraft(@PathVariable String versionId, + @RequestBody(required = false) TeFlowVersionActionVO request) { + Integer revision = request == null ? null : request.getDraftRevision(); + return success(detectionItemVersionService.restoreDraft(versionId, revision, getUsername())); + } + + @ApiOperation("回退到指定发布版本") + @PostMapping("/version/{versionId}/rollback") + public AjaxResult rollback(@PathVariable String versionId, + @RequestBody(required = false) TeFlowVersionActionVO request) { + String note = request == null ? null : request.getPublishNote(); + return success(detectionItemVersionService.rollback(versionId, note, getUsername())); } @ApiOperation("演示任务执行入口") diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/engine/FlowTaskRuntimeEntry.java b/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/engine/FlowTaskRuntimeEntry.java index e26880d..7711ed9 100644 --- a/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/engine/FlowTaskRuntimeEntry.java +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/engine/FlowTaskRuntimeEntry.java @@ -14,13 +14,13 @@ import com.cmvr.test.flow.context.TaskContext; import com.cmvr.test.flow.context.TaskInstHolder; import com.cmvr.test.model.domain.TeTaskInst; import com.cmvr.test.model.dto.TeQueryTaskDetailDTO; -import com.cmvr.test.model.vo.TeDetectItemDeployFlowVO; +import com.cmvr.test.model.domain.TeTaskInstFlowVersion; import com.cmvr.test.model.vo.TeTaskExecuteNormalVO; import com.cmvr.test.model.vo.TeTaskExecuteProjectVO; import com.cmvr.test.model.vo.TeTaskExecuteTrailVO; import com.cmvr.test.model.vo.ResourceRuntimeAssignmentVO; import com.cmvr.test.model.vo.RobotRuntimeMemberVO; -import com.cmvr.test.service.ITeDetectionItemService; +import com.cmvr.test.service.ITeTaskInstFlowVersionService; import com.cmvr.test.service.ITeTaskInstService; import com.cmvr.test.service.ITeTaskOrchestrationService; import lombok.RequiredArgsConstructor; @@ -48,7 +48,7 @@ public class FlowTaskRuntimeEntry implements FlowTaskRuntimeService { private final ITeTaskOrchestrationService taskOrchestrationService; private final ITeTaskInstService taskInstService; - private final ITeDetectionItemService detectionItemService; + private final ITeTaskInstFlowVersionService taskInstFlowVersionService; private final TaskInstHolder taskInstHolder; private final FlowTaskAsyncDispatcher flowTaskAsyncDispatcher; private final ObjectProvider targetResolverProvider; @@ -127,11 +127,6 @@ public class FlowTaskRuntimeEntry implements FlowTaskRuntimeService { String itemId = taskExecuteTrailVO.getItemId(); runtimeDefinitionValidator.validate(taskExecuteTrailVO.getFlowData(), resources.assignments()); - TeDetectItemDeployFlowVO deployFlowVO = new TeDetectItemDeployFlowVO(); - deployFlowVO.setId(itemId); - deployFlowVO.setFlowData(taskExecuteTrailVO.getFlowData()); - detectionItemService.deployFlow(deployFlowVO); - String taskId = taskOrchestrationService.buildTrialTask(itemId); // 创建并保存任务实例 @@ -196,6 +191,12 @@ public class FlowTaskRuntimeEntry implements FlowTaskRuntimeService { List details = new ArrayList<>(taskOrchestrationService.queryTaskDetail(taskId)); details.sort(Comparator.comparing(TeQueryTaskDetailDTO::getOrderNum, Comparator.nullsLast(Integer::compareTo))); + TeQueryTaskDetailDTO unpublished = details.stream() + .filter(detail -> StrUtil.isBlank(detail.getVersionId()) || StrUtil.isBlank(detail.getFlowData())) + .findFirst().orElse(null); + if (unpublished != null) { + throw new GlobalException("任务中的检测项尚未发布:" + unpublished.getDetectItemId()); + } Map validationOccurrences = new HashMap<>(); details.forEach(detail -> { int occurrence = validationOccurrences.merge(detail.getDetectItemId(), 1, Integer::sum); @@ -203,13 +204,10 @@ public class FlowTaskRuntimeEntry implements FlowTaskRuntimeService { FlowRuntimeAssignmentScope.forItem( resources.assignments(), detail.getDetectItemId(), occurrence)); }); - int count = (int) details.stream().filter(dto -> ObjUtil.equals(dto.getIsDeploy(), "1")).count(); - if (count > 0) { - throw new GlobalException("当前任务存在未发布的检测项!"); - } // 创建任务实例 TeTaskInst instance = createAndSaveTaskInstance(taskId, target, runMode, originalVO); String instId = instance.getId(); + saveFlowVersionManifest(instId, details); try { // 注册上下文 @@ -229,6 +227,25 @@ public class FlowTaskRuntimeEntry implements FlowTaskRuntimeService { } } + private void saveFlowVersionManifest(String instId, List details) { + Map occurrences = new HashMap<>(); + List manifest = new ArrayList<>(); + for (int i = 0; i < details.size(); i++) { + TeQueryTaskDetailDTO detail = details.get(i); + TeTaskInstFlowVersion entry = new TeTaskInstFlowVersion(); + entry.setInstId(instId); + entry.setItemId(detail.getDetectItemId()); + entry.setItemOrder(detail.getOrderNum() == null ? i + 1 : detail.getOrderNum()); + entry.setItemOccurrence(occurrences.merge(detail.getDetectItemId(), 1, Integer::sum)); + entry.setVersionId(detail.getVersionId()); + entry.setVersionNo(detail.getVersionNo()); + manifest.add(entry); + } + if (!manifest.isEmpty() && !taskInstFlowVersionService.saveBatch(manifest)) { + throw new GlobalException("保存任务工作流版本清单失败"); + } + } + /** * 创建并保存任务实例 */ diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/mapper/TeDetectionItemVersionMapper.java b/cmvr-iot-test/src/main/java/com/cmvr/test/mapper/TeDetectionItemVersionMapper.java new file mode 100644 index 0000000..011fc5f --- /dev/null +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/mapper/TeDetectionItemVersionMapper.java @@ -0,0 +1,7 @@ +package com.cmvr.test.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.cmvr.test.model.domain.TeDetectionItemVersion; + +public interface TeDetectionItemVersionMapper extends BaseMapper { +} diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/mapper/TeTaskInstFlowVersionMapper.java b/cmvr-iot-test/src/main/java/com/cmvr/test/mapper/TeTaskInstFlowVersionMapper.java new file mode 100644 index 0000000..15a013c --- /dev/null +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/mapper/TeTaskInstFlowVersionMapper.java @@ -0,0 +1,7 @@ +package com.cmvr.test.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.cmvr.test.model.domain.TeTaskInstFlowVersion; + +public interface TeTaskInstFlowVersionMapper extends BaseMapper { +} diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/model/domain/TeDetectionItem.java b/cmvr-iot-test/src/main/java/com/cmvr/test/model/domain/TeDetectionItem.java index 35fa744..bda7cfb 100644 --- a/cmvr-iot-test/src/main/java/com/cmvr/test/model/domain/TeDetectionItem.java +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/model/domain/TeDetectionItem.java @@ -36,6 +36,21 @@ public class TeDetectionItem extends BaseEntity { @ApiModelProperty("检测项流程数据") private String flowData; + @ApiModelProperty("当前发布版本ID") + private String publishedVersionId; + + @ApiModelProperty("当前发布版本号") + private Integer publishedVersionNo; + + @ApiModelProperty("草稿修订号") + private Integer draftRevision; + + @ApiModelProperty("草稿内容摘要") + private String draftHash; + + @ApiModelProperty("最近发布的草稿内容摘要") + private String publishedDraftHash; + @Excel(name = "状态") @ApiModelProperty(value = "状态 (0正常 1停用)") @TableField(fill = FieldFill.INSERT) diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/model/domain/TeDetectionItemVersion.java b/cmvr-iot-test/src/main/java/com/cmvr/test/model/domain/TeDetectionItemVersion.java new file mode 100644 index 0000000..1b5a069 --- /dev/null +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/model/domain/TeDetectionItemVersion.java @@ -0,0 +1,42 @@ +package com.cmvr.test.model.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.cmvr.common.core.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel("工作流发布版本") +public class TeDetectionItemVersion extends BaseEntity { + + @TableId(value = "id", type = IdType.ASSIGN_UUID) + private String id; + + @ApiModelProperty("检测项ID") + private String detectionItemId; + + @ApiModelProperty("发布版本号") + private Integer versionNo; + + @ApiModelProperty("不可变流程定义") + private String flowData; + + @ApiModelProperty("不可变运行参数定义") + private String config; + + @ApiModelProperty("流程JSON结构版本") + private Integer schemaVersion; + + @ApiModelProperty("发布内容摘要") + private String contentHash; + + @ApiModelProperty("回退来源版本ID") + private String sourceVersionId; + + @ApiModelProperty("发布说明") + private String publishNote; +} diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/model/domain/TeTaskInstFlowVersion.java b/cmvr-iot-test/src/main/java/com/cmvr/test/model/domain/TeTaskInstFlowVersion.java new file mode 100644 index 0000000..a4036de --- /dev/null +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/model/domain/TeTaskInstFlowVersion.java @@ -0,0 +1,21 @@ +package com.cmvr.test.model.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.cmvr.common.core.domain.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class TeTaskInstFlowVersion extends BaseEntity { + + @TableId(value = "id", type = IdType.ASSIGN_UUID) + private String id; + private String instId; + private String itemId; + private Integer itemOrder; + private Integer itemOccurrence; + private String versionId; + private Integer versionNo; +} diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/model/dto/TeQueryTaskDetailDTO.java b/cmvr-iot-test/src/main/java/com/cmvr/test/model/dto/TeQueryTaskDetailDTO.java index eefefd5..2441b85 100644 --- a/cmvr-iot-test/src/main/java/com/cmvr/test/model/dto/TeQueryTaskDetailDTO.java +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/model/dto/TeQueryTaskDetailDTO.java @@ -8,6 +8,8 @@ public class TeQueryTaskDetailDTO { private String detectItemId; private String flowData; private String config; + private String versionId; + private Integer versionNo; private Integer orderNum; private String isDeploy; private String sceneType; diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/model/vo/TeDetectItemDeployFlowVO.java b/cmvr-iot-test/src/main/java/com/cmvr/test/model/vo/TeDetectItemDeployFlowVO.java index 0c771b6..7a7298b 100644 --- a/cmvr-iot-test/src/main/java/com/cmvr/test/model/vo/TeDetectItemDeployFlowVO.java +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/model/vo/TeDetectItemDeployFlowVO.java @@ -21,4 +21,7 @@ public class TeDetectItemDeployFlowVO { @ApiModelProperty(value = "流程运行参数") private JSONObject config; + + @ApiModelProperty("客户端加载草稿时的修订号") + private Integer draftRevision; } diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/model/vo/TeFlowPublishVO.java b/cmvr-iot-test/src/main/java/com/cmvr/test/model/vo/TeFlowPublishVO.java new file mode 100644 index 0000000..9737710 --- /dev/null +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/model/vo/TeFlowPublishVO.java @@ -0,0 +1,20 @@ +package com.cmvr.test.model.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +@ApiModel("工作流发布请求") +public class TeFlowPublishVO { + + @NotBlank(message = "检测项ID不能为空") + private String id; + + @ApiModelProperty("发布所基于的草稿修订号") + private Integer draftRevision; + + @ApiModelProperty("发布说明") + private String publishNote; +} diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/model/vo/TeFlowVersionActionVO.java b/cmvr-iot-test/src/main/java/com/cmvr/test/model/vo/TeFlowVersionActionVO.java new file mode 100644 index 0000000..7bd7b6d --- /dev/null +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/model/vo/TeFlowVersionActionVO.java @@ -0,0 +1,12 @@ +package com.cmvr.test.model.vo; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +public class TeFlowVersionActionVO { + @ApiModelProperty("客户端加载草稿时的修订号") + private Integer draftRevision; + @ApiModelProperty("回退说明") + private String publishNote; +} diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/model/vo/TeQueryTaskOrchestraItemVO.java b/cmvr-iot-test/src/main/java/com/cmvr/test/model/vo/TeQueryTaskOrchestraItemVO.java index 0bf3446..0de2fae 100644 --- a/cmvr-iot-test/src/main/java/com/cmvr/test/model/vo/TeQueryTaskOrchestraItemVO.java +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/model/vo/TeQueryTaskOrchestraItemVO.java @@ -16,6 +16,12 @@ public class TeQueryTaskOrchestraItemVO { @ApiModelProperty("检测项参数信息") private String config; + @ApiModelProperty("当前发布版本ID") + private String versionId; + + @ApiModelProperty("当前发布版本号") + private Integer versionNo; + @ApiModelProperty("执行顺序号") private Integer orderNum; } diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/service/ITeDetectionItemService.java b/cmvr-iot-test/src/main/java/com/cmvr/test/service/ITeDetectionItemService.java index 993ce6a..96c04aa 100644 --- a/cmvr-iot-test/src/main/java/com/cmvr/test/service/ITeDetectionItemService.java +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/service/ITeDetectionItemService.java @@ -58,6 +58,9 @@ public interface ITeDetectionItemService extends IService { */ public int deployFlow(TeDetectItemDeployFlowVO teDeployFlowVo); + /** Save the editable workflow draft without changing the published release. */ + TeDetectionItem saveDraft(TeDetectItemDeployFlowVO request, String operator); + /** * 发布流程 */ diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/service/ITeDetectionItemVersionService.java b/cmvr-iot-test/src/main/java/com/cmvr/test/service/ITeDetectionItemVersionService.java new file mode 100644 index 0000000..361b2ae --- /dev/null +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/service/ITeDetectionItemVersionService.java @@ -0,0 +1,16 @@ +package com.cmvr.test.service; + +import com.baomidou.mybatisplus.spring.service.IService; +import com.cmvr.test.model.domain.TeDetectionItem; +import com.cmvr.test.model.domain.TeDetectionItemVersion; +import com.cmvr.test.model.vo.TeFlowPublishVO; + +import java.util.List; + +public interface ITeDetectionItemVersionService extends IService { + TeDetectionItemVersion publish(TeFlowPublishVO request, String operator); + List listVersions(String itemId); + TeDetectionItemVersion getVersion(String versionId); + TeDetectionItem restoreDraft(String versionId, Integer expectedRevision, String operator); + TeDetectionItemVersion rollback(String versionId, String publishNote, String operator); +} diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/service/ITeTaskInstFlowVersionService.java b/cmvr-iot-test/src/main/java/com/cmvr/test/service/ITeTaskInstFlowVersionService.java new file mode 100644 index 0000000..2a9bf79 --- /dev/null +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/service/ITeTaskInstFlowVersionService.java @@ -0,0 +1,7 @@ +package com.cmvr.test.service; + +import com.baomidou.mybatisplus.spring.service.IService; +import com.cmvr.test.model.domain.TeTaskInstFlowVersion; + +public interface ITeTaskInstFlowVersionService extends IService { +} diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/service/impl/TeDetectionItemServiceImpl.java b/cmvr-iot-test/src/main/java/com/cmvr/test/service/impl/TeDetectionItemServiceImpl.java index ad276e5..86492b1 100644 --- a/cmvr-iot-test/src/main/java/com/cmvr/test/service/impl/TeDetectionItemServiceImpl.java +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/service/impl/TeDetectionItemServiceImpl.java @@ -1,13 +1,15 @@ package com.cmvr.test.service.impl; import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.digest.DigestUtil; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.spring.service.impl.ServiceImpl; import com.cmvr.common.core.domain.entity.SysGroup; +import com.cmvr.common.exception.GlobalException; import com.cmvr.common.utils.EntityCopyUtils; import com.cmvr.test.enums.NodeTypeEnum; import com.cmvr.test.mapper.TeDetectionItemMapper; @@ -15,6 +17,7 @@ import com.cmvr.test.model.domain.TeDetectionItem; import com.cmvr.test.model.vo.TeDetectItemDeployFlowVO; import com.cmvr.test.model.vo.TeDetectionItemVO; import com.cmvr.test.service.ITeDetectionItemService; +import com.cmvr.test.service.ITeDetectionItemVersionService; import com.github.yulichang.wrapper.MPJLambdaWrapper; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -33,6 +36,8 @@ import java.util.List; @RequiredArgsConstructor public class TeDetectionItemServiceImpl extends ServiceImpl implements ITeDetectionItemService { + private final ITeDetectionItemVersionService versionService; + /** * 查询检测项配置 * @@ -81,6 +86,7 @@ public class TeDetectionItemServiceImpl extends ServiceImpl() - .eq("id", teDeployFlowVo.getId())); + saveDraft(teDeployFlowVo, null); + return 1; + } - if (!exists) { - throw new IllegalArgumentException("ID为" + teDeployFlowVo.getId() + "的检测项不存在!"); + @Override + @Transactional(rollbackFor = Exception.class) + public TeDetectionItem saveDraft(TeDetectItemDeployFlowVO request, String operator) { + TeDetectionItem current = this.baseMapper.selectById(request.getId()); + if (current == null) { + throw new IllegalArgumentException("ID为" + request.getId() + "的检测项不存在!"); + } + int currentRevision = current.getDraftRevision() == null ? 0 : current.getDraftRevision(); + if (request.getDraftRevision() != null && request.getDraftRevision() != currentRevision) { + throw new GlobalException("草稿已被其他窗口修改,请刷新后再保存"); } - // 保存流程 JSON + String config = extractStartConfig(request.getFlowData()); TeDetectionItem updateData = new TeDetectionItem(); - updateData.setId(teDeployFlowVo.getId()); - updateData.setFlowData(teDeployFlowVo.getFlowData()); - //获取params - JSONObject flow = JSON.parseObject(teDeployFlowVo.getFlowData()); + updateData.setFlowData(request.getFlowData()); + updateData.setConfig(config); + updateData.setDraftRevision(currentRevision + 1); + updateData.setDraftHash(contentHash(request.getFlowData(), config)); + updateData.setUpdateBy(operator); + int rows = this.baseMapper.update(updateData, + new LambdaUpdateWrapper() + .eq(TeDetectionItem::getId, request.getId()) + .eq(TeDetectionItem::getDraftRevision, currentRevision)); + if (rows != 1) { + throw new GlobalException("草稿已被其他窗口修改,请刷新后再保存"); + } + return this.baseMapper.selectById(request.getId()); + } + + private String extractStartConfig(String flowData) { + JSONObject flow = JSON.parseObject(flowData); JSONArray nodeArray = flow.getJSONArray("nodes"); if (nodeArray == null) { throw new IllegalArgumentException("流程图中未找到任何节点"); @@ -170,20 +198,22 @@ public class TeDetectionItemServiceImpl extends ServiceImpl + implements ITeDetectionItemVersionService { + + private final TeDetectionItemMapper detectionItemMapper; + + @Override + @Transactional(rollbackFor = Exception.class) + public TeDetectionItemVersion publish(TeFlowPublishVO request, String operator) { + TeDetectionItem item = requireItem(request.getId(), true); + assertRevision(item, request.getDraftRevision()); + if (StrUtil.isBlank(item.getFlowData())) { + throw new GlobalException("请先保存工作流草稿"); + } + String hash = contentHash(item.getFlowData(), item.getConfig()); + TeDetectionItemVersion current = StrUtil.isBlank(item.getPublishedVersionId()) + ? null : baseMapper.selectById(item.getPublishedVersionId()); + if (current != null && hash.equals(current.getContentHash())) { + return current; + } + + TeDetectionItemVersion version = createVersion(item, nextVersionNo(item.getId()), + item.getFlowData(), item.getConfig(), hash, null, request.getPublishNote(), operator); + baseMapper.insert(version); + pointToVersion(item.getId(), version, hash, operator); + return version; + } + + @Override + public List listVersions(String itemId) { + return baseMapper.selectList(new LambdaQueryWrapper() + .select(TeDetectionItemVersion::getId, TeDetectionItemVersion::getDetectionItemId, + TeDetectionItemVersion::getVersionNo, TeDetectionItemVersion::getSchemaVersion, + TeDetectionItemVersion::getContentHash, TeDetectionItemVersion::getSourceVersionId, + TeDetectionItemVersion::getPublishNote, TeDetectionItemVersion::getCreateBy, + TeDetectionItemVersion::getCreateTime) + .eq(TeDetectionItemVersion::getDetectionItemId, itemId) + .orderByDesc(TeDetectionItemVersion::getVersionNo)); + } + + @Override + public TeDetectionItemVersion getVersion(String versionId) { + TeDetectionItemVersion version = baseMapper.selectById(versionId); + if (version == null) throw new GlobalException("发布版本不存在"); + return version; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public TeDetectionItem restoreDraft(String versionId, Integer expectedRevision, String operator) { + TeDetectionItemVersion version = getVersion(versionId); + TeDetectionItem item = requireItem(version.getDetectionItemId(), false); + assertRevision(item, expectedRevision); + int revision = item.getDraftRevision() == null ? 0 : item.getDraftRevision(); + TeDetectionItem update = new TeDetectionItem(); + update.setFlowData(version.getFlowData()); + update.setConfig(version.getConfig()); + update.setDraftHash(version.getContentHash()); + update.setDraftRevision(revision + 1); + update.setUpdateBy(operator); + int rows = detectionItemMapper.update(update, new LambdaUpdateWrapper() + .eq(TeDetectionItem::getId, item.getId()) + .eq(TeDetectionItem::getDraftRevision, revision)); + if (rows != 1) throw new GlobalException("草稿已被其他窗口修改,请刷新后重试"); + return detectionItemMapper.selectById(item.getId()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public TeDetectionItemVersion rollback(String versionId, String publishNote, String operator) { + TeDetectionItemVersion source = getVersion(versionId); + TeDetectionItem item = requireItem(source.getDetectionItemId(), true); + String note = StrUtil.blankToDefault(publishNote, "回退到 V" + source.getVersionNo()); + TeDetectionItemVersion version = createVersion(item, nextVersionNo(item.getId()), + source.getFlowData(), source.getConfig(), source.getContentHash(), source.getId(), note, operator); + baseMapper.insert(version); + pointToVersion(item.getId(), version, source.getContentHash(), operator); + return version; + } + + private TeDetectionItem requireItem(String itemId, boolean lock) { + LambdaQueryWrapper query = new LambdaQueryWrapper() + .eq(TeDetectionItem::getId, itemId); + if (lock) query.last("for update"); + TeDetectionItem item = detectionItemMapper.selectOne(query); + if (item == null) throw new GlobalException("检测项不存在"); + return item; + } + + private void assertRevision(TeDetectionItem item, Integer expected) { + int actual = item.getDraftRevision() == null ? 0 : item.getDraftRevision(); + if (expected != null && expected != actual) { + throw new GlobalException("草稿已被其他窗口修改,请刷新后重试"); + } + } + + private int nextVersionNo(String itemId) { + TeDetectionItemVersion latest = baseMapper.selectOne(new LambdaQueryWrapper() + .eq(TeDetectionItemVersion::getDetectionItemId, itemId) + .orderByDesc(TeDetectionItemVersion::getVersionNo) + .last("limit 1")); + return latest == null ? 1 : latest.getVersionNo() + 1; + } + + private TeDetectionItemVersion createVersion(TeDetectionItem item, int versionNo, + String flowData, String config, String hash, + String sourceVersionId, String note, String operator) { + TeDetectionItemVersion version = new TeDetectionItemVersion(); + version.setDetectionItemId(item.getId()); + version.setVersionNo(versionNo); + version.setFlowData(flowData); + version.setConfig(config); + version.setSchemaVersion(schemaVersion(flowData)); + version.setContentHash(hash); + version.setSourceVersionId(sourceVersionId); + version.setPublishNote(note); + version.setCreateBy(operator); + return version; + } + + private int schemaVersion(String flowData) { + try { + JSONObject flow = JSON.parseObject(flowData); + Integer version = flow.getInteger("schemaVersion"); + if (version != null) return version; + if (flow.getJSONArray("nodes") != null) { + for (Object value : flow.getJSONArray("nodes")) { + JSONObject node = (JSONObject) value; + if ("start".equalsIgnoreCase(node.getString("type")) && node.getJSONObject("properties") != null) { + return node.getJSONObject("properties").getIntValue("schemaVersion", 1); + } + } + } + return 1; + } catch (RuntimeException ignored) { + return 1; + } + } + + private String contentHash(String flowData, String config) { + return DigestUtil.sha256Hex(flowData + "\n" + StrUtil.nullToEmpty(config)); + } + + private void pointToVersion(String itemId, TeDetectionItemVersion version, String hash, String operator) { + TeDetectionItem update = new TeDetectionItem(); + update.setPublishedVersionId(version.getId()); + update.setPublishedVersionNo(version.getVersionNo()); + update.setPublishedDraftHash(hash); + update.setIsDeploy("0"); + update.setStatus("0"); + update.setUpdateBy(operator); + detectionItemMapper.update(update, new LambdaUpdateWrapper() + .eq(TeDetectionItem::getId, itemId)); + } +} diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/service/impl/TeTaskInstFlowVersionServiceImpl.java b/cmvr-iot-test/src/main/java/com/cmvr/test/service/impl/TeTaskInstFlowVersionServiceImpl.java new file mode 100644 index 0000000..629415f --- /dev/null +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/service/impl/TeTaskInstFlowVersionServiceImpl.java @@ -0,0 +1,13 @@ +package com.cmvr.test.service.impl; + +import com.baomidou.mybatisplus.spring.service.impl.ServiceImpl; +import com.cmvr.test.mapper.TeTaskInstFlowVersionMapper; +import com.cmvr.test.model.domain.TeTaskInstFlowVersion; +import com.cmvr.test.service.ITeTaskInstFlowVersionService; +import org.springframework.stereotype.Service; + +@Service +public class TeTaskInstFlowVersionServiceImpl + extends ServiceImpl + implements ITeTaskInstFlowVersionService { +} diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/service/impl/TeTaskOrchestrationServiceImpl.java b/cmvr-iot-test/src/main/java/com/cmvr/test/service/impl/TeTaskOrchestrationServiceImpl.java index 28cb67e..2eb56d0 100644 --- a/cmvr-iot-test/src/main/java/com/cmvr/test/service/impl/TeTaskOrchestrationServiceImpl.java +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/service/impl/TeTaskOrchestrationServiceImpl.java @@ -8,6 +8,7 @@ import com.cmvr.common.utils.uuid.IdUtils; import com.cmvr.test.enums.RunModeEnum; import com.cmvr.test.mapper.TeTaskOrchestrationMapper; import com.cmvr.test.model.domain.TeDetectionItem; +import com.cmvr.test.model.domain.TeDetectionItemVersion; import com.cmvr.test.model.domain.TeTaskConfigInfo; import com.cmvr.test.model.domain.TeTaskOrchestration; import com.cmvr.test.model.dto.TeQueryTaskDetailDTO; @@ -43,7 +44,10 @@ public class TeTaskOrchestrationServiceImpl extends ServiceImpl ''; + +update te_detection_item item +join te_detection_item_version version + on version.detection_item_id = item.id and version.version_no = 1 +set item.published_version_id = version.id, + item.published_version_no = 1, + item.draft_hash = version.content_hash, + item.published_draft_hash = version.content_hash, + item.is_deploy = '0'; + +update te_detection_item +set draft_hash = sha2(concat(ifnull(flow_data, ''), '\n', ifnull(config, '')), 256) +where draft_hash is null and flow_data is not null; + +create table te_task_inst_flow_version ( + id varchar(32) not null, + inst_id varchar(32) not null, + item_id varchar(32) not null, + item_order int not null, + item_occurrence int not null, + version_id varchar(32) not null, + version_no int not null, + create_by varchar(64) null, + create_time datetime null, + update_by varchar(64) null, + update_time datetime null, + remark varchar(500) null, + primary key (id), + unique key uk_task_inst_flow_item (inst_id, item_order), + key idx_task_inst_flow_inst (inst_id), + key idx_task_inst_flow_version (version_id) +) comment='Workflow release manifest frozen for a task instance';