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})
+
+
+