feat: 添加批量检查编号存在性和批量插入链接任务的方法,优化链接生成逻辑,更新相关XML映射配置
This commit is contained in:
1
.idea/compiler.xml
generated
1
.idea/compiler.xml
generated
@@ -14,7 +14,6 @@
|
|||||||
<outputRelativeToContentRoot value="true" />
|
<outputRelativeToContentRoot value="true" />
|
||||||
<processorPath useClasspath="false">
|
<processorPath useClasspath="false">
|
||||||
<entry name="$MAVEN_REPOSITORY$/org/projectlombok/lombok/1.18.32/lombok-1.18.32.jar" />
|
<entry name="$MAVEN_REPOSITORY$/org/projectlombok/lombok/1.18.32/lombok-1.18.32.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/org/projectlombok/lombok/1.18.32/lombok-1.18.32.jar" />
|
|
||||||
</processorPath>
|
</processorPath>
|
||||||
<module name="server" />
|
<module name="server" />
|
||||||
</profile>
|
</profile>
|
||||||
|
|||||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -8,7 +8,7 @@
|
|||||||
</list>
|
</list>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="graalvm-jdk-21 (2)" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="graalvm-jdk-21" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@@ -139,4 +139,14 @@ public interface LinkTaskMapper extends BaseMapper<LinkTask> {
|
|||||||
@Param("region") String region,
|
@Param("region") String region,
|
||||||
@Param("deviceId") String deviceId,
|
@Param("deviceId") String deviceId,
|
||||||
@Param("qrExpireSeconds") int qrExpireSeconds);
|
@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")
|
@Schema(description = "每页大小", example = "20")
|
||||||
@Min(value = 1, message = "每页大小必须大于0")
|
@Min(value = 1, message = "每页大小必须大于0")
|
||||||
@Max(value = 100, message = "每页大小不能超过100")
|
@Max(value = 10000, message = "每页大小不能超过10000")
|
||||||
private Integer pageSize = 20;
|
private Integer pageSize = 20;
|
||||||
|
|
||||||
@Schema(description = "链接状态过滤", example = "NEW")
|
@Schema(description = "链接状态过滤", example = "NEW")
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ import reactor.core.scheduler.Schedulers;
|
|||||||
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.List;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class LinkGenerationService {
|
public class LinkGenerationService {
|
||||||
@@ -95,18 +95,24 @@ public class LinkGenerationService {
|
|||||||
linkBatchMapper.insert(batch);
|
linkBatchMapper.insert(batch);
|
||||||
|
|
||||||
// NEW状态的链接不设置过期时间,只有激活使用时才设置过期时间
|
// 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();
|
LinkTask t = new LinkTask();
|
||||||
t.setBatchId(batch.getId());
|
t.setBatchId(batch.getId());
|
||||||
t.setAgentId(operator.getId());
|
t.setAgentId(operator.getId());
|
||||||
t.setCodeNo(generateCodeNo());
|
t.setCodeNo(uniqueCodeNos.get(i));
|
||||||
t.setTokenHash(DigestUtils.sha256Hex(generateToken()));
|
t.setTokenHash(DigestUtils.sha256Hex(generateToken()));
|
||||||
t.setExpireAt(null); // NEW状态不设置过期时间
|
t.setExpireAt(null); // NEW状态不设置过期时间
|
||||||
t.setStatus("NEW");
|
t.setStatus("NEW");
|
||||||
linkTaskMapper.insert(t);
|
|
||||||
tasks.add(t);
|
tasks.add(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量插入
|
||||||
|
linkTaskMapper.batchInsert(tasks);
|
||||||
|
|
||||||
if (!isAdminOperator) {
|
if (!isAdminOperator) {
|
||||||
// 扣点流水 + 账户余额
|
// 扣点流水 + 账户余额
|
||||||
@@ -140,29 +146,85 @@ public class LinkGenerationService {
|
|||||||
|
|
||||||
private static final String CODE_CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; // avoid confusing chars
|
private static final String CODE_CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; // avoid confusing chars
|
||||||
private static final SecureRandom RANDOM = new SecureRandom();
|
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() {
|
private List<String> generateUniqueCodeNos(int count) {
|
||||||
for (int attempt = 1; attempt <= MAX_RETRY_ATTEMPTS; attempt++) {
|
if (count <= 0) {
|
||||||
String codeNo = generateRandomCodeNo();
|
return Collections.emptyList();
|
||||||
|
|
||||||
// 检查是否已存在(利用数据库唯一约束)
|
|
||||||
if (isCodeNoUnique(codeNo)) {
|
|
||||||
log.debug("生成唯一编号成功: {} (第{}次尝试)", codeNo, attempt);
|
|
||||||
return codeNo;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.warn("编号{}已存在,第{}次尝试生成新编号", codeNo, attempt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果多次重试都失败,使用时间戳后缀确保唯一性
|
List<String> uniqueCodeNos = new ArrayList<>(count);
|
||||||
String fallbackCodeNo = generateRandomCodeNo() + System.currentTimeMillis() % 10000;
|
int remainingCount = count;
|
||||||
log.warn("使用后备编号生成策略: {}", fallbackCodeNo);
|
|
||||||
return fallbackCodeNo.substring(0, Math.min(8, fallbackCodeNo.length()));
|
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();
|
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() {
|
private String generateToken() {
|
||||||
byte[] bytes = new byte[32];
|
byte[] bytes = new byte[32];
|
||||||
|
|||||||
@@ -299,4 +299,22 @@
|
|||||||
AND conflict_check.has_conflict IS NULL
|
AND conflict_check.has_conflict IS NULL
|
||||||
</update>
|
</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>
|
</mapper>
|
||||||
|
|||||||
Reference in New Issue
Block a user