广丰卷烟厂数采质量分析系统
zhuguifei
2026-03-02 974c7aa4010d77bb410b99931b4435d5442deb4b
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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
package org.dromara.workflow.service.impl;
 
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ObjectUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.dto.DictTypeDTO;
import org.dromara.common.core.service.DictService;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.warm.flow.core.FlowEngine;
import org.dromara.warm.flow.core.utils.CollUtil;
import org.dromara.warm.flow.core.utils.ExpressionUtil;
import org.dromara.warm.flow.ui.service.NodeExtService;
import org.dromara.warm.flow.ui.vo.NodeExt;
import org.dromara.workflow.common.ConditionalOnEnable;
import org.dromara.workflow.common.enums.ButtonPermissionEnum;
import org.dromara.workflow.common.enums.CopySettingEnum;
import org.dromara.workflow.common.enums.NodeExtEnum;
import org.dromara.workflow.common.enums.VariablesEnum;
import org.dromara.workflow.domain.vo.ButtonPermissionVo;
import org.dromara.workflow.domain.vo.NodeExtVo;
import org.dromara.workflow.service.IFlwNodeExtService;
import org.springframework.stereotype.Service;
 
import java.util.*;
import java.util.stream.Collectors;
 
/**
 * 流程设计器-节点扩展属性
 *
 * @author AprilWind
 */
@ConditionalOnEnable
@Slf4j
@RequiredArgsConstructor
@Service
public class FlwNodeExtServiceImpl implements NodeExtService, IFlwNodeExtService {
 
    /**
     * 存储不同 dictType 对应的配置信息
     */
    private static final Map<String, Map<String, Object>> CHILD_NODE_MAP;
 
    static {
        CHILD_NODE_MAP = Map.of(
            CopySettingEnum.class.getSimpleName(),
            Map.of(
                "label", "抄送对象",
                "type", 5,
                "must", false,
                "multiple", false,
                "desc", "设置该节点的抄送办理人"
            ),
            VariablesEnum.class.getSimpleName(),
            Map.of(
                "label", "自定义参数",
                "type", 2,
                "must", false,
                "multiple", false,
                "desc", "节点执行时可设置自定义参数,多个参数以逗号分隔,如:key1=value1,key2=value2"
            ),
            ButtonPermissionEnum.class.getSimpleName(),
            Map.of(
                "label", "权限按钮",
                "type", 4,
                "must", false,
                "multiple", true,
                "desc", "控制该节点的按钮权限"
            )
        );
    }
 
    private final DictService dictService;
 
    /**
     * 获取节点扩展属性
     *
     * @return 节点扩展属性列表
     */
    @Override
    public List<NodeExt> getNodeExt() {
        List<NodeExt> nodeExtList = new ArrayList<>();
        // 构建基础设置页面
        nodeExtList.add(buildNodeExt("wf_basic_tab", "基础设置", 1,
            List.of(CopySettingEnum.class, VariablesEnum.class)));
        // 构建按钮权限页面
        nodeExtList.add(buildNodeExt("wf_button_tab", "权限", 2,
            List.of(ButtonPermissionEnum.class)));
        // 自定义构建 规则参考 NodeExt 与 warm-flow文档说明
        // nodeExtList.add(buildNodeExt("xxx_xxx", "xxx", 1, List);
        return nodeExtList;
    }
 
    /**
     * 构建一个 `NodeExt` 对象
     *
     * @param code    唯一编码
     * @param name    名称(新页签时,作为页签名称)
     * @param type    节点类型(1: 基础设置,2: 新页签)
     * @param sources 数据来源(枚举类或字典类型)
     * @return 构建的 `NodeExt` 对象
     */
    @SuppressWarnings("unchecked cast")
    private NodeExt buildNodeExt(String code, String name, int type, List<Object> sources) {
        NodeExt nodeExt = new NodeExt();
        nodeExt.setCode(code);
        nodeExt.setType(type);
        nodeExt.setName(name);
        nodeExt.setChilds(sources.stream()
            .map(source -> {
                if (source instanceof Class<?> clazz && NodeExtEnum.class.isAssignableFrom(clazz)) {
                    return buildChildNode((Class<? extends NodeExtEnum>) clazz);
                } else if (source instanceof String dictType) {
                    return buildChildNode(dictType);
                }
                return null;
            })
            .filter(ObjectUtil::isNotNull)
            .toList()
        );
        return nodeExt;
    }
 
    /**
     * 根据枚举类型构建一个 `ChildNode` 对象
     *
     * @param enumClass 枚举类,必须实现 `NodeExtEnum` 接口
     * @return 构建的 `ChildNode` 对象
     */
    private NodeExt.ChildNode buildChildNode(Class<? extends NodeExtEnum> enumClass) {
        if (!enumClass.isEnum()) {
            return null;
        }
        String simpleName = enumClass.getSimpleName();
        NodeExt.ChildNode childNode = new NodeExt.ChildNode();
        Map<String, Object> map = CHILD_NODE_MAP.get(simpleName);
        // 编码,此json中唯
        childNode.setCode(simpleName);
        // label名称
        childNode.setLabel(Convert.toStr(map.get("label")));
        // 1:输入框 2:文本域 3:下拉框 4:选择框 5:用户选择器
        childNode.setType(Convert.toInt(map.get("type"), 1));
        // 是否必填
        childNode.setMust(Convert.toBool(map.get("must"), false));
        // 是否多选
        childNode.setMultiple(Convert.toBool(map.get("multiple"), true));
        // 描述
        childNode.setDesc(Convert.toStr(map.get("desc"), null));
        // 字典,下拉框和复选框时用到
        childNode.setDict(Arrays.stream(enumClass.getEnumConstants())
            .map(NodeExtEnum.class::cast)
            .map(x ->
                new NodeExt.DictItem(x.getLabel(), x.getValue(), x.isSelected())
            ).toList());
        return childNode;
    }
 
    /**
     * 根据字典类型构建 `ChildNode` 对象
     *
     * @param dictType 字典类型
     * @return 构建的 `ChildNode` 对象
     */
    private NodeExt.ChildNode buildChildNode(String dictType) {
        DictTypeDTO dictTypeDTO = dictService.getDictType(dictType);
        if (ObjectUtil.isNull(dictTypeDTO)) {
            return null;
        }
        NodeExt.ChildNode childNode = new NodeExt.ChildNode();
        // 编码,此json中唯一
        childNode.setCode(dictType);
        // label名称
        childNode.setLabel(dictTypeDTO.getDictName());
        // 1:输入框 2:文本域 3:下拉框 4:选择框 5:用户选择器
        childNode.setType(3);
        // 是否必填
        childNode.setMust(false);
        // 是否多选
        childNode.setMultiple(true);
        // 描述 (可根据描述参数解析更多配置,如type,must,multiple等)
        childNode.setDesc(dictTypeDTO.getRemark());
        // 字典,下拉框和复选框时用到
        childNode.setDict(dictService.getDictData(dictType)
            .stream().map(x ->
                new NodeExt.DictItem(x.getDictLabel(), x.getDictValue(), Convert.toBool(x.getIsDefault(), false))
            ).toList());
        return childNode;
    }
 
    /**
     * 解析扩展属性 JSON 并构建 Node 扩展属性对象
     * <p>
     * 根据传入的 JSON 字符串,将扩展属性分为三类:
     * 1. ButtonPermissionEnum:解析为按钮权限列表,标记每个按钮是否勾选
     * 2. CopySettingEnum:解析为抄送对象 ID 集合
     * 3. VariablesEnum:解析为自定义参数 Map
     *
     * <p>示例 JSON:
     * [
     * {"code": "ButtonPermissionEnum", "value": "back,termination"},
     * {"code": "CopySettingEnum", "value": "1,3,4,#{@spelRuleComponent.selectDeptLeaderById(#deptId", "#roleId)}"},
     * {"code": "VariablesEnum", "value": "key1=value1,key2=value2"}
     * ]
     *
     * @param ext      扩展属性 JSON 字符串
     * @param variable 流程变量
     * @return NodeExtVo 对象,封装按钮权限列表、抄送对象集合和自定义参数 Map
     */
    @Override
    public NodeExtVo parseNodeExt(String ext, Map<String, Object> variable) {
        NodeExtVo nodeExtVo = new NodeExtVo();
 
        // 解析 JSON 为 Dict 列表
        List<Dict> nodeExtMap = JsonUtils.parseArrayMap(ext);
        if (ObjectUtil.isEmpty(nodeExtMap)) {
            return nodeExtVo;
        }
 
        for (Dict nodeExt : nodeExtMap) {
            String code = nodeExt.getStr("code");
            String value = nodeExt.getStr("value");
 
            if (ButtonPermissionEnum.class.getSimpleName().equals(code)) {
                // 解析按钮权限
                // 将 value 拆分为 Set<String>,便于精确匹配
                Set<String> buttonSet = StringUtils.str2Set(value, StringUtils.SEPARATOR);
 
                // 获取按钮字典配置
                NodeExt.ChildNode childNode = buildChildNode(ButtonPermissionEnum.class);
 
                // 构建 ButtonPermissionVo 列表
                List<ButtonPermissionVo> buttonList = Optional.ofNullable(childNode)
                    .map(NodeExt.ChildNode::getDict)
                    .orElse(List.of())
                    .stream()
                    .map(dict -> new ButtonPermissionVo(dict.getValue(), buttonSet.contains(dict.getValue())))
                    .toList();
 
                nodeExtVo.setButtonPermissions(buttonList);
 
            } else if (CopySettingEnum.class.getSimpleName().equals(code)) {
                List<String> permissions = spelSmartSplit(value).stream()
                    .map(s -> {
                        List<String> result = ExpressionUtil.evalVariable(s, variable);
                        if (CollUtil.isNotEmpty(result)) {
                            return result;
                        }
                        return Collections.singletonList(s);
                    }).filter(Objects::nonNull)
                    .flatMap(List::stream)
                    .distinct()
                    .collect(Collectors.toList());
                List<String> copySettings = FlowEngine.permissionHandler().convertPermissions(permissions);
                // 解析抄送对象 ID 集合
                nodeExtVo.setCopySettings(new HashSet<>(copySettings));
 
            } else if (VariablesEnum.class.getSimpleName().equals(code)) {
                // 解析自定义参数
                // 将 key=value 字符串拆分为 Map
                Map<String, String> variables = Arrays.stream(StringUtils.split(value, StringUtils.SEPARATOR))
                    .map(s -> StringUtils.split(s, "="))
                    .filter(arr -> arr.length == 2)
                    .collect(Collectors.toMap(arr -> arr[0], arr -> arr[1]));
 
                nodeExtVo.setVariables(variables);
            } else {
                // 未知扩展类型,记录日志
                log.warn("未知扩展类型:code={}, value={}", code, value);
            }
        }
        return nodeExtVo;
    }
 
    /**
     * 按逗号分割字符串,但保留 #{...} 表达式和字符串常量中的逗号
     */
    private static List<String> spelSmartSplit(String str) {
        List<String> result = new ArrayList<>();
        if (str == null || str.trim().isEmpty()) {
            return result;
        }
 
        StringBuilder token = new StringBuilder();
        // #{...} 的嵌套深度
        int depth = 0;
        // 是否在字符串常量中(" 或 ')
        boolean inString = false;
        // 当前字符串引号类型
        char stringQuote = 0;
 
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
 
            // 检测进入 SpEL 表达式 #{...}
            if (!inString && c == '#' && depth == 0 && checkNext(str, i, '{')) {
                depth++;
                token.append("#{");
                // 跳过 {
                i++;
                continue;
            }
 
            // 在表达式中遇到 { 或 } 改变嵌套深度
            if (!inString && depth > 0) {
                if (c == '{') {
                    depth++;
                } else if (c == '}') {
                    depth--;
                }
                token.append(c);
                continue;
            }
 
            // 检测字符串开始/结束
            if (depth > 0 && (c == '"' || c == '\'')) {
                if (!inString) {
                    inString = true;
                    stringQuote = c;
                } else if (stringQuote == c) {
                    inString = false;
                }
                token.append(c);
                continue;
            }
 
            // 外层逗号才分割
            if (c == ',' && depth == 0 && !inString) {
                String part = token.toString().trim();
                if (!part.isEmpty()) {
                    result.add(part);
                }
                token.setLength(0);
                continue;
            }
 
            token.append(c);
        }
 
        // 添加最后一个
        String part = token.toString().trim();
        if (!part.isEmpty()) {
            result.add(part);
        }
 
        return result;
    }
 
    private static boolean checkNext(String str, int index, char expected) {
        return index + 1 < str.length() && str.charAt(index + 1) == expected;
    }
 
}