feat: 添加批量检查编号存在性和批量插入链接任务的方法,优化链接生成逻辑,更新相关XML映射配置
This commit is contained in:
@@ -139,4 +139,14 @@ public interface LinkTaskMapper extends BaseMapper<LinkTask> {
|
||||
@Param("region") String region,
|
||||
@Param("deviceId") String deviceId,
|
||||
@Param("qrExpireSeconds") int qrExpireSeconds);
|
||||
|
||||
/**
|
||||
* 批量检查编号是否存在,返回已存在的编号列表
|
||||
*/
|
||||
List<String> findExistingCodeNos(@Param("codeNos") List<String> codeNos);
|
||||
|
||||
/**
|
||||
* 批量插入链接任务
|
||||
*/
|
||||
int batchInsert(@Param("tasks") List<LinkTask> tasks);
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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<LinkTask> tasks = new ArrayList<>();
|
||||
for (int i = 0; i < linkCount; i++) { // 生成linkCount个链接
|
||||
// 批量生成唯一的编号
|
||||
List<String> uniqueCodeNos = generateUniqueCodeNos(linkCount);
|
||||
|
||||
// 批量创建任务对象
|
||||
List<LinkTask> 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<String> 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<String> 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<String> candidates = generateRandomCodeNos(candidateCount);
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("第{}次批量生成: 需要{}个,生成{}个候选编号", attempt, remainingCount, candidates.size());
|
||||
}
|
||||
|
||||
// 批量检查哪些编号已存在
|
||||
List<String> 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<String> existingSet = new HashSet<>(existingCodeNos);
|
||||
|
||||
// 过滤出未使用的编号
|
||||
List<String> 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<String> generateRandomCodeNos(int count) {
|
||||
Set<String> 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];
|
||||
|
||||
@@ -299,4 +299,22 @@
|
||||
AND conflict_check.has_conflict IS NULL
|
||||
</update>
|
||||
|
||||
<!-- 批量检查编号是否存在,返回已存在的编号列表 -->
|
||||
<select id="findExistingCodeNos" resultType="string">
|
||||
SELECT code_no FROM link_task
|
||||
WHERE code_no IN
|
||||
<foreach collection="codeNos" item="codeNo" open="(" close=")" separator=",">
|
||||
#{codeNo}
|
||||
</foreach>
|
||||
</select>
|
||||
|
||||
<!-- 批量插入链接任务 -->
|
||||
<insert id="batchInsert" parameterType="java.util.List">
|
||||
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
|
||||
<foreach collection="tasks" item="task" separator=",">
|
||||
(#{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})
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
</mapper>
|
||||
|
||||
Reference in New Issue
Block a user