From c6238277b8ad17de41f6aef979eb081199369be2 Mon Sep 17 00:00:00 2001 From: zyh <50652658+zyh530@users.noreply.github.com> Date: Fri, 3 Oct 2025 11:02:39 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E6=A3=80=E6=9F=A5=E7=BC=96=E5=8F=B7=E5=AD=98=E5=9C=A8=E6=80=A7?= =?UTF-8?q?=E5=92=8C=E6=89=B9=E9=87=8F=E6=8F=92=E5=85=A5=E9=93=BE=E6=8E=A5?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E7=9A=84=E6=96=B9=E6=B3=95=EF=BC=8C=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E9=93=BE=E6=8E=A5=E7=94=9F=E6=88=90=E9=80=BB=E8=BE=91?= =?UTF-8?q?=EF=BC=8C=E6=9B=B4=E6=96=B0=E7=9B=B8=E5=85=B3XML=E6=98=A0?= =?UTF-8?q?=E5=B0=84=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/compiler.xml | 1 - .idea/misc.xml | 2 +- .../server/mapper/agent/LinkTaskMapper.java | 10 ++ .../model/dto/link/LinkListRequest.java | 2 +- .../service/link/LinkGenerationService.java | 124 ++++++++++++------ .../resources/mapper/agent/LinkTaskMapper.xml | 18 +++ 6 files changed, 116 insertions(+), 41 deletions(-) diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 896f350..7488d91 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -14,7 +14,6 @@ - diff --git a/.idea/misc.xml b/.idea/misc.xml index 2701299..baa5bf6 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -8,7 +8,7 @@ - + \ No newline at end of file diff --git a/src/main/java/com/gameplatform/server/mapper/agent/LinkTaskMapper.java b/src/main/java/com/gameplatform/server/mapper/agent/LinkTaskMapper.java index e720093..6dda8ec 100644 --- a/src/main/java/com/gameplatform/server/mapper/agent/LinkTaskMapper.java +++ b/src/main/java/com/gameplatform/server/mapper/agent/LinkTaskMapper.java @@ -139,4 +139,14 @@ public interface LinkTaskMapper extends BaseMapper { @Param("region") String region, @Param("deviceId") String deviceId, @Param("qrExpireSeconds") int qrExpireSeconds); + + /** + * 批量检查编号是否存在,返回已存在的编号列表 + */ + List findExistingCodeNos(@Param("codeNos") List codeNos); + + /** + * 批量插入链接任务 + */ + int batchInsert(@Param("tasks") List tasks); } diff --git a/src/main/java/com/gameplatform/server/model/dto/link/LinkListRequest.java b/src/main/java/com/gameplatform/server/model/dto/link/LinkListRequest.java index 5c63838..a15eb47 100644 --- a/src/main/java/com/gameplatform/server/model/dto/link/LinkListRequest.java +++ b/src/main/java/com/gameplatform/server/model/dto/link/LinkListRequest.java @@ -18,7 +18,7 @@ public class LinkListRequest { @Schema(description = "每页大小", example = "20") @Min(value = 1, message = "每页大小必须大于0") - @Max(value = 100, message = "每页大小不能超过100") + @Max(value = 10000, message = "每页大小不能超过10000") private Integer pageSize = 20; @Schema(description = "链接状态过滤", example = "NEW") diff --git a/src/main/java/com/gameplatform/server/service/link/LinkGenerationService.java b/src/main/java/com/gameplatform/server/service/link/LinkGenerationService.java index b8c67a0..6542e21 100644 --- a/src/main/java/com/gameplatform/server/service/link/LinkGenerationService.java +++ b/src/main/java/com/gameplatform/server/service/link/LinkGenerationService.java @@ -20,8 +20,8 @@ import reactor.core.scheduler.Schedulers; import java.security.SecureRandom; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; +import java.util.*; +import java.util.stream.Collectors; @Service public class LinkGenerationService { @@ -95,18 +95,24 @@ public class LinkGenerationService { linkBatchMapper.insert(batch); // NEW状态的链接不设置过期时间,只有激活使用时才设置过期时间 - List tasks = new ArrayList<>(); - for (int i = 0; i < linkCount; i++) { // 生成linkCount个链接 + // 批量生成唯一的编号 + List uniqueCodeNos = generateUniqueCodeNos(linkCount); + + // 批量创建任务对象 + List tasks = new ArrayList<>(linkCount); + for (int i = 0; i < linkCount; i++) { LinkTask t = new LinkTask(); t.setBatchId(batch.getId()); t.setAgentId(operator.getId()); - t.setCodeNo(generateCodeNo()); + t.setCodeNo(uniqueCodeNos.get(i)); t.setTokenHash(DigestUtils.sha256Hex(generateToken())); t.setExpireAt(null); // NEW状态不设置过期时间 t.setStatus("NEW"); - linkTaskMapper.insert(t); tasks.add(t); } + + // 批量插入 + linkTaskMapper.batchInsert(tasks); if (!isAdminOperator) { // 扣点流水 + 账户余额 @@ -140,29 +146,85 @@ public class LinkGenerationService { private static final String CODE_CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; // avoid confusing chars private static final SecureRandom RANDOM = new SecureRandom(); - private static final int MAX_RETRY_ATTEMPTS = 5; // 最大重试次数 + private static final int MAX_BATCH_RETRY = 3; // 批量生成最大重试次数 + private static final double CANDIDATE_MULTIPLIER = 1.2; // 候选数量系数,生成比需要多20%的候选 /** - * 生成唯一的链接编号 - * 利用数据库唯一约束确保编号不重复 + * 批量生成唯一的链接编号 + * 优化策略:一次生成多个候选编号,批量检查唯一性,减少数据库查询次数 */ - private String generateCodeNo() { - for (int attempt = 1; attempt <= MAX_RETRY_ATTEMPTS; attempt++) { - String codeNo = generateRandomCodeNo(); - - // 检查是否已存在(利用数据库唯一约束) - if (isCodeNoUnique(codeNo)) { - log.debug("生成唯一编号成功: {} (第{}次尝试)", codeNo, attempt); - return codeNo; - } - - log.warn("编号{}已存在,第{}次尝试生成新编号", codeNo, attempt); + private List generateUniqueCodeNos(int count) { + if (count <= 0) { + return Collections.emptyList(); } - // 如果多次重试都失败,使用时间戳后缀确保唯一性 - String fallbackCodeNo = generateRandomCodeNo() + System.currentTimeMillis() % 10000; - log.warn("使用后备编号生成策略: {}", fallbackCodeNo); - return fallbackCodeNo.substring(0, Math.min(8, fallbackCodeNo.length())); + List uniqueCodeNos = new ArrayList<>(count); + int remainingCount = count; + + for (int attempt = 1; attempt <= MAX_BATCH_RETRY; attempt++) { + // 生成候选编号(比需要的数量多一些,以应对冲突) + int candidateCount = (int) Math.ceil(remainingCount * CANDIDATE_MULTIPLIER); + Set candidates = generateRandomCodeNos(candidateCount); + + if (log.isDebugEnabled()) { + log.debug("第{}次批量生成: 需要{}个,生成{}个候选编号", attempt, remainingCount, candidates.size()); + } + + // 批量检查哪些编号已存在 + List existingCodeNos = Collections.emptyList(); + if (!candidates.isEmpty()) { + try { + existingCodeNos = linkTaskMapper.findExistingCodeNos(new ArrayList<>(candidates)); + } catch (Exception e) { + log.warn("批量检查编号唯一性时发生异常: {}", e.getMessage()); + existingCodeNos = new ArrayList<>(candidates); // 异常时保守处理,认为全部重复 + } + } + + Set existingSet = new HashSet<>(existingCodeNos); + + // 过滤出未使用的编号 + List availableCodeNos = candidates.stream() + .filter(code -> !existingSet.contains(code)) + .limit(remainingCount) + .collect(Collectors.toList()); + + uniqueCodeNos.addAll(availableCodeNos); + remainingCount = count - uniqueCodeNos.size(); + + if (log.isDebugEnabled()) { + log.debug("第{}次批量生成结果: 获得{}个唯一编号,还需{}个", + attempt, availableCodeNos.size(), remainingCount); + } + + if (remainingCount <= 0) { + log.info("成功批量生成{}个唯一编号,共尝试{}次", count, attempt); + return uniqueCodeNos; + } + } + + // 如果经过多次重试仍不够,使用时间戳后缀确保唯一性 + log.warn("批量生成后仍缺少{}个编号,使用后备策略生成", remainingCount); + long timestamp = System.currentTimeMillis(); + for (int i = 0; i < remainingCount; i++) { + String fallbackCodeNo = generateRandomCodeNo() + (timestamp + i) % 10000; + uniqueCodeNos.add(fallbackCodeNo.substring(0, Math.min(8, fallbackCodeNo.length()))); + } + + return uniqueCodeNos; + } + + /** + * 生成一批随机编号(使用Set去重) + */ + private Set generateRandomCodeNos(int count) { + Set codeNos = new HashSet<>(count); + // 生成稍多一些以应对Set去重 + int attempts = count * 2; + for (int i = 0; i < attempts && codeNos.size() < count; i++) { + codeNos.add(generateRandomCodeNo()); + } + return codeNos; } /** @@ -175,20 +237,6 @@ public class LinkGenerationService { } return sb.toString(); } - - /** - * 检查编号是否唯一 - */ - private boolean isCodeNoUnique(String codeNo) { - try { - LinkTask existingTask = linkTaskMapper.findByCodeNo(codeNo); - return existingTask == null; - } catch (Exception e) { - log.warn("检查编号{}唯一性时发生异常: {}", codeNo, e.getMessage()); - // 异常时保守处理,认为不唯一 - return false; - } - } private String generateToken() { byte[] bytes = new byte[32]; diff --git a/src/main/resources/mapper/agent/LinkTaskMapper.xml b/src/main/resources/mapper/agent/LinkTaskMapper.xml index 8bc3cb0..23b4e75 100644 --- a/src/main/resources/mapper/agent/LinkTaskMapper.xml +++ b/src/main/resources/mapper/agent/LinkTaskMapper.xml @@ -299,4 +299,22 @@ AND conflict_check.has_conflict IS NULL + + + + + + INSERT INTO link_task (batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, need_refresh, refresh_time, qr_created_at, qr_expire_at, first_region_select_at) + VALUES + + (#{task.batchId}, #{task.agentId}, #{task.codeNo}, #{task.tokenHash}, #{task.expireAt}, #{task.status}, #{task.region}, #{task.machineId}, #{task.loginAt}, #{task.refundAt}, #{task.revokedAt}, #{task.needRefresh}, #{task.refreshTime}, #{task.qrCreatedAt}, #{task.qrExpireAt}, #{task.firstRegionSelectAt}) + + +