feat(def): 流程定义唯一键改按流程线管 + 归属应用筛选
build / build-push (push) Successful in 8m31s

- flw_def 唯一键 (sch,code,ver)→(sch,flow_guid,ver)(uk_flw_def_guid_ver,生产/本地已手工切索引):升版本 maxByGuid+1 与唯一键同口径,同编码多条流程线(work-leave 三条线实锤追尾)不再死锁
- 新建永远新开流程线 v1,删除同编码草稿复用;撞唯一键转"保存冲突"友好提示
- 新增 flw_def.app_key 归属应用列:save 全链路透传(升版本缺省继承源线、传非空覆盖);/admin/def/list 支持 appKey 过滤;新增 /admin/def/appkeys 应用清单(flw_def 已标注 ∪ 已注册回调)
- FlowDefAdminServiceSaveTest 10 用例锁 save 分派语义
This commit is contained in:
chenxing
2026-10-10 14:34:25 +08:00
parent 7c92e953ec
commit a1f99fd7e0
6 changed files with 248 additions and 38 deletions
@@ -12,12 +12,15 @@ 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.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
import java.util.stream.Collectors;
@@ -32,15 +35,28 @@ public class FlowDefAdminService {
private final FlwDefRepository defRepository;
private final FlwAssigneeMapRepository mapRepository;
private final com.yunhai.flowcenter.repository.FlwRouterRepository routerRepository;
private final com.yunhai.flowcenter.repository.FlwCallbackRegRepository regRepository;
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);
/** 定义列表:status/appKey 可选过滤(各流程线各版本都展示;appKey 精确匹配,2026-10-10 按应用筛选) */
public List<FlwDef> list(Long schId, Integer status, String appKey) {
List<FlwDef> rows = status != null
? defRepository.findBySchIdAndStatusOrderByUpdateTimeDesc(schId, status)
: defRepository.findBySchIdOrderByUpdateTimeDesc(schId);
String key = appKey == null ? null : appKey.trim();
if (key != null && !key.isEmpty()) {
rows = rows.stream().filter(d -> key.equals(d.getAppKey())).collect(Collectors.toList());
}
return defRepository.findBySchIdOrderByUpdateTimeDesc(schId);
return rows;
}
/** 应用清单("按应用筛选"下拉数据源):本校 flw_def 已标注 ∪ 已注册回调的应用,去重排序 */
public List<String> distinctAppKeys(Long schId) {
TreeSet<String> keys = new TreeSet<>();
keys.addAll(defRepository.findDistinctAppKeys(schId));
keys.addAll(regRepository.findDistinctAppKeysBySchId(schId));
return new ArrayList<>(keys);
}
/** 全量列表(管理端首页:各流程线最新版本+状态聚合展示简化为全量返回) */
@@ -54,14 +70,15 @@ public class FlowDefAdminService {
}
/**
* 保存(新建语义按 baseGuid/同名草稿分派,避免 uk(sch,code,version) 冲突):
* 保存(2026-10-10 起唯一键为 uk(sch,flow_guid,version),编码降级为可重名的分类标签):
* - id 非空:草稿直改;已发布/停用=复制升版本(同 flowGuid,version=max+1)
* - id 空 + baseGuid 非空:在该流程线上开新版本草稿(version=max+1,flowGuid 不变)——前端"升版本"
* - id 空 + 同名草稿存在:复用该草稿直改
* - id 空 + 全新 code:新流程线(flowGuid 生成,version=1)
* - id 空:永远新开一条流程线(flowGuid 生成,version=1);同编码多条线合法,前端保存前有软提示
* appKey=归属应用(筛选维度):新建/草稿直改直接落库;升版本(baseGuid/已发布行)缺省继承源线,传非空则覆盖
*/
@Transactional
public FlwDef save(Long schId, Long id, String code, String name, String contentJson, String baseGuid) {
public FlwDef save(Long schId, Long id, String code, String name, String contentJson, String baseGuid, String appKey) {
String cleanAppKey = (appKey == null || appKey.trim().isEmpty()) ? null : appKey.trim();
// JSON 先解析再落库(语法错误不进库)
FlowDefModel parsed = FlowDefModel.parse(objectMapper, contentJson);
if (code == null || code.trim().isEmpty() || name == null || name.trim().isEmpty()) {
@@ -69,8 +86,8 @@ public class FlowDefAdminService {
}
FlwDef def;
if (id == null && baseGuid != null && !baseGuid.trim().isEmpty()) {
// 在既有流程线上开新版本草稿
defRepository.findFirstBySchIdAndFlowGuidOrderByVersionDesc(schId, baseGuid.trim())
// 在既有流程线上开新版本草稿(appKey 缺省继承源线)
FlwDef base = defRepository.findFirstBySchIdAndFlowGuidOrderByVersionDesc(schId, baseGuid.trim())
.orElseThrow(() -> new BizException(ErrorCode.PARAM_ERROR, "baseGuid 流程线不存在"));
def = new FlwDef();
def.setSchId(schId);
@@ -78,25 +95,24 @@ public class FlowDefAdminService {
def.setCode(code.trim());
def.setVersion(defRepository.maxVersionByGuid(schId, baseGuid.trim()) + 1);
def.setStatus(FlwEnums.DefStatus.DRAFT.getCode());
def.setAppKey(cleanAppKey != null ? cleanAppKey : base.getAppKey());
} 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());
}
// 新建:永远新开一条流程线,版本从 1 起(不再做同编码草稿复用)
def = new FlwDef();
def.setSchId(schId);
def.setFlowGuid(UUID.randomUUID().toString().replace("-", "").toLowerCase());
def.setCode(code.trim());
def.setVersion(1);
def.setStatus(FlwEnums.DefStatus.DRAFT.getCode());
def.setAppKey(cleanAppKey);
} else {
def = requireDef(schId, id);
if (!def.getCode().equals(code.trim())) {
throw new BizException(ErrorCode.PARAM_ERROR, "流程编码不可修改");
}
if (cleanAppKey != null) {
def.setAppKey(cleanAppKey);
}
if (def.getStatus() != FlwEnums.DefStatus.DRAFT.getCode()) {
// 升版本:复制行,保持 flowGuid
FlwDef next = new FlwDef();
@@ -105,6 +121,7 @@ public class FlowDefAdminService {
next.setCode(def.getCode());
next.setVersion(defRepository.maxVersionByGuid(schId, def.getFlowGuid()) + 1);
next.setStatus(FlwEnums.DefStatus.DRAFT.getCode());
next.setAppKey(cleanAppKey != null ? cleanAppKey : def.getAppKey());
def = next;
}
}
@@ -118,7 +135,12 @@ public class FlowDefAdminService {
if (parsed.name != null && !parsed.name.trim().isEmpty()) {
def.setName(parsed.name.trim());
}
return defRepository.save(def);
try {
return defRepository.save(def);
} catch (DataIntegrityViolationException e) {
// 撞唯一键 (sch,flow_guid,version):并发升版本同一线时可能撞号,转可理解提示(原样抛出=笼统"内部服务错误")
throw new BizException(ErrorCode.PARAM_ERROR, "保存冲突:该流程可能正被他人同时升版本,请返回列表刷新后重试");
}
}
/** 发布:静态校验(START/END 唯一、无孤儿无环、DEPT_KEY 有启用映射)后置为已发布 */