package com.ycwl.basic.pricing.service; import com.ycwl.basic.pricing.service.impl.VoucherPrintServiceImpl; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import java.lang.reflect.Method; import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.*; import java.util.stream.Collectors; /** * 优惠券打印服务流水号生成测试 * 专门测试generateCode方法的重复率和性能 */ @Slf4j @SpringBootTest public class VoucherPrintServiceCodeGenerationTest { private static final String CODE_PREFIX = "VT"; /** * 模拟当前的generateCode方法实现 */ private String generateCode() { SimpleDateFormat sdf = new SimpleDateFormat("ss"); String timestamp = sdf.format(new Date()); String randomSuffix = String.valueOf((int)(Math.random() * 100000)).formatted("%05d"); return CODE_PREFIX + timestamp + randomSuffix; } /** * 测试单线程环境下1秒内生成10个流水号的重复率 */ @Test public void testGenerateCodeDuplicationRateInOneSecond() { log.info("=== 开始测试1秒内生成10个流水号的重复率 ==="); int totalRounds = 1000; // 测试1000轮 int codesPerRound = 10; // 每轮生成10个流水号 int totalDuplicates = 0; int totalCodes = 0; for (int round = 0; round < totalRounds; round++) { Set codes = new HashSet<>(); List codeList = new ArrayList<>(); // 在很短时间内生成10个流水号 for (int i = 0; i < codesPerRound; i++) { String code = generateCode(); codes.add(code); codeList.add(code); } int duplicates = codeList.size() - codes.size(); if (duplicates > 0) { totalDuplicates += duplicates; log.warn("第{}轮发现{}个重复: {}", round + 1, duplicates, codeList); } totalCodes += codesPerRound; // 稍微休息一下,避免在完全同一时间生成 try { Thread.sleep(10); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } double duplicationRate = (double) totalDuplicates / totalCodes * 100; log.info("=== 单线程测试结果 ==="); log.info("总轮数: {}", totalRounds); log.info("每轮生成数: {}", codesPerRound); log.info("总生成数: {}", totalCodes); log.info("总重复数: {}", totalDuplicates); log.info("重复率: {:.4f}%", duplicationRate); // 记录一些示例生成的流水号 log.info("=== 示例流水号 ==="); for (int i = 0; i < 20; i++) { log.info("示例{}: {}", i + 1, generateCode()); } } /** * 测试严格在1秒内生成流水号的重复率 */ @Test public void testStrictOneSecondGeneration() { log.info("=== 开始测试严格1秒内生成流水号重复率 ==="); int rounds = 100; int totalDuplicates = 0; int totalCodes = 0; for (int round = 0; round < rounds; round++) { Set codes = new HashSet<>(); List codeList = new ArrayList<>(); long startTime = System.currentTimeMillis(); // 在1秒内尽可能多地生成流水号 while (System.currentTimeMillis() - startTime < 1000) { String code = generateCode(); codes.add(code); codeList.add(code); } int duplicates = codeList.size() - codes.size(); totalDuplicates += duplicates; totalCodes += codeList.size(); if (duplicates > 0) { log.warn("第{}轮: 生成{}个,重复{}个,重复率{:.2f}%", round + 1, codeList.size(), duplicates, (double) duplicates / codeList.size() * 100); } // 等待下一秒开始 try { Thread.sleep(1100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } double overallDuplicationRate = (double) totalDuplicates / totalCodes * 100; log.info("=== 严格1秒测试结果 ==="); log.info("测试轮数: {}", rounds); log.info("总生成数: {}", totalCodes); log.info("总重复数: {}", totalDuplicates); log.info("总体重复率: {:.4f}%", overallDuplicationRate); log.info("平均每轮生成: {:.1f}个", (double) totalCodes / rounds); } /** * 分析流水号的分布特征 */ @Test public void testCodeDistributionAnalysis() { log.info("=== 开始分析流水号分布特征 ==="); int sampleSize = 10000; List codes = new ArrayList<>(); Map prefixCount = new HashMap<>(); // 时间前缀统计 Map suffixCount = new HashMap<>(); // 随机后缀统计 // 生成样本 for (int i = 0; i < sampleSize; i++) { String code = generateCode(); codes.add(code); // 提取时间前缀 (VTxx) String prefix = code.substring(0, 4); prefixCount.put(prefix, prefixCount.getOrDefault(prefix, 0) + 1); // 提取随机后缀 (最后5位) String suffix = code.substring(4); suffixCount.put(suffix, suffixCount.getOrDefault(suffix, 0) + 1); // 稍微间隔一下,避免全在同一秒 if (i % 100 == 0) { try { Thread.sleep(1); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } // 计算去重率 Set uniqueCodes = new HashSet<>(codes); double uniqueRate = (double) uniqueCodes.size() / sampleSize * 100; // 分析时间前缀分布 log.info("=== 时间前缀分布 (前10个) ==="); prefixCount.entrySet().stream() .sorted(Map.Entry.comparingByValue().reversed()) .limit(10) .forEach(entry -> log.info("前缀 {}: {}次 ({:.2f}%)", entry.getKey(), entry.getValue(), (double) entry.getValue() / sampleSize * 100)); // 检查随机后缀的重复情况 long duplicatedSuffixes = suffixCount.entrySet().stream() .filter(entry -> entry.getValue() > 1) .count(); log.info("=== 分布分析结果 ==="); log.info("样本总数: {}", sampleSize); log.info("唯一流水号数: {}", uniqueCodes.size()); log.info("去重率: {:.4f}%", uniqueRate); log.info("时间前缀种类: {}", prefixCount.size()); log.info("随机后缀种类: {}", suffixCount.size()); log.info("重复的随机后缀数: {}", duplicatedSuffixes); log.info("随机后缀重复率: {:.4f}%", (double) duplicatedSuffixes / suffixCount.size() * 100); } /** * 模拟真实业务场景:快速连续请求 */ @Test public void testRealBusinessScenario() { log.info("=== 开始模拟真实业务场景测试 ==="); // 模拟场景:10个用户几乎同时请求打印小票 int simultaneousUsers = 10; int testsPerUser = 5; // 每个用户发送5次请求 int totalTests = 50; // 总共进行50次这样的场景测试 int totalDuplicates = 0; int totalCodes = 0; for (int test = 0; test < totalTests; test++) { Set allCodes = new HashSet<>(); List allCodesList = new ArrayList<>(); // 模拟同一时刻多个用户的请求 for (int user = 0; user < simultaneousUsers; user++) { for (int request = 0; request < testsPerUser; request++) { String code = generateCode(); allCodes.add(code); allCodesList.add(code); } } int duplicates = allCodesList.size() - allCodes.size(); if (duplicates > 0) { totalDuplicates += duplicates; log.warn("第{}次场景测试发现{}个重复流水号", test + 1, duplicates); // 找出重复的流水号 Map codeCount = new HashMap<>(); for (String code : allCodesList) { codeCount.put(code, codeCount.getOrDefault(code, 0) + 1); } codeCount.entrySet().stream() .filter(entry -> entry.getValue() > 1) .forEach(entry -> log.warn("重复流水号: {} (出现{}次)", entry.getKey(), entry.getValue())); } totalCodes += allCodesList.size(); // 模拟请求间隔 try { Thread.sleep(50); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } double duplicationRate = (double) totalDuplicates / totalCodes * 100; log.info("=== 真实业务场景测试结果 ==="); log.info("场景测试次数: {}", totalTests); log.info("每次场景用户数: {}", simultaneousUsers); log.info("每用户请求数: {}", testsPerUser); log.info("总生成流水号数: {}", totalCodes); log.info("总重复数: {}", totalDuplicates); log.info("重复率: {:.4f}%", duplicationRate); if (duplicationRate > 0.1) { log.warn("警告:重复率超过0.1%,建议优化generateCode方法!"); } } /** * 高并发多线程测试 */ @Test public void testHighConcurrencyGeneration() throws InterruptedException { log.info("=== 开始高并发多线程测试 ==="); int threadCount = 20; // 20个并发线程 int codesPerThread = 50; // 每个线程生成50个流水号 int totalExpectedCodes = threadCount * codesPerThread; ExecutorService executor = Executors.newFixedThreadPool(threadCount); CountDownLatch latch = new CountDownLatch(threadCount); ConcurrentHashMap allCodes = new ConcurrentHashMap<>(); List allCodesList = Collections.synchronizedList(new ArrayList<>()); // 启动所有线程 for (int i = 0; i < threadCount; i++) { final int threadId = i; executor.submit(() -> { try { List threadCodes = new ArrayList<>(); // 每个线程快速生成流水号 for (int j = 0; j < codesPerThread; j++) { String code = generateCode(); threadCodes.add(code); allCodesList.add(code); // 统计重复 Integer count = allCodes.put(code, 1); if (count != null) { allCodes.put(code, count + 1); } } log.debug("线程{}完成,生成{}个流水号", threadId, threadCodes.size()); } finally { latch.countDown(); } }); } // 等待所有线程完成 boolean finished = latch.await(30, TimeUnit.SECONDS); executor.shutdown(); if (!finished) { log.error("测试超时!"); return; } // 分析结果 Set uniqueCodes = new HashSet<>(allCodesList); int duplicates = totalExpectedCodes - uniqueCodes.size(); double duplicationRate = (double) duplicates / totalExpectedCodes * 100; // 找出重复的流水号 List> duplicatedCodes = allCodes.entrySet().stream() .filter(entry -> entry.getValue() > 1) .sorted(Map.Entry.comparingByValue().reversed()) .limit(20) // 只显示前20个最多重复的 .toList(); log.info("=== 高并发测试结果 ==="); log.info("并发线程数: {}", threadCount); log.info("每线程生成数: {}", codesPerThread); log.info("预期总数: {}", totalExpectedCodes); log.info("实际总数: {}", allCodesList.size()); log.info("唯一流水号数: {}", uniqueCodes.size()); log.info("重复数: {}", duplicates); log.info("重复率: {:.4f}%", duplicationRate); if (!duplicatedCodes.isEmpty()) { log.warn("=== 发现重复流水号 ==="); duplicatedCodes.forEach(entry -> log.warn("流水号: {} 重复了 {} 次", entry.getKey(), entry.getValue())); } if (duplicationRate > 1.0) { log.error("严重警告:高并发下重复率超过1.0%,必须优化generateCode方法!"); } } /** * 模拟极端高压场景:短时间内大量请求 */ @Test public void testExtremeHighPressure() throws InterruptedException { log.info("=== 开始极端高压测试 ==="); int threadCount = 50; // 50个并发线程 int codesPerThread = 20; // 每个线程生成20个 long timeWindowMs = 1000; // 在1秒内完成 ExecutorService executor = Executors.newFixedThreadPool(threadCount); CountDownLatch startLatch = new CountDownLatch(1); CountDownLatch endLatch = new CountDownLatch(threadCount); ConcurrentHashMap> codeToThreads = new ConcurrentHashMap<>(); List allCodes = Collections.synchronizedList(new ArrayList<>()); // 准备所有线程 for (int i = 0; i < threadCount; i++) { final int threadId = i; executor.submit(() -> { try { // 等待开始信号 startLatch.await(); long startTime = System.currentTimeMillis(); List threadCodes = new ArrayList<>(); // 在时间窗口内尽可能快地生成 while (System.currentTimeMillis() - startTime < timeWindowMs && threadCodes.size() < codesPerThread) { String code = generateCode(); threadCodes.add(code); allCodes.add(code); // 记录哪个线程生成了这个流水号 codeToThreads.computeIfAbsent(code, k -> Collections.synchronizedList(new ArrayList<>())).add(threadId); } log.debug("线程{}在{}ms内生成{}个流水号", threadId, System.currentTimeMillis() - startTime, threadCodes.size()); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { endLatch.countDown(); } }); } // 开始测试 long testStartTime = System.currentTimeMillis(); startLatch.countDown(); // 等待完成 boolean finished = endLatch.await(timeWindowMs + 5000, TimeUnit.MILLISECONDS); executor.shutdown(); long testDuration = System.currentTimeMillis() - testStartTime; if (!finished) { log.error("极端高压测试超时!"); return; } // 分析结果 Set uniqueCodes = new HashSet<>(allCodes); int totalGenerated = allCodes.size(); int duplicates = totalGenerated - uniqueCodes.size(); double duplicationRate = (double) duplicates / totalGenerated * 100; double generationRate = (double) totalGenerated / testDuration * 1000; // 每秒生成数 // 分析重复模式 Map> duplicatedCodes = codeToThreads.entrySet().stream() .filter(entry -> entry.getValue().size() > 1) .collect(Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue )); log.info("=== 极端高压测试结果 ==="); log.info("并发线程数: {}", threadCount); log.info("预期每线程生成数: {}", codesPerThread); log.info("测试持续时间: {}ms", testDuration); log.info("实际总生成数: {}", totalGenerated); log.info("唯一流水号数: {}", uniqueCodes.size()); log.info("重复数: {}", duplicates); log.info("重复率: {:.4f}%", duplicationRate); log.info("生成速率: {:.1f} codes/sec", generationRate); if (!duplicatedCodes.isEmpty()) { log.warn("=== 极端高压下的重复情况 ==="); duplicatedCodes.entrySet().stream() .limit(10) // 只显示前10个 .forEach(entry -> { String code = entry.getKey(); List threads = entry.getValue(); log.warn("流水号: {} 被线程 {} 重复生成", code, threads); }); } // 评估结果 if (duplicationRate > 5.0) { log.error("极严重警告:极端高压下重复率超过5.0%,generateCode方法不适合高并发场景!"); } else if (duplicationRate > 1.0) { log.warn("警告:极端高压下重复率超过1.0%,建议优化generateCode方法"); } if (generationRate > 10000) { log.info("性能良好:生成速率超过10,000 codes/sec"); } } /** * 综合测试报告 */ @Test public void generateComprehensiveReport() { log.info("=== 生成综合测试报告 ==="); // 基础性能测试 long startTime = System.nanoTime(); List sample = new ArrayList<>(); for (int i = 0; i < 1000; i++) { sample.add(generateCode()); } long duration = System.nanoTime() - startTime; double avgTimePerCode = duration / 1000.0 / 1_000_000; // 毫秒 // 唯一性分析 Set uniqueSample = new HashSet<>(sample); double sampleDuplicationRate = (double) (sample.size() - uniqueSample.size()) / sample.size() * 100; // 长度和格式分析 String sampleCode = generateCode(); int codeLength = sampleCode.length(); boolean hasCorrectPrefix = sampleCode.startsWith(CODE_PREFIX); // 理论分析 double theoreticalCollisionProbability = calculateBirthdayParadoxProbability(10, 100000); log.info("=== generateCode方法综合评估报告 ==="); log.info("基础信息:"); log.info(" - 代码前缀: {}", CODE_PREFIX); log.info(" - 流水号长度: {}", codeLength); log.info(" - 格式正确: {}", hasCorrectPrefix); log.info(" - 示例流水号: {}", sampleCode); log.info("性能指标:"); log.info(" - 平均生成时间: {:.3f}ms", avgTimePerCode); log.info(" - 理论最大生成速率: {:.0f} codes/sec", 1000.0 / avgTimePerCode); log.info("唯一性分析:"); log.info(" - 样本重复率: {:.4f}% (1000个样本)", sampleDuplicationRate); log.info(" - 理论冲突概率: {:.4f}% (1秒内10个)", theoreticalCollisionProbability * 100); log.info(" - 随机数范围: 100,000 (00000-99999)"); log.info("风险评估:"); if (sampleDuplicationRate > 0.5) { log.error(" - 高风险:样本重复率过高,不适合生产环境"); } else if (sampleDuplicationRate > 0.1) { log.warn(" - 中风险:存在一定重复概率,建议优化"); } else { log.info(" - 低风险:重复概率较低,基本可用"); } log.info("优化建议:"); log.info(" - 建议1:使用毫秒级时间戳替代秒级"); log.info(" - 建议2:增加机器标识或进程ID"); log.info(" - 建议3:使用原子递增计数器"); log.info(" - 建议4:采用UUID算法确保全局唯一性"); } /** * 计算生日悖论概率 */ private double calculateBirthdayParadoxProbability(int n, int d) { if (n > d) return 1.0; double probability = 1.0; for (int i = 0; i < n; i++) { probability *= (double) (d - i) / d; } return 1.0 - probability; } }