diff --git a/.gitignore b/.gitignore index a80d2f7..4485b06 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,5 @@ Thumbs.db # Temporary files *.tmp *.temp + + 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 643e4c2..6901d04 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,7 @@ package com.gameplatform.server.controller.link; +import com.gameplatform.server.model.dto.link.BatchDeleteRequest; +import com.gameplatform.server.model.dto.link.BatchDeleteResponse; import com.gameplatform.server.model.dto.link.LinkGenerateRequest; import com.gameplatform.server.model.dto.link.LinkGenerateResponse; import com.gameplatform.server.model.dto.link.LinkListRequest; @@ -14,12 +16,9 @@ import io.swagger.v3.oas.annotations.tags.Tag; import javax.validation.Valid; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.ReactiveSecurityContextHolder; import org.springframework.web.bind.annotation.*; -import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @RestController @@ -56,7 +55,13 @@ public class LinkController { log.info("认证用户: {}", authentication.getName()); // 获取用户ID - Claims claims = (Claims) authentication.getCredentials(); + 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); @@ -189,6 +194,95 @@ public class LinkController { log.debug("检查链接是否有效: codeNo={}", codeNo); return linkStatusService.isLinkValid(codeNo); } + + @DeleteMapping("/{codeNo}") + @Operation(summary = "删除链接", description = "删除指定的链接,用户只能删除自己创建的链接") + public Mono deleteLink(@PathVariable("codeNo") String codeNo, Authentication authentication) { + log.info("=== 开始删除链接 ==="); + log.info("链接编号: {}", codeNo); + + if (authentication == null) { + log.error("=== 认证失败:Authentication为空 ==="); + return Mono.error(new IllegalArgumentException("用户未认证:Authentication为空")); + } + + // 获取用户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.deleteLink(codeNo, agentId) + .doOnSuccess(success -> { + if (success) { + log.info("链接删除成功: codeNo={}, agentId={}", codeNo, agentId); + } else { + log.warn("链接删除失败: codeNo={}, agentId={}", codeNo, agentId); + } + }) + .doOnError(error -> { + log.error("删除链接时发生错误: codeNo={}, agentId={}, error={}", + codeNo, agentId, error.getMessage(), error); + }); + } + + @PostMapping("/batch-delete") + @Operation(summary = "批量删除链接", description = "批量删除指定的链接,用户只能删除自己创建的链接,最多一次删除100个") + public Mono batchDeleteLinks(@RequestBody BatchDeleteRequest request, Authentication authentication) { + log.info("=== 开始批量删除链接 ==="); + log.info("要删除的链接数量: {}", request.getCodeNos().size()); + log.info("链接编号列表: {}", request.getCodeNos()); + + if (authentication == null) { + log.error("=== 认证失败:Authentication为空 ==="); + return Mono.error(new IllegalArgumentException("用户未认证:Authentication为空")); + } + + // 获取用户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.batchDeleteLinks(request.getCodeNos(), 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={}, codeNos={}, error={}", + agentId, request.getCodeNos(), error.getMessage(), error); + }); + } } 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 c460299..71f1524 100644 --- a/src/main/java/com/gameplatform/server/mapper/agent/LinkTaskMapper.java +++ b/src/main/java/com/gameplatform/server/mapper/agent/LinkTaskMapper.java @@ -70,4 +70,24 @@ public interface LinkTaskMapper extends BaseMapper { @Param("status") String status, @Param("batchId") Long batchId, @Param("isExpired") Boolean isExpired); + + /** + * 根据链接编号删除链接任务 + */ + int deleteByCodeNo(@Param("codeNo") String codeNo); + + /** + * 根据链接编号和代理ID删除链接任务(确保用户只能删除自己的链接) + */ + int deleteByCodeNoAndAgentId(@Param("codeNo") String codeNo, @Param("agentId") Long agentId); + + /** + * 批量删除链接任务(根据链接编号列表和代理ID) + */ + int batchDeleteByCodeNosAndAgentId(@Param("codeNos") List codeNos, @Param("agentId") Long agentId); + + /** + * 根据链接编号列表和代理ID查询链接任务(用于验证权限) + */ + List findByCodeNosAndAgentId(@Param("codeNos") List codeNos, @Param("agentId") Long agentId); } diff --git a/src/main/java/com/gameplatform/server/model/dto/link/BatchDeleteRequest.java b/src/main/java/com/gameplatform/server/model/dto/link/BatchDeleteRequest.java new file mode 100644 index 0000000..11621e4 --- /dev/null +++ b/src/main/java/com/gameplatform/server/model/dto/link/BatchDeleteRequest.java @@ -0,0 +1,35 @@ +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 BatchDeleteRequest { + + @NotEmpty(message = "删除的链接编号列表不能为空") + @Size(max = 100, message = "单次最多只能删除100个链接") + @Schema(description = "要删除的链接编号列表", example = "[\"EPRGF7ZJ\", \"XKLD9F2M\", \"QWER5TYU\"]", required = true) + private List codeNos; + + public BatchDeleteRequest() {} + + public BatchDeleteRequest(List codeNos) { + this.codeNos = codeNos; + } + + public List getCodeNos() { + return codeNos; + } + + public void setCodeNos(List codeNos) { + this.codeNos = codeNos; + } +} diff --git a/src/main/java/com/gameplatform/server/model/dto/link/BatchDeleteResponse.java b/src/main/java/com/gameplatform/server/model/dto/link/BatchDeleteResponse.java new file mode 100644 index 0000000..cfe263b --- /dev/null +++ b/src/main/java/com/gameplatform/server/model/dto/link/BatchDeleteResponse.java @@ -0,0 +1,106 @@ +package com.gameplatform.server.model.dto.link; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.List; + +/** + * 批量删除链接响应DTO + * + * @author GamePlatform + */ +@Schema(description = "批量删除链接响应") +public class BatchDeleteResponse { + + @Schema(description = "成功删除的数量", example = "3") + private int successCount; + + @Schema(description = "删除失败的数量", example = "1") + private int failedCount; + + @Schema(description = "总数量", example = "4") + private int totalCount; + + @Schema(description = "成功删除的链接编号列表", example = "[\"EPRGF7ZJ\", \"XKLD9F2M\"]") + private List successCodeNos; + + @Schema(description = "删除失败的链接编号列表", example = "[\"QWER5TYU\"]") + private List failedCodeNos; + + @Schema(description = "删除失败的原因列表", example = "[\"链接不存在或无权删除\"]") + private List failedReasons; + + @Schema(description = "是否全部成功", example = "false") + private boolean allSuccess; + + public BatchDeleteResponse() {} + + public BatchDeleteResponse(int successCount, int failedCount, int totalCount, + List successCodeNos, List failedCodeNos, + List failedReasons) { + this.successCount = successCount; + this.failedCount = failedCount; + this.totalCount = totalCount; + this.successCodeNos = successCodeNos; + this.failedCodeNos = failedCodeNos; + this.failedReasons = failedReasons; + this.allSuccess = failedCount == 0; + } + + // Getters and Setters + public int getSuccessCount() { + return successCount; + } + + public void setSuccessCount(int successCount) { + this.successCount = successCount; + } + + public int getFailedCount() { + return failedCount; + } + + public void setFailedCount(int failedCount) { + this.failedCount = failedCount; + } + + public int getTotalCount() { + return totalCount; + } + + public void setTotalCount(int totalCount) { + this.totalCount = totalCount; + } + + public List getSuccessCodeNos() { + return successCodeNos; + } + + public void setSuccessCodeNos(List successCodeNos) { + this.successCodeNos = successCodeNos; + } + + public List getFailedCodeNos() { + return failedCodeNos; + } + + public void setFailedCodeNos(List failedCodeNos) { + this.failedCodeNos = failedCodeNos; + } + + public List getFailedReasons() { + return failedReasons; + } + + public void setFailedReasons(List failedReasons) { + this.failedReasons = failedReasons; + } + + public boolean isAllSuccess() { + return allSuccess; + } + + public void setAllSuccess(boolean allSuccess) { + this.allSuccess = allSuccess; + } +} 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 d17d200..d8eb7fe 100644 --- a/src/main/java/com/gameplatform/server/service/link/LinkStatusService.java +++ b/src/main/java/com/gameplatform/server/service/link/LinkStatusService.java @@ -2,19 +2,24 @@ package com.gameplatform.server.service.link; import com.gameplatform.server.mapper.agent.LinkBatchMapper; import com.gameplatform.server.mapper.agent.LinkTaskMapper; +import com.gameplatform.server.model.dto.link.BatchDeleteResponse; import com.gameplatform.server.model.dto.link.LinkStatusResponse; import com.gameplatform.server.model.entity.agent.LinkBatch; import com.gameplatform.server.model.entity.agent.LinkTask; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.stream.Collectors; @Service public class LinkStatusService { @@ -121,4 +126,114 @@ public class LinkStatusService { ("NEW".equals(response.getStatus()) || "USING".equals(response.getStatus()))) .onErrorReturn(false); } + + /** + * 删除链接(确保用户只能删除自己的链接) + */ + public Mono deleteLink(String codeNo, Long agentId) { + return Mono.fromCallable(() -> { + log.info("开始删除链接: codeNo={}, agentId={}", codeNo, agentId); + + // 首先检查链接是否存在且属于该用户 + LinkTask linkTask = linkTaskMapper.findByCodeNo(codeNo); + if (linkTask == null) { + log.warn("链接不存在: codeNo={}", codeNo); + throw new IllegalArgumentException("链接不存在"); + } + + if (!linkTask.getAgentId().equals(agentId)) { + log.warn("用户无权删除该链接: codeNo={}, linkOwner={}, requestUser={}", + codeNo, linkTask.getAgentId(), agentId); + throw new IllegalArgumentException("无权删除该链接"); + } + + // 执行删除 + int deleteCount = linkTaskMapper.deleteByCodeNoAndAgentId(codeNo, agentId); + boolean success = deleteCount > 0; + + if (success) { + log.info("链接删除成功: codeNo={}, agentId={}", codeNo, agentId); + } else { + log.warn("链接删除失败: codeNo={}, agentId={}", codeNo, agentId); + } + + return success; + }).subscribeOn(Schedulers.boundedElastic()); + } + + /** + * 批量删除链接(确保用户只能删除自己的链接) + */ + @Transactional(rollbackFor = Exception.class) + public Mono batchDeleteLinks(List codeNos, Long agentId) { + return Mono.fromCallable(() -> { + log.info("开始批量删除链接: codeNos={}, agentId={}, count={}", codeNos, agentId, codeNos.size()); + + if (codeNos == null || codeNos.isEmpty()) { + throw new IllegalArgumentException("要删除的链接编号列表不能为空"); + } + + if (codeNos.size() > 100) { + throw new IllegalArgumentException("单次最多只能删除100个链接"); + } + + // 查询用户拥有的链接 + List userLinks = linkTaskMapper.findByCodeNosAndAgentId(codeNos, agentId); + List userCodeNos = userLinks.stream() + .map(LinkTask::getCodeNo) + .collect(Collectors.toList()); + + log.info("用户拥有的链接数量: {}, 链接编号: {}", userCodeNos.size(), userCodeNos); + + // 准备响应数据 + List successCodeNos = new ArrayList<>(); + List failedCodeNos = new ArrayList<>(); + List failedReasons = new ArrayList<>(); + + // 检查每个链接的权限 + for (String codeNo : codeNos) { + if (!userCodeNos.contains(codeNo)) { + failedCodeNos.add(codeNo); + failedReasons.add("链接不存在或无权删除"); + log.warn("用户无权删除链接: codeNo={}, agentId={}", codeNo, agentId); + } + } + + // 执行批量删除(只删除用户拥有的链接) + int deleteCount = 0; + if (!userCodeNos.isEmpty()) { + deleteCount = linkTaskMapper.batchDeleteByCodeNosAndAgentId(userCodeNos, agentId); + log.info("批量删除执行结果: 预期删除数量={}, 实际删除数量={}", userCodeNos.size(), deleteCount); + + // 更新成功列表 + if (deleteCount > 0) { + // 由于批量删除可能部分成功,我们按实际删除数量来处理 + successCodeNos.addAll(userCodeNos); + log.info("批量删除成功的链接: {}", successCodeNos); + } else { + // 如果删除失败,将所有用户拥有的链接标记为失败 + failedCodeNos.addAll(userCodeNos); + for (int i = 0; i < userCodeNos.size(); i++) { + failedReasons.add("删除操作失败"); + } + log.warn("批量删除失败: agentId={}, codeNos={}", agentId, userCodeNos); + } + } + + // 构建响应 + BatchDeleteResponse response = new BatchDeleteResponse( + successCodeNos.size(), + failedCodeNos.size(), + codeNos.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 9c9e3eb..cf27840 100644 --- a/src/main/resources/mapper/agent/LinkTaskMapper.xml +++ b/src/main/resources/mapper/agent/LinkTaskMapper.xml @@ -170,4 +170,31 @@ + + + DELETE FROM link_task WHERE code_no = #{codeNo} + + + + DELETE FROM link_task WHERE code_no = #{codeNo} AND agent_id = #{agentId} + + + + DELETE FROM link_task + WHERE agent_id = #{agentId} + AND code_no IN + + #{codeNo} + + + +