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;
/**
* 业务编码计数器兜底初始化。
*
*
全系统编码统一由 Redis 的全局自增 KEY({@link BizNoUtils#GLOBAL_SEQ_KEY})生成,
* 用于保证「16 位纯数字 + 全系统唯一」。但 Redis 是内存存储,若被清空 / 丢失,
* 该 KEY 会归零,从 1 重新开始,导致与历史编码重复。
*
*
因此:当启动时发现该 KEY **不存在**(说明 Redis 丢失过计数),
* 从数据库中扫描所有业务编码列的最大值,把计数器恢复到该最大值,避免重号。
*
*
说明:
*
* - 仅在 KEY 缺失时扫描(正常启动无额外开销);
* - 扫描是 best-effort,失败只告警、不影响启动;
* - 只统计「16 位纯数字」的编码,旧的带字母编码会被忽略。
*
*/
@Component
@Slf4j
public class BizNoSeqInitializer implements ApplicationRunner {
private final StringRedisTemplate stringRedisTemplate;
private final JdbcTemplate jdbcTemplate;
public BizNoSeqInitializer(StringRedisTemplate stringRedisTemplate, DataSource dataSource) {
this.stringRedisTemplate = stringRedisTemplate;
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public void run(ApplicationArguments args) {
String current = stringRedisTemplate.opsForValue().get(BizNoUtils.GLOBAL_SEQ_KEY);
if (current != null) {
log.info("[run][业务编码计数器兜底] Redis 计数已存在({}),跳过库内扫描", current);
return;
}
long start = System.currentTimeMillis();
try {
long maxDb = computeMaxCodeInDb();
stringRedisTemplate.opsForValue().set(BizNoUtils.GLOBAL_SEQ_KEY, String.valueOf(maxDb));
log.info("[run][业务编码计数器兜底] Redis 计数缺失,已从库内最大编码恢复为 {}", maxDb);
} catch (Exception e) {
log.warn("[run][业务编码计数器兜底] 初始化失败(不影响启动),请排查", e);
} finally {
log.info("[run][业务编码计数器兜底] 库内扫描耗时 {} ms", System.currentTimeMillis() - start);
}
}
/**
* 扫描库内所有可能存放业务编码的列,返回「16 位纯数字」编码的最大值。
*/
private long computeMaxCodeInDb() {
String schema = jdbcTemplate.queryForObject("SELECT DATABASE()", String.class);
// 找出所有 varchar/char 且名字像编码的列(no / code / *_no / *_code / temp_code)
List