增加付费片单,修复种子列表搜索排序

Change-Id: Ib645906c0f240f954676790daf2ff0e5f16f6e0a
diff --git a/src/main/java/com/example/myproject/controller/FileController.java b/src/main/java/com/example/myproject/controller/FileController.java
new file mode 100644
index 0000000..169d8e8
--- /dev/null
+++ b/src/main/java/com/example/myproject/controller/FileController.java
@@ -0,0 +1,34 @@
+package com.example.myproject.controller;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.lang.UUID;
+import com.example.myproject.common.base.Result;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+@RestController
+@RequestMapping("/file")
+public class FileController {
+    @PostMapping
+    public Result upload(MultipartFile file){
+        try{
+            String originalFilename = file.getOriginalFilename();
+            String fileName = FileUtil.mainName(originalFilename) + UUID.fastUUID().toString() + "." + FileUtil.getSuffix(originalFilename);  // 以用户ID作为文件名
+            Path path = Paths.get("uploads/" + fileName);
+            Files.createDirectories(path.getParent());
+            Files.write(path, file.getBytes());
+            String filepath = "/uploads/" + fileName;
+            return Result.ok(filepath);  // 返回相对URL路径
+
+        }catch (Exception ignore){
+            return Result.failure("文件上传失败");
+        }
+
+    }
+}
diff --git a/src/main/java/com/example/myproject/controller/GroupController.java b/src/main/java/com/example/myproject/controller/GroupController.java
index a0830b6..c9b0ea7 100644
--- a/src/main/java/com/example/myproject/controller/GroupController.java
+++ b/src/main/java/com/example/myproject/controller/GroupController.java
@@ -1,5 +1,8 @@
 package com.example.myproject.controller;
 
+import cn.dev33.satoken.annotation.SaCheckLogin;
+import cn.dev33.satoken.annotation.SaCheckRole;
+import cn.dev33.satoken.annotation.SaMode;
 import com.example.myproject.entity.Group;
 import com.example.myproject.entity.GroupComments;
 import com.example.myproject.service.GroupService;
@@ -19,6 +22,8 @@
     private GroupService groupService;
 
     @PostMapping("/createGroup")
+    @SaCheckLogin
+    @SaCheckRole(value = { "chocolate", "ice-cream"}, mode = SaMode.OR)
     public ResponseEntity<Map<String, Object>> createGroup(@RequestBody Group groupRequest) {
         // 调用服务层方法创建小组
         System.out.println("Received group name: " + groupRequest.getGroupName());
@@ -28,6 +33,8 @@
 
     // 加入小组接口
     @PostMapping("/{group_id}/join")
+    @SaCheckLogin
+    @SaCheckRole(value = {"chocolate", "ice-cream"}, mode = SaMode.OR)
     public ResponseEntity<Map<String, Object>> joinGroup(@PathVariable("group_id") Long groupId,
                                                          @RequestBody Map<String, Long> requestBody) {
         Long userId = requestBody.get("user_id");
@@ -36,6 +43,8 @@
 
     // 退出小组接口
     @PostMapping("/{group_id}/leave")
+    @SaCheckLogin
+    @SaCheckRole(value = { "chocolate", "ice-cream"}, mode = SaMode.OR)
     public ResponseEntity<Map<String, Object>> leaveGroup(@PathVariable("group_id") Long groupId,
                                                           @RequestBody Map<String, Long> requestBody) {
         Long userId = requestBody.get("user_id");
@@ -50,6 +59,8 @@
 
     //发布帖子
     @PostMapping("/{group_id}/createPost")
+    @SaCheckLogin
+    @SaCheckRole(value = { "chocolate", "ice-cream"}, mode = SaMode.OR)
     public ResponseEntity<Map<String, Object>> createPost(
             @PathVariable("group_id") Long groupId,
             @RequestParam("user_id") Long userId,
@@ -77,6 +88,8 @@
 
     // 点赞帖子
     @PostMapping("/{group_post_id}/like")
+    @SaCheckLogin
+    @SaCheckRole(value = { "chocolate", "ice-cream"}, mode = SaMode.OR)
     public ResponseEntity<Map<String, Object>> likePost(
             @PathVariable("group_post_id") Long groupPostId) {
         Map<String, Object> response = groupService.likePost(groupPostId);
@@ -85,6 +98,8 @@
 
     // 取消点赞
     @PostMapping("/{group_post_id}/unlike")
+    @SaCheckLogin
+    @SaCheckRole(value = {"chocolate", "ice-cream"}, mode = SaMode.OR)
     public ResponseEntity<Map<String, Object>> unlikePost(
             @PathVariable("group_post_id") Long groupPostId) {
         Map<String, Object> response = groupService.unlikePost(groupPostId);
@@ -93,6 +108,8 @@
 
     // 添加评论接口
     @PostMapping("/{group_post_id}/comment")
+    @SaCheckLogin
+    @SaCheckRole(value = { "chocolate", "ice-cream"}, mode = SaMode.OR)
     public ResponseEntity<Map<String, Object>> addComment(
 
             @PathVariable("group_post_id") Long postId,
diff --git a/src/main/java/com/example/myproject/controller/PlaylistController.java b/src/main/java/com/example/myproject/controller/PlaylistController.java
new file mode 100644
index 0000000..240eeeb
--- /dev/null
+++ b/src/main/java/com/example/myproject/controller/PlaylistController.java
@@ -0,0 +1,97 @@
+package com.example.myproject.controller;
+
+import java.util.List;
+
+import javax.validation.Valid;
+
+import cn.dev33.satoken.annotation.SaCheckRole;
+import cn.dev33.satoken.annotation.SaMode;
+import org.springframework.data.domain.Page;
+import org.springframework.web.bind.annotation.*;
+
+import com.example.myproject.common.base.ResPage;
+import com.example.myproject.common.base.Result;
+import com.example.myproject.dto.CreatePlaylistRequest;
+import com.example.myproject.dto.PagePlaylistDto;
+import com.example.myproject.dto.PlaylistDetail;
+import com.example.myproject.dto.QueryPlaylistRequest;
+import com.example.myproject.dto.UpdatedPlaylistRequest;
+import com.example.myproject.entity.PaidPlaylistEntity;
+import com.example.myproject.service.PlaylistService;
+
+import cn.dev33.satoken.annotation.SaCheckLogin;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+@RestController
+@Slf4j
+@RequiredArgsConstructor
+@RequestMapping("/playlist")
+@SaCheckLogin
+@Api(tags = "片单管理")
+public class PlaylistController {
+    private final PlaylistService playlistService;
+
+    // 创建片单
+    @PostMapping
+    @ApiOperation("创建片单")
+    public Result<PaidPlaylistEntity> createPlayList(
+            @RequestBody @Valid @ApiParam("创建片单请求") CreatePlaylistRequest request) {
+        return Result.ok(playlistService.createPlaylist(request));
+    }
+
+    // 修改片单
+    @PutMapping
+    @ApiOperation("修改片单")
+    public Result<PaidPlaylistEntity> updatePlayList(
+            @RequestBody @Valid @ApiParam("更新片单请求") UpdatedPlaylistRequest request) {
+        return Result.ok(playlistService.updatePlaylist(request));
+    }
+
+    // 删除片单
+    @DeleteMapping
+    @ApiOperation("删除片单")
+    public Result<Void> removePlaylists(@RequestParam @ApiParam("片单ID列表") List<Long> ids) {
+        playlistService.removePlaylists(ids);
+        return Result.ok();
+    }
+
+    // 片单分页
+    @GetMapping("/page")
+    @ApiOperation("片单分页")
+    public Result getPlaylists(@Valid @ModelAttribute QueryPlaylistRequest request) {
+        try {
+            Page<PagePlaylistDto> playlists = playlistService.getPlaylists(request);
+            List<PagePlaylistDto> list = playlists.get().toList();
+            return Result.ok(list, new ResPage(playlists.getTotalElements(), request.getPage(), request.getSize()));
+        } catch (Exception e) {
+            e.printStackTrace();
+            return Result.error("服务器异常:" + e.getMessage());
+
+        }
+    }
+
+
+    // 支付片单
+    // 扣除用户余额就行
+    @PostMapping("/{id:\\d+}/pay")
+    @ApiOperation("支付片单")
+    @SaCheckLogin
+    @SaCheckRole(value = {"cookie", "chocolate", "ice-cream"}, mode = SaMode.OR)
+    public Result<Void> payPlaylist(@PathVariable @ApiParam("片单ID") Long id) {
+        playlistService.payPlaylist(id);
+        return Result.ok();
+    }
+
+    // 片单详情
+    // 购买过后才能查看片单
+    @GetMapping("/{id:\\d+}")
+    @ApiOperation("获取片单详情")
+    public Result<PlaylistDetail> getDetail(@PathVariable @ApiParam("片单ID") Long id) {
+        return Result.ok(playlistService.getDetail(id));
+    }
+
+}
diff --git a/src/main/java/com/example/myproject/controller/SeedRatingController.java b/src/main/java/com/example/myproject/controller/SeedRatingController.java
index 16e0b22..879b4a3 100644
--- a/src/main/java/com/example/myproject/controller/SeedRatingController.java
+++ b/src/main/java/com/example/myproject/controller/SeedRatingController.java
@@ -1,5 +1,8 @@
 package com.example.myproject.controller;
 
+import cn.dev33.satoken.annotation.SaCheckLogin;
+import cn.dev33.satoken.annotation.SaCheckRole;
+import cn.dev33.satoken.annotation.SaMode;
 import com.example.myproject.service.SeedRatingService;
 import org.springframework.web.bind.annotation.*;
 
@@ -7,6 +10,8 @@
 
 @RestController
 @RequestMapping("/echo/ratings")
+@SaCheckLogin
+@SaCheckRole(value = {"cookie", "chocolate", "ice-cream"}, mode = SaMode.OR)
 public class SeedRatingController {
 
     private final SeedRatingService ratingService;
diff --git a/src/main/java/com/example/myproject/controller/TorrentController.java b/src/main/java/com/example/myproject/controller/TorrentController.java
index c0e4e86..99573a3 100644
--- a/src/main/java/com/example/myproject/controller/TorrentController.java
+++ b/src/main/java/com/example/myproject/controller/TorrentController.java
@@ -1,12 +1,16 @@
 package com.example.myproject.controller;
 
+import cn.dev33.satoken.annotation.SaCheckRole;
+import cn.dev33.satoken.annotation.SaMode;
 import cn.hutool.core.util.StrUtil;
 import com.example.myproject.common.base.PageUtil;
 import com.example.myproject.dto.TrackerProtocol;
 import com.example.myproject.entity.TorrentEntity;
 import com.example.myproject.entity.TorrentReport;
+import com.example.myproject.exception.BusinessException;
 import com.example.myproject.mapper.UserMapper;
 import com.example.myproject.repository.TorrentReportRepository;
+import com.example.myproject.service.LevelService;
 import com.example.myproject.service.TorrentService;
 import com.example.myproject.service.PromotionService;
 import com.example.myproject.dto.param.TorrentParam;
@@ -76,6 +80,9 @@
     @Autowired
     private UserRepository userRepository;
 
+    @Autowired
+    private LevelService levelService;
+
     @SaCheckLogin
     @Operation(summary = "种子列表查询", description = "种子列表条件查询-分页-排序")
     @ApiResponse(responseCode = "0", description = "操作成功",
@@ -83,11 +90,15 @@
                     schema = @Schema(implementation = TorrentVO.class))
             })
     @GetMapping("/list")
-    public Result list( TorrentParam param) {
-        // 构建排序和模糊查询条件
-        param.validOrder(param.getOrderKey(TorrentEntity.class));
+    @SaCheckRole(value = {"candy", "cookie", "chocolate", "ice-cream"}, mode = SaMode.OR)
+    public Result list(TorrentParam param) {
+        // 校验排序字段和规则
+        param.validOrder();
+
+        // 构建模糊查询条件
         param.buildLike();
 
+        // 启动分页
         PageUtil.startPage(param);
 
         // 查询数据
@@ -115,12 +126,15 @@
 
     @Operation(summary = "上传种子")
     @PostMapping("/upload")
+//    @SaCheckRole(value = {"cookie", "chocolate", "ice-cream"}, mode = SaMode.OR)
     public Result uploadTorrent(
             @RequestParam("file") MultipartFile file,
             @RequestParam("coverImage") MultipartFile coverImage,
             @ModelAttribute @Validated TorrentUploadParam param,
             HttpServletRequest request
-    ) throws IOException {
+    )  {
+        // 代码形式替换
+        StpUtil.checkRoleOr("cookie", "chocolate", "ice-cream");
         try {
             // 验证用户权限
             // Long userId = StpUtil.getLoginIdAsLong();
@@ -144,7 +158,6 @@
                     return Result.error("仅支持 JPG/PNG 格式的封面图片");
                 }
 
-
                 // 项目根目录下的 /uploads/torrents/
                 String uploadDir = System.getProperty("user.dir") + "/uploads/torrents/";
                 File dir = new File(uploadDir);
@@ -155,13 +168,17 @@
 
                 String imageUrl = request.getScheme() + "://" + request.getServerName() + ":" +
                         request.getServerPort() + "/uploads/torrents/" + fileName;
-//                String imageUrl ="/uploads/torrents/" + fileName;
+
+
+
                 param.setImageUrl(imageUrl);
             }
 
             torrentService.uploadTorrent(file, param);
-            return Result.ok();
-        } catch (Exception e) {
+            Map<String, Object> result = levelService.updateExperience(Long.valueOf(userId), 20, "upload_torrent");
+            return Result.ok(result);
+//            return Result.ok();
+        } catch (IOException e) {
             return Result.error("种子上传失败: " + e.getMessage());
         }
     }
@@ -183,6 +200,15 @@
             return;
         }
 
+        // 判断该种子是否付费,然后再调用 StpUtil.checkRoleOr("chocolate", "ice-cream") 判断是否有权限
+        if(torrentService.needPay(entity.getId()) ){
+            StpUtil.checkRoleOr("chocolate", "ice-cream");// 这里没有权限就会报错
+            // 检查用户是否购买该种子
+            if (!torrentService.isPaid(entity.getId())) {
+                throw new BusinessException("您还未购买该种子");
+            }
+        }
+
         // 获取并处理种子文件内容(需在service中实现passkey注入)
         byte[] torrentBytes = torrentService.fetch(seedId, passkey);