feat: 新增按状态批量删除链接功能

主要修改:
1. 在LinkController中新增按状态批量删除链接的接口,允许用户根据指定状态批量删除自己创建的链接。
2. 在LinkStatusService中实现批量删除逻辑,确保用户只能删除自己的链接,并进行状态验证。
3. 更新LinkTaskMapper和对应的XML文件,增加查询和删除链接任务的相关方法。

技术细节:
- 通过新增的批量删除功能,提升了用户对链接的管理能力,确保操作的安全性和有效性,同时优化了数据库操作的灵活性。
This commit is contained in:
zyh
2025-08-28 12:41:44 +08:00
parent 080c55059a
commit 0801394999
7 changed files with 455 additions and 0 deletions

View File

@@ -1,5 +1,6 @@
package com.gameplatform.server.controller.link;
import com.gameplatform.server.model.dto.link.BatchDeleteByStatusRequest;
import com.gameplatform.server.model.dto.link.BatchDeleteRequest;
import com.gameplatform.server.model.dto.link.BatchDeleteResponse;
import com.gameplatform.server.model.dto.link.LinkGenerateRequest;
@@ -296,6 +297,58 @@ public Mono<Boolean> deleteLink(@PathVariable("codeNo") String codeNo, Authentic
});
}
@PostMapping("/batch-delete-by-status")
@Operation(summary = "按状态批量删除链接", description = "根据指定的状态批量删除链接用户只能删除自己创建的链接支持删除的状态NEW、USING、LOGGED_IN、COMPLETED、REFUNDED、EXPIRED")
public Mono<BatchDeleteResponse> batchDeleteLinksByStatus(@Valid @RequestBody BatchDeleteByStatusRequest request, Authentication authentication) {
log.info("=== 开始按状态批量删除链接 ===");
log.info("要删除的状态列表: {}", request.getStatusList());
log.info("是否确认删除: {}", request.getConfirmDelete());
if (authentication == null) {
log.error("=== 认证失败Authentication为空 ===");
return Mono.error(new IllegalArgumentException("用户未认证Authentication为空"));
}
// 检查是否确认删除
if (!Boolean.TRUE.equals(request.getConfirmDelete())) {
log.error("=== 未确认删除操作 ===");
return Mono.error(new IllegalArgumentException("必须确认删除操作请设置confirmDelete为true"));
}
// 获取用户ID
Claims claims = (Claims) authentication.getDetails();
if (claims == null) {
log.error("=== 认证失败Claims为空 ===");
log.error("Authentication details: {}", authentication.getDetails());
return Mono.error(new IllegalArgumentException("用户未认证Claims为空"));
}
Long agentId = claims.get("userId", Long.class);
String userType = claims.get("userType", String.class);
log.info("用户信息: agentId={}, userType={}", agentId, userType);
if (agentId == null) {
log.error("=== 无法获取用户ID ===");
return Mono.error(new IllegalArgumentException("无法获取用户ID"));
}
return linkStatusService.batchDeleteLinksByStatus(request.getStatusList(), agentId)
.doOnSuccess(response -> {
log.info("按状态批量删除链接完成: 总数={}, 成功={}, 失败={}, agentId={}",
response.getTotalCount(), response.getSuccessCount(),
response.getFailedCount(), agentId);
if (!response.isAllSuccess()) {
log.warn("部分链接删除失败: 失败的链接={}, 失败原因={}",
response.getFailedCodeNos(), response.getFailedReasons());
}
})
.doOnError(error -> {
log.error("按状态批量删除链接时发生错误: agentId={}, statusList={}, error={}",
agentId, request.getStatusList(), error.getMessage(), error);
});
}
@PostMapping("/batch-delete")
@Operation(summary = "批量删除链接", description = "批量删除指定的链接用户只能删除自己创建的链接最多一次删除100个")
public Mono<BatchDeleteResponse> batchDeleteLinks(@RequestBody BatchDeleteRequest request, Authentication authentication) {

View File

@@ -95,4 +95,19 @@ public interface LinkTaskMapper extends BaseMapper<LinkTask> {
* 根据设备ID和状态查询链接任务
*/
List<LinkTask> findByMachineIdAndStatus(@Param("machineId") String machineId, @Param("status") String status);
/**
* 根据状态列表和代理ID查询链接任务用于验证权限和获取要删除的链接
*/
List<LinkTask> findByStatusListAndAgentId(@Param("statusList") List<String> statusList, @Param("agentId") Long agentId);
/**
* 根据状态列表和代理ID统计链接任务数量
*/
long countByStatusListAndAgentId(@Param("statusList") List<String> statusList, @Param("agentId") Long agentId);
/**
* 根据状态列表和代理ID批量删除链接任务
*/
int batchDeleteByStatusListAndAgentId(@Param("statusList") List<String> statusList, @Param("agentId") Long agentId);
}

View File

@@ -0,0 +1,48 @@
package com.gameplatform.server.model.dto.link;
import io.swagger.v3.oas.annotations.media.Schema;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;
import java.util.List;
/**
* 按狀態批量刪除鏈接請求DTO
*
* @author GamePlatform
*/
@Schema(description = "按狀態批量刪除鏈接請求")
public class BatchDeleteByStatusRequest {
@NotEmpty(message = "要刪除的狀態列表不能為空")
@Size(max = 10, message = "單次最多只能指定10個狀態")
@Schema(description = "要刪除的鏈接狀態列表可選值NEW、USING、LOGGED_IN、COMPLETED、REFUNDED、EXPIRED",
example = "[\"EXPIRED\", \"REFUNDED\"]", required = true)
private List<String> statusList;
@Schema(description = "是否確認刪除必須設置為true才能執行刪除操作", example = "true", required = true)
private Boolean confirmDelete = false;
public BatchDeleteByStatusRequest() {}
public BatchDeleteByStatusRequest(List<String> statusList, Boolean confirmDelete) {
this.statusList = statusList;
this.confirmDelete = confirmDelete;
}
public List<String> getStatusList() {
return statusList;
}
public void setStatusList(List<String> statusList) {
this.statusList = statusList;
}
public Boolean getConfirmDelete() {
return confirmDelete;
}
public void setConfirmDelete(Boolean confirmDelete) {
this.confirmDelete = confirmDelete;
}
}

View File

@@ -266,6 +266,93 @@ public class LinkStatusService {
}).subscribeOn(Schedulers.boundedElastic());
}
/**
* 按状态批量删除链接(确保用户只能删除自己的链接)
*/
@Transactional(rollbackFor = Exception.class)
public Mono<BatchDeleteResponse> batchDeleteLinksByStatus(List<String> statusList, Long agentId) {
return Mono.fromCallable(() -> {
log.info("开始按状态批量删除链接: statusList={}, agentId={}", statusList, agentId);
if (statusList == null || statusList.isEmpty()) {
throw new IllegalArgumentException("要删除的状态列表不能为空");
}
if (statusList.size() > 10) {
throw new IllegalArgumentException("单次最多只能指定10个状态");
}
// 验证状态值是否有效
List<String> validStatuses = List.of("NEW", "USING", "LOGGED_IN", "COMPLETED", "REFUNDED", "EXPIRED");
for (String status : statusList) {
if (!validStatuses.contains(status)) {
throw new IllegalArgumentException("无效的状态值: " + status);
}
}
// 统计要删除的链接数量
long totalCount = linkTaskMapper.countByStatusListAndAgentId(statusList, agentId);
log.info("用户拥有的指定状态链接数量: {}", totalCount);
if (totalCount == 0) {
// 没有符合条件的链接
BatchDeleteResponse response = new BatchDeleteResponse(0, 0, 0,
List.of(), List.of(), List.of());
log.info("没有找到符合条件的链接");
return response;
}
// 查询要删除的链接详情(用于记录日志)
List<LinkTask> linksToDelete = linkTaskMapper.findByStatusListAndAgentId(statusList, agentId);
List<String> codeNosToDelete = linksToDelete.stream()
.map(LinkTask::getCodeNo)
.collect(Collectors.toList());
log.info("即将删除的链接: {}", codeNosToDelete);
// 执行批量删除
int deleteCount = linkTaskMapper.batchDeleteByStatusListAndAgentId(statusList, agentId);
log.info("批量删除执行结果: 预期删除数量={}, 实际删除数量={}", totalCount, deleteCount);
// 构建响应
List<String> successCodeNos = new ArrayList<>();
List<String> failedCodeNos = new ArrayList<>();
List<String> failedReasons = new ArrayList<>();
if (deleteCount == totalCount) {
// 全部删除成功
successCodeNos.addAll(codeNosToDelete);
} else if (deleteCount > 0) {
// 部分删除成功(这种情况比较少见,可能是并发操作导致)
successCodeNos.addAll(codeNosToDelete.subList(0, deleteCount));
failedCodeNos.addAll(codeNosToDelete.subList(deleteCount, codeNosToDelete.size()));
for (int i = deleteCount; i < codeNosToDelete.size(); i++) {
failedReasons.add("删除操作部分失败");
}
} else {
// 全部删除失败
failedCodeNos.addAll(codeNosToDelete);
for (int i = 0; i < codeNosToDelete.size(); i++) {
failedReasons.add("删除操作失败");
}
}
BatchDeleteResponse response = new BatchDeleteResponse(
successCodeNos.size(),
failedCodeNos.size(),
codeNosToDelete.size(),
successCodeNos,
failedCodeNos,
failedReasons
);
log.info("按状态批量删除完成: 总数={}, 成功={}, 失败={}",
response.getTotalCount(), response.getSuccessCount(), response.getFailedCount());
return response;
}).subscribeOn(Schedulers.boundedElastic());
}
/**
* 批量删除链接(确保用户只能删除自己的链接)
*/

View File

@@ -218,4 +218,34 @@
FROM link_task
WHERE machine_id = #{machineId} AND status = #{status}
</select>
<select id="findByStatusListAndAgentId" resultMap="LinkTaskMap">
SELECT id, batch_id, agent_id, code_no, token_hash, expire_at, status, region, machine_id, login_at, refund_at, revoked_at, created_at, updated_at, need_refresh, refresh_time, qr_created_at, qr_expire_at, first_region_select_at, completed_points, completion_images
FROM link_task
WHERE agent_id = #{agentId}
AND status IN
<foreach collection="statusList" item="status" open="(" close=")" separator=",">
#{status}
</foreach>
ORDER BY created_at DESC
</select>
<select id="countByStatusListAndAgentId" resultType="long">
SELECT COUNT(1)
FROM link_task
WHERE agent_id = #{agentId}
AND status IN
<foreach collection="statusList" item="status" open="(" close=")" separator=",">
#{status}
</foreach>
</select>
<delete id="batchDeleteByStatusListAndAgentId">
DELETE FROM link_task
WHERE agent_id = #{agentId}
AND status IN
<foreach collection="statusList" item="status" open="(" close=")" separator=",">
#{status}
</foreach>
</delete>
</mapper>