B1 后端基础:flw_def.change_log + 在途统计接口 + 路由 trace 透出 rawResponse

- change_log(议题二③):FlwDef 附加列 + save 加 changeLog 入参,只在三个新建行分支写入
  (def.getId()==null 判据),草稿直改不写也不清空;>500 service 侧截断不抛错;补生产备案 SQL
- GET /admin/def/inflight(议题二④):flowGuid→审批中(10)办件数,口径=def_id ∈ 该线全部版本;
  请求的每个 guid 都建 0 键;逗号分隔去重限 100;FlowDefAdminService 注入 FlwInstanceRepository
- routeTraceRow(议题三②):kind=ROUTE 行增 rawResponse(null→"",>500 截断加 …)

验证:mvn.cmd 单模块编译通过;spring-boot:run 启动成功无循环依赖;ddl-auto 已自动建 change_log 列;
SQL 口径核对在途 37 件/8 条线,flw_route_log 24 行均有 raw_response
This commit is contained in:
chenxing
2026-10-12 15:59:47 +08:00
parent c896fbb90b
commit de332d1c66
7 changed files with 62 additions and 3 deletions
@@ -102,7 +102,7 @@ public class FlowAdminController {
return defAdminService.get(adminGuard.requireAdmin(user, null), id); return defAdminService.get(adminGuard.requireAdmin(user, null), id);
} }
/** 保存(新建草稿/编辑;已发布定义保存=升版本,flowGuid 不变;baseGuid=在指定流程线上开新版本草稿) */ /** 保存(新建草稿/编辑;已发布定义保存=升版本,flowGuid 不变;baseGuid=在指定流程线上开新版本草稿;changeLog=升版本改动说明,仅新版本行落库,>500 截断) */
@PostMapping("/def/save") @PostMapping("/def/save")
public FlwDef defSave(BoaUser user, @RequestBody Map<String, Object> body) { public FlwDef defSave(BoaUser user, @RequestBody Map<String, Object> body) {
Long schId = adminGuard.requireAdmin(user, null); Long schId = adminGuard.requireAdmin(user, null);
@@ -110,7 +110,7 @@ public class FlowAdminController {
return defAdminService.save(schId, id, return defAdminService.save(schId, id,
(String) body.get("code"), (String) body.get("name"), (String) body.get("code"), (String) body.get("name"),
(String) body.get("contentJson"), (String) body.get("baseGuid"), (String) body.get("contentJson"), (String) body.get("baseGuid"),
(String) body.get("appKey")); (String) body.get("appKey"), (String) body.get("changeLog"));
} }
/** /**
@@ -146,6 +146,16 @@ public class FlowAdminController {
return defAdminService.versions(adminGuard.requireAdmin(user, null), flowGuid); return defAdminService.versions(adminGuard.requireAdmin(user, null), flowGuid);
} }
/** 在途办件数(议题二④):flowGuids 逗号分隔,去重后最多 100;返回 flowGuid → 审批中办件数(无在途也返 0 键) */
@GetMapping("/def/inflight")
public Map<String, Long> defInflight(BoaUser user, @RequestParam("flowGuids") String flowGuids) {
Long schId = adminGuard.requireAdmin(user, null);
List<String> guids = java.util.Arrays.stream(flowGuids.split(","))
.map(String::trim).filter(s -> !s.isEmpty()).distinct().limit(100)
.collect(java.util.stream.Collectors.toList());
return defAdminService.inflightCounts(schId, guids);
}
/** 流程定义示意图(方案 §3.1):requireAdmin 后按 defId 任意版本出图(草稿可预览) */ /** 流程定义示意图(方案 §3.1):requireAdmin 后按 defId 任意版本出图(草稿可预览) */
@GetMapping("/def/diagram") @GetMapping("/def/diagram")
public FlowModels.DiagramVo defDiagram(BoaUser user, @RequestParam("id") Long id, public FlowModels.DiagramVo defDiagram(BoaUser user, @RequestParam("id") Long id,
@@ -71,4 +71,8 @@ public class FlwDef extends BaseEntity {
/** 发布时间 */ /** 发布时间 */
@Column(name = "publish_time") @Column(name = "publish_time")
private LocalDateTime publishTime; private LocalDateTime publishTime;
/** 升版本改动说明(议题二③,可跳过;仅新版本行有值,草稿直改不写) */
@Column(name = "change_log", length = 500)
private String changeLog;
} }
@@ -5,6 +5,7 @@ import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@@ -36,4 +37,7 @@ public interface FlwDefRepository extends JpaRepository<FlwDef, Long> {
+ "and d.version = (select max(d2.version) from FlwDef d2 where d2.schId = d.schId " + "and d.version = (select max(d2.version) from FlwDef d2 where d2.schId = d.schId "
+ "and d2.flowGuid = d.flowGuid and d2.status = 1) order by d.updateTime desc") + "and d2.flowGuid = d.flowGuid and d2.status = 1) order by d.updateTime desc")
List<FlwDef> findPublishedLatestPerGuid(@Param("schId") Long schId); List<FlwDef> findPublishedLatestPerGuid(@Param("schId") Long schId);
/** 在途统计(议题二④):一次取回多条流程线的全部版本行 */
List<FlwDef> findBySchIdAndFlowGuidIn(Long schId, Collection<String> flowGuids);
} }
@@ -7,6 +7,7 @@ import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@@ -39,4 +40,8 @@ public interface FlwInstanceRepository extends JpaRepository<FlwInstance, Long>
+ "and (:status is null or i.status = :status) order by i.id desc") + "and (:status is null or i.status = :status) order by i.id desc")
Page<FlwInstance> pageByBiz(@Param("schId") Long schId, @Param("bizType") String bizType, Page<FlwInstance> pageByBiz(@Param("schId") Long schId, @Param("bizType") String bizType,
@Param("status") Integer status, Pageable pageable); @Param("status") Integer status, Pageable pageable);
/** 在途办件数(议题二④):按 def_id 分组统计审批中(10)办件,一行一版本 */
@Query("select i.defId, count(i) from FlwInstance i where i.schId = :schId and i.defId in :defIds and i.status = 10 group by i.defId")
List<Object[]> countApprovingByDefIds(@Param("schId") Long schId, @Param("defIds") Collection<Long> defIds);
} }
@@ -18,7 +18,10 @@ import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.UUID; import java.util.UUID;
@@ -36,6 +39,7 @@ public class FlowDefAdminService {
private final com.yunhai.flowcenter.repository.FlwRouterRepository routerRepository; private final com.yunhai.flowcenter.repository.FlwRouterRepository routerRepository;
private final com.yunhai.flowcenter.repository.FlwCallbackRegRepository regRepository; private final com.yunhai.flowcenter.repository.FlwCallbackRegRepository regRepository;
private final com.yunhai.flowcenter.repository.FlwAppRepository appRepository; private final com.yunhai.flowcenter.repository.FlwAppRepository appRepository;
private final com.yunhai.flowcenter.repository.FlwInstanceRepository instanceRepository;
private final FlowDeptAdminService deptAdminService; private final FlowDeptAdminService deptAdminService;
private final FlowEngine flowEngine; private final FlowEngine flowEngine;
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
@@ -77,9 +81,13 @@ public class FlowDefAdminService {
* - id 空:永远新开一条流程线(flowGuid 生成,version=1);同编码多条线合法,前端保存前有软提示 * - id 空:永远新开一条流程线(flowGuid 生成,version=1);同编码多条线合法,前端保存前有软提示
* appKey=归属应用(筛选维度,2026-10-12 拍板必填):新建必须显式传;升版本/直改可继承源行, * appKey=归属应用(筛选维度,2026-10-12 拍板必填):新建必须显式传;升版本/直改可继承源行,
* 但源行为空时必须显式补填——存量 NULL 线随编辑自然收敛(白名单只校验显式传入值,继承值不重校) * 但源行为空时必须显式补填——存量 NULL 线随编辑自然收敛(白名单只校验显式传入值,继承值不重校)
* changeLog=升版本改动说明(议题二③):只随新建行落库(三个建新行分支),草稿直改行不写也不清空
*/ */
@Transactional @Transactional
public FlwDef save(Long schId, Long id, String code, String name, String contentJson, String baseGuid, String appKey) { public FlwDef save(Long schId, Long id, String code, String name, String contentJson,
String baseGuid, String appKey, String changeLog) {
String cleanLog = (changeLog == null || changeLog.trim().isEmpty()) ? null
: changeLog.trim().substring(0, Math.min(500, changeLog.trim().length()));
String cleanAppKey = (appKey == null || appKey.trim().isEmpty()) ? null : appKey.trim(); String cleanAppKey = (appKey == null || appKey.trim().isEmpty()) ? null : appKey.trim();
// 归属应用白名单(2026-10-10 应用设置):显式传入非空时必须 ∈ flw_app(sch, enabled=1); // 归属应用白名单(2026-10-10 应用设置):显式传入非空时必须 ∈ flw_app(sch, enabled=1);
// 继承值不重校(既成事实,应用停用不应卡死升版本)。不在校池/未开启的应用由此拦下(拍板 D2:不做存量兜底) // 继承值不重校(既成事实,应用停用不应卡死升版本)。不在校池/未开启的应用由此拦下(拍板 D2:不做存量兜底)
@@ -141,6 +149,10 @@ public class FlowDefAdminService {
throw new BizException(ErrorCode.PARAM_ERROR, throw new BizException(ErrorCode.PARAM_ERROR,
"归属应用必填:请选择归属应用后再保存(应用需先在 流程管理 > 应用设置 中开启)"); "归属应用必填:请选择归属应用后再保存(应用需先在 流程管理 > 应用设置 中开启)");
} }
// 改动说明只写新建行(新建/baseGuid 开版/已发布复制升版三分支 id 必为 null);草稿直改不动
if (def.getId() == null) {
def.setChangeLog(cleanLog);
}
def.setContentJson(contentJson); def.setContentJson(contentJson);
// 定义 JSON 里的 code/name 与行字段一致性校验(给绑定回显用行字段,JSON 内为主) // 定义 JSON 里的 code/name 与行字段一致性校验(给绑定回显用行字段,JSON 内为主)
if (parsed.code != null && !parsed.code.trim().isEmpty() && !parsed.code.trim().equals(def.getCode())) { if (parsed.code != null && !parsed.code.trim().isEmpty() && !parsed.code.trim().equals(def.getCode())) {
@@ -205,6 +217,23 @@ public class FlowDefAdminService {
return defRepository.findBySchIdAndFlowGuidOrderByVersionDesc(schId, flowGuid); return defRepository.findBySchIdAndFlowGuidOrderByVersionDesc(schId, flowGuid);
} }
/** 在途办件数(议题二④):flowGuid → 审批中(10)办件数;口径=def_id 精确匹配该线各版本;请求的 guid 全部有键(无在途=0) */
public Map<String, Long> inflightCounts(Long schId, Collection<String> flowGuids) {
Map<String, Long> result = new HashMap<>();
if (flowGuids == null || flowGuids.isEmpty()) return result;
flowGuids.forEach(g -> result.put(g, 0L));
List<FlwDef> defs = defRepository.findBySchIdAndFlowGuidIn(schId, flowGuids);
if (defs.isEmpty()) return result;
Map<Long, String> idToGuid = new HashMap<>();
defs.forEach(d -> idToGuid.put(d.getId(), d.getFlowGuid()));
instanceRepository.countApprovingByDefIds(schId, idToGuid.keySet()).forEach(r -> {
Long defId = ((Number) r[0]).longValue();
Long cnt = ((Number) r[1]).longValue();
result.merge(idToGuid.get(defId), cnt, Long::sum);
});
return result;
}
/** /**
* 元数据原地编辑(2026-10-10 四议题·议题2,拍板 D4):已发布/停用/草稿均可只改 name/appKey * 元数据原地编辑(2026-10-10 四议题·议题2,拍板 D4):已发布/停用/草稿均可只改 name/appKey
* 而<b>不升版本</b>——结构改动仍走 save 的升版本路径,两者完全隔离(本方法不碰 * 而<b>不升版本</b>——结构改动仍走 save 的升版本路径,两者完全隔离(本方法不碰
@@ -383,6 +383,9 @@ public class FlowQueryService {
row.put("attempt", r.getAttempt()); row.put("attempt", r.getAttempt());
row.put("status", null); row.put("status", null);
row.put("time", str(r.getDecideTime())); row.put("time", str(r.getDecideTime()));
// 议题三②:调试弹窗「从路由日志导入」需要看失败摘要(>500 截断)
row.put("rawResponse", r.getRawResponse() == null ? "" :
(r.getRawResponse().length() > 500 ? r.getRawResponse().substring(0, 500) + "…" : r.getRawResponse()));
return row; return row;
} }
@@ -0,0 +1,4 @@
-- 2026-10-12 议题二:升版本改动说明(版本行即说明;独立版本日志表留 P2)
-- ddl-auto=update 会自动加列;本脚本为生产备案(生产建议停自动 DDL 后手工执行)
ALTER TABLE `flw_def`
ADD COLUMN `change_log` VARCHAR(500) NULL COMMENT '升版本改动说明(可跳过;NULL=未填写)' AFTER `name`;