Merge branch 'master' of ssh://gerrit.lilingkun.com:29418/echo-backend

Change-Id: I1b49fb8032ecd127e91b5cc5fe4962e8dabfb1fd
diff --git a/src/main/java/com/example/myproject/controller/TorrentController.java b/src/main/java/com/example/myproject/controller/TorrentController.java
index 846e5da..ef62c26 100644
--- a/src/main/java/com/example/myproject/controller/TorrentController.java
+++ b/src/main/java/com/example/myproject/controller/TorrentController.java
@@ -1,323 +1,3 @@
-// package com.example.myproject.controller;
-
-// import com.example.myproject.common.base.PageUtil;
-// import com.example.myproject.entity.TorrentEntity;
-// import com.example.myproject.service.TorrentService;
-// import com.example.myproject.service.PromotionService;
-// import com.example.myproject.dto.param.TorrentParam;
-// import com.example.myproject.dto.vo.TorrentVO;
-// import com.example.myproject.common.base.Result;
-// import com.example.myproject.dto.param.TorrentUploadParam;
-// import com.example.myproject.dto.TorrentUpdateDTO;
-// import com.example.myproject.dto.PromotionCreateDTO;
-// import com.example.myproject.entity.Promotion;
-// import com.example.myproject.service.UserService;
-
-// import io.swagger.v3.oas.annotations.Operation;
-// import io.swagger.v3.oas.annotations.media.Content;
-// import io.swagger.v3.oas.annotations.media.Schema;
-// import io.swagger.v3.oas.annotations.responses.ApiResponse;
-
-// import org.springframework.beans.factory.annotation.Autowired;
-// import org.springframework.http.HttpStatus;
-// import org.springframework.http.ResponseEntity;
-// import org.springframework.validation.annotation.Validated;
-// import org.springframework.web.bind.annotation.*;
-// import org.springframework.web.multipart.MultipartFile;
-
-// import javax.servlet.http.HttpServletRequest;
-// import javax.servlet.http.HttpServletResponse;
-// import java.io.File;
-// import java.io.IOException;
-// import java.util.List;
-
-// import cn.dev33.satoken.annotation.SaCheckLogin;
-// import cn.dev33.satoken.stp.StpUtil;
-
-// @RestController
-// @RequestMapping("/seeds")
-// public class TorrentController {
-
-//     @Autowired
-//     private TorrentService torrentService;
-
-//     @Autowired
-//     private PromotionService promotionService;
-
-//     @Autowired
-//     private UserService userService;
-
-
-//     @SaCheckLogin
-//     @Operation(summary = "种子列表查询", description = "种子列表条件查询-分页-排序")
-//     @ApiResponse(responseCode = "0", description = "操作成功",
-//             content = {@Content(mediaType = "application/json",
-//                     schema = @Schema(implementation = TorrentVO.class))
-//             })
-//     @PostMapping("/list")
-//     public Result list(@RequestBody TorrentParam param) {
-//         // 构建排序和模糊查询条件
-//         param.validOrder(param.getOrderKey(TorrentEntity.class));
-//         param.buildLike();
-
-//         PageUtil.startPage(param);
-
-//         // 查询数据
-//         List<TorrentEntity> list = torrentService.search(param);
-
-//         // 返回分页结果
-//         return Result.ok(list, PageUtil.getPage(list));
-//     }
-
-//     @SaCheckLogin
-//     @Operation(summary = "种子详情查询")
-//     @ApiResponse(responseCode = "0", description = "操作成功", content = {
-//             @Content(mediaType = "application/json", schema = @Schema(implementation =
-//                     TorrentEntity.class))
-//     })
-//     @PostMapping("/info/{id}")
-//     public Result info(@PathVariable("id")Long id) {
-
-//         TorrentEntity entity = torrentService.selectBySeedId(id);
-//         return Result.ok(entity);
-//     }
-
-// @Operation(summary = "上传种子")
-
-//     @PostMapping("/upload")
-//     public Result uploadTorrent(
-//             @RequestParam("file") MultipartFile file,
-//             @RequestParam("coverImage") MultipartFile coverImage,
-//             @ModelAttribute @Validated TorrentUploadParam param,
-//             HttpServletRequest request
-//             ) throws IOException {
-//         try {
-//             // 验证用户权限
-//             // Long userId = StpUtil.getLoginIdAsLong();
-//             String userId = String.valueOf(param.getUploader());
-//             param.setUploader(userId);
-
-//             // 验证文件大小和类型
-//             if (file.isEmpty() || file.getSize() > 10 * 1024 * 1024) { // 10MB限制
-//                 return Result.error("文件大小不符合要求");
-//             }
-
-//             if (!file.getOriginalFilename().toLowerCase().endsWith(".torrent")) {
-//                 return Result.error("只支持.torrent文件");
-//             }
-//             if (!coverImage.isEmpty()) {
-//                 String originalFilename = coverImage.getOriginalFilename();
-//                 String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
-//                 String fileName = System.currentTimeMillis() + suffix;
-
-//                 if (!originalFilename.toLowerCase().matches(".*\\.(jpg|jpeg|png)$")) {
-//                     return Result.error("仅支持 JPG/PNG 格式的封面图片");
-//                 }
-
-
-//                 // 项目根目录下的 /uploads/torrents/
-//                 String uploadDir = System.getProperty("user.dir") + "/uploads/torrents/";
-//                 File dir = new File(uploadDir);
-//                 if (!dir.exists()) dir.mkdirs(); // 自动创建目录
-
-//                 File dest = new File(uploadDir + fileName);
-//                 coverImage.transferTo(dest);
-
-
-//                 String imageUrl = request.getScheme() + "://" + request.getServerName() + ":" +
-//                         request.getServerPort() + "/torrent-images/" + fileName;
-
-
-//                 param.setImageUrl(imageUrl);
-//             }
-
-//             torrentService.uploadTorrent(file, param);
-//             return Result.ok();
-//         } catch (Exception e) {
-//             return Result.error("种子上传失败: " + e.getMessage());
-//         }
-//     }
-
-//     /**
-//      * 获取种子文件
-//      */
-//     @GetMapping("/{seed_id}/download")
-//     public void downloadTorrent(
-//             @PathVariable("seed_id") Long seedId,
-//             @RequestParam("passkey") String passkey,
-//             HttpServletResponse response) throws IOException {
-
-//         // 获取种子实体
-//         TorrentEntity entity = torrentService.selectBySeedId(seedId);
-//         if (entity == null) {
-//             response.sendError(HttpServletResponse.SC_NOT_FOUND, "种子不存在");
-//             return;
-//         }
-
-//         // 获取并处理种子文件内容(需在service中实现passkey注入)
-//         byte[] torrentBytes = torrentService.fetch(seedId, passkey);
-
-//         // 设置下载文件名
-//         String filename = entity.getFileName();
-//         if (filename == null || filename.isBlank()) {
-//             filename = seedId + ".torrent";
-//         }
-//         if (!filename.toLowerCase().endsWith(".torrent")) {
-//             filename = filename + ".torrent";
-//         }
-//         filename = java.net.URLEncoder.encode(filename, java.nio.charset.StandardCharsets.UTF_8).replaceAll("\\+",
-//                 "%20");
-
-//         // 设置响应头
-//         response.setCharacterEncoding(java.nio.charset.StandardCharsets.UTF_8.name());
-//         response.setContentLength(torrentBytes.length);
-//         response.setContentType("application/x-bittorrent");
-//         response.setHeader("Content-Disposition", "attachment;filename=" + filename);
-
-//         // 写入文件内容
-//         response.getOutputStream().write(torrentBytes);
-//         response.getOutputStream().flush();
-//     }
-
-//     /**
-//      * 收藏或者取消收藏
-//      */
-//     @PostMapping("/{seed_id}/favorite-toggle")
-//     public Result favorite(
-//             @PathVariable("seed_id") Long seedId,
-//             @RequestParam("user_id") Long  userId) {
-//         try {
-
-//             return  torrentService.favorite(seedId, userId);
-//         } catch (Exception e) {
-//             return Result.error("失败: ");
-//         }
-//     }
-
-//     @SaCheckLogin
-//     @Operation(summary = "删除种子")
-//     @DeleteMapping("/{torrentId}")
-//     public Result deleteTorrent(@PathVariable Long torrentId) {
-//         try {
-//            //  验证用户权限
-//             Long userId = StpUtil.getLoginIdAsLong();
-//             if (!torrentService.canUserDeleteTorrent(torrentId, userId)) {
-//                 return Result.error("没有权限删除此种子");
-//             }
-
-//             torrentService.deleteTorrent(torrentId);
-//             return Result.ok();
-//         } catch (Exception e) {
-//             return Result.error("删除失败: " + e.getMessage());
-//         }
-//     }
-
-//     @SaCheckLogin
-//     @Operation(summary = "修改种子信息")
-//     @PutMapping("/{torrentId}")
-//     public Result updateTorrent(
-//             @PathVariable Long torrentId,
-//             @RequestBody @Validated TorrentUpdateDTO updateDTO) {
-//         try {
-//             // 验证用户权限
-//             Long userId = StpUtil.getLoginIdAsLong();
-//             if (!torrentService.canUserUpdateTorrent(torrentId, userId)) {
-//                 return Result.error("没有权限修改此种子");
-//             }
-
-//             torrentService.updateTorrent(torrentId, updateDTO);
-//             return Result.ok();
-//         } catch (Exception e) {
-//             return Result.error("更新失败: " + e.getMessage());
-//         }
-//     }
-
-//     @SaCheckLogin
-//     @Operation(summary = "创建促销活动")
-//     @PostMapping("/promotions")
-//     public Result createPromotion(@RequestBody @Validated PromotionCreateDTO promotionDTO) {
-//         try {
-//             // 验证用户权限(只有管理员可以创建促销)
-// //            if (!StpUtil.hasRole("admin")) {
-// //                return Result.error("没有权限创建促销活动");
-// //            }
-// //
-//             Promotion promotion = promotionService.createPromotion(promotionDTO);
-//             return Result.ok(promotion);
-//         } catch (Exception e) {
-//             return Result.error("创建促销失败: " + e.getMessage());
-//         }
-//     }
-
-//     @SaCheckLogin
-//     @Operation(summary = "获取促销活动列表")
-//     @GetMapping("/promotions")
-//     public Result getPromotions() {
-//         try {
-//             List<Promotion> promotions = promotionService.getAllActivePromotions();
-//             return Result.ok(promotions);
-//         } catch (Exception e) {
-//             return Result.error("获取促销列表失败: " + e.getMessage());
-//         }
-//     }
-
-//     @SaCheckLogin
-//     @Operation(summary = "获取促销详情")
-//     @GetMapping("/promotions/{promotionId}")
-//     public Result getPromotionDetails(@PathVariable Long promotionId) {
-//         try {
-//             Promotion promotion = promotionService.getPromotionById(promotionId);
-//             if (promotion == null) {
-//                 return Result.error("促销活动不存在");
-//             }
-//             return Result.ok(promotion);
-//         } catch (Exception e) {
-//             return Result.error("获取促销详情失败: " + e.getMessage());
-//         }
-//     }
-
-//     @SaCheckLogin
-//     @Operation(summary = "删除促销活动")
-//     @DeleteMapping("/promotions/{promotionId}")
-//     public Result deletePromotion(@PathVariable Long promotionId) {
-//         try {
-//             // 验证用户权限(只有管理员可以删除促销)
-//             if (!StpUtil.hasRole("admin")) {
-//                 return Result.error("没有权限删除促销活动");
-//             }
-
-//             promotionService.deletePromotion(promotionId);
-//             return Result.ok();
-//         } catch (Exception e) {
-//             return Result.error("删除促销失败: " + e.getMessage());
-//         }
-//     }
-
-//     // 下载种子(包含反作弊机制)
-//     @PostMapping("/{torrentId}/download")
-//     public ResponseEntity<?> downloadTorrent(@PathVariable Long torrentId,
-//                                            @RequestParam Long userId) {
-// //        // 验证用户身份和权限
-// //        if (!userService.validateUser(userId)) {
-// //            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
-// //        }
-
-//         // 检查用户上传量是否足够
-//         if (!torrentService.checkUserUploadRatio(userId)) {
-//             return ResponseEntity.status(HttpStatus.FORBIDDEN)
-//                 .body("上传量不足,无法下载");
-//         }
-
-//         // 应用促销折扣(如果有)
-//         double downloadSize = torrentService.calculateDownloadSize(torrentId, userId);
-
-//         // 记录下载
-//         torrentService.recordDownload(torrentId, userId, downloadSize);
-
-//         return ResponseEntity.ok().build();
-//     }
-
-// }
 package com.example.myproject.controller;
 
 import cn.hutool.core.util.StrUtil;
@@ -338,6 +18,7 @@
 import com.example.myproject.entity.Promotion;
 
 import com.example.myproject.service.UserService;
+import com.example.myproject.repository.UserRepository;
 
 import com.turn.ttorrent.bcodec.BEValue;
 import com.turn.ttorrent.bcodec.BEncoder;
@@ -365,6 +46,9 @@
 import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
 import java.util.*;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
 
 import cn.dev33.satoken.annotation.SaCheckLogin;
 import cn.dev33.satoken.stp.StpUtil;
@@ -388,6 +72,8 @@
     private UserService userService;
     private final List<BEValue> peers = new Vector<>();
 
+    @Autowired
+    private UserRepository userRepository;
 
     @SaCheckLogin
     @Operation(summary = "种子列表查询", description = "种子列表条件查询-分页-排序")
@@ -430,7 +116,7 @@
             @RequestParam("coverImage") MultipartFile coverImage,
             @ModelAttribute @Validated TorrentUploadParam param,
             HttpServletRequest request
-            ) throws IOException {
+    ) throws IOException {
         try {
             // 验证用户权限
             // Long userId = StpUtil.getLoginIdAsLong();
@@ -541,16 +227,78 @@
     public Result getMyFavorite(
             @RequestParam("user_id") Long  userId) {
         try {
+
             return  torrentService.getMyfavorite(userId);
         } catch (Exception e) {
             return Result.error("失败: ");
         }
     }
 
+    @SaCheckLogin
+    @Operation(summary = "创建促销活动")
+    @PostMapping("/promotions")
+    public Result createPromotion(@RequestBody @Validated PromotionCreateDTO promotionDTO) {
+        try {
+            // 验证用户权限(只有管理员可以创建促销)
+//            if (!StpUtil.hasRole("admin")) {
+//                return Result.error("没有权限创建促销活动");
+//            }
+//
+            Promotion promotion = promotionService.createPromotion(promotionDTO);
+            return Result.ok(promotion);
+        } catch (Exception e) {
+            return Result.error("创建促销失败: " + e.getMessage());
+        }
+    }
 
+    @SaCheckLogin
+    @Operation(summary = "获取促销活动列表")
+    @GetMapping("/promotions")
+    public Result getPromotions() {
+        try {
+            List<Promotion> promotions = promotionService.getAllActivePromotions();
+            return Result.ok(promotions);
+        } catch (Exception e) {
+            return Result.error("获取促销列表失败: " + e.getMessage());
+        }
+    }
+
+    @SaCheckLogin
+    @Operation(summary = "获取促销详情")
+    @GetMapping("/promotions/{promotionId}")
+    public Result getPromotionDetails(@PathVariable Long promotionId) {
+        try {
+            Promotion promotion = promotionService.getPromotionById(promotionId);
+            if (promotion == null) {
+                return Result.error("促销活动不存在");
+            }
+            return Result.ok(promotion);
+        } catch (Exception e) {
+            return Result.error("获取促销详情失败: " + e.getMessage());
+        }
+    }
+
+    @SaCheckLogin
+    @Operation(summary = "删除促销活动")
+    @DeleteMapping("/promotions/{promotionId}")
+    public Result deletePromotion(@PathVariable Long promotionId) {
+        try {
+            Long userId = StpUtil.getLoginIdAsLong();
+            // 查询用户
+            var userOpt = userRepository.findById(userId);
+            if (userOpt.isEmpty() || !"admin".equals(userOpt.get().getRole())) {
+                return Result.error("没有权限删除促销活动");
+            }
+
+            promotionService.deletePromotion(promotionId);
+            return Result.ok();
+        } catch (Exception e) {
+            return Result.error("删除促销失败: " + e.getMessage());
+        }
+    }
     @GetMapping("/announce")
     public ResponseEntity<byte[]> announce(TrackerProtocol trackerProtocol, HttpServletRequest request, @RequestParam(value = "info_hash") String encodedInfoHash){
-
+        log.info("client report: {}", trackerProtocol);
         HashMap<String, BEValue> map = new HashMap<>();
         if (StrUtil.isBlank(trackerProtocol.getIp())) {
             trackerProtocol.setIp(request.getRemoteAddr());
diff --git a/src/main/java/com/example/myproject/mapper/PromotionMapper.java b/src/main/java/com/example/myproject/mapper/PromotionMapper.java
index caf96ee..4ff79ea 100644
--- a/src/main/java/com/example/myproject/mapper/PromotionMapper.java
+++ b/src/main/java/com/example/myproject/mapper/PromotionMapper.java
@@ -25,7 +25,7 @@
             "WHERE p.is_deleted = false " +
             "AND p.start_time <= #{now} AND p.end_time >= #{now} " +
 //            "AND FIND_IN_SET(#{torrentId}, p.applicable_torrent_ids) > 0"
-             "AND JSON_CONTAINS(p.applicable_torrent_ids, CAST(#{torrentId} AS JSON))")
+            "AND JSON_CONTAINS(p.applicable_torrent_ids, CAST(#{torrentId} AS JSON))")
     List<Promotion> findActivePromotionsForTorrent(
             @Param("torrentId") Long torrentId,
             @Param("now") LocalDateTime now);