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.enums.FormBindingErrorCode;
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;
/**
* 表单中心目录客户端(v3/TASK_1)。
*
*
字段注入而不是加进构造参数:本类的构造器已被 {@code FlowAdminInstanceHungTest} 与
* {@code FlowDiagramServiceTest} 逐位钉住,而那两份测试不在本任务的可改清单里——多一个入参
* 会让整个测试树编译不过。
*/
@org.springframework.beans.factory.annotation.Autowired
private com.yunhai.flowcenter.service.FormCenterCatalogClient formCenterCatalogClient;
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 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 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 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 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 appStats(BoaUser user) {
return appAdminService.stats(adminGuard.requireAdmin(user, null));
}
// ==================== 应用使用授权(T01) ====================
/**
* 使用授权管理面(字段注入:构造被既有单测逐位钉住,同 formCenterCatalogClient 先例)。
* 授权对象是稳定应用主体(UserBase appId),非 appKey 凭据;撤权只拦新发起。
*/
@org.springframework.beans.factory.annotation.Autowired
private com.yunhai.flowcenter.service.FlowUsageGrantAdminService usageGrantAdminService;
/** 某流程线的使用授权清单(含已撤销行,审计可见) */
@GetMapping("/usage-grant/list")
public List usageGrantList(
BoaUser user, @RequestParam("flowGuid") String flowGuid) {
return usageGrantAdminService.listByFlow(adminGuard.requireAdmin(user, null), flowGuid);
}
/** 授予/重申使用授权(appKey 当场解析为 UserBase 稳定主体落库;enforce 开启时校学校开通) */
@PostMapping("/usage-grant/grant")
public com.yunhai.flowcenter.entity.FlwUsageGrant usageGrantGrant(
BoaUser user, @RequestBody Map body) {
Long schId = adminGuard.requireAdmin(user, null);
return usageGrantAdminService.grant(schId,
body.get("flowGuid") == null ? null : String.valueOf(body.get("flowGuid")),
body.get("appKey") == null ? null : String.valueOf(body.get("appKey")),
body.get("remark") == null ? null : String.valueOf(body.get("remark")),
user.getName());
}
/** 撤销使用授权(enabled=0 行保留;只拦新发起,在途与已受理命令不受影响) */
@PostMapping("/usage-grant/revoke")
public com.yunhai.flowcenter.entity.FlwUsageGrant usageGrantRevoke(
BoaUser user, @RequestBody Map body) {
Long schId = adminGuard.requireAdmin(user, null);
return usageGrantAdminService.revoke(schId,
body.get("flowGuid") == null ? null : String.valueOf(body.get("flowGuid")),
body.get("appId") == null ? null : Integer.valueOf(String.valueOf(body.get("appId"))),
body.get("remark") == null ? null : String.valueOf(body.get("remark")),
user.getName());
}
@GetMapping("/def/get")
public FlwDef defGet(BoaUser user, @RequestParam("id") Long id) {
return defAdminService.get(adminGuard.requireAdmin(user, null), id);
}
/**
* 表单中心目录代理(v3/TASK_1):编辑器"关联表单"选择器的数据源,浏览器只打本应用同源接口,
* appKey/secret 不在前端出现。校域由 {@link FlowAdminGuard#requireAdmin} 从 CAS 会话派生,
* 前端没有可越界的 schId 入口;中心侧只回"已发布 ∩ 本应用可读"的表单,
* 没授权的表压根不出现(不是"看得见但选不了")。
*/
@GetMapping("/form-center/forms")
public com.yunhai.flowcenter.service.FormCenterCatalogClient.CatalogPage formCenterForms(
BoaUser user,
@RequestParam(value = "appCode", required = false) String appCode,
@RequestParam(value = "keyword", required = false) String keyword,
@RequestParam(value = "page", required = false) Integer page,
@RequestParam(value = "pageSize", required = false) Integer pageSize) {
return formCenterCatalogClient.listForms(adminGuard.requireAdmin(user, null),
appCode, keyword, page, pageSize);
}
/**
* 保存(新建草稿/编辑;已发布定义保存=升版本,flowGuid 不变;baseGuid=在指定流程线上开新版本草稿;
* changeLog=升版本改动说明,仅新版本行落库,>500 截断;
* formUuid=关联表单定义 uuid(C1,2026-10-20):null=保持、空串=取消关联、发布后不可改改=升版;
* formVersion=v3 确切版本(与 formUuid 成对,单传一个即拒;取消关联时同批清空)。
*/
@PostMapping("/def/save")
public FlwDef defSave(BoaUser user, @RequestBody Map 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"),
parseFormVersion(body.get("formVersion")));
}
private static Integer parseFormVersion(Object value) {
if (value == null) return null;
try {
return Integer.valueOf(String.valueOf(value));
} catch (NumberFormatException e) {
throw new BizException(FormBindingErrorCode.INVALID_COORDINATE, "formVersion 必须是正整数", e);
}
}
/**
* 元数据原地编辑(2026-10-10 议题2):只改 name/appKey 不升版本(version/flowGuid/status/contentJson
* 结构均不动,仅同步 contentJson 顶层 name);已发布亦可。结构改动请走编辑器升版本。
*/
@PostMapping("/def/updateMeta")
public FlwDef defUpdateMeta(BoaUser user, @RequestBody Map 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 defVersions(BoaUser user, @RequestParam("flowGuid") String flowGuid) {
return defAdminService.versions(adminGuard.requireAdmin(user, null), flowGuid);
}
// ==================== 线级停发/恢复(T03) ====================
/** 线级停发:整条 flowGuid 停止新发起/预览;在途实例按原版本跑完不受影响 */
@PostMapping("/def/offlineLine")
public int defOfflineLine(BoaUser user, @RequestBody Map body) {
Long schId = adminGuard.requireAdmin(user, null);
return defAdminService.offlineLine(schId,
body.get("flowGuid") == null ? null : String.valueOf(body.get("flowGuid")));
}
/** 线级恢复:只恢复线可用性,不改写任何版本状态或发布内容 */
@PostMapping("/def/enableLine")
public int defEnableLine(BoaUser user, @RequestBody Map body) {
Long schId = adminGuard.requireAdmin(user, null);
return defAdminService.enableLine(schId,
body.get("flowGuid") == null ? null : String.valueOf(body.get("flowGuid")));
}
/** 在途办件数(议题二④):flowGuids 逗号分隔,去重后最多 100;返回 flowGuid → 审批中办件数(无在途也返 0 键) */
@GetMapping("/def/inflight")
public Map defInflight(BoaUser user, @RequestParam("flowGuids") String flowGuids) {
Long schId = adminGuard.requireAdmin(user, null);
List 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 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 deptList(BoaUser user) {
return deptAdminService.list(adminGuard.requireAdmin(user, null));
}
@PostMapping("/dept/create")
public com.yunhai.flowcenter.entity.FlwDept deptCreate(BoaUser user, @RequestBody Map 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 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 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 body) {
Long schId = adminGuard.requireAdmin(user, null);
deptAdminService.delete(schId, requireId(body.get("id")));
}
@GetMapping("/dept/members")
public List 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 body) {
Long schId = adminGuard.requireAdmin(user, null);
List 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 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 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 userTree(BoaUser user) {
return mapAdminService.userTree(adminGuard.requireAdmin(user, null));
}
/**
* 选人组件树·教师版(2026-09-17,仿 Notice /api/base/GetSchoolAll):组树骨架(剔除学生组)+
* userclass 教师按 GroupIds 挂组,管理端「选择用户」统一走它。
*/
@GetMapping("/user/teacherTree")
public List teacherTree(BoaUser user) {
return mapAdminService.teacherTree(adminGuard.requireAdmin(user, null));
}
// ==================== 回调注册/推送日志/死信 ====================
@GetMapping("/callback/regList")
public List 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 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 probe(BoaUser user, @PathVariable("id") Long id) {
return callbackAdminService.probe(adminGuard.requireAdmin(user, null), id);
}
@GetMapping("/callback/logs")
public BasePageResult 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 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 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 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 callbackHealth(BoaUser user) {
Long schId = adminGuard.requireAdmin(user, null);
java.util.Map 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 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 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 routerEval(BoaUser user, @RequestBody Map 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 result = new java.util.HashMap<>();
if (expression.isEmpty()) {
result.put("result", false);
result.put("error", "表达式为空");
return result;
}
Map env;
if (variablesJson.isEmpty()) {
env = new java.util.HashMap<>();
} else {
try {
env = objectMapper.readValue(variablesJson,
new com.fasterxml.jackson.core.type.TypeReference