Spring Boot 流程引擎服务:流程定义/实例/任务、审批人解析、 回调分发、oapi 接口及单元测试。 保留远程 .gitea/workflows/build.yml 与 docker/Dockerfile, 新增 .gitignore 排除 target/ 构建产物。
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
package com.yunhai.flowcenter.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.yunhai.flowcenter.engine.DefValidator;
|
||||
import com.yunhai.flowcenter.engine.FlowDefModel;
|
||||
import com.yunhai.flowcenter.engine.FlowEngine;
|
||||
import com.yunhai.flowcenter.entity.FlwAssigneeMap;
|
||||
import com.yunhai.flowcenter.entity.FlwDef;
|
||||
import com.yunhai.flowcenter.enums.FlwEnums;
|
||||
import com.yunhai.flowcenter.repository.FlwAssigneeMapRepository;
|
||||
import com.yunhai.flowcenter.repository.FlwDefRepository;
|
||||
import com.yunhai.platform.apistd.BizException;
|
||||
import com.yunhai.platform.utils.exception.ErrorCode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 流程定义管理(M1:JSON 文本编辑模式,概览 §9.2):
|
||||
* 保存=草稿直改/已发布升版本(flowGuid 跨版本不变);发布跑静态校验(把"任务无主"拦在发布期)。
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class FlowDefAdminService {
|
||||
|
||||
private final FlwDefRepository defRepository;
|
||||
private final FlwAssigneeMapRepository mapRepository;
|
||||
private final FlowEngine flowEngine;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
/** 定义列表:status 空=全量(各流程线各版本都展示,管理端"版本历史"内聚在行操作里) */
|
||||
public List<FlwDef> list(Long schId, Integer status) {
|
||||
if (status != null) {
|
||||
return defRepository.findBySchIdAndStatusOrderByUpdateTimeDesc(schId, status);
|
||||
}
|
||||
return defRepository.findBySchIdOrderByUpdateTimeDesc(schId);
|
||||
}
|
||||
|
||||
/** 全量列表(管理端首页:各流程线最新版本+状态聚合展示简化为全量返回) */
|
||||
public List<FlwDef> listAll(Long schId) {
|
||||
return defRepository.findPublishedLatestPerGuid(schId);
|
||||
}
|
||||
|
||||
public FlwDef get(Long schId, Long id) {
|
||||
FlwDef def = requireDef(schId, id);
|
||||
return def;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存(新建语义按 baseGuid/同名草稿分派,避免 uk(sch,code,version) 冲突):
|
||||
* - id 非空:草稿直改;已发布/停用=复制升版本(同 flowGuid,version=max+1)
|
||||
* - id 空 + baseGuid 非空:在该流程线上开新版本草稿(version=max+1,flowGuid 不变)——前端"升版本"
|
||||
* - id 空 + 同名草稿存在:复用该草稿直改
|
||||
* - id 空 + 全新 code:新流程线(flowGuid 生成,version=1)
|
||||
*/
|
||||
@Transactional
|
||||
public FlwDef save(Long schId, Long id, String code, String name, String contentJson, String baseGuid) {
|
||||
// JSON 先解析再落库(语法错误不进库)
|
||||
FlowDefModel parsed = FlowDefModel.parse(objectMapper, contentJson);
|
||||
if (code == null || code.trim().isEmpty() || name == null || name.trim().isEmpty()) {
|
||||
throw new BizException(ErrorCode.PARAM_ERROR, "code/name 必填");
|
||||
}
|
||||
FlwDef def;
|
||||
if (id == null && baseGuid != null && !baseGuid.trim().isEmpty()) {
|
||||
// 在既有流程线上开新版本草稿
|
||||
defRepository.findFirstBySchIdAndFlowGuidOrderByVersionDesc(schId, baseGuid.trim())
|
||||
.orElseThrow(() -> new BizException(ErrorCode.PARAM_ERROR, "baseGuid 流程线不存在"));
|
||||
def = new FlwDef();
|
||||
def.setSchId(schId);
|
||||
def.setFlowGuid(baseGuid.trim());
|
||||
def.setCode(code.trim());
|
||||
def.setVersion(defRepository.maxVersionByGuid(schId, baseGuid.trim()) + 1);
|
||||
def.setStatus(FlwEnums.DefStatus.DRAFT.getCode());
|
||||
} else if (id == null) {
|
||||
FlwDef draft = defRepository.findFirstBySchIdAndCodeAndStatus(
|
||||
schId, code.trim(), FlwEnums.DefStatus.DRAFT.getCode()).orElse(null);
|
||||
if (draft != null) {
|
||||
// 同名草稿复用直改
|
||||
def = draft;
|
||||
} else {
|
||||
def = new FlwDef();
|
||||
def.setSchId(schId);
|
||||
def.setFlowGuid(UUID.randomUUID().toString().replace("-", "").toLowerCase());
|
||||
def.setCode(code.trim());
|
||||
def.setVersion(defRepository.maxVersionByCode(schId, code.trim()) + 1);
|
||||
def.setStatus(FlwEnums.DefStatus.DRAFT.getCode());
|
||||
}
|
||||
} else {
|
||||
def = requireDef(schId, id);
|
||||
if (!def.getCode().equals(code.trim())) {
|
||||
throw new BizException(ErrorCode.PARAM_ERROR, "流程编码不可修改");
|
||||
}
|
||||
if (def.getStatus() != FlwEnums.DefStatus.DRAFT.getCode()) {
|
||||
// 升版本:复制行,保持 flowGuid
|
||||
FlwDef next = new FlwDef();
|
||||
next.setSchId(schId);
|
||||
next.setFlowGuid(def.getFlowGuid());
|
||||
next.setCode(def.getCode());
|
||||
next.setVersion(defRepository.maxVersionByGuid(schId, def.getFlowGuid()) + 1);
|
||||
next.setStatus(FlwEnums.DefStatus.DRAFT.getCode());
|
||||
def = next;
|
||||
}
|
||||
}
|
||||
def.setCode(code.trim());
|
||||
def.setName(name.trim());
|
||||
def.setContentJson(contentJson);
|
||||
// 定义 JSON 里的 code/name 与行字段一致性校验(给绑定回显用行字段,JSON 内为主)
|
||||
if (parsed.code != null && !parsed.code.trim().isEmpty() && !parsed.code.trim().equals(def.getCode())) {
|
||||
def.setCode(parsed.code.trim());
|
||||
}
|
||||
if (parsed.name != null && !parsed.name.trim().isEmpty()) {
|
||||
def.setName(parsed.name.trim());
|
||||
}
|
||||
return defRepository.save(def);
|
||||
}
|
||||
|
||||
/** 发布:静态校验(START/END 唯一、无孤儿无环、DEPT_KEY 有启用映射)后置为已发布 */
|
||||
@Transactional
|
||||
public FlwDef publish(Long schId, Long id) {
|
||||
FlwDef def = requireDef(schId, id);
|
||||
if (def.getStatus() == FlwEnums.DefStatus.PUBLISHED.getCode()) {
|
||||
throw new BizException(ErrorCode.PARAM_ERROR, "已是发布状态");
|
||||
}
|
||||
FlowDefModel model = FlowDefModel.parse(objectMapper, def.getContentJson());
|
||||
Set<String> enabledDeptKeys = mapRepository.findEnabledBySchId(schId).stream()
|
||||
.map(FlwAssigneeMap::getDeptKey).collect(Collectors.toSet());
|
||||
DefValidator.validate(model, enabledDeptKeys);
|
||||
def.setStatus(FlwEnums.DefStatus.PUBLISHED.getCode());
|
||||
def.setPublishTime(LocalDateTime.now());
|
||||
return defRepository.save(def);
|
||||
}
|
||||
|
||||
/** 停用:不可新发起,存量跑完 */
|
||||
@Transactional
|
||||
public void offline(Long schId, Long id) {
|
||||
FlwDef def = requireDef(schId, id);
|
||||
def.setStatus(FlwEnums.DefStatus.DISABLED.getCode());
|
||||
defRepository.save(def);
|
||||
}
|
||||
|
||||
/** 同一流程线全部版本(降序) */
|
||||
public List<FlwDef> versions(Long schId, String flowGuid) {
|
||||
return defRepository.findBySchIdAndFlowGuidOrderByVersionDesc(schId, flowGuid);
|
||||
}
|
||||
|
||||
private FlwDef requireDef(Long schId, Long id) {
|
||||
FlwDef def = defRepository.findById(id)
|
||||
.orElseThrow(() -> new BizException(ErrorCode.PARAM_ERROR, "流程定义不存在:" + id));
|
||||
if (!def.getSchId().equals(schId)) {
|
||||
throw new BizException(ErrorCode.FORBIDDEN, "流程定义不属于该学校");
|
||||
}
|
||||
return def;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user