Files
gxtyzx-java/src/main/java/com/gxwebsoft/approval/service/ApprovalFlowConfigService.java
T

184 lines
7.2 KiB
Java

package com.gxwebsoft.approval.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.gxwebsoft.approval.document.ApprovalDocumentRegistry;
import com.gxwebsoft.approval.document.ApprovalDocumentType;
import com.gxwebsoft.approval.entity.ApprovalFlowDefinition;
import com.gxwebsoft.approval.mapper.ApprovalFlowDefinitionMapper;
import com.gxwebsoft.approval.param.ApprovalFlowPublishParam;
import com.gxwebsoft.approval.support.ApprovalFieldSpec;
import com.gxwebsoft.approval.support.ApprovalNode;
import com.gxwebsoft.approval.vo.ApprovalFlowVO;
import com.gxwebsoft.approval.vo.ApprovalFlowVersionVO;
import com.gxwebsoft.common.core.exception.BusinessException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 流程配置服务(ADR 0015):在后台查看与发布流程定义。
*
* <p>不改 {@link ApprovalFlowService#getOrCreate}:后台发布的新版本号恒大于代码基线,
* 因此 getOrCreate 的「库中版本 >= 基线即用库中版本」判定自然选用后台配置。</p>
*
* <p>版本不可变:发布只是插入新行,已提交实例继续引用其 definition_id;新版本
* 只对之后提交的单据生效。保存即发布,没有草稿态。</p>
*/
@Service
public class ApprovalFlowConfigService {
private static final Logger log = LoggerFactory.getLogger(ApprovalFlowConfigService.class);
private static final DateTimeFormatter TIME_FORMAT =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final int MAX_PUBLISH_ATTEMPTS = 3;
private static final int HISTORY_LIMIT = 50;
@Resource
private ApprovalFlowDefinitionMapper flowDefinitionMapper;
@Resource
private ApprovalDocumentRegistry documentRegistry;
@Resource
private ApprovalNodeValidator nodeValidator;
@Resource
private ObjectMapper objectMapper;
/** 全部已注册流程的当前版本;未发布过的流程返回空节点,前端据此显示「尚未发布」。 */
public List<ApprovalFlowVO> list() {
List<ApprovalFlowVO> list = new ArrayList<>();
for (ApprovalDocumentType type : documentRegistry.all()) {
list.add(toVO(type, findLatest(type.flowCode())));
}
return list;
}
/** 历史版本(倒序,最多 HISTORY_LIMIT 条)。 */
public List<ApprovalFlowVersionVO> history(String flowCode) {
documentRegistry.require(flowCode);
List<ApprovalFlowDefinition> definitions = flowDefinitionMapper.selectList(
new LambdaQueryWrapper<ApprovalFlowDefinition>()
.eq(ApprovalFlowDefinition::getFlowCode, flowCode)
.orderByDesc(ApprovalFlowDefinition::getVersion)
.last("LIMIT " + HISTORY_LIMIT));
List<ApprovalFlowVersionVO> list = new ArrayList<>();
for (ApprovalFlowDefinition definition : definitions) {
ApprovalFlowVersionVO vo = new ApprovalFlowVersionVO();
vo.setDefinitionId(definition.getId());
vo.setVersion(definition.getVersion());
vo.setName(definition.getName());
vo.setPublishTime(formatTime(definition));
vo.setNodes(parseNodes(definition.getNodesJson()));
list.add(vo);
}
return list;
}
/** 发布新版本:校验节点后插入新行,版本号取库中最新版与代码基线的较大者加一。 */
public ApprovalFlowVO publish(ApprovalFlowPublishParam param) {
if (param == null || param.getFlowCode() == null) {
throw new BusinessException("请选择要发布的流程");
}
String flowCode = param.getFlowCode();
ApprovalDocumentType type = documentRegistry.require(flowCode);
List<ApprovalNode> nodes = nodeValidator.validate(flowCode, param.getNodes());
String nodesJson = writeNodes(nodes);
for (int attempt = 0; attempt < MAX_PUBLISH_ATTEMPTS; attempt++) {
ApprovalFlowDefinition latest = findLatest(flowCode);
ApprovalFlowDefinition created = new ApprovalFlowDefinition();
created.setFlowCode(flowCode);
created.setVersion(nextVersion(latest, type));
created.setName(flowName(latest, type));
created.setNodesJson(nodesJson);
try {
flowDefinitionMapper.insert(created);
log.info("审批流程定义已发布: flowCode={}, version={}", flowCode, created.getVersion());
return toVO(type, created);
} catch (DuplicateKeyException e) {
log.info("审批流程定义发布冲突,重试: flowCode={}", flowCode);
}
}
throw new BusinessException("流程定义发布冲突,请重试");
}
private int nextVersion(ApprovalFlowDefinition latest, ApprovalDocumentType type) {
int baseline = Math.max(1, type.latestVersion());
if (latest == null) {
return baseline;
}
return Math.max(latest.getVersion(), baseline) + 1;
}
private String flowName(ApprovalFlowDefinition latest, ApprovalDocumentType type) {
if (latest != null && latest.getName() != null && !latest.getName().trim().isEmpty()) {
return latest.getName();
}
return type.flowName();
}
private ApprovalFlowVO toVO(ApprovalDocumentType type, ApprovalFlowDefinition latest) {
ApprovalFlowVO vo = new ApprovalFlowVO();
vo.setFlowCode(type.flowCode());
vo.setFlowName(type.flowName());
vo.setFactoryVersion(type.latestVersion());
vo.setFactoryNodes(parseNodes(type.nodesJson()));
vo.setResolvers(nodeValidator.resolverOptions());
List<ApprovalFieldSpec> specs = type.fieldSpecs();
vo.setFieldSpecs(specs == null ? Collections.emptyList() : specs);
if (latest != null) {
vo.setDefinitionId(latest.getId());
vo.setVersion(latest.getVersion());
vo.setPublishTime(formatTime(latest));
vo.setNodes(parseNodes(latest.getNodesJson()));
} else {
vo.setNodes(Collections.emptyList());
}
return vo;
}
private List<ApprovalNode> parseNodes(String nodesJson) {
if (nodesJson == null || nodesJson.trim().isEmpty()) {
return Collections.emptyList();
}
try {
List<ApprovalNode> nodes = objectMapper.readValue(nodesJson,
objectMapper.getTypeFactory().constructCollectionType(List.class, ApprovalNode.class));
return nodes == null ? Collections.emptyList() : nodes;
} catch (Exception e) {
log.warn("审批流程定义节点解析失败", e);
throw new BusinessException("审批流程定义格式错误");
}
}
private String writeNodes(List<ApprovalNode> nodes) {
try {
return objectMapper.writeValueAsString(nodes);
} catch (Exception e) {
throw new BusinessException("流程节点格式错误");
}
}
private String formatTime(ApprovalFlowDefinition definition) {
return definition.getCreateTime() == null ? null : definition.getCreateTime().format(TIME_FORMAT);
}
private ApprovalFlowDefinition findLatest(String flowCode) {
return flowDefinitionMapper.selectOne(new LambdaQueryWrapper<ApprovalFlowDefinition>()
.eq(ApprovalFlowDefinition::getFlowCode, flowCode)
.orderByDesc(ApprovalFlowDefinition::getVersion)
.last("LIMIT 1"));
}
}