From 08013949998673dcc8a85aa17d47224c6b8ccbb4 Mon Sep 17 00:00:00 2001 From: zyh Date: Thu, 28 Aug 2025 12:41:44 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E6=8C=89=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E6=89=B9=E9=87=8F=E5=88=A0=E9=99=A4=E9=93=BE=E6=8E=A5?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主要修改: 1. 在LinkController中新增按状态批量删除链接的接口,允许用户根据指定状态批量删除自己创建的链接。 2. 在LinkStatusService中实现批量删除逻辑,确保用户只能删除自己的链接,并进行状态验证。 3. 更新LinkTaskMapper和对应的XML文件,增加查询和删除链接任务的相关方法。 技术细节: - 通过新增的批量删除功能,提升了用户对链接的管理能力,确保操作的安全性和有效性,同时优化了数据库操作的灵活性。 --- docs/按狀態批量刪除鏈接接口說明.md | 140 ++++++++++++++++++ .../controller/link/LinkController.java | 53 +++++++ .../server/mapper/agent/LinkTaskMapper.java | 15 ++ .../dto/link/BatchDeleteByStatusRequest.java | 48 ++++++ .../service/link/LinkStatusService.java | 87 +++++++++++ .../resources/mapper/agent/LinkTaskMapper.xml | 30 ++++ test_batch_delete_by_status.http | 82 ++++++++++ 7 files changed, 455 insertions(+) create mode 100644 docs/按狀態批量刪除鏈接接口說明.md create mode 100644 src/main/java/com/gameplatform/server/model/dto/link/BatchDeleteByStatusRequest.java create mode 100644 test_batch_delete_by_status.http diff --git a/docs/按狀態批量刪除鏈接接口說明.md b/docs/按狀態批量刪除鏈接接口說明.md new file mode 100644 index 0000000..4698b28 --- /dev/null +++ b/docs/按狀態批量刪除鏈接接口說明.md @@ -0,0 +1,140 @@ +# 按狀態批量刪除鏈接接口說明 + +## 功能概述 +新增了一個按鏈接狀態批量刪除鏈接的接口,允許用戶根據指定的狀態列表批量刪除自己的鏈接。 + +## 接口詳情 + +### 請求路徑 +`POST /api/link/batch-delete-by-status` + +### 請求參數 +```json +{ + "statusList": ["EXPIRED", "REFUNDED"], + "confirmDelete": true +} +``` + +#### 參數說明 +- `statusList`: 要刪除的鏈接狀態列表,支持的狀態值: + - `NEW`: 新建 + - `USING`: 使用中 + - `LOGGED_IN`: 已登錄 + - `COMPLETED`: 已完成 + - `REFUNDED`: 已退款 + - `EXPIRED`: 已過期 +- `confirmDelete`: 是否確認刪除操作,必須設置為 `true` 才能執行刪除 + +#### 驗證規則 +- 狀態列表不能為空 +- 單次最多指定10個狀態 +- 必須確認刪除操作(`confirmDelete = true`) +- 只能刪除用戶自己創建的鏈接 + +### 響應格式 +```json +{ + "successCount": 15, + "failedCount": 0, + "totalCount": 15, + "successCodeNos": ["ABC123", "DEF456", "..."], + "failedCodeNos": [], + "failedReasons": [], + "allSuccess": true +} +``` + +#### 響應字段說明 +- `successCount`: 成功刪除的數量 +- `failedCount`: 刪除失敗的數量 +- `totalCount`: 總數量 +- `successCodeNos`: 成功刪除的鏈接編號列表 +- `failedCodeNos`: 刪除失敗的鏈接編號列表 +- `failedReasons`: 刪除失敗的原因列表 +- `allSuccess`: 是否全部成功 + +## 實現細節 + +### 新增文件 +1. `BatchDeleteByStatusRequest.java` - 請求DTO +2. `test_batch_delete_by_status.http` - 接口測試文件 +3. `docs/按狀態批量刪除鏈接接口說明.md` - 接口說明文檔 + +### 修改文件 +1. `LinkTaskMapper.java` - 添加數據庫查詢方法 +2. `LinkTaskMapper.xml` - 添加SQL映射 +3. `LinkStatusService.java` - 添加業務邏輯方法 +4. `LinkController.java` - 添加控制器接口 + +### 數據庫操作 +- `findByStatusListAndAgentId`: 查詢指定狀態和用戶的鏈接列表 +- `countByStatusListAndAgentId`: 統計指定狀態和用戶的鏈接數量 +- `batchDeleteByStatusListAndAgentId`: 批量刪除指定狀態和用戶的鏈接 + +## 安全考慮 + +### 權限控制 +- 用戶只能刪除自己創建的鏈接 +- 通過JWT token進行用戶身份驗證 +- 通過 `agentId` 過濾確保數據隔離 + +### 操作確認 +- 必須設置 `confirmDelete = true` 才能執行刪除操作 +- 避免誤操作導致的數據丟失 + +### 參數驗證 +- 驗證狀態值的有效性 +- 限制單次操作的狀態數量(最多10個) +- 參數非空驗證 + +## 使用示例 + +### 1. 刪除過期鏈接 +```http +POST /api/link/batch-delete-by-status +Authorization: Bearer {token} +Content-Type: application/json + +{ + "statusList": ["EXPIRED"], + "confirmDelete": true +} +``` + +### 2. 清理已退款和已過期的鏈接 +```http +POST /api/link/batch-delete-by-status +Authorization: Bearer {token} +Content-Type: application/json + +{ + "statusList": ["EXPIRED", "REFUNDED"], + "confirmDelete": true +} +``` + +## 錯誤處理 + +### 常見錯誤 +- `用戶未認證` - JWT token無效或過期 +- `必須確認刪除操作` - confirmDelete不為true +- `要删除的状态列表不能为空` - statusList為空 +- `无效的状态值` - 狀態值不在允許範圍內 +- `单次最多只能指定10个状态` - 狀態數量超過限制 + +### 響應狀態碼 +- `200` - 操作成功 +- `400` - 請求參數錯誤 +- `401` - 用戶未認證 +- `500` - 服務器內部錯誤 + +## 日誌記錄 +系統會記錄以下關鍵信息: +- 刪除操作的發起用戶 +- 指定的狀態列表 +- 實際刪除的鏈接數量 +- 操作結果(成功/失敗) +- 失敗原因(如有) + +這些日誌有助於問題排查和操作審計。 diff --git a/src/main/java/com/gameplatform/server/controller/link/LinkController.java b/src/main/java/com/gameplatform/server/controller/link/LinkController.java index a062411..6a62dd3 100644 --- a/src/main/java/com/gameplatform/server/controller/link/LinkController.java +++ b/src/main/java/com/gameplatform/server/controller/link/LinkController.java @@ -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 deleteLink(@PathVariable("codeNo") String codeNo, Authentic }); } + @PostMapping("/batch-delete-by-status") + @Operation(summary = "按状态批量删除链接", description = "根据指定的状态批量删除链接,用户只能删除自己创建的链接,支持删除的状态:NEW、USING、LOGGED_IN、COMPLETED、REFUNDED、EXPIRED") + public Mono 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 batchDeleteLinks(@RequestBody BatchDeleteRequest request, Authentication authentication) { 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 e15daed..1822b91 100644 --- a/src/main/java/com/gameplatform/server/mapper/agent/LinkTaskMapper.java +++ b/src/main/java/com/gameplatform/server/mapper/agent/LinkTaskMapper.java @@ -95,4 +95,19 @@ public interface LinkTaskMapper extends BaseMapper { * 根据设备ID和状态查询链接任务 */ List findByMachineIdAndStatus(@Param("machineId") String machineId, @Param("status") String status); + + /** + * 根据状态列表和代理ID查询链接任务(用于验证权限和获取要删除的链接) + */ + List findByStatusListAndAgentId(@Param("statusList") List statusList, @Param("agentId") Long agentId); + + /** + * 根据状态列表和代理ID统计链接任务数量 + */ + long countByStatusListAndAgentId(@Param("statusList") List statusList, @Param("agentId") Long agentId); + + /** + * 根据状态列表和代理ID批量删除链接任务 + */ + int batchDeleteByStatusListAndAgentId(@Param("statusList") List statusList, @Param("agentId") Long agentId); } diff --git a/src/main/java/com/gameplatform/server/model/dto/link/BatchDeleteByStatusRequest.java b/src/main/java/com/gameplatform/server/model/dto/link/BatchDeleteByStatusRequest.java new file mode 100644 index 0000000..22bd48e --- /dev/null +++ b/src/main/java/com/gameplatform/server/model/dto/link/BatchDeleteByStatusRequest.java @@ -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 statusList; + + @Schema(description = "是否確認刪除,必須設置為true才能執行刪除操作", example = "true", required = true) + private Boolean confirmDelete = false; + + public BatchDeleteByStatusRequest() {} + + public BatchDeleteByStatusRequest(List statusList, Boolean confirmDelete) { + this.statusList = statusList; + this.confirmDelete = confirmDelete; + } + + public List getStatusList() { + return statusList; + } + + public void setStatusList(List statusList) { + this.statusList = statusList; + } + + public Boolean getConfirmDelete() { + return confirmDelete; + } + + public void setConfirmDelete(Boolean confirmDelete) { + this.confirmDelete = confirmDelete; + } +} diff --git a/src/main/java/com/gameplatform/server/service/link/LinkStatusService.java b/src/main/java/com/gameplatform/server/service/link/LinkStatusService.java index 53d7aef..ed5ef74 100644 --- a/src/main/java/com/gameplatform/server/service/link/LinkStatusService.java +++ b/src/main/java/com/gameplatform/server/service/link/LinkStatusService.java @@ -266,6 +266,93 @@ public class LinkStatusService { }).subscribeOn(Schedulers.boundedElastic()); } + /** + * 按状态批量删除链接(确保用户只能删除自己的链接) + */ + @Transactional(rollbackFor = Exception.class) + public Mono batchDeleteLinksByStatus(List 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 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 linksToDelete = linkTaskMapper.findByStatusListAndAgentId(statusList, agentId); + List codeNosToDelete = linksToDelete.stream() + .map(LinkTask::getCodeNo) + .collect(Collectors.toList()); + + log.info("即将删除的链接: {}", codeNosToDelete); + + // 执行批量删除 + int deleteCount = linkTaskMapper.batchDeleteByStatusListAndAgentId(statusList, agentId); + log.info("批量删除执行结果: 预期删除数量={}, 实际删除数量={}", totalCount, deleteCount); + + // 构建响应 + List successCodeNos = new ArrayList<>(); + List failedCodeNos = new ArrayList<>(); + List 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()); + } + /** * 批量删除链接(确保用户只能删除自己的链接) */ diff --git a/src/main/resources/mapper/agent/LinkTaskMapper.xml b/src/main/resources/mapper/agent/LinkTaskMapper.xml index 07539bc..9cbe841 100644 --- a/src/main/resources/mapper/agent/LinkTaskMapper.xml +++ b/src/main/resources/mapper/agent/LinkTaskMapper.xml @@ -218,4 +218,34 @@ FROM link_task WHERE machine_id = #{machineId} AND status = #{status} + + + + + + + DELETE FROM link_task + WHERE agent_id = #{agentId} + AND status IN + + #{status} + + diff --git a/test_batch_delete_by_status.http b/test_batch_delete_by_status.http new file mode 100644 index 0000000..050b777 --- /dev/null +++ b/test_batch_delete_by_status.http @@ -0,0 +1,82 @@ +### 按狀態批量刪除鏈接接口測試 + +# 需要先獲取JWT token +POST http://localhost:8080/api/auth/login +Content-Type: application/json + +{ + "username": "admin", + "password": "admin" +} + +### 使用獲取到的token進行按狀態批量刪除 + +# 1. 刪除已過期的鏈接 +POST http://localhost:8080/api/link/batch-delete-by-status +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "statusList": ["EXPIRED"], + "confirmDelete": true +} + +### 2. 刪除已退款的鏈接 +POST http://localhost:8080/api/link/batch-delete-by-status +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "statusList": ["REFUNDED"], + "confirmDelete": true +} + +### 3. 同時刪除多種狀態的鏈接(已過期和已退款) +POST http://localhost:8080/api/link/batch-delete-by-status +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "statusList": ["EXPIRED", "REFUNDED"], + "confirmDelete": true +} + +### 4. 測試錯誤情況:未確認刪除操作 +POST http://localhost:8080/api/link/batch-delete-by-status +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "statusList": ["EXPIRED"], + "confirmDelete": false +} + +### 5. 測試錯誤情況:無效的狀態值 +POST http://localhost:8080/api/link/batch-delete-by-status +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "statusList": ["INVALID_STATUS"], + "confirmDelete": true +} + +### 6. 測試錯誤情況:空狀態列表 +POST http://localhost:8080/api/link/batch-delete-by-status +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "statusList": [], + "confirmDelete": true +} + +### 7. 刪除新建狀態的鏈接(謹慎使用) +POST http://localhost:8080/api/link/batch-delete-by-status +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "statusList": ["NEW"], + "confirmDelete": true +}