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
package cn.iocoder.yudao.module.bpm.framework.flowable.core.util;
 
import cn.hutool.core.util.StrUtil;
 
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
 
/**
 * Flowable 流程定义编号工具
 *
 * <p>流程定义编号形如 {@code qingjia:2:553a7ac9-...},包含冒号。
 * 前端如果对该值做了 URL 编码后再作为查询参数传递(双重编码),
 * Spring 只解码一层,最终拿到的仍是 {@code qingjia%3A2%3A...},
 * Flowable 按该编号查不到流程定义,直接抛 FlowableObjectNotFoundException 导致 500。</p>
 *
 * <p>这里在入口处做一次幂等的归一化:只在确实残留 % 转义时才解码,
 * 合法的流程定义编号不可能包含 %,因此不会误伤。</p>
 *
 * @author 超级管理员
 */
public class BpmProcessDefinitionIdUtils {
 
    /** 最多解码层数,防止异常输入无限循环 */
    private static final int MAX_DECODE_TIMES = 5;
 
    private BpmProcessDefinitionIdUtils() {
    }
 
    /**
     * 归一化流程定义编号:逐层解码残留的 URL 转义
     *
     * @param id 流程定义编号
     * @return 归一化后的编号;入参为空时原样返回
     */
    public static String normalize(String id) {
        if (StrUtil.isBlank(id) || id.indexOf('%') < 0) {
            return id;
        }
        String current = id;
        for (int i = 0; i < MAX_DECODE_TIMES && current.indexOf('%') >= 0; i++) {
            String decoded;
            try {
                decoded = URLDecoder.decode(current, StandardCharsets.UTF_8);
            } catch (Exception ignored) {
                // 解码失败说明不是 URL 转义,保持原值
                break;
            }
            if (decoded.equals(current)) {
                break;
            }
            current = decoded;
        }
        return current;
    }
 
}