tuqin
16 小时以前 0ddbb4ec727ffce844ef672107e3410d84b1f11a
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/number/BizNoUtils.java
@@ -1,29 +1,30 @@
package cn.iocoder.yudao.framework.common.util.number;
import java.util.concurrent.ThreadLocalRandom;
/**
 * 业务编码(单据号 / 主数据编码)生成的公共约定。
 *
 * <p>需求:系统中所有自动生成的业务编码统一为「16 位纯数字」,且全系统唯一(任意两条编码都不重复)。
 * <p>需求:系统中所有自动生成的业务编码统一为「16 位纯数字」,且**随机生成、不使用自增/顺序**,
 * 同时保证全系统唯一(任意两条编码都不重复)。
 *
 * <p>实现约定:全系统共用同一个 Redis 自增 KEY({@link #GLOBAL_SEQ_KEY}),
 * 每次生成时对其执行 INCR 得到单调递增的序列号,再左补 '0' 到 {@link #CODE_LENGTH} 位。
 * 由于所有编码都来自同一条递增序列,故天然保证「全系统唯一」。
 *
 * <p>注意:
 * <p>实现约定:
 * <ul>
 *     <li>该 KEY 不设置过期时间,避免过期归零导致重号;</li>
 *     <li>Redis 若发生数据丢失 / 清空,需要从数据库现有编码的最大值恢复计数器,否则可能重号。</li>
 *     <li>编码 = 随机 16 位数字(范围 0 ~ 10^16-1,左侧补 '0');</li>
 *     <li>唯一性由 Redis 集合 {@link #USED_SET_KEY} 查重保证:随机生成后写入集合,
 *         写成功(返回 1)说明没用过才采用,否则重试;</li>
 *     <li>各生成点:见 {@link #randomCode()} 与 {@link #MAX_RETRY}。</li>
 * </ul>
 *
 * <p>注意:Redis 集合丢失后,历史编码可能被重新随机命中;应用启动时会将库内已有编码
 * 重新载入集合(见 yudao-server 的启动兜底)。
 */
public class BizNoUtils {
    /**
     * 全系统业务编码统一使用的 Redis 自增 KEY。
     *
     * <p>所有模块(erp / crm / trade / aftersales / hrm / bpm / mes)共用此 KEY,
     * 以保证「全系统唯一」。
     * 已使用业务编码的 Redis 集合 KEY,用于随机编码查重,保证全系统唯一。
     */
    public static final String GLOBAL_SEQ_KEY = "biz:seq_no:global";
    public static final String USED_SET_KEY = "biz:code:used";
    /**
     * 业务编码的总长度:16 位
@@ -31,13 +32,24 @@
    public static final int CODE_LENGTH = 16;
    /**
     * 将自增序列号格式化为 16 位纯数字编码(左侧补 '0')
     *
     * @param seq 自增序列号
     * @return 16 位纯数字编码
     * 随机编码的空间上限(不含):10^16
     */
    public static String format(Long seq) {
        return String.format("%0" + CODE_LENGTH + "d", seq);
    private static final long CODE_SPACE = 10_000_000_000_000_000L;
    /**
     * 生成随机编码时的最大重试次数(命中已用编码时重试)
     */
    public static final int MAX_RETRY = 10;
    /**
     * 生成一个随机 16 位纯数字编码(左侧补 '0'),**不做查重**。
     *
     * <p>调用方需自行用 {@link #USED_SET_KEY} 查重,命中则重试。
     *
     * @return 随机 16 位纯数字编码
     */
    public static String randomCode() {
        return String.format("%0" + CODE_LENGTH + "d", ThreadLocalRandom.current().nextLong(CODE_SPACE));
    }
}