package com.doumee.core.utils;
|
|
import java.security.SecureRandom;
|
import java.util.HashSet;
|
import java.util.Random;
|
import java.util.Set;
|
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.List;
|
import java.util.ArrayList;
|
|
/**
|
* 不重复核销码生成器
|
* 用于生成唯一且易于识别的核销码
|
*/
|
public class UniqueVerificationCodeGenerator {
|
|
private final Set<String> usedCodes;
|
private final int codeLength;
|
private final String prefix;
|
private final String suffix;
|
private final Random random;
|
private final boolean useSecureRandom;
|
private final CodeFormat format;
|
|
// 核销码格式枚举
|
public enum CodeFormat {
|
NUMERIC_ONLY("0123456789"),
|
ALPHANUMERIC("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"),
|
LETTERS_ONLY("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
|
|
private final String characters;
|
|
CodeFormat(String characters) {
|
this.characters = characters;
|
}
|
|
public String getCharacters() {
|
return characters;
|
}
|
}
|
|
/**
|
* 构造函数 - 默认数字格式
|
* @param codeLength 核销码长度
|
*/
|
public UniqueVerificationCodeGenerator(int codeLength) {
|
this(codeLength, "", "", CodeFormat.NUMERIC_ONLY, false);
|
}
|
|
/**
|
* 构造函数 - 完整配置
|
* @param codeLength 核销码长度
|
* @param prefix 前缀
|
* @param suffix 后缀
|
* @param format 核销码格式
|
* @param useSecureRandom 是否使用安全随机数生成器
|
*/
|
public UniqueVerificationCodeGenerator(int codeLength, String prefix, String suffix,
|
CodeFormat format, boolean useSecureRandom) {
|
if (codeLength < 1) {
|
throw new IllegalArgumentException("核销码长度必须大于0");
|
}
|
|
this.codeLength = codeLength;
|
this.prefix = prefix != null ? prefix : "";
|
this.suffix = suffix != null ? suffix : "";
|
this.format = format;
|
this.usedCodes = new HashSet<>();
|
this.useSecureRandom = useSecureRandom;
|
this.random = useSecureRandom ? new SecureRandom() : ThreadLocalRandom.current();
|
}
|
|
/**
|
* 生成下一个不重复的核销码
|
* @return 核销码,如果没有可用组合则返回null
|
*/
|
public String nextUniqueCode() {
|
int maxAttempts = 1000;
|
int attempts = 0;
|
|
while (attempts < maxAttempts) {
|
String code = generateCode();
|
if (!usedCodes.contains(code)) {
|
usedCodes.add(code);
|
return code;
|
}
|
attempts++;
|
}
|
|
// 如果随机生成失败,尝试顺序生成
|
return generateSequentialCode();
|
}
|
|
/**
|
* 生成指定数量的不重复核销码
|
* @param count 数量
|
* @return 核销码列表
|
*/
|
public List<String> nextUniqueCodes(int count) {
|
List<String> result = new ArrayList<>();
|
for (int i = 0; i < count; i++) {
|
String code = nextUniqueCode();
|
if (code == null) {
|
break; // 没有更多可用组合
|
}
|
result.add(code);
|
}
|
return result;
|
}
|
|
/**
|
* 生成单个核销码(不检查重复)
|
* @return 核销码
|
*/
|
private String generateCode() {
|
StringBuilder sb = new StringBuilder();
|
sb.append(prefix);
|
|
String characters = format.getCharacters();
|
for (int i = 0; i < codeLength; i++) {
|
int index = random.nextInt(characters.length());
|
sb.append(characters.charAt(index));
|
}
|
|
sb.append(suffix);
|
return sb.toString();
|
}
|
|
/**
|
* 顺序生成核销码(作为备选方案)
|
*/
|
private String generateSequentialCode() {
|
String characters = format.getCharacters();
|
long totalCombinations = (long) Math.pow(characters.length(), codeLength);
|
|
if (usedCodes.size() >= totalCombinations) {
|
return null; // 所有可能组合都已使用
|
}
|
|
// 寻找下一个未使用的组合
|
for (long i = 0; i < totalCombinations; i++) {
|
String code = convertToCode(i, characters);
|
String fullCode = prefix + code + suffix;
|
if (!usedCodes.contains(fullCode)) {
|
usedCodes.add(fullCode);
|
return fullCode;
|
}
|
}
|
|
return null;
|
}
|
|
/**
|
* 将数字转换为指定字符集的编码
|
*/
|
private String convertToCode(long number, String characters) {
|
StringBuilder sb = new StringBuilder();
|
long base = characters.length();
|
|
for (int i = 0; i < codeLength; i++) {
|
sb.append(characters.charAt((int) (number % base)));
|
number /= base;
|
}
|
|
return sb.reverse().toString();
|
}
|
|
/**
|
* 重置生成器
|
*/
|
public void reset() {
|
usedCodes.clear();
|
}
|
|
/**
|
* 获取已使用的核销码数量
|
*/
|
public int getUsedCount() {
|
return usedCodes.size();
|
}
|
|
/**
|
* 检查是否还有可用的核销码组合
|
*/
|
public boolean hasMoreCodes() {
|
String characters = format.getCharacters();
|
long totalCombinations = (long) Math.pow(characters.length(), codeLength);
|
return usedCodes.size() < totalCombinations;
|
}
|
|
// ==================== 静态便捷方法 ====================
|
|
/**
|
* 生成数字型核销码(默认6位)
|
*/
|
public static String generateNumericCode() {
|
return generateNumericCode(6);
|
}
|
|
/**
|
* 生成指定长度的数字型核销码
|
*/
|
public static String generateNumericCode(int length) {
|
UniqueVerificationCodeGenerator generator =
|
new UniqueVerificationCodeGenerator(length, "", "", CodeFormat.NUMERIC_ONLY, true);
|
return generator.nextUniqueCode();
|
}
|
|
/**
|
* 批量生成数字型核销码
|
*/
|
public static List<String> generateNumericCodes(int length, int count) {
|
UniqueVerificationCodeGenerator generator =
|
new UniqueVerificationCodeGenerator(length, "", "", CodeFormat.NUMERIC_ONLY, true);
|
return generator.nextUniqueCodes(count);
|
}
|
|
/**
|
* 生成带前缀的核销码
|
*/
|
public static String generatePrefixedCode(String prefix, int length) {
|
UniqueVerificationCodeGenerator generator =
|
new UniqueVerificationCodeGenerator(length, prefix, "", CodeFormat.NUMERIC_ONLY, true);
|
return generator.nextUniqueCode();
|
}
|
|
/**
|
* 生成字母数字混合核销码
|
*/
|
public static String generateAlphanumericCode(int length) {
|
UniqueVerificationCodeGenerator generator =
|
new UniqueVerificationCodeGenerator(length, "", "", CodeFormat.ALPHANUMERIC, true);
|
return generator.nextUniqueCode();
|
}
|
|
/**
|
* 生成订单号格式的核销码(如:ORD20241201XXXXXX)
|
*/
|
public static String generateOrderCode(String prefix) {
|
String datePart = java.time.LocalDateTime.now().format(
|
java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd"));
|
UniqueVerificationCodeGenerator generator =
|
new UniqueVerificationCodeGenerator(6, prefix + datePart, "", CodeFormat.NUMERIC_ONLY, true);
|
return generator.nextUniqueCode();
|
}
|
|
/**
|
* 生成优惠券码格式(如:COUPON-XXXXXX)
|
*/
|
public static String generateCouponCode() {
|
UniqueVerificationCodeGenerator generator =
|
new UniqueVerificationCodeGenerator(8, "COUPON-", "", CodeFormat.ALPHANUMERIC, true);
|
return generator.nextUniqueCode();
|
}
|
}
|