惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
The Exploit Database - CXSecurity.com
F
Fortinet All Blogs
U
Unit 42
F
Full Disclosure
雷峰网
雷峰网
博客园 - 司徒正美
云风的 BLOG
云风的 BLOG
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Tailwind CSS Blog
The Cloudflare Blog
Last Week in AI
Last Week in AI
罗磊的独立博客
D
DataBreaches.Net
C
Check Point Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
O
OpenAI News
C
CXSECURITY Database RSS Feed - CXSecurity.com
aimingoo的专栏
aimingoo的专栏
S
Security @ Cisco Blogs
大猫的无限游戏
大猫的无限游戏
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
S
SegmentFault 最新的问题
NISL@THU
NISL@THU
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Hacker News
The Hacker News
Webroot Blog
Webroot Blog
Security Latest
Security Latest
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Google DeepMind News
Google DeepMind News
酷 壳 – CoolShell
酷 壳 – CoolShell
N
News | PayPal Newsroom
P
Proofpoint News Feed
B
Blog RSS Feed
MongoDB | Blog
MongoDB | Blog
C
Cybersecurity and Infrastructure Security Agency CISA
N
News and Events Feed by Topic
Google Online Security Blog
Google Online Security Blog
H
Help Net Security
Spread Privacy
Spread Privacy
T
Threat Research - Cisco Blogs
GbyAI
GbyAI
I
Intezer
Application and Cybersecurity Blog
Application and Cybersecurity Blog
M
MIT News - Artificial intelligence
Vercel News
Vercel News
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
IT之家
IT之家
MyScale Blog
MyScale Blog
腾讯CDC

博客园 - VipSoft

MinerU - 将非结构化文档(PDF、图片、Office 文件等)转换为机器可读的 Markdown 和 JSON LangChain 入门 服务端部署-FastAPI LangChain 入门 LangSmith LangChain 入门 实战 - 食谱推荐 LangChain 入门 Memory 会话记忆 LangChain 入门 Tools 工具 LangChain 入门 Tools 工具 LangChain 入门 Prompts 提示词 LangChain 入门 Message 消息 LangChain 入门 Model 的初始化和调用 LangChain 入门 Agent 的基本运行机制 AI 0基础学习,名词解析 LangChain 和 LangGraph AI大模型知识体系 Dify — Workflow - 数据可视化 Dify — 连接MySQL配置 Dify — Chatflow - 数据库智能查询 Dify — Chatflow - 文档知识库 Dify — Agent 智能体 Dify — 文本生成应用 Dify — 聊天助手 -- 知识库 Windows 下 Docker 安装 Dify Ollama — 命令 Ollama — 为什么能够运行不同的模型 Ollama — 接口 Qwen — 自定义模型文件 Ollama Qwen — 安装测试 Ollama Windows 安装 & 指定安装目录 跟着AI学AI - 诊断结论信息抽取 - 批量处理脚本 跟着AI学AI - 诊断结论信息抽取 - 模型压缩与部署 跟着AI学AI - 诊断结论信息抽取 - 模型评估与调试 跟着AI学AI - 诊断结论信息抽取 - 模型训练 轻型民用无人驾驶航空器安全操控理论考试培训材料 FreeRedis Helper Windbg w3wp.DMP 内存分析 跟着AI学AI - 诊断结论信息抽取 - 数据增强 QQ 录屏软件 Java - 加权随机算法 - 示例 Java - 加权随机算法--Demo Java LoadBalanceUtil 负载均衡、轮询加权 SpringBoot 集成 IP2Region 获取IP地域信息 Windows 服务器和虚拟主机,创建.开头的文件夹 .well-known 跟着AI学AI - 诊断结论信息抽取 - 数据格式转换BERT训练格式 跟着AI学AI - 诊断结论信息抽取 - LabelStudio 标注 -- 结论标注 跟着AI学AI - 诊断结论信息抽取 - 学习路径 跟着AI学AI - UV 安装 数据标注工具 Label-Studio `VIRTUAL_ENV=venv` does not match the project environment 跟着AI学AI - 学习路径 - 命名实体识别(NER)和信息抽取(IE) 跟着AI学AI - 需要购买显卡吗? C# 无BOM的UTF-8编码 Vue ref reactive Vue - el-table 嵌套表 Spring `@Scheduled` 中这些参数的区别、组合和应用场景 Python 找出同步日志中的重复数据 Python - UV PyCharm 不能识别 .venv 的环境
高安全券码、注册码生成
VipSoft · 2026-05-28 · via 博客园 - VipSoft

下面给你提供一个生产级 Java 工具类,可以生成 C6Y2-6CK8-VF7J 这种格式的券码:

  • 去掉易混淆字符(I、1、L、O、0)
  • 可自定义:长度、分组大小、分隔符
  • 高安全性:基于 SecureRandom
  • 支持批量生成 + 去重校验

你给的示例 C6Y2-6CK8-VF7J 实际上只有 13 位有效字符(不含横杠),下面代码按 12 位 + 3 组 实现,你也可以灵活调整。

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;

/**
 * 生产级券码生成器 - JDK 8 兼容版本
 */
public class CouponCodeGenerator {
    private static final Logger logger = Logger.getLogger(CouponCodeGenerator.class.getName());
    private static final String ALPHABET = "ABCDEFGHJKMNOPQRSTUVWXYZ23456789";
    private static final int BASE = ALPHABET.length();  // 32, 2^5
    private static final SecureRandom RANDOM = new SecureRandom();

    // JDK 8 使用 ThreadLocal 避免 StringBuilder 重复创建
    private static final ThreadLocal<StringBuilder> THREAD_BUFFER =
            ThreadLocal.withInitial(() -> new StringBuilder(64));

    /**
     * 生成单个券码
     */
    public static String generate(int totalLen, int groupSize, String separator) {
        validateParams(totalLen, groupSize);

        char[] chars = new char[totalLen];
        for (int i = 0; i < totalLen; i++) {
            chars[i] = ALPHABET.charAt(RANDOM.nextInt(BASE));
        }

        String actualSeparator = separator == null ? "" : separator;
        return formatGroups(new String(chars), groupSize, actualSeparator);
    }

    /**
     * 格式化分组(JDK 8 优化版)
     */
    private static String formatGroups(String str, int groupSize, String separator) {
        if (separator.isEmpty()) {
            return str;
        }

        int len = str.length();
        StringBuilder sb = THREAD_BUFFER.get();
        sb.setLength(0);  // 清空重用

        for (int i = 0; i < len; i++) {
            if (i > 0 && i % groupSize == 0) {
                sb.append(separator);
            }
            sb.append(str.charAt(i));
        }

        return sb.toString();
    }

    /**
     * 批量生成(自动选择串行/并行)
     */
    public static Set<String> generateBatch(int count, int totalLen, int groupSize,
                                            String separator, boolean parallel) {
        // 容量检查
        checkCapacity(count, totalLen);

        if (parallel && count > 5000) {  // JDK 8 并行阈值调低
            return generateBatchParallel(count, totalLen, groupSize, separator);
        } else {
            return generateBatchSequential(count, totalLen, groupSize, separator);
        }
    }

    /**
     * 串行生成(修正计数逻辑)
     */
    private static Set<String> generateBatchSequential(int count, int totalLen,
                                                       int groupSize, String separator) {
        Set<String> codes = new LinkedHashSet<>(count * 2);
        int collisions = 0;
        int maxCollisions = count * 10;
        String actualSeparator = separator == null ? "" : separator;

        while (codes.size() < count && collisions < maxCollisions) {
            String code = generate(totalLen, groupSize, actualSeparator);
            if (!codes.add(code)) {
                collisions++;
            }
        }

        if (codes.size() < count) {
            double rate = collisions * 100.0 / (collisions + codes.size());
            throw new CouponExhaustedException(
                    String.format(Locale.US,
                            "仅生成 %d/%d 个券码,碰撞次数: %d,碰撞率: %.2f%%",
                            codes.size(), count, collisions, rate));
        }
        logger.info(String.format("串行生成 %d 个券码完成,碰撞次数: %d", count, collisions));
        return codes;
    }

    /**
     * 并行生成(JDK 8 兼容版本)
     */
    private static Set<String> generateBatchParallel(int count, int totalLen,
                                                     int groupSize, String separator) {
        // JDK 8 使用 ConcurrentHashMap.newKeySet() 需要 Java 8+
        Set<String> result = ConcurrentHashMap.newKeySet();
        AtomicInteger collisions = new AtomicInteger(0);
        AtomicInteger completed = new AtomicInteger(0);
        int maxCollisions = count * 10;
        String actualSeparator = separator == null ? "" : separator;

        int cores = Runtime.getRuntime().availableProcessors();
        ExecutorService executor = Executors.newFixedThreadPool(cores);

        // 每个线程的目标生成数量
        int perThreadCount = (count + cores - 1) / cores;
        List<Future<Void>> futures = new ArrayList<>();

        for (int t = 0; t < cores; t++) {
            final int targetPerThread = perThreadCount;
            futures.add(executor.submit(() -> {
                Set<String> localSet = new HashSet<>(targetPerThread);
                int localCollisions = 0;
                int maxLocalCollisions = targetPerThread * 10;

                while (localSet.size() < targetPerThread &&
                        result.size() < count &&
                        collisions.get() < maxCollisions) {

                    String code = generate(totalLen, groupSize, actualSeparator);

                    // 先尝试加本地集,再加全局集(减少全局锁竞争)
                    if (localSet.add(code)) {
                        if (result.add(code)) {
                            completed.incrementAndGet();
                        } else {
                            // 全局已存在,从本地移除并计数碰撞
                            localSet.remove(code);
                            localCollisions++;
                            collisions.incrementAndGet();
                        }
                    } else {
                        // 本地重复
                        localCollisions++;
                        collisions.incrementAndGet();
                    }
                }

                // 将本地剩余数据合并到全局
                for (String code : localSet) {
                    result.add(code);
                }

                return null;
            }));
        }

        // 等待所有线程完成
        for (Future<Void> future : futures) {
            try {
                future.get(30, TimeUnit.SECONDS);
            } catch (Exception e) {
                executor.shutdownNow();
                throw new CouponExhaustedException("并行生成失败: " + e.getMessage());
            }
        }

        executor.shutdown();

        if (result.size() < count) {
            throw new CouponExhaustedException(
                    String.format(Locale.US,
                            "并行生成仅得到 %d/%d 个券码,碰撞次数: %d",
                            result.size(), count, collisions.get()));
        }

        logger.info(() -> String.format("并行生成 %d 个券码完成,碰撞次数: %d", count, collisions.get()));
        return new LinkedHashSet<>(result);
    }

    /**
     * 流式生成到文件(避免 OOM,JDK 8 兼容)
     */
    public static void generateBatchToFile(int count, int totalLen, int groupSize,
                                           String separator, String filePath) throws IOException {
        try (PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(filePath)))) {
            Set<String> batchCache = new HashSet<>();
            int batchSize = 10000;  // 每批1万条
            int generated = 0;
            int collisions = 0;
            int maxCollisions = count * 10;
            String actualSeparator = separator == null ? "" : separator;

            while (generated < count && collisions < maxCollisions) {
                String code = generate(totalLen, groupSize, actualSeparator);

                if (batchCache.add(code)) {
                    writer.println(code);
                    generated++;

                    if (generated % batchSize == 0) {
                        writer.flush();
                        batchCache.clear();  // 释放内存
                        logger.info(String.format("已生成 %d/%d 个券码", generated, count));
                    }
                } else {
                    collisions++;
                }
            }

            if (generated < count) {
                throw new CouponExhaustedException(
                        String.format("仅生成 %d/%d 个券码,碰撞次数: %d", generated, count, collisions));
            }

            writer.flush();
            logger.info(() -> String.format("券码已保存到文件: %s", filePath));
        }
    }

    /**
     * 容量检查(使用 BigInteger 避免溢出)
     */
    private static void checkCapacity(int count, int totalLen) {
        BigInteger capacity = BigInteger.valueOf(BASE).pow(totalLen);
        BigInteger needed = BigInteger.valueOf(count);
        BigInteger threshold = capacity.multiply(BigInteger.valueOf(70))
                .divide(BigInteger.valueOf(100));

        if (needed.compareTo(threshold) > 0) {
            double ratio = needed.doubleValue() / capacity.doubleValue() * 100;
            logger.warning(String.format(Locale.US,
                    "警告:生成数量 %d 接近理论容量 %.0e,碰撞概率较高 (%.1f%%)",
                    count, Math.pow(BASE, totalLen), ratio));
        }
    }

    /**
     * 验证券码格式
     */
    public static boolean validate(String code, int totalLen, int groupSize, String separator) {
        if (code == null || code.isEmpty()) return false;

        String actualSeparator = separator == null ? "" : separator;
        String clean = code.replace(actualSeparator, "");

        if (clean.length() != totalLen) return false;

        for (char c : clean.toCharArray()) {
            if (ALPHABET.indexOf(c) == -1) return false;
        }

        // 验证分隔符位置
        if (!actualSeparator.isEmpty()) {
            String reformatted = formatGroups(clean, groupSize, actualSeparator);
            if (!reformatted.equals(code)) return false;
        }

        return true;
    }

    private static void validateParams(int totalLen, int groupSize) {
        if (totalLen < 4 || totalLen > 32) {
            throw new IllegalArgumentException("totalLen 必须介于 4-32 之间");
        }
        if (groupSize < 1 || groupSize > totalLen) {
            throw new IllegalArgumentException("groupSize 必须介于 1-" + totalLen);
        }
    }

    /**
     * 自定义异常
     */
    public static class CouponExhaustedException extends RuntimeException {
        public CouponExhaustedException(String message) {
            super(message);
        }
    }

    /**
     * 测试示例
     */
    public static void main(String[] args) {
        // 1. 基本生成测试
        String code = generate(16, 4, "-");
        System.out.println("生成单个券码: " + code);
        System.out.println("验证结果: " + validate(code, 16, 4, "-") + "\n");

        // 2. 串行批量生成
        long start = System.nanoTime();
        Set<String> batch1 = generateBatch(1000, 12, 4, "-", false);
        long time = (System.nanoTime() - start) / 1_000_000;
        System.out.printf("串行生成 1000 个券码: %d ms%n", time);
        System.out.println("示例: " + batch1.iterator().next() + "\n");

        // 3. 并行批量生成(JDK 8 ForkJoinPool)
        start = System.nanoTime();
        Set<String> batch2 = generateBatch(5000, 12, 4, "-", true);
        time = (System.nanoTime() - start) / 1_000_000;
        System.out.printf("并行生成 5000 个券码: %d ms%n", time);
        System.out.println("示例: " + batch2.iterator().next() + "\n");

        // 4. 理论容量测试
        BigInteger cap = BigInteger.valueOf(BASE).pow(12);
        System.out.printf("12位券码理论容量: %s (%.2e)%n", cap, Math.pow(BASE, 12));

        // 5. 异常测试
        try {
            generateBatch(1000000, 8, 4, "-", false);
        } catch (CouponExhaustedException e) {
            System.out.println("\n预期异常: " + e.getMessage());
        }
    }
}

五、生产环境还需要考虑什么(可选)

  1. 不要用毫秒时间戳参与生成:会降低熵,且分布式下可能重复。
  2. 如果需要更短(8~10位):可以将 ALPHABET 改为 "ABCDEFGHJKLMNPQRSTUVWXYZ123456789"(进一步增加位数密度),但不建议低于 10 位。
  3. 如果券码需要反解出业务信息:可以在前面加固定业务前缀(如 JD),但会降低安全性,需要配合其他风控。

一句话总结:上面的代码就是你要的 C6Y2-6CK8-VF7J 这种风格的高安全、可读性好的 Java 实现,拿来即用。

高熵随机是一个信息论/密码学的概念,简单理解就是:结果非常难以预测,且每个可能的值出现的概率几乎相等

把它拆成两个词来解释,你就明白了。

1. 先看“熵”

在这里代表不确定性混乱程度

  • 低熵:非常确定,可预测。比如流水号 000001, 000002...。你看到 000001,就能猜出下一个大概率是 000002
  • 高熵:非常不确定,不可预测。比如一个真正的随机数。你看到 A3F9,完全无法猜出下一个是 K2M7 还是 9D4F

熵越高,意味着你掌握的信息越少,猜测的难度越大。

2. 再来看“高熵随机”

它不是一个具体算法,而是对随机性质量的形容,通常具备三个特点:

  • 真正的随机源:不是用 new Random() 这种基于时间戳的“伪随机”(可预测),而是用 SecureRandom 这类基于硬件噪音、鼠标移动等物理现象的“密码学安全随机”。
  • 充足的位数:比如12位,每位有32种可能(去掉混淆字符后),总组合空间巨大(32^12),无法暴力枚举。
  • 均匀分布:每个字符(如A、B、C...9)出现的概率是相等的,不会出现某个字母明显偏多。

举个直观的例子

类型 例子 熵的高低 原因
流水号 MP240615000001 极低熵 看到前面,就能推算后面;攻击者可遍历。
伪随机 new Random().nextInt() 生成的 845321 中等熵 看似随机,但如果你知道种子(时间戳),就能推算出整个序列。
高熵随机 SecureRandom 生成的 C6Y2-6CK8-VF7J 极高熵 无法预测下一张券码;哪怕你拿到1000万个历史码,也无法推算第1001个。

对你之前的券码问题来说

  • 你问 C6YV2-6CKK8-VF7JB 怎么样
    → 这就是高熵随机的典型例子。它无法被暴力遍历,因为攻击者面对的是天文数字级别的可能性,只能一个个瞎猜。

  • 为什么不用 MP240615000123
    → 因为它是低熵的,攻击者可以系统性地枚举(从1试到100万),几分钟就能扫光。

一句话总结

高熵随机 = 用真正的随机源生成的、组合空间巨大、无法预测的随机数。它是防暴力遍历、防猜测的核心武器。

在实际开发中,用 java.security.SecureRandom 生成的券码,就可以称为“高熵随机券码”。