UUID方案:将uuid分成等份,转成16进制即可。(代码里有11位和8位数的券码代码参考)
雪花id方案:实现思路很简单,生成雪花id(可根据需求,换成使用uuid的方案,测试代码里有两种方法),将雪花id的值转换成自定义的55进制,长度会大大缩短,且基于雪花id,所以不需要判断去重。目前我们公司接受券码不超过12位就行,所以没有再对其进一步优化。
话不多说,直接上代码,欢迎各位大佬留言指正
优惠券码生成工具类CouponCodeUtil2(基于uuid,测试使用,注意修改):
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
public class CouponCodeUtil2 {
private static final char[] FIX_STR = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M',
'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n',
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'2', '3', '4', '5', '6', '7', '8', '9'};
private static final String STR = new String(FIX_STR);
private static String strToSplitHex16(String uuid) {
StringBuilder shortBuffer = new StringBuilder();
//我们这里想要保证券码为11位,所以32位uuid加了一位随机数,再分成11等份;(如果是8位券码,则32位uuid分成8等份进行计算即可)
for (int i = 0; i < 11; i++) {
String str = uuid.substring(i * 3, i * 3 + 3);
int x = Integer.parseInt(str, 16);
shortBuffer.append(FIX_STR[x % FIX_STR.length]);
}
return shortBuffer.toString();
}
private static String strToSplitHex16V2(String uuid) {
StringBuilder shortBuffer = new StringBuilder();
//8位券码,则32位uuid分成8等份进行计算即可
for (int i = 0; i < 8; i++) {
String str = uuid.substring(i * 4, i * 4 + 4);
int x = Integer.parseInt(str, 16);
shortBuffer.append(FIX_STR[x % FIX_STR.length]);
}
return shortBuffer.toString();
}
/**
* 产生优惠券编码的方法
*
* @return
*/
public static String generateCouponCode() {
String uuid = UUIDGenerator.get() + new Random().nextInt(10);
return strToSplitHex16(uuid);
}
public static String generateCouponCodeV2() {
String uuid = UUIDGenerator.get();
return strToSplitHex16V2(uuid);
}
public static void main(String[] args) {
System.out.println("---------------- 分割线 -----------------------");
/*基于uuid生成:因为基于雪花id产生的券码,被猜的可能性较大*/
Set<String> hashSet = new HashSet<>(10000000);
for (int i = 0; i < 10000000; i++) {
//产生的券码
String key = generateCouponCodeV2();
if (hashSet.contains(key)) {
System.out.println(key);
System.out.println("我重复了-------------====================-----------------");
} else {
hashSet.add(key);
}
}
System.out.println("---------------- 分割线 -----------------------");
}
优惠券码生成工具类CouponCodeUtil1(基于雪花id,测试使用,注意修改):
import java.util.HashSet;
import java.util.Set;
public class CouponCodeUtil1 {
private static final char[] FIX_STR = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M',
'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n',
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'2', '3', '4', '5', '6', '7', '8', '9'};
private static final String STR = new String(FIX_STR);
public static String fFToStr(long i) {
StringBuilder sb = new StringBuilder();
//55进制
while (i >= 55) {
long a = i % 55;
i /= 55;
sb.append(FIX_STR[Math.toIntExact(a)]);
}
sb.append(FIX_STR[Math.toIntExact(i)]);
return sb.reverse().toString();
}
public static long strToFF(String str) {
long r = 0L;
char[] chars = str.toCharArray();
for (int i = chars.length - 1, j = 0; i >= 0; i--, j++) {
r += STR.indexOf(chars[i]) * Math.pow(55, j);
}
//todo 此处有坑,会有精度丢失问题,有时候转换出来的long值貌似会和原值不一样。。
return r;
}
/**
* 产生券码的方法
*
* @return
*/
public static String generateCouponCode() {
return fFToStr(SnowFlakeUtils.nextId());
}
public static void main(String[] args) {
System.out.println("---------------- 分割线 -----------------------");
Set<String> hashSet = new HashSet<>(10);
for (int i = 0; i < 10; i++) {
//产生的券码
String key = generateCouponCode();
if (hashSet.contains(key)) {
System.out.println(key);
System.out.println("我重复了-------------====================-----------------");
} else {
hashSet.add(key);
}
}
System.out.println("---------------- 分割线 -----------------------");
/*long nextId = SnowFlakeUtils.nextId();
System.out.println(nextId);
//转成55进制后的字符(即我们需要的券码)
System.out.println(fFToStr(nextId));
//55进制字符转换成10进制的值 todo 此处有坑,会有精度丢失问题,有时候转换出来的long值貌似会和原值不一样。。
System.out.println(strToFF(fFToStr(nextId)));
//理论上说,一个id为20位的10进制数值,用55进制不会超过12位字符。
System.out.println(Math.pow(10, 20) >= Math.pow(55, 12));*/
}
}
雪花id工具类:(测试用,注意修改)
public class SnowFlakeUtils {
/**
* 起始的时间戳
*/
private final static long twepoch = 1557825652094L;
/**
* 每一部分占用的位数
*/
private final static long workerIdBits = 5L;
private final static long datacenterIdBits = 5L;
private final static long sequenceBits = 12L;
/**
* 每一部分的最大值
*/
private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);
private final static long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
private final static long maxSequence = -1L ^ (-1L << sequenceBits);
/**
* 每一部分向左的位移
*/
private final static long workerIdShift = sequenceBits;
private final static long datacenterIdShift = sequenceBits + workerIdBits;
private final static long timestampShift = sequenceBits + workerIdBits + datacenterIdBits;
private static long datacenterId = 1; // 数据中心ID
private final static long workerId = Long.valueOf(0L); // 机器ID
private static long sequence = 0L; // 序列号
private static long lastTimestamp = -1L; // 上一次时间戳
public static synchronized long nextId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException(String.format(
"Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
if (timestamp == lastTimestamp) {
sequence = (sequence + 1) & maxSequence;
if (sequence == 0L) {
timestamp = tilNextMillis();
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
return (timestamp - twepoch) << timestampShift // 时间戳部分
| datacenterId << datacenterIdShift // 数据中心部分
| workerId << workerIdShift // 机器标识部分
| sequence; // 序列号部分
}
private static long tilNextMillis() {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
private static long timeGen() {
return System.currentTimeMillis();
}
public static void main(String[] args) {
System.out.println(nextId());
}
}