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; /** * 已用业务编码集合兜底初始化。 * *

业务编码为「随机 16 位纯数字」,唯一性靠 Redis 集合({@link BizNoUtils#USED_SET_KEY})查重。 * 若 Redis 丢失该集合,已生成的历史编码会被"忘记",之后可能被重新随机命中而重号。 * *

因此:启动时若发现该集合 **不存在**,则从数据库中扫描所有业务编码列里的 * 「16 位纯数字」编码,重新载入集合,恢复防重能力。 * *

说明: *

*/ @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> 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 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 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); } } }