刘光辉
5 小时以前 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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
package jnpf.flowable.service;
 
import com.fasterxml.jackson.databind.ObjectMapper;
import jnpf.base.entity.VisualdevEntity;
import jnpf.flowable.entity.TemplateNodeEntity;
import jnpf.flowable.mapper.FilePermissionRuntimeMapper;
import jnpf.flowable.mapper.TemplateNodeMapper;
import jnpf.flowable.model.filepermission.FilePermissionNativeState;
import jnpf.flowable.model.filepermission.FilePermissionNativeState.Member;
import jnpf.flowable.model.filepermission.FilePermissionPolicy.*;
import jnpf.flowable.model.templatenode.nodejson.NodeModel;
import jnpf.flowable.util.ServiceUtil;
import jnpf.flowable.util.filepermission.FilePermissionRuntime;
import jnpf.flowable.util.filepermission.FilePermissionSnapshot;
import jnpf.util.JsonUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import java.nio.charset.StandardCharsets;
import java.util.*;
 
/**
 * 以业务库已提交状态为生效点,每次动作重新读取;不把控制器末尾的事件当作授权源。
 * 不缓存允许结果、不改原生流转事务。revision仅用于详情刷新,不能用作授权票据。
 */
@Service
@RequiredArgsConstructor
public class NativeFilePermissionRuntimeSource implements FilePermissionRuntimeSource {
    private final FilePermissionRuntimeMapper mapper;
    private final TemplateNodeMapper nodes;
    private final ServiceUtil forms;
 
    @Override
    public FilePermissionRuntime.Facts read(String tenantId, String taskId, String formId, String userId,
                                            FilePermissionSnapshot snapshot) {
        return readWithPublishedForm(tenantId, taskId, formId, userId, snapshot, null);
    }
 
    @Override
    public FilePermissionRuntime.Facts readWithPublishedForm(String tenantId, String taskId, String formId, String userId,
                                                            FilePermissionSnapshot snapshot, String formData) {
        FilePermissionNativeState state = mapper.find(taskId);
        if (state == null) throw new IllegalArgumentException("流程不存在");
        List<TemplateNodeEntity> nodeList = nodes.getList(state.getFlowId());
        if (formData == null) {
            VisualdevEntity form = forms.getReleaseInfo(formId);
            if (form == null) throw new IllegalArgumentException("发布表单不存在");
            formData = form.getFormData();
        }
        FilePermissionRuntime.Facts facts = project(tenantId, formId, userId, snapshot, state, nodeList,
                JsonUtil.getJsonToBean(formData, Map.class));
        // 配置读取可能跨服务;若期间实例已推进,不返回旧人员/新配置混合的允许结果。
        if (!fingerprint(state).equals(fingerprint(mapper.find(taskId)))) facts.transitioning = true;
        return facts;
    }
 
    /** 使用原生任务/经办/记录模型做投影,测试可直接覆盖多节点和历史节点情形。 */
    public static FilePermissionRuntime.Facts project(String tenantId, String formId, String userId,
            FilePermissionSnapshot snapshot, FilePermissionNativeState state, List<TemplateNodeEntity> nodes,
            Map<String, Object> form) {
        FilePermissionRuntime.Facts f = new FilePermissionRuntime.Facts();
        f.tenantId = tenantId; f.taskId = state.getTaskId(); f.formId = formId;
        f.userId = userId; f.creatorId = state.getCreatorId();
        f.revision = fingerprint(state);
        // 自由流程会重新生成编码,映射尚未验证时拒绝,不能套用其他节点规则。
        if (Integer.valueOf(3).equals(state.getFlowType()) || nodes == null || form == null || state.getStatus() == null) return f;
        int status = state.getStatus();
        if (!Arrays.asList(0, 1, 2, 3, 4, 7, 8, 9).contains(status)) return f;
        if (status == 2 || status == 3) f.terminal = FilePermissionRuntime.Terminal.COMPLETED;
        if (status == 7) f.terminal = FilePermissionRuntime.Terminal.CANCELLED;
        if (status == 4) f.terminal = FilePermissionRuntime.Terminal.TERMINATED;
        Map<String, TemplateNodeEntity> byCode = new HashMap<>();
        TemplateNodeEntity start = null;
        for (TemplateNodeEntity node : nodes) {
            byCode.put(node.getNodeCode(), node);
            if ("start".equals(node.getNodeType())) start = node;
        }
        if (start == null) return f;
        // 节点引用已改变时,不以最新编码猜测历史快照的指向。
        for (Rule rule : snapshot.getRules()) {
            if (rule.triggerNodeId != null && !byCode.containsKey(rule.triggerNodeId)
                    || rule.subjectNodeId != null && !byCode.containsKey(rule.subjectNodeId)) return f;
        }
        if (status == 0 || status == 8 || status == 9) f.activeNodes.add(start.getNodeCode());
        if (status == 1 && state.getCurrentNodeCode() != null) {
            for (String node : state.getCurrentNodeCode().split(",")) if (!node.trim().isEmpty()) f.activeNodes.add(node.trim());
        }
        Set<String> entered = new HashSet<>(snapshot.getEnteredNodes());
        Set<String> readableNodes = new HashSet<>();
        Set<String> writableNodes = new HashSet<>();
        Set<String> assignedNodes = new HashSet<>();
        if (Objects.equals(userId, f.creatorId)) {
            readableNodes.add(start.getNodeCode());
            if (status == 0 || status == 8 || status == 9) writableNodes.add(start.getNodeCode());
        }
        for (Member member : state.getMembers()) {
            boolean operator = "operator".equals(member.getKind());
            boolean record = "record".equals(member.getKind());
            boolean validOperator = operator && member.getStatus() != null && member.getStatus() >= 0;
            if (validOperator && member.getUserId() != null) assignedNodes.add(member.getNodeCode());
            // 失效经办/尚未激活的未来经办不能用于授予参与人权限;真实操作记录保留参与事实。
            if (validOperator || record || "circulate".equals(member.getKind())) {
                if (member.getUserId() != null) f.participants.computeIfAbsent(member.getNodeCode(), key -> new HashSet<>()).add(member.getUserId());
                if (Objects.equals(userId, member.getUserId())) readableNodes.add(member.getNodeCode());
            }
            if (validOperator || record) entered.add(member.getNodeCode());
            if (validOperator && status == 1 && Integer.valueOf(0).equals(member.getCompletion())
                    && member.getHandleStatus() == null && member.getStartHandleTime() != null
                    && member.getUserId() != null && f.activeNodes.contains(member.getNodeCode())) {
                f.currentHandlers.computeIfAbsent(member.getNodeCode(), key -> new HashSet<>()).add(member.getUserId());
                if (Objects.equals(userId, member.getUserId())) writableNodes.add(member.getNodeCode());
            }
        }
        entered.addAll(f.activeNodes);
        for (Rule rule : snapshot.getRules()) {
            if (rule.trigger != Trigger.ENTERED) continue;
            if (rule.onReturn == OnReturn.RELEASE) {
                if (snapshot.getActiveReleaseRules().contains(rule.id)) f.activeEnteredRules.add(rule.id);
            } else if (entered.contains(rule.triggerNodeId)) {
                f.activeEnteredRules.add(rule.id);
            }
        }
        // 当前节点与经办尚未一致时不放行;不把未落库办理人当成空角色。
        if (status == 1) {
            if (f.activeNodes.isEmpty()) return f;
            for (String code : f.activeNodes) {
                TemplateNodeEntity node = byCode.get(code);
                if (node == null) return f;
                if (Arrays.asList("approver", "processing").contains(node.getNodeType()) && !assignedNodes.contains(code)) return f;
            }
        }
        Map<String, NodeModel> readable = nodeModels(readableNodes, byCode, formId, start.getFormId());
        Map<String, NodeModel> writable = nodeModels(writableNodes, byCode, formId, start.getFormId());
        if (readable.isEmpty()) return f;
        collect(form.get("fields"), null, Boolean.TRUE.equals(form.get("disabled")), readable.values(), writable.values(), f);
        f.verified = true;
        return f;
    }
 
    private static Map<String, NodeModel> nodeModels(Set<String> codes, Map<String, TemplateNodeEntity> nodes, String formId, String startForm) {
        Map<String, NodeModel> result = new HashMap<>();
        for (String code : codes) {
            TemplateNodeEntity node = nodes.get(code);
            if (node == null) continue;
            String id = node.getFormId() == null || node.getFormId().isEmpty() ? startForm : node.getFormId();
            if (formId.equals(id)) result.put(code, JsonUtil.getJsonToBean(node.getNodeJson(), NodeModel.class));
        }
        return result;
    }
 
    private static void collect(Object value, String parent, boolean disabled, Collection<NodeModel> readable,
                                Collection<NodeModel> writable, FilePermissionRuntime.Facts facts) {
        if (!(value instanceof List)) throw new IllegalArgumentException("表单字段结构无效");
        for (Object item : (List<?>) value) {
            if (!(item instanceof Map)) throw new IllegalArgumentException("表单字段结构无效");
            Map<?, ?> field = (Map<?, ?>) item;
            if (!(field.get("__config__") instanceof Map)) throw new IllegalArgumentException("表单字段结构无效");
            Map<?, ?> config = (Map<?, ?>) field.get("__config__");
            String key = Objects.toString(field.get("__vModel__"), "");
            String id = parent == null ? key : parent + "-" + key;
            boolean locked = disabled || Boolean.TRUE.equals(field.get("disabled")) || Boolean.TRUE.equals(config.get("isDisplayOnly"));
            if ("uploadFile".equals(config.get("jnpfKey"))) {
                boolean read = readable.stream().anyMatch(node -> permit(node, id, "read") && (parent == null || permit(node, parent, "read")));
                boolean write = read && !locked && writable.stream().anyMatch(node -> permit(node, id, "read") && permit(node, id, "write")
                        && (parent == null || permit(node, parent, "read") && permit(node, parent, "write")));
                Map<Action, Boolean> bounds = new EnumMap<>(Action.class);
                bounds.put(Action.VIEW, read);
                bounds.put(Action.DOWNLOAD, read && (!field.containsKey("allowDownload") || Boolean.TRUE.equals(field.get("allowDownload"))));
                // 显式文件EDIT独立于节点字段write;INHERIT仍遵守原生可写条件。
                bounds.put(Action.EDIT, read && !locked && facts.terminal == FilePermissionRuntime.Terminal.NONE
                        && Boolean.TRUE.equals(field.get("onlineEdit")));
                bounds.put(Action.REVIEW, Boolean.TRUE.equals(bounds.get(Action.EDIT))
                        && facts.currentHandlers.entrySet().stream().anyMatch(e -> facts.activeNodes.contains(e.getKey())
                        && e.getValue().contains(facts.userId)));
                bounds.put(Action.ADD, write); bounds.put(Action.REPLACE, write); bounds.put(Action.REMOVE, write);
                facts.nativeUpperBounds.put(id, bounds);
                Map<Action, Boolean> inherited = new EnumMap<>(bounds);
                inherited.put(Action.EDIT, write && Boolean.TRUE.equals(field.get("onlineEdit")));
                inherited.put(Action.REVIEW, false);
                facts.inherited.put(id, inherited);
            }
            if (config.get("children") instanceof List) {
                collect(config.get("children"), "table".equals(config.get("jnpfKey")) ? key : parent, locked, readable, writable, facts);
            }
        }
    }
 
    private static boolean permit(NodeModel node, String field, String action) {
        if (node == null) return false;
        if (node.getFormOperates() == null || node.getFormOperates().isEmpty()) return true;
        for (Map<String, Object> item : node.getFormOperates()) if (field.equals(item.get("id"))) return Boolean.TRUE.equals(item.get(action));
        return true; // 与原生字段无单独配置时的默认行为一致。
    }
 
    private static String fingerprint(FilePermissionNativeState value) {
        if (value == null) return "missing";
        try { return DigestUtils.md5DigestAsHex(new ObjectMapper().writeValueAsString(value).getBytes(StandardCharsets.UTF_8)); }
        catch (java.io.IOException e) { throw new IllegalArgumentException("运行态读取失败", e); }
    }
}