package jnpf.onlinedev.service; import com.baomidou.dynamic.datasource.annotation.DSTransactional; import jnpf.base.entity.VisualdevEntity; import jnpf.base.mapper.UploadEditDraftMapper; import jnpf.base.model.flow.DataModel; import jnpf.base.model.flow.FlowFormDataModel; import jnpf.base.model.uploadedit.UploadEditDraft; import jnpf.base.service.UploadEditDraftService; import jnpf.base.service.UploadFilePermissionQuery; import jnpf.base.service.VisualdevService; import jnpf.base.util.FlowFormDataUtil; import jnpf.base.util.UploadEditStateMachine; import jnpf.base.util.UploadEditWorkingCopy; import jnpf.exception.DataException; import jnpf.exception.WorkFlowException; import jnpf.flowable.model.filepermission.FilePermissionView; import jnpf.flowable.model.filepermission.WorkflowFileCheckpoint; import jnpf.onlinedev.model.OnlineInfoModel; import jnpf.onlinedev.util.FlowFormCustomUtils; import jnpf.util.JsonUtil; import jnpf.util.UserProvider; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.util.*; import java.util.function.Consumer; /** 聚合部署的流程附件检查点。只调用原生保存/历史,不直接写业务表。 */ @Service @RequiredArgsConstructor public class WorkflowFileCheckpointService implements WorkflowFileCheckpoint { private final VisualdevService models; private final FlowFormDataUtil nativeData; private final FlowFormCustomUtils nativeForms; private final UploadEditDraftMapper drafts; private final UploadFilePermissionQuery permissions; @Override @DSTransactional(rollbackFor = Exception.class) @jnpf.audit.sdk.annotation.AuditLog(action = "WORKFLOW_FILE_CHECKPOINT", label = "保存流程共享附件", bizType = "UPLOAD_EDIT", bizCodeExpr = "'UPLOAD_EDIT'", bizCodeRequired = false) public void save(String taskId, String formId, Map formData, Consumer> continuation) { save(taskId, formId, null, formData, continuation); } @Override @DSTransactional(rollbackFor = Exception.class) @jnpf.audit.sdk.annotation.AuditLog(action = "WORKFLOW_FILE_CHECKPOINT", label = "保存流程共享附件", bizType = "UPLOAD_EDIT", bizCodeExpr = "'UPLOAD_EDIT'", bizCodeRequired = false) public void save(String taskId, String formId, String operatorId, Map formData, Consumer> continuation) { FilePermissionView view = permissions.current(taskId, formId); if (view == null || !view.isEnabled() || !"ready".equals(view.getStatus()) || !Objects.equals(taskId, view.getTaskId()) || !Objects.equals(formId, view.getFormId())) throw new DataException("流程文件权限暂不可用,未执行节点操作"); VisualdevEntity model = models.getReleaseInfo(formId); if (model == null) throw new DataException("流程表单不存在"); Map persisted = nativeData.getFilePermissionDataInfo(model, taskId, OnlineInfoModel.builder().build()); String recordId = Objects.toString(persisted.get("id"), ""); if (recordId.isEmpty() || !Objects.equals(taskId, Objects.toString(persisted.get("flowTaskId"), ""))) throw new DataException("流程与原生表单记录不匹配"); if (nativeData.lockDmsAttachmentRecord(formId, recordId)) persisted = nativeData.getFilePermissionDataInfo(model, recordId, OnlineInfoModel.builder().build()); List shared = new ArrayList<>(); for (UploadEditDraft draft : drafts.lockRecord(UploadEditDraftService.tenant(UserProvider.getUser()), formId, recordId)) { if (!UploadEditWorkingCopy.shared(draft) || !taskId.equals(draft.getTaskId())) continue; if (UploadEditWorkingCopy.active(draft)) throw new DataException("流程附件正在编辑,请关闭编辑器并等待保存后再办理"); if (!UploadEditStateMachine.sameFiles(draft.getBaselineJson(), draft.getCurrentJson())) shared.add(draft); } Map result = formData == null ? new LinkedHashMap<>() : new LinkedHashMap<>(formData); // 以完整真实记录作为基底,不能把审批弹窗的局部数据变成主子表增删。 Map checkpoint = new LinkedHashMap<>(persisted); Map form = JsonUtil.getJsonToBean(model.getFormData(), Map.class); boolean submittedAttachments = mergeSubmittedAttachments(checkpoint, result, form == null ? null : form.get("fields")); if (shared.isEmpty() && !submittedAttachments) { continuation.accept(result); return; } if (result.containsKey(UploadEditDraftService.SUBMIT_STATE)) checkpoint.put(UploadEditDraftService.SUBMIT_STATE, result.get(UploadEditDraftService.SUBMIT_STATE)); DataModel saved; try { saved = nativeForms.checkpointWorkflowFiles(model, FlowFormDataModel.builder().id(recordId).formId(formId) .map(checkpoint).workflowOperatorId(operatorId).skipRequiredValidation(true).build()); } catch (WorkFlowException error) { throw new DataException(error.getMessage()); } // 节点暂存的其他字段仍保持用户输入,仅同步真正采用的附件和原生版本号。 for (UploadEditDraft draft : shared) { String parent = draft.getParentField(); String field = parent == null || parent.isEmpty() ? draft.getField() : parent + "-" + draft.getField(); if (!saved.getWorkflowWorkingFields().contains(field)) continue; if (parent == null || parent.isEmpty()) result.put(draft.getField(), checkpoint.get(draft.getField())); else overlayChild(result, checkpoint, draft); } result.remove(UploadEditDraftService.SUBMIT_STATE); String version = jnpf.util.TableFeildsEnum.VERSION.getField(); if (checkpoint.containsKey(version)) result.put(version, checkpoint.get(version)); continuation.accept(result); } private static boolean mergeSubmittedAttachments(Map target, Map submitted, Object fields) { Map> uploads = new LinkedHashMap<>(); collectUploadFields(fields, null, uploads); boolean changed = false; for (String field : uploads.getOrDefault("", Collections.emptySet())) { if (!submitted.containsKey(field) || UploadEditStateMachine.sameFiles(target.get(field), submitted.get(field))) continue; target.put(field, submitted.get(field)); changed = true; } for (Map.Entry> table : uploads.entrySet()) { if (table.getKey().isEmpty() || !(submitted.get(table.getKey()) instanceof List) || !(target.get(table.getKey()) instanceof List)) continue; List> rows = copyRows(target.get(table.getKey())); Map> submittedRows = new LinkedHashMap<>(); for (Object value : (List) submitted.get(table.getKey())) { if (!(value instanceof Map)) continue; Map row = (Map) value; String rowId = jnpf.base.service.UploadEditAccess.rowId(row); if (!rowId.isEmpty() && submittedRows.put(rowId, row) != null) throw new DataException("附件子表行标识重复"); } boolean tableChanged = false; for (Map row : rows) { Map supplied = submittedRows.get(jnpf.base.service.UploadEditAccess.rowId(row)); if (supplied == null) continue; for (String field : table.getValue()) { if (!supplied.containsKey(field) || UploadEditStateMachine.sameFiles(row.get(field), supplied.get(field))) continue; row.put(field, supplied.get(field)); tableChanged = true; } } if (tableChanged) { target.put(table.getKey(), rows); changed = true; } } return changed; } private static void collectUploadFields(Object value, String parent, Map> result) { if (!(value instanceof List)) return; for (Object item : (List) value) { if (!(item instanceof Map)) continue; Map field = (Map) item; if (!(field.get("__config__") instanceof Map)) continue; Map config = (Map) field.get("__config__"); String key = Objects.toString(field.get("__vModel__"), ""); String type = Objects.toString(config.get("jnpfKey"), ""); if ("uploadFile".equals(type) && !key.isEmpty()) { result.computeIfAbsent(Objects.toString(parent, ""), ignored -> new LinkedHashSet<>()).add(key); } collectUploadFields(config.get("children"), "table".equals(type) ? key : parent, result); } } @SuppressWarnings("unchecked") private static List> copyRows(Object rows) { return (List) JsonUtil.getJsonToList(JsonUtil.getObjectToString(rows), Map.class); } @SuppressWarnings("unchecked") private static void overlayChild(Map target, Map source, UploadEditDraft draft) { Object rows = target.get(draft.getParentField()); if (!(rows instanceof List)) return; // 不替用户恢复缺失/已删除的行。 List> copy = copyRows(rows); Object files = jnpf.base.service.UploadEditAccess.fieldValue(source, UploadEditDraftService.context(draft)); for (Map row : copy) { if (draft.getRowId().equals(jnpf.base.service.UploadEditAccess.rowId(row))) row.put(draft.getField(), files); } target.put(draft.getParentField(), copy); } }