刘光辉
7 小时以前 06f314545ac3514d7ce28939920866ae8188d31c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
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<String, Object> formData, Consumer<Map<String, Object>> 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<String, Object> formData,
                     Consumer<Map<String, Object>> 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<String, Object> 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<UploadEditDraft> 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<String, Object> result = formData == null ? new LinkedHashMap<>() : new LinkedHashMap<>(formData);
        // 以完整真实记录作为基底,不能把审批弹窗的局部数据变成主子表增删。
        Map<String, Object> checkpoint = new LinkedHashMap<>(persisted);
        Map<String, Object> 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<String, Object> target, Map<String, Object> submitted, Object fields) {
        Map<String, Set<String>> 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<String, Set<String>> table : uploads.entrySet()) {
            if (table.getKey().isEmpty() || !(submitted.get(table.getKey()) instanceof List<?>)
                    || !(target.get(table.getKey()) instanceof List<?>)) continue;
            List<Map<String, Object>> rows = copyRows(target.get(table.getKey()));
            Map<String, 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<String, Object> 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<String, Set<String>> 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<Map<String, Object>> copyRows(Object rows) {
        return (List) JsonUtil.getJsonToList(JsonUtil.getObjectToString(rows), Map.class);
    }
 
    @SuppressWarnings("unchecked")
    private static void overlayChild(Map<String, Object> target, Map<String, Object> source, UploadEditDraft draft) {
        Object rows = target.get(draft.getParentField());
        if (!(rows instanceof List<?>)) return; // 不替用户恢复缺失/已删除的行。
        List<Map<String, Object>> copy = copyRows(rows);
        Object files = jnpf.base.service.UploadEditAccess.fieldValue(source, UploadEditDraftService.context(draft));
        for (Map<String, Object> row : copy) {
            if (draft.getRowId().equals(jnpf.base.service.UploadEditAccess.rowId(row))) row.put(draft.getField(), files);
        }
        target.put(draft.getParentField(), copy);
    }
}