package jnpf.onlinedev.controller; import jnpf.base.ActionResult; import jnpf.base.DataSourceApi; import jnpf.base.entity.VisualAliasEntity; import jnpf.base.entity.VisualdevEntity; import jnpf.base.entity.VisualdevReleaseEntity; import jnpf.base.model.FormDeleteResult; import jnpf.base.model.VisualDevJsonModel; import jnpf.base.model.export.VisualExportVo; import jnpf.base.model.flow.DataModel; import jnpf.base.model.flow.FlowFormDataModel; import jnpf.base.model.flow.FlowStateModel; import jnpf.base.model.online.VisualdevModelDataInfoVO; import jnpf.base.service.FlowFormRelationService; import jnpf.base.service.VisualAliasService; import jnpf.base.service.VisualdevReleaseService; import jnpf.base.service.VisualdevService; import jnpf.base.util.FlowFormDataUtil; import jnpf.base.util.FlowFormHttpReqUtils; import jnpf.database.model.entity.DbLinkEntity; import jnpf.onlinedev.VisualdevOnlineApi; import jnpf.onlinedev.model.OnlineInfoModel; import jnpf.onlinedev.model.VisualParamModel; import jnpf.onlinedev.model.log.VisualLogForm; import jnpf.onlinedev.service.VisualDevInfoService; import jnpf.onlinedev.service.VisualDevListService; import jnpf.onlinedev.service.VisualLogService; import jnpf.onlinedev.service.VisualdevModelDataService; import jnpf.onlinedev.util.FlowFormCustomUtils; import jnpf.onlinedev.util.OnlinePublicUtils; import jnpf.util.*; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @Slf4j @RestController @RequiredArgsConstructor @RequestMapping("/VisualdevForFlow") public class VisualdevForFlowController implements VisualdevOnlineApi { private final VisualdevReleaseService visualdevReleaseService; private final VisualDevListService visualDevListService; private final VisualDevInfoService visualDevInfoService; private final FlowFormHttpReqUtils flowFormHttpReqUtils; private final FlowFormCustomUtils flowFormCustomUtils; private final FlowFormDataUtil flowDataUtil; private final DataSourceApi dblinkService; private final FlowFormRelationService flowFormRelationService; private final VisualdevModelDataService visualdevModelDataService; private final VisualdevService visualdevService; private final VisualAliasService aliasService; // AUDIT 补丁(2026-08-05,登记 tasks/jnpf-platform-patches.md #7): // 流程实例删除会连带删掉业务表单数据,平台原生此路径不发任何 VSLOG 事件。 private final VisualLogService visualLogService; @Override @PostMapping("/saveOrUpdate") public ActionResult saveOrUpdate(@RequestBody FlowFormDataModel flowFormDataModel) { DataModel dataModel = null; try { String id = flowFormDataModel.getId(); String formId = flowFormDataModel.getFormId(); String flowId = flowFormDataModel.getFlowId(); Map map = flowFormDataModel.getMap(); List> formOperates = flowFormDataModel.getFormOperates(); VisualdevReleaseEntity entity = visualdevReleaseService.getById(formId); VisualdevEntity visualdevEntity = JsonUtil.getJsonToBean(entity, VisualdevEntity.class); map.put(FlowFormConstant.FLOWID, flowId); if (map.get(TableFeildsEnum.VERSION.getField().toUpperCase()) != null) {//针对Oracle数据库大小写敏感,出现大写字段补充修复 map.put(TableFeildsEnum.VERSION.getField(), map.get(TableFeildsEnum.VERSION.getField().toUpperCase())); } //系统表单 if (entity.getType() == 2) { map.put("formOperates", formOperates); flowFormHttpReqUtils.saveOrUpdate(visualdevEntity, id, UserProvider.getToken(), map); } else { dataModel = flowFormCustomUtils.saveOrUpdate(visualdevEntity, flowFormDataModel); } } catch (Exception e) { log.error(e.getMessage(), e); return ActionResult.fail(e.getMessage()); } return ActionResult.success(dataModel); } @Override @DeleteMapping("/delete") public boolean delete(@RequestParam("formId") String formId, @RequestParam("id") String id) { try { VisualdevReleaseEntity entity = visualdevReleaseService.getById(formId); VisualdevEntity visualdevEntity = JsonUtil.getJsonToBean(entity, VisualdevEntity.class); //系统表单 if (entity.getType() == 2) { // 系统表单的数据不在本库(走 HTTP 打到外部系统),本地无快照可取, // 也不该产出指向本库的审计事件——这条分支不发。 flowFormHttpReqUtils.delete(visualdevEntity, id, UserProvider.getToken()); } else { VisualDevJsonModel visualJsonModel = OnlinePublicUtils.getVisualJsonModel(visualdevEntity); DbLinkEntity linkEntity = StringUtil.isNotEmpty(visualdevEntity.getDbLinkId()) ? dblinkService.getInfo(visualdevEntity.getDbLinkId()) : null; // ── AUDIT 补丁(2026-08-05,登记 tasks/jnpf-platform-patches.md #7)第 1/2 段 ── // 触发链:用户在流程列表删除流程实例 → TaskServiceImpl.delete/deleteBatch // → OperatorUtil.deleteFormData → ServiceUtil.deleteFormData // → Feign visualdevApi.delete(formId, id) → 本方法。 // 🔴 顺序是硬约束:主键解析与快照都必须在 deleteTable **之前**——删完再查什么都没有。 // 入参 id 可能是 flowTaskId(deleteTable 按「主键 = id OR flowTaskId = id」定位), // 而审计事件的 dataId 必须是业务主键,否则查询侧对不上记录。 Object auditMainId = null; Map auditSnapshot = null; try { auditMainId = flowDataUtil.resolveMainId(id, visualJsonModel, linkEntity); if (auditMainId != null) { auditSnapshot = auditDeleteSnapshot(String.valueOf(auditMainId), visualdevEntity); } } catch (Throwable t) { log.warn("[AUDIT] 流程删除取快照失败,本次删除不带内容快照。formId={}, id={}, error={}", formId, id, t.getMessage()); auditMainId = null; auditSnapshot = null; } // ── 第 1/2 段结束 ────────────────────────────────────────────────────────── FormDeleteResult delResult = flowDataUtil.deleteTable(id, visualJsonModel, linkEntity); // ── AUDIT 补丁 第 2/2 段 ─────────────────────────────────────────────────── // dataId 回落到 delResult.getMainId():上面解析失败但删除成功时, // 「这次删过」这个事实不能缺——缺的只能是内容快照。 // 不设 batchOpId/batchSize:这是单条删除,审计侧 batchOpId 为空时回落到 // EVENT_ID_PREFIX + eventId,batchSize 为 null 时不缀「共 N 条」。 // 不设 bizSign:流程实例删除没有电子签名入口。 Object eventDataId = auditMainId != null ? auditMainId : (delResult == null ? null : delResult.getMainId()); if (delResult != null && delResult.isDeleted() && eventDataId != null) { try { visualLogService.createEventLog(VisualLogForm.builder() .modelId(formId) .dataId(String.valueOf(eventDataId)) .newData(auditSnapshot) .type(2) .build()); } catch (Throwable t) { log.warn("[AUDIT] 流程删除事件发布失败,已跳过。formId={}, id={}, error={}", formId, id, t.getMessage()); } } // ── AUDIT 补丁结束 ──────────────────────────────────────────────────────── } } catch (Exception e) { return false; } return true; } @Override @GetMapping("/info") public ActionResult info(@RequestParam("formId") String formId, @RequestParam("id") String id) { ActionResult result = new ActionResult<>(); Map allDataMap = new HashMap<>(); VisualdevReleaseEntity entity = visualdevReleaseService.getById(formId); VisualdevEntity visualdevEntity = JsonUtil.getJsonToBean(entity, VisualdevEntity.class); result.setCode(visualdevEntity == null ? 400 : 200); result.setMsg(visualdevEntity == null ? "表单信息不存在" : ""); if (visualdevEntity != null) { //判断是否为系统表单 boolean b = visualdevEntity.getType() == 2; if (b) { allDataMap.putAll(flowFormHttpReqUtils.info(visualdevEntity, id, UserProvider.getToken())); } else { allDataMap.putAll(flowFormCustomUtils.info(visualdevEntity, id)); } } result.setData(allDataMap); return result; } @Override @GetMapping("/getFormConfig") public VisualdevEntity getFormConfig(@RequestParam("formId") String formId) { VisualdevReleaseEntity entity = visualdevReleaseService.getById(formId); return JsonUtil.getJsonToBean(entity, VisualdevEntity.class); } @Override @PostMapping("/getFormConfigList") public List getFormConfigList(@RequestBody List formIds) { List list = visualdevReleaseService.selectByIds(formIds, VisualdevReleaseEntity::getId, VisualdevReleaseEntity::getEnCode, VisualdevReleaseEntity::getFullName, VisualdevReleaseEntity::getWebType, VisualdevReleaseEntity::getType); return CollectionUtils.isNotEmpty(list) ? JsonUtil.getJsonToList(list, VisualdevEntity.class) : new ArrayList<>(); } @Override @PostMapping("/saveFlowIdByFormIds") public void saveFlowIdByFormIds(@RequestBody String flowId, @RequestParam("formId") List formIds) { flowFormRelationService.saveFlowIdByFormIds(flowId, formIds); } @Override @GetMapping("/getReleaseInfo") public VisualdevEntity getReleaseInfo(@RequestParam("formId") String formId) { return visualdevService.getReleaseInfo(formId); } @Override @PostMapping("/getListWithTableList") public List> getListWithTableList(@RequestBody VisualParamModel model) { return visualDevListService.getListWithTableList(model.getVisualDevJsonModel(), model.getPagination(), model.getUserInfo()); } @Override @PostMapping("/getEditDataInfo") public VisualdevModelDataInfoVO getEditDataInfo(@RequestBody VisualParamModel model) { return visualDevInfoService.getEditDataInfo(model.getId(), model.getVisualdevEntity(), OnlineInfoModel.builder().build()); } @Override @PostMapping("/visualCreate") public ActionResult visualCreate(@RequestBody VisualParamModel model) { DataModel dataModel; try { dataModel = visualdevModelDataService.visualCreate(VisualParamModel.builder().visualdevEntity(model.getVisualdevEntity()).data(model.getData()).build()); } catch (Exception e) { e.printStackTrace(); return ActionResult.fail(e.getMessage()); } return ActionResult.success(dataModel); } @Override @PostMapping("/visualUpdate") public ActionResult visualUpdate(@RequestBody VisualParamModel model) { DataModel dataModel; try { dataModel = visualdevModelDataService.visualUpdate(model); } catch (Exception e) { e.printStackTrace(); return ActionResult.fail(e.getMessage()); } return ActionResult.success(dataModel); } @Override @PostMapping("/visualDelete") public ActionResult visualDelete(@RequestBody VisualParamModel model) { try { visualdevModelDataService.visualDelete(model.getVisualdevEntity(), model.getDataList()); } catch (Exception e) { e.printStackTrace(); return ActionResult.fail(e.getMessage()); } return ActionResult.success(); } @Override @PostMapping("/deleteByTableName") public ActionResult deleteByTableName(@RequestBody FlowFormDataModel model) { try { visualdevModelDataService.deleteByTableName(model); } catch (Exception e) { e.printStackTrace(); return ActionResult.fail(e.getMessage()); } return ActionResult.success(); } @Override @PostMapping("/saveState") public void saveState(@RequestBody FlowStateModel model) { List formIds = model.getFormIds(); String flowTaskId = model.getFlowTaskId(); Integer flowState = model.getFlowState(); for (String formId : formIds) { VisualdevReleaseEntity entity = visualdevReleaseService.getById(formId); if (entity == null) continue; VisualdevEntity visualdevEntity = JsonUtil.getJsonToBean(entity, VisualdevEntity.class); //系统表单 if (entity.getType() == 2) { flowFormHttpReqUtils.saveState(visualdevEntity, flowTaskId, flowState); } else { flowDataUtil.saveState(visualdevEntity, flowTaskId, flowState); } } } @Override @GetMapping("/getExportList") public List getExportList(@RequestParam("systemId") String systemId) { return visualdevService.getExportList(systemId); } @Override @PostMapping("/importCopy") public boolean importCopy(@RequestBody List list, @RequestParam("systemId") String systemId) { try { for (VisualExportVo item : list) { VisualdevEntity entity = JsonUtil.getJsonToBean(item, VisualdevEntity.class); String id = RandomUtil.uuId(); entity.setId(id); entity.setCreatorTime(DateUtil.getNowDate()); entity.setCreatorUserId(UserProvider.getUser().getUserId()); entity.setLastModifyTime(null); entity.setLastModifyUserId(null); entity.setState(0); entity.setSystemId(systemId); visualdevService.setAutoEnCode(entity); visualdevService.save(entity); if (StringUtil.isNotEmpty(item.getAliasListJson())) { List jsonToList = JsonUtil.getJsonToList(item.getAliasListJson(), VisualAliasEntity.class); for (VisualAliasEntity aliasEntity : jsonToList) { aliasService.copyEntity(aliasEntity, id); } } } return true; } catch (Exception e) { log.error(e.getMessage()); } return false; } @Override @GetMapping("/deleteBySystemId") public void deleteBySystemId(@RequestParam("systemId") String systemId) { visualdevService.deleteBySystemId(systemId); } /** * AUDIT 补丁(2026-08-05,登记 tasks/jnpf-platform-patches.md #7): * 取被删数据的快照,供审计层 0 渲染标题与留存 data_snapshot。 * *

与 {@code VisualdevModelDataController.auditDeleteSnapshot} 同款参数 * (needRlationFiled=true, needSwap=false),保证两条删除路径产出的 newData 同构, * 审计侧的渲染链路得以原样复用。 * *

入参必须是**业务主键**(不是 flowTaskId)——调用方先经 * {@code flowDataUtil.resolveMainId} 解析。 * *

取不到返回 null:事件照发(「这次删过」不能缺),只是没有快照可渲染。 */ private Map auditDeleteSnapshot(String id, VisualdevEntity visualdevEntity) { try { VisualdevModelDataInfoVO info = visualDevInfoService.getEditDataInfo(id, visualdevEntity, OnlineInfoModel.builder().needRlationFiled(true).needSwap(false).build()); return info == null ? null : JsonUtil.stringToMap(info.getData()); } catch (Throwable t) { log.warn("[AUDIT] 取删除快照失败 id={}, error={}", id, t.getMessage()); return null; } } }