tuqin
16 小时以前 0ddbb4ec727ffce844ef672107e3410d84b1f11a
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
package cn.iocoder.yudao.server.framework.bizno;
 
import cn.iocoder.yudao.framework.common.util.number.BizNoUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
 
import javax.sql.DataSource;
import java.util.List;
import java.util.Map;
 
/**
 * 已用业务编码集合兜底初始化。
 *
 * <p>业务编码为「随机 16 位纯数字」,唯一性靠 Redis 集合({@link BizNoUtils#USED_SET_KEY})查重。
 * 若 Redis 丢失该集合,已生成的历史编码会被"忘记",之后可能被重新随机命中而重号。
 *
 * <p>因此:启动时若发现该集合 **不存在**,则从数据库中扫描所有业务编码列里的
 * 「16 位纯数字」编码,重新载入集合,恢复防重能力。
 *
 * <p>说明:
 * <ul>
 *     <li>仅在集合缺失时扫描(正常启动无额外开销);</li>
 *     <li>best-effort,失败只告警、不影响启动;</li>
 *     <li>旧的带字母编码会被忽略。</li>
 * </ul>
 */
@Component
@Slf4j
public class BizCodeSetInitializer implements ApplicationRunner {
 
    /** 集合初始化标记(非 16 位,不会与真实编码冲突),用于在库内无编码时也标记为已初始化 */
    private static final String INIT_MARKER = "__initialized__";
 
    private final StringRedisTemplate stringRedisTemplate;
    private final JdbcTemplate jdbcTemplate;
 
    public BizCodeSetInitializer(StringRedisTemplate stringRedisTemplate, DataSource dataSource) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }
 
    @Override
    public void run(ApplicationArguments args) {
        if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(BizNoUtils.USED_SET_KEY))) {
            log.info("[run][已用编码集合兜底] 集合已存在,跳过库内扫描");
            return;
        }
        long start = System.currentTimeMillis();
        long total = 0L;
        try {
            String schema = jdbcTemplate.queryForObject("SELECT DATABASE()", String.class);
            List<Map<String, Object>> columns = jdbcTemplate.queryForList(
                    "SELECT table_name, column_name FROM information_schema.columns " +
                            "WHERE table_schema = ? AND data_type IN ('varchar', 'char') " +
                            "AND (column_name = 'no' OR column_name = 'code' OR column_name = 'temp_code' " +
                            "     OR column_name LIKE '%\\_no' OR column_name LIKE '%\\_code')",
                    schema);
            for (Map<String, Object> column : columns) {
                String table = String.valueOf(column.get("table_name"));
                String col = String.valueOf(column.get("column_name"));
                if (table.toLowerCase().startsWith("qrtz_") || table.toLowerCase().startsWith("act_")) {
                    continue;
                }
                List<String> codes = jdbcTemplate.queryForList(
                        "SELECT `" + col + "` FROM `" + table + "` WHERE `" + col + "` REGEXP '^[0-9]{"
                                + BizNoUtils.CODE_LENGTH + "}$'",
                        String.class);
                if (!codes.isEmpty()) {
                    stringRedisTemplate.opsForSet().add(BizNoUtils.USED_SET_KEY, codes.toArray(new String[0]));
                    total += codes.size();
                }
            }
            // 标记为已初始化,避免下次启动重复扫描
            stringRedisTemplate.opsForSet().add(BizNoUtils.USED_SET_KEY, INIT_MARKER);
            log.info("[run][已用编码集合兜底] 集合缺失,已载入库内 {} 个编码", total);
        } catch (Exception e) {
            log.warn("[run][已用编码集合兜底] 初始化失败(不影响启动),请排查", e);
        } finally {
            log.info("[run][已用编码集合兜底] 库内扫描耗时 {} ms", System.currentTimeMillis() - start);
        }
    }
 
}