504 lines
26 KiB
Java
504 lines
26 KiB
Java
package com.yunhai.flowcenter.controller.api;
|
||
|
||
import com.yunhai.flowcenter.entity.FlwAssigneeMap;
|
||
import com.yunhai.flowcenter.entity.FlwCallbackLog;
|
||
import com.yunhai.flowcenter.entity.FlwCallbackReg;
|
||
import com.yunhai.flowcenter.entity.FlwDef;
|
||
import com.yunhai.flowcenter.entity.FlwInstance;
|
||
import com.yunhai.flowcenter.model.FlowModels;
|
||
import com.yunhai.flowcenter.repository.FlwInstanceRepository;
|
||
import com.yunhai.flowcenter.service.FlowAdminGuard;
|
||
import com.yunhai.flowcenter.service.FlowCallbackAdminService;
|
||
import com.yunhai.flowcenter.service.FlowDefAdminService;
|
||
import com.yunhai.flowcenter.service.FlowDiagramService;
|
||
import com.yunhai.flowcenter.service.FlowMapAdminService;
|
||
import com.yunhai.platform.apistd.BizException;
|
||
import com.yunhai.platform.utils.exception.ErrorCode;
|
||
import com.yunhai.platform.utils.model.BasePageResult;
|
||
import com.yunhai.platform.utils.model.BoaUser;
|
||
import lombok.RequiredArgsConstructor;
|
||
import org.springframework.data.domain.Page;
|
||
import org.springframework.data.domain.PageRequest;
|
||
import org.springframework.data.domain.Sort;
|
||
import org.springframework.http.MediaType;
|
||
import org.springframework.http.ResponseEntity;
|
||
import org.springframework.web.bind.annotation.GetMapping;
|
||
import org.springframework.web.bind.annotation.PathVariable;
|
||
import org.springframework.web.bind.annotation.PostMapping;
|
||
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 java.util.List;
|
||
import java.util.Map;
|
||
|
||
/**
|
||
* 管理端(/api/flow/admin/**,CAS + 管理员角色,FlowAdminGuard 校验):
|
||
* 定义 JSON 编辑/发布/停用、办理人映射 CRUD(成员检索调 UserBase)、
|
||
* 回调注册管理/推送日志/死信重推、办件查询。
|
||
*/
|
||
@RestController
|
||
@RequestMapping("/api/flow/admin")
|
||
@RequiredArgsConstructor
|
||
public class FlowAdminController {
|
||
|
||
private final FlowAdminGuard adminGuard;
|
||
private final FlowDefAdminService defAdminService;
|
||
private final FlowMapAdminService mapAdminService;
|
||
private final FlowCallbackAdminService callbackAdminService;
|
||
private final com.yunhai.flowcenter.service.FlowRouterAdminService routerAdminService;
|
||
private final FlwInstanceRepository instanceRepository;
|
||
private final FlowDiagramService diagramService;
|
||
private final com.yunhai.flowcenter.service.DiagramImageService imageService;
|
||
private final com.fasterxml.jackson.databind.ObjectMapper objectMapper;
|
||
private final com.yunhai.flowcenter.service.FlowAppAdminService appAdminService;
|
||
private final com.yunhai.flowcenter.service.FlowDeptAdminService deptAdminService;
|
||
private final com.yunhai.flowcenter.repository.FlwRouteLogRepository routeLogRepository;
|
||
|
||
// ==================== 流程定义 ====================
|
||
|
||
@GetMapping("/def/list")
|
||
public List<FlwDef> defList(BoaUser user,
|
||
@RequestParam(value = "status", required = false) Integer status,
|
||
@RequestParam(value = "appKey", required = false) String appKey) {
|
||
return defAdminService.list(adminGuard.requireAdmin(user, null), status, appKey);
|
||
}
|
||
|
||
/** 应用清单("按应用筛选"下拉):本校 flw_def 已标注 ∪ 已注册回调的应用 */
|
||
@GetMapping("/def/appkeys")
|
||
public List<String> defAppKeys(BoaUser user) {
|
||
return defAdminService.distinctAppKeys(adminGuard.requireAdmin(user, null));
|
||
}
|
||
|
||
// ==================== 应用设置(2026-10-10 四议题) ====================
|
||
|
||
/**
|
||
* 应用管理列表:本校"可对接流程"白名单(flw_app)。附带一次学校池静默同步
|
||
* (UserBase list-by-school;不可达降级本地行)。开通过程在 UserBase 管理端做。
|
||
*/
|
||
@GetMapping("/app/list")
|
||
public List<com.yunhai.flowcenter.entity.FlwApp> appList(BoaUser user) {
|
||
return appAdminService.list(adminGuard.requireAdmin(user, null));
|
||
}
|
||
|
||
/** 开关某应用是否对接流程(停用后 defSave 归属应用校验拦截新引用) */
|
||
@PostMapping("/app/enable")
|
||
public void appEnable(BoaUser user,
|
||
@org.springframework.web.bind.annotation.RequestBody Map<String, Object> body) {
|
||
Long schId = adminGuard.requireAdmin(user, null);
|
||
appAdminService.enable(schId,
|
||
body.get("id") == null ? null : Long.valueOf(String.valueOf(body.get("id"))),
|
||
Boolean.parseBoolean(String.valueOf(body.get("enabled"))));
|
||
}
|
||
|
||
/** 应用辅助统计(2026-10-11 增强):appKey → 回调注册数(本校),应用设置页"对接深度"列 */
|
||
@GetMapping("/app/stats")
|
||
public Map<String, Long> appStats(BoaUser user) {
|
||
return appAdminService.stats(adminGuard.requireAdmin(user, null));
|
||
}
|
||
|
||
@GetMapping("/def/get")
|
||
public FlwDef defGet(BoaUser user, @RequestParam("id") Long id) {
|
||
return defAdminService.get(adminGuard.requireAdmin(user, null), id);
|
||
}
|
||
|
||
/** 保存(新建草稿/编辑;已发布定义保存=升版本,flowGuid 不变;baseGuid=在指定流程线上开新版本草稿;changeLog=升版本改动说明,仅新版本行落库,>500 截断;formUuid=关联表单定义 uuid(C1,2026-10-20):null=保持、空串=取消关联、发布后不可改改=升版) */
|
||
@PostMapping("/def/save")
|
||
public FlwDef defSave(BoaUser user, @RequestBody Map<String, Object> body) {
|
||
Long schId = adminGuard.requireAdmin(user, null);
|
||
Long id = body.get("id") == null ? null : Long.valueOf(String.valueOf(body.get("id")));
|
||
return defAdminService.save(schId, id,
|
||
(String) body.get("code"), (String) body.get("name"),
|
||
(String) body.get("contentJson"), (String) body.get("baseGuid"),
|
||
(String) body.get("appKey"), (String) body.get("changeLog"),
|
||
(String) body.get("formUuid"));
|
||
}
|
||
|
||
/**
|
||
* 元数据原地编辑(2026-10-10 议题2):只改 name/appKey 不升版本(version/flowGuid/status/contentJson
|
||
* 结构均不动,仅同步 contentJson 顶层 name);已发布亦可。结构改动请走编辑器升版本。
|
||
*/
|
||
@PostMapping("/def/updateMeta")
|
||
public FlwDef defUpdateMeta(BoaUser user, @RequestBody Map<String, Object> body) {
|
||
Long schId = adminGuard.requireAdmin(user, null);
|
||
return defAdminService.updateMeta(schId,
|
||
body.get("id") == null ? null : Long.valueOf(String.valueOf(body.get("id"))),
|
||
(String) body.get("name"), (String) body.get("appKey"));
|
||
}
|
||
|
||
@PostMapping("/def/publish/{id}")
|
||
public FlwDef defPublish(BoaUser user, @PathVariable("id") Long id) {
|
||
return defAdminService.publish(adminGuard.requireAdmin(user, null), id);
|
||
}
|
||
|
||
@PostMapping("/def/offline/{id}")
|
||
public void defOffline(BoaUser user, @PathVariable("id") Long id) {
|
||
defAdminService.offline(adminGuard.requireAdmin(user, null), id);
|
||
}
|
||
|
||
/** 启用(与 offline 对称):仅停用版 →已发布,重跑发布期静态校验 */
|
||
@PostMapping("/def/enable/{id}")
|
||
public FlwDef defEnable(BoaUser user, @PathVariable("id") Long id) {
|
||
return defAdminService.enable(adminGuard.requireAdmin(user, null), id);
|
||
}
|
||
|
||
@GetMapping("/def/versions")
|
||
public List<FlwDef> defVersions(BoaUser user, @RequestParam("flowGuid") String 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 任意版本出图(草稿可预览) */
|
||
@GetMapping("/def/diagram")
|
||
public FlowModels.DiagramVo defDiagram(BoaUser user, @RequestParam("id") Long id,
|
||
@RequestParam(value = "mode", required = false) String mode) {
|
||
Long schId = adminGuard.requireAdmin(user, null);
|
||
FlwDef def = defAdminService.get(schId, id);
|
||
return diagramService.build(def, null, mode);
|
||
}
|
||
|
||
/**
|
||
* 流程定义示意图 PNG 流(2026-09-17,示意图页"进度图"视图):<img> 直嵌走同域 CAS 会话,
|
||
* 可见性与 /def/diagram 同口径(requireAdmin)。
|
||
*/
|
||
@GetMapping("/def/diagram.png")
|
||
public ResponseEntity<byte[]> defDiagramPng(BoaUser user, @RequestParam("id") Long id,
|
||
@RequestParam(value = "mode", required = false) String mode) {
|
||
Long schId = adminGuard.requireAdmin(user, null);
|
||
FlwDef def = defAdminService.get(schId, id);
|
||
return ResponseEntity.ok()
|
||
.contentType(MediaType.IMAGE_PNG)
|
||
.cacheControl(org.springframework.http.CacheControl.noStore())
|
||
.body(imageService.renderPng(diagramService.build(def, null, mode)));
|
||
}
|
||
|
||
// ==================== 科室管理(2026-10-10 四议题·议题3,取代 /map/* 映射 CRUD) ====================
|
||
|
||
/** 科室列表(编辑器 ctx.depts 同结构:deptKey=id 字符串 + 启用成员数) */
|
||
@GetMapping("/dept/list")
|
||
public List<com.yunhai.flowcenter.service.FlowDeptAdminService.DeptVo> deptList(BoaUser user) {
|
||
return deptAdminService.list(adminGuard.requireAdmin(user, null));
|
||
}
|
||
|
||
@PostMapping("/dept/create")
|
||
public com.yunhai.flowcenter.entity.FlwDept deptCreate(BoaUser user, @RequestBody Map<String, Object> body) {
|
||
return deptAdminService.create(adminGuard.requireAdmin(user, null), (String) body.get("deptName"));
|
||
}
|
||
|
||
@PostMapping("/dept/rename")
|
||
public com.yunhai.flowcenter.entity.FlwDept deptRename(BoaUser user, @RequestBody Map<String, Object> body) {
|
||
Long schId = adminGuard.requireAdmin(user, null);
|
||
return deptAdminService.rename(schId, requireId(body.get("id")), (String) body.get("deptName"));
|
||
}
|
||
|
||
/** 停用/启用科室(停用=解析不出办理人、发布校验拦新引用;成员不动) */
|
||
@PostMapping("/dept/status")
|
||
public void deptStatus(BoaUser user, @RequestBody Map<String, Object> body) {
|
||
Long schId = adminGuard.requireAdmin(user, null);
|
||
deptAdminService.setStatus(schId, requireId(body.get("id")), boolVal(body.get("enabled")));
|
||
}
|
||
|
||
/** 删除科室(级联删成员;被流程引用的 deptKey 将解析失败——发布校验/办理时会拦) */
|
||
@PostMapping("/dept/delete")
|
||
public void deptDelete(BoaUser user, @RequestBody Map<String, Object> body) {
|
||
Long schId = adminGuard.requireAdmin(user, null);
|
||
deptAdminService.delete(schId, requireId(body.get("id")));
|
||
}
|
||
|
||
@GetMapping("/dept/members")
|
||
public List<com.yunhai.flowcenter.entity.FlwDeptMember> deptMembers(BoaUser user,
|
||
@RequestParam("deptId") Long deptId) {
|
||
return deptAdminService.members(adminGuard.requireAdmin(user, null), deptId);
|
||
}
|
||
|
||
/** 批量加成员(已存在跳过;姓名经 UserBase 补全),返回新增数 */
|
||
@PostMapping("/dept/memberAdd")
|
||
public int deptMemberAdd(BoaUser user, @RequestBody Map<String, Object> body) {
|
||
Long schId = adminGuard.requireAdmin(user, null);
|
||
List<Integer> userIds = new java.util.ArrayList<>();
|
||
Object raw = body.get("userIds");
|
||
if (raw instanceof List) {
|
||
for (Object o : (List<?>) raw) {
|
||
if (o != null) {
|
||
userIds.add(Integer.valueOf(String.valueOf(o)));
|
||
}
|
||
}
|
||
}
|
||
return deptAdminService.memberAdd(schId, requireId(body.get("deptId")), userIds);
|
||
}
|
||
|
||
@PostMapping("/dept/memberRemove")
|
||
public void deptMemberRemove(BoaUser user, @RequestBody Map<String, Object> body) {
|
||
Long schId = adminGuard.requireAdmin(user, null);
|
||
deptAdminService.memberRemove(schId, requireId(body.get("memberId")));
|
||
}
|
||
|
||
/**
|
||
* 一键拉取教师组·预览:学校子树内教师组容器 → 科室+组内成员(UserBase 组 id 即将落
|
||
* flw_dept.source_group_id)。导入前先展示给管理员确认,不落库。
|
||
*/
|
||
@GetMapping("/dept/groupPreview")
|
||
public List<com.yunhai.flowcenter.service.FlowMapAdminService.GroupDeptVo> groupPreview(BoaUser user) {
|
||
return mapAdminService.previewGroupDepts(adminGuard.requireAdmin(user, null));
|
||
}
|
||
|
||
/** 一键拉取教师组·导入:upsert flw_dept(source_group_id 幂等,重复拉取=刷新组名与成员集) */
|
||
@PostMapping("/dept/importGroups")
|
||
public com.yunhai.flowcenter.service.FlowDeptAdminService.GroupImportResultVo importGroups(BoaUser user) {
|
||
return deptAdminService.importGroups(adminGuard.requireAdmin(user, null));
|
||
}
|
||
|
||
private static Long longVal(Object o) {
|
||
return o == null ? null : Long.valueOf(String.valueOf(o));
|
||
}
|
||
|
||
/** 科室/成员行 id 缺失=调用方bug/脏请求:回业务错提示,不让 findById(null) 炸成笼统 500 */
|
||
private static Long requireId(Object o) {
|
||
Long v = longVal(o);
|
||
if (v == null) {
|
||
throw new BizException(ErrorCode.PARAM_ERROR, "缺少 id");
|
||
}
|
||
return v;
|
||
}
|
||
|
||
private static boolean boolVal(Object o) {
|
||
return o == null ? false : Boolean.parseBoolean(String.valueOf(o));
|
||
}
|
||
|
||
/** 成员检索(UserBase /oapi/user/byId;编辑器选人/姓名补全共用,非映射 CRUD,保留) */
|
||
@GetMapping("/map/lookupUser")
|
||
public com.yunhai.platform.utils.model.BoaUser lookupUser(BoaUser user,
|
||
@RequestParam("userId") Integer userId) {
|
||
adminGuard.requireAdmin(user, null);
|
||
return mapAdminService.lookupUser(userId);
|
||
}
|
||
|
||
// ==================== 选人组件数据源 ====================
|
||
|
||
/** 选人组件树:学校组树(组→组内用户叶子),管理端「选择用户」弹窗共用 */
|
||
@GetMapping("/user/tree")
|
||
public List<FlowMapAdminService.SelectUserNodeVo> userTree(BoaUser user) {
|
||
return mapAdminService.userTree(adminGuard.requireAdmin(user, null));
|
||
}
|
||
|
||
/**
|
||
* 选人组件树·教师版(2026-09-17,仿 Notice /api/base/GetSchoolAll):组树骨架(剔除学生组)+
|
||
* userclass 教师按 GroupIds 挂组,管理端「选择用户」统一走它。
|
||
*/
|
||
@GetMapping("/user/teacherTree")
|
||
public List<FlowMapAdminService.SelectUserNodeVo> teacherTree(BoaUser user) {
|
||
return mapAdminService.teacherTree(adminGuard.requireAdmin(user, null));
|
||
}
|
||
|
||
// ==================== 回调注册/推送日志/死信 ====================
|
||
|
||
@GetMapping("/callback/regList")
|
||
public List<FlwCallbackReg> regList(BoaUser user) {
|
||
return callbackAdminService.listRegs(adminGuard.requireAdmin(user, null));
|
||
}
|
||
|
||
@PostMapping("/callback/regStatus/{id}")
|
||
public void regStatus(BoaUser user, @PathVariable("id") Long id,
|
||
@RequestParam("enabled") boolean enabled) {
|
||
callbackAdminService.setRegStatus(adminGuard.requireAdmin(user, null), id, enabled);
|
||
}
|
||
|
||
/** secret 重置(新 secret 返回给前端展示一次) */
|
||
@PostMapping("/callback/resetSecret/{id}")
|
||
public Map<String, String> resetSecret(BoaUser user, @PathVariable("id") Long id) {
|
||
String secret = callbackAdminService.resetSecret(adminGuard.requireAdmin(user, null), id);
|
||
return java.util.Collections.singletonMap("secret", secret);
|
||
}
|
||
|
||
/**
|
||
* 回调探测(2026-10-11 管理端增强):对该注册同步推送一条 flow.ping(未知事件码,接收方按
|
||
* 规范 §6.4 记日志后 200),返回 {success, httpStatus, elapsedMs, respSnippet, error};
|
||
* 结果同时落推送日志一行(event=flow.ping)。
|
||
*/
|
||
@PostMapping("/callback/probe/{id}")
|
||
public Map<String, Object> probe(BoaUser user, @PathVariable("id") Long id) {
|
||
return callbackAdminService.probe(adminGuard.requireAdmin(user, null), id);
|
||
}
|
||
|
||
@GetMapping("/callback/logs")
|
||
public BasePageResult<FlwCallbackLog> logs(BoaUser user,
|
||
@RequestParam(value = "status", required = false) Integer status,
|
||
@RequestParam(value = "event", required = false) String event,
|
||
@RequestParam(value = "instanceId", required = false) Long instanceId,
|
||
@RequestParam(value = "page", required = false) Integer page,
|
||
@RequestParam(value = "size", required = false) Integer size) {
|
||
return callbackAdminService.pageLogs(adminGuard.requireAdmin(user, null), status, event, instanceId, page, size);
|
||
}
|
||
|
||
/** 挂起列表(待推且重试达阈值;持续重试中疑似消费方停机的可视入口) */
|
||
@GetMapping("/callback/hung")
|
||
public BasePageResult<FlwCallbackLog> hung(BoaUser user,
|
||
@RequestParam(value = "page", required = false) Integer page,
|
||
@RequestParam(value = "size", required = false) Integer size) {
|
||
return callbackAdminService.pageHung(adminGuard.requireAdmin(user, null), page, size);
|
||
}
|
||
|
||
/** 管理端手工标记死信(死信唯一人工入口;标记后可 rePush 复活) */
|
||
@PostMapping("/callback/markDead/{id}")
|
||
public void markDead(BoaUser user, @PathVariable("id") Long id) {
|
||
callbackAdminService.markDead(adminGuard.requireAdmin(user, null), id);
|
||
}
|
||
|
||
@GetMapping("/callback/dead")
|
||
public BasePageResult<FlwCallbackLog> dead(BoaUser user,
|
||
@RequestParam(value = "page", required = false) Integer page,
|
||
@RequestParam(value = "size", required = false) Integer size) {
|
||
return callbackAdminService.pageDead(adminGuard.requireAdmin(user, null), page, size);
|
||
}
|
||
|
||
@PostMapping("/callback/repush")
|
||
public void repush(BoaUser user, @RequestBody List<Long> logIds) {
|
||
callbackAdminService.rePush(adminGuard.requireAdmin(user, null), logIds);
|
||
}
|
||
|
||
/**
|
||
* 对接健康度体检(2026-10-15 议题二 P2):每应用一行六项检查 + 综合评分。
|
||
* 数据全部来自已有资产(flw_callback_reg / flw_callback_log),零新增表;
|
||
* 应用名从 flw_app 白名单补齐(缺失回落 appKey)。
|
||
*/
|
||
@GetMapping("/callback/health")
|
||
public List<com.yunhai.flowcenter.model.AppHealthVo> callbackHealth(BoaUser user) {
|
||
Long schId = adminGuard.requireAdmin(user, null);
|
||
java.util.Map<String, String> appNames = new java.util.HashMap<>();
|
||
for (com.yunhai.flowcenter.entity.FlwApp app : appAdminService.list(schId)) {
|
||
if (app.getAppKey() != null) {
|
||
appNames.put(app.getAppKey(), app.getAppName());
|
||
}
|
||
}
|
||
return callbackAdminService.health(schId, appNames);
|
||
}
|
||
|
||
// ==================== 条件路由注册 / 重跑路由 ====================
|
||
|
||
/** 判断服务注册列表(flw_router 全局表;编辑器 routerKey 下拉与管理页共用) */
|
||
@GetMapping("/router/list")
|
||
public List<com.yunhai.flowcenter.entity.FlwRouter> routerList(BoaUser user) {
|
||
adminGuard.requireAdmin(user, null);
|
||
return routerAdminService.list();
|
||
}
|
||
|
||
/** 保存注册(id 空=新建;serviceKey=服务目录键,path=判断端点,basePath=对端 context-path 生产必核对) */
|
||
@PostMapping("/router/save")
|
||
public com.yunhai.flowcenter.entity.FlwRouter routerSave(BoaUser user,
|
||
@RequestBody Map<String, Object> body) {
|
||
adminGuard.requireAdmin(user, null);
|
||
return routerAdminService.save(
|
||
body.get("id") == null ? null : Long.valueOf(String.valueOf(body.get("id"))),
|
||
(String) body.get("routerKey"), (String) body.get("name"),
|
||
(String) body.get("serviceKey"), (String) body.get("path"),
|
||
(String) body.get("basePath"));
|
||
}
|
||
|
||
/** 启用/停用注册(停用后发布校验拦截新引用) */
|
||
@PostMapping("/router/status/{id}")
|
||
public void routerStatus(BoaUser user, @PathVariable("id") Long id,
|
||
@RequestParam("enabled") boolean enabled) {
|
||
adminGuard.requireAdmin(user, null);
|
||
routerAdminService.setStatus(id, enabled);
|
||
}
|
||
|
||
/**
|
||
* 重跑路由(判断失败挂起的恢复入口,2026-09-24 §4.4):重新调判断服务,成功即按出口续走;
|
||
* 仍失败回业务错(办件继续挂起,route_log 已落新行)。
|
||
* useLatestDef=true(F5-4 僵尸逃生门):按该 flowGuid 当前已发布最高版本重跑并刷新办件定义快照。
|
||
*/
|
||
@PostMapping("/instance/{id}/reroute")
|
||
public void instanceReroute(BoaUser user, @PathVariable("id") Long id,
|
||
@RequestParam(value = "useLatestDef", required = false, defaultValue = "false")
|
||
boolean useLatestDef) {
|
||
routerAdminService.reroute(adminGuard.requireAdmin(user, null), id, useLatestDef);
|
||
}
|
||
|
||
/**
|
||
* 表达式调试求值(2026-10-10 四议题):body {expression, variablesJson},与运行时同一求值核心
|
||
* (ExpressionEvaluator);不落库、不走流程,发布前/挂起排查用。内置变量需在 variablesJson 里
|
||
* 自行提供样例值(线上是引擎注入的办件上下文)。
|
||
*/
|
||
@PostMapping("/router/eval")
|
||
public Map<String, Object> routerEval(BoaUser user, @RequestBody Map<String, Object> body) {
|
||
adminGuard.requireAdmin(user, null);
|
||
String expression = body.get("expression") == null ? "" : String.valueOf(body.get("expression")).trim();
|
||
String variablesJson = body.get("variablesJson") == null ? "" : String.valueOf(body.get("variablesJson")).trim();
|
||
Map<String, Object> result = new java.util.HashMap<>();
|
||
if (expression.isEmpty()) {
|
||
result.put("result", false);
|
||
result.put("error", "表达式为空");
|
||
return result;
|
||
}
|
||
Map<String, Object> env;
|
||
if (variablesJson.isEmpty()) {
|
||
env = new java.util.HashMap<>();
|
||
} else {
|
||
try {
|
||
env = objectMapper.readValue(variablesJson,
|
||
new com.fasterxml.jackson.core.type.TypeReference<Map<String, Object>>() {
|
||
});
|
||
} catch (Exception e) {
|
||
result.put("result", false);
|
||
result.put("error", "样例变量不是合法 JSON 对象:" + e.getMessage());
|
||
return result;
|
||
}
|
||
}
|
||
try {
|
||
Object value = com.yunhai.flowcenter.engine.ExpressionEvaluator.evaluate(expression, env);
|
||
result.put("result", Boolean.TRUE.equals(value));
|
||
result.put("value", String.valueOf(value));
|
||
} catch (Exception e) {
|
||
result.put("result", false);
|
||
result.put("error", String.valueOf(e.getMessage() != null ? e.getMessage() : e.toString()));
|
||
}
|
||
return result;
|
||
}
|
||
|
||
// ==================== 办件查询 ====================
|
||
|
||
/**
|
||
* 管理端办件分页(F11-4 增 hung 筛选与行级红标):hung=true 只看路由挂起办件
|
||
* (口径见 {@link com.yunhai.flowcenter.repository.FlwInstanceRepository#pageAdmin});
|
||
* 每行回填 {@code routeHung}(按页一次 {@code findHungInstanceIds} 查询,非逐行 N+1)。
|
||
*/
|
||
@GetMapping("/instance/page")
|
||
public BasePageResult<FlwInstance> instancePage(
|
||
BoaUser user,
|
||
@RequestParam(value = "status", required = false) Integer status,
|
||
@RequestParam(value = "keyword", required = false) String keyword,
|
||
@RequestParam(value = "hung", required = false) Boolean hung,
|
||
@RequestParam(value = "page", required = false) Integer page,
|
||
@RequestParam(value = "size", required = false) Integer size) {
|
||
Long schId = adminGuard.requireAdmin(user, null);
|
||
Page<FlwInstance> p = instanceRepository.pageAdmin(schId, status, keyword, hung,
|
||
PageRequest.of(page == null || page < 1 ? 0 : page - 1,
|
||
size == null || size < 1 || size > 100 ? 10 : size,
|
||
Sort.by(Sort.Order.desc("id"))));
|
||
List<Long> pageIds = new java.util.ArrayList<>();
|
||
for (FlwInstance i : p.getContent()) {
|
||
pageIds.add(i.getId());
|
||
}
|
||
// 行级红标:不在挂起集合内的行显式置 false(前端只判 truthy,null 也行,但显式更可读)
|
||
java.util.Set<Long> hungIds = pageIds.isEmpty() ? java.util.Collections.emptySet()
|
||
: new java.util.HashSet<>(routeLogRepository.findHungInstanceIds(pageIds));
|
||
for (FlwInstance i : p.getContent()) {
|
||
i.setRouteHung(hungIds.contains(i.getId()));
|
||
}
|
||
int pi = page == null || page < 1 ? 1 : page;
|
||
int ps = size == null || size < 1 || size > 100 ? 10 : size;
|
||
return new BasePageResult<>(p.getContent(), (int) p.getTotalElements(), pi, ps,
|
||
p.getTotalPages(), p.hasNext());
|
||
}
|
||
}
|