跟大家分享一个自增的主键id策略OUID

        自增的UUID,时间顺序的通用唯一标识符。官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一,单机递增),而是推荐连续自增的主键id,此自增的OUID,可以满足你在开发数据库是MySQL时对于主键的需求。

import java.net.InetAddress;
import java.util.Iterator;
import java.util.ServiceLoader;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 时间顺序的通用唯一标识符
 */
public class OUIDGenerator {

    public interface Ipv4Provider {
        byte[]  getIp();
    }

    /**
     * 开头时间戳编码表(为了保持有序性,该表即使替换字符集也需保持Ascii有序性)
     */
    final static char[] TIMESTAMP_DIGITS = {
            '0' , '1' , '2' , '3' , '4' , '5' ,
            '6' , '7' , '8' , '9' , 'a' , 'b' ,
            'c' , 'd' , 'e' , 'f' , 'g' , 'h' ,
            'j' , 'k' , 'm' , 'n' , 'p' , 'r' ,
            's' , 't' , 'u' , 'v' , 'w' , 'x' ,
            'y' , 'z'
    };

    /**
     * JVM标识-状态相关字符集
     * <p>该表根据需要随意替换相同数量字符集</p>
     */
    final static char[] JVM_STAT_DIGITS = {
            'a' , 'b' , 'c' , 'd' , 'e' , 'f' ,
            'g' , 'h' , 'j' , 'k' , 'm' , 'n' ,
            'p' , 'r' , 's' , 't' , 'u' , 'v' ,
            'w' , 'x' , 'y' , 'z' , '0' , '1' ,
            '2' , '3' , '4' , '5' , '6' , '7' ,
            '8' , '9'
    };

    /**
     * JVM标识-IP相关字符集
     * <p>如需IP隐秘性,可根据需要随意替换相同数量字符集</p>
     */
    final static char[] JVM_IP_DIGITS = {
            'a' , 'b' , 'c' , 'd' , 'e' , 'f' ,
            '0' , '1' , '2' , '3' , '4' , '5' ,
            '6' , '7' , '8' , '9' , 'g' , 'h' ,
            'j' , 'k' , 'm' , 'n' , 'p' , 'r' ,
            's' , 't' , 'u' , 'v' , 'w' , 'x' ,
            'y' , 'z'
    };

    /**
     * IP位替换
     * <p>如需IP隐秘性,可根据需要随意替换相同数量字符集</p>
     * <p>由于私网IP网段有限,容易通过确定的网段结合统计攻击猜测IP,可设置替换特定IP位增加猜测难度</p>
     */
    private final static Byte[] REWRITE_IP_SEGMENT = { (byte) 112, null, null, null};

    public static String generate() {
        return getTime(TIMESTAMP_DIGITS) +
                JVM_ID +
                getSerial();
    }

    private static String getTime(char[] digits) {
        long currentTimeMillis = System.currentTimeMillis();
        // 10位32进制,足以表示以毫秒计的3万年时间(当前系统时间肯定大于1970年,因此直接省略符号位处理)
        return format32(digits, currentTimeMillis, 10);
    }

    private static String format32(char[] digit, long val, int len) {
        char[] chars = new char[len];
        for (int i = chars.length - 1; i >= 0; i--) {
            // 获取字节的低5位有效值
            int j = (int) (val & 0x1f);
            chars[i] = digit[j];
            val = val >> 5;
        }
        return new String(chars);
    }

    public static final String JVM_ID = initJvm();

    /**
     * spi机制获取IP
     */
    private static byte[] getIpBySpi() {
        byte[] address = null;
        ServiceLoader<Ipv4Provider> providerServiceLoader = ServiceLoader.load(Ipv4Provider.class);
        Iterator<Ipv4Provider> iterator = providerServiceLoader.iterator();
        if (iterator.hasNext()) {
            // SPI 机制获取IP
            Ipv4Provider provider = iterator.next();
            address = provider.getIp();
            if (address != null && address.length != 4) {
                new IllegalArgumentException("ipv4 provider not provide ipv4 address").printStackTrace();
                return null;
            }
        }
        return address;
    }

    /**
     * 从环境变量获取重写的IP位
     */
    private static Byte[] getRewriteIpSegmentsByEnv() {
        Byte[] rewriteIpBytes = null;
        String ouidRewriteIp = System.getenv("OUID_REWRITE_IP");
        if (ouidRewriteIp != null) {
            String[] split = ouidRewriteIp.split("\\.");
            rewriteIpBytes = new Byte[4];
            try {
                for (int i = 0; i < 4; i++) {
                    if ("%".equals(split[i])) {
                        continue;
                    }
                    int num = Integer.parseInt(split[i]);
                    if (num > 255 || num < 0) {
                        throw new IllegalArgumentException("invalid rewrite ipv4 address " + ouidRewriteIp);
                    }
                    rewriteIpBytes[i] = (byte) (num > 127 ? num - 256 : num);
                }
            } catch (Exception e) {
                e.printStackTrace();
                rewriteIpBytes = null;
            }
        }
        return rewriteIpBytes;
    }

    @SuppressWarnings({"ConstantConditions", "RedundantSuppression"})
    private static String initJvm() {
        long ipAddr;
        try {

            // spi机制获取IP
            byte[] address = getIpBySpi();
            if (address == null) {
                address = InetAddress.getLocalHost().getAddress();
            }

            // 从环境变量获取替换IP
            Byte[] rewriteIpSegments = getRewriteIpSegmentsByEnv();
            if (rewriteIpSegments == null) {
                rewriteIpSegments = REWRITE_IP_SEGMENT;
            }

            // 替换特定位IP
            for (int i = 0; i < address.length; i++) {
                if (rewriteIpSegments.length > i && rewriteIpSegments[i] != null) {
                    address[i] = rewriteIpSegments[i];
                }
            }

            ipAddr = toLong(address);
        } catch (Exception e) {
            e.printStackTrace();
            ipAddr = 0;
        }

        return format32(JVM_IP_DIGITS, ipAddr, 7) + getTime(JVM_STAT_DIGITS);
    }

    private static final AtomicInteger SEQ = new AtomicInteger((int) (Math.random() * Integer.MAX_VALUE / 1000));

    private static String getSerial() {
        long serial = SEQ.incrementAndGet() & 0x1ffffff;
        // 单实例每毫秒最大允许产生 33554431 个ID,
        // MAC i7 4核下3线程测试每毫秒产生ID数在1.9万左右,故该容量导致ID重复的概率几乎为0
        return format32(TIMESTAMP_DIGITS, serial, 5);
    }

    private static long toLong(byte[] bytes) {
        long result = 0;
        for (int i = 0; i < 4; i++) {
            result = (result << 8) + (0xff & bytes[i]);
        }
        return result;
    }

}

相关推荐

  1. 大家分享一个id策略OUID

    2024-04-12 20:06:05       22 阅读
  2. uuid作为优缺点

    2024-04-12 20:06:05       19 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-04-12 20:06:05       19 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-12 20:06:05       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-12 20:06:05       19 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-12 20:06:05       20 阅读

热门阅读

  1. 数据结构(初阶):顺序表实战通讯录

    2024-04-12 20:06:05       16 阅读
  2. LeetCode hot100-25

    2024-04-12 20:06:05       23 阅读
  3. CDF与PDF(描述随机变量的分布情况)

    2024-04-12 20:06:05       21 阅读
  4. macOS idea配置mysql

    2024-04-12 20:06:05       19 阅读
  5. CSS中的类命名

    2024-04-12 20:06:05       14 阅读
  6. WPF 跨线程-Dispatcher:详解与示例

    2024-04-12 20:06:05       15 阅读
  7. C++Book对象数组初始化

    2024-04-12 20:06:05       15 阅读
  8. SpringBoot多数据源配置及使用

    2024-04-12 20:06:05       12 阅读
  9. AcWing 791. 高精度加法——算法基础课题解

    2024-04-12 20:06:05       13 阅读
  10. UI设计需要学习什么?我们应该掌握什么软件?

    2024-04-12 20:06:05       12 阅读