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

Change-Id: Ib645906c0f240f954676790daf2ff0e5f16f6e0a
diff --git a/src/main/java/com/example/myproject/common/base/OrderPageParam.java b/src/main/java/com/example/myproject/common/base/OrderPageParam.java
index 21ff38b..dfbf6a2 100644
--- a/src/main/java/com/example/myproject/common/base/OrderPageParam.java
+++ b/src/main/java/com/example/myproject/common/base/OrderPageParam.java
@@ -1,17 +1,13 @@
 package com.example.myproject.common.base;
 
-
 import com.example.myproject.common.CommonResultStatus;
 import com.example.myproject.common.Constants;
 import com.example.myproject.common.exception.RocketPTException;
-
+import com.example.myproject.entity.TorrentEntity;
 import org.apache.commons.lang3.StringUtils;
-
 import java.lang.reflect.Field;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
-
 import cn.hutool.core.util.ArrayUtil;
 import cn.hutool.core.util.StrUtil;
 import io.swagger.v3.oas.annotations.media.Schema;
@@ -22,78 +18,91 @@
 @Setter
 public class OrderPageParam extends PageParam {
 
-    /**
-     * 排序字段
-     */
     @Schema(description = "排序字段")
     protected String prop;
 
-    /**
-     * 排序规则
-     */
     @Schema(description = "排序规则")
     protected String sort;
 
-    public void validOrder(List<String> orderKey) throws RocketPTException {
-        prop = StringUtils.isBlank(prop) ? null : StrUtil.toUnderlineCase(prop);
-        sort = StringUtils.isBlank(sort) ? Constants.Order.DEFAULT_ORDER_TYPE : sort;
-
-        if (Arrays.asList(Constants.Order.ORDER_TYPE).indexOf(sort) < 0) {
-            throw new RocketPTException(CommonResultStatus.PARAM_ERROR, "排序方式錯誤");
-        }
-
-        if (StringUtils.isNotBlank(prop) && Arrays.asList(orderKey).indexOf(prop) < 0) {
-            throw new RocketPTException(CommonResultStatus.PARAM_ERROR, "排序欄位錯誤");
-        }
-    }
-
     public void validOrder() throws RocketPTException {
-        List<String> orderKey = getOrderKey();
+        List<String> orderKey = getOrderKey(TorrentEntity.class);
         prop = StringUtils.isBlank(prop) ? null : StrUtil.toUnderlineCase(prop);
         sort = StringUtils.isBlank(sort) ? Constants.Order.DEFAULT_ORDER_TYPE : sort;
 
         if (!ArrayUtil.contains(Constants.Order.ORDER_TYPE, sort)) {
-            throw new RocketPTException(CommonResultStatus.PARAM_ERROR, "排序方式錯誤");
+            throw new RocketPTException(CommonResultStatus.PARAM_ERROR, "排序方式错误");
         }
 
         if (StringUtils.isNotBlank(prop) && !orderKey.contains(prop)) {
-            throw new RocketPTException(CommonResultStatus.PARAM_ERROR, "排序欄位錯誤");
+            throw new RocketPTException(CommonResultStatus.PARAM_ERROR, "排序字段错误");
         }
     }
 
-    /**
-     * @return 反射获取字段列表
-     */
-    public List<String> getOrderKey() {
+    public List<String> getOrderKey(Class<?> entityClass) {
         List<String> list = new ArrayList<>();
+        Class<?> clazz = entityClass;
 
-        Field[] fields = getClass().getDeclaredFields();
-        if (fields != null) {
+        while (clazz != null && clazz != Object.class) {
+            Field[] fields = clazz.getDeclaredFields();
             for (Field field : fields) {
-                field.setAccessible(true);
-                String name = field.getName();
-
-                list.add(StrUtil.toUnderlineCase(name));
+                list.add(StrUtil.toUnderlineCase(field.getName()));
             }
+            clazz = clazz.getSuperclass();
         }
+
         return list;
     }
-    /**
-     * @return 反射获取字段列表
-     */
-    public List<String> getOrderKey(Class clazz) {
-        List<String> list = new ArrayList<>();
-
-        Field[] fields = clazz.getDeclaredFields();
-        if (fields != null) {
-            for (Field field : fields) {
-                field.setAccessible(true);
-                String name = field.getName();
-
-                list.add(StrUtil.toUnderlineCase(name));
-            }
-        }
-        return list;
-    }
-
 }
+
+
+//@Getter
+//@Setter
+//public class OrderPageParam extends PageParam {
+//
+//    /**
+//     * 排序字段
+//     */
+//    @Schema(description = "排序字段")
+//    protected String prop;
+//
+//    /**
+//     * 排序规则
+//     */
+//    @Schema(description = "排序规则")
+//    protected String sort;
+//
+//    public void validOrder() throws RocketPTException {
+//        List<String> orderKey = getOrderKey(TorrentEntity.class); // ✅ 使用 TorrentEntity 字段
+//        prop = StringUtils.isBlank(prop) ? null : StrUtil.toUnderlineCase(prop);
+//        sort = StringUtils.isBlank(sort) ? Constants.Order.DEFAULT_ORDER_TYPE : sort;
+//
+//        if (!ArrayUtil.contains(Constants.Order.ORDER_TYPE, sort)) {
+//            throw new RocketPTException(CommonResultStatus.PARAM_ERROR, "排序方式错误");
+//        }
+//
+//        if (StringUtils.isNotBlank(prop) && !orderKey.contains(prop)) {
+//            throw new RocketPTException(CommonResultStatus.PARAM_ERROR, "排序字段错误");
+//        }
+//    }
+//
+//
+//    public List<String> getOrderKey(Class<?> entityClass) {
+//        List<String> list = new ArrayList<>();
+//        Class<?> clazz = entityClass;
+//
+//        while (clazz != null && clazz != Object.class) {
+//            Field[] fields = clazz.getDeclaredFields();
+//            for (Field field : fields) {
+//                field.setAccessible(true);
+//                String name = field.getName();
+//                list.add(StrUtil.toUnderlineCase(name));
+//            }
+//            clazz = clazz.getSuperclass();
+//        }
+//
+//        return list;
+//    }
+//
+//
+//
+//}
diff --git a/src/main/java/com/example/myproject/common/base/Result.java b/src/main/java/com/example/myproject/common/base/Result.java
index 6d20f4f..5041a87 100644
--- a/src/main/java/com/example/myproject/common/base/Result.java
+++ b/src/main/java/com/example/myproject/common/base/Result.java
@@ -85,6 +85,13 @@
         this.page = page;
     }
 
+    public Result(Integer code, String msg){
+        this.code = code;
+        this.msg = msg;
+    }
+
+
+
     @JsonIgnore
     public boolean isSuccess() {
         return this.code == Status.SUCCESS.getCode();
@@ -134,6 +141,9 @@
     public static <T> Result<T> error(String msg) {
         return new Result<T>(Status.FAILURE, msg);
     }
+    public static <T> Result<T> error(Integer code, String msg) {
+        return new Result<T>(code , msg);
+    }
 
     public static <T> Result<T> conflict() {
         return new Result<T>(Status.CONFLICT);
diff --git a/src/main/java/com/example/myproject/config/GlobalExceptionHandler.java b/src/main/java/com/example/myproject/config/GlobalExceptionHandler.java
new file mode 100644
index 0000000..abcd055
--- /dev/null
+++ b/src/main/java/com/example/myproject/config/GlobalExceptionHandler.java
@@ -0,0 +1,29 @@
+package com.example.myproject.config;
+
+import cn.dev33.satoken.exception.NotLoginException;
+import cn.dev33.satoken.exception.NotPermissionException;
+import cn.dev33.satoken.exception.NotRoleException;
+import com.example.myproject.common.base.Result;
+import com.example.myproject.exception.BusinessException;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+
+    @ExceptionHandler(BusinessException.class)
+    public Result handleException(BusinessException e) {
+        // 这里可以根据不同的异常类型返回不同的结果
+        return Result.error(e.getMessage());
+    }
+
+    @ExceptionHandler(value = {NotLoginException.class})
+    public Result unLoginHandler(){
+        return Result.error(401, "您还未登录");
+    }
+    @ExceptionHandler(value = {NotRoleException.class, NotPermissionException.class})
+    public Result handlerException() {
+        return Result.error(403, "您没有权限");
+    }
+}
diff --git a/src/main/java/com/example/myproject/config/StpInterfaceImpl.java b/src/main/java/com/example/myproject/config/StpInterfaceImpl.java
new file mode 100644
index 0000000..8bf2c13
--- /dev/null
+++ b/src/main/java/com/example/myproject/config/StpInterfaceImpl.java
@@ -0,0 +1,48 @@
+package com.example.myproject.config;
+
+import cn.dev33.satoken.stp.StpInterface;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+
+import com.example.myproject.entity.Users;
+import com.example.myproject.repository.UserRepository;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 自定义权限加载接口实现类
+ */
+@Component // 保证此类被 SpringBoot 扫描,完成 Sa-Token 的自定义权限验证扩展
+@RequiredArgsConstructor
+public class StpInterfaceImpl implements StpInterface {
+    private final UserRepository userRepository;
+
+    /**
+     * 返回一个账号所拥有的权限码集合
+     */
+    @Override
+    public List<String> getPermissionList(Object loginId, String loginType) {
+        return new ArrayList<>();
+    }
+
+    /**
+     * 返回一个账号所拥有的角色标识集合 (权限与角色可分开校验)
+     */
+    @Override
+    @Transactional
+    public List<String> getRoleList(Object loginId, String loginType) {
+        List<String> list = new ArrayList<String>();
+        long id = 0;
+        if(loginId instanceof String){
+            id = Long.parseLong(loginId.toString());
+        }else{
+            id = (long) loginId;
+        }
+        Users user = userRepository.getById(id);
+        list.add(user.getLevelRole());
+        return list;
+    }
+
+}
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);
 
diff --git a/src/main/java/com/example/myproject/dto/CreatePlaylistRequest.java b/src/main/java/com/example/myproject/dto/CreatePlaylistRequest.java
new file mode 100644
index 0000000..2d13713
--- /dev/null
+++ b/src/main/java/com/example/myproject/dto/CreatePlaylistRequest.java
@@ -0,0 +1,28 @@
+package com.example.myproject.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+@Data
+@ApiModel("创建片单请求DTO")
+public class CreatePlaylistRequest {
+    @ApiModelProperty(value = "片单标题", example = "热门电影合集")
+    private String title;
+
+    @ApiModelProperty(value = "封面图片URL", example = "/uploads/PlaylistCoverUrl/1.jpg")
+    private String coverUrl;
+
+    @ApiModelProperty(value = "价格,单位元", example = "1")
+    private BigDecimal price;
+
+    @ApiModelProperty(value = "片单描述", example = "包含多部热门电影的合集")
+    private String description;
+
+    @ApiModelProperty(value = "关联的种子列表", example = "[1, 2, 3]")
+    private List<Long> torrentList;
+
+}
diff --git a/src/main/java/com/example/myproject/dto/PagePlaylistDto.java b/src/main/java/com/example/myproject/dto/PagePlaylistDto.java
new file mode 100644
index 0000000..0d12feb
--- /dev/null
+++ b/src/main/java/com/example/myproject/dto/PagePlaylistDto.java
@@ -0,0 +1,24 @@
+package com.example.myproject.dto;
+
+import com.example.myproject.entity.PaidPlaylistEntity;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+@ApiModel("分页片单DTO")
+public class PagePlaylistDto extends PaidPlaylistEntity {
+    @ApiModelProperty(value = "已支付", example = "false")
+    private Boolean isPaid;
+
+    @Data
+    @ApiModel("片单种子DTO")
+    public static class  PagePlaylistItemDto  {
+        @ApiModelProperty(value = "种子ID", example = "1")
+        private Long id;
+        @ApiModelProperty(value = "种子文件名", example = "movie_torrent_file.torrent")
+        private String filename;
+    }
+}
diff --git a/src/main/java/com/example/myproject/dto/PlaylistDetail.java b/src/main/java/com/example/myproject/dto/PlaylistDetail.java
new file mode 100644
index 0000000..d46c42e
--- /dev/null
+++ b/src/main/java/com/example/myproject/dto/PlaylistDetail.java
@@ -0,0 +1,19 @@
+package com.example.myproject.dto;
+
+import com.example.myproject.entity.PaidPlaylistEntity;
+import com.example.myproject.entity.TorrentEntity;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.List;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+@ApiModel
+public class PlaylistDetail extends PaidPlaylistEntity {
+    @ApiModelProperty(value = "关联的种子列表")
+    private List<TorrentEntity> torrentList;
+
+}
diff --git a/src/main/java/com/example/myproject/dto/QueryPlaylistRequest.java b/src/main/java/com/example/myproject/dto/QueryPlaylistRequest.java
new file mode 100644
index 0000000..5144b64
--- /dev/null
+++ b/src/main/java/com/example/myproject/dto/QueryPlaylistRequest.java
@@ -0,0 +1,32 @@
+package com.example.myproject.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class QueryPlaylistRequest {
+
+    @ApiModelProperty(value = "片单ID", example = "1")
+    private Long id;
+
+    @ApiModelProperty(value = "片单标题", example = "热门电影合集")
+    private String title;
+
+    @ApiModelProperty(value = "封面图片URL", example = "/uploads/PlaylistCoverUrl/1.jpg")
+    private String coverUrl;
+
+    @ApiModelProperty(value = "价格,单位元", example = "1")
+    private BigDecimal price;
+
+    @ApiModelProperty(value = "片单描述", example = "包含多部热门电影的合集")
+    private String description;
+
+    @ApiModelProperty(value = "页码,从0开始", example = "0")
+    private Integer page = 0;  // 默认第一页
+
+    @ApiModelProperty(value = "每页大小", example = "10")
+    private Integer size = 10;  // 默认每页10条
+}
+
diff --git a/src/main/java/com/example/myproject/dto/UpdatedPlaylistRequest.java b/src/main/java/com/example/myproject/dto/UpdatedPlaylistRequest.java
new file mode 100644
index 0000000..c70f019
--- /dev/null
+++ b/src/main/java/com/example/myproject/dto/UpdatedPlaylistRequest.java
@@ -0,0 +1,14 @@
+package com.example.myproject.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+@ApiModel("更新片单请求DTO")
+public class UpdatedPlaylistRequest extends CreatePlaylistRequest{
+    @ApiModelProperty(value = "片单ID", example = "1")
+    private Long id;
+}
diff --git a/src/main/java/com/example/myproject/dto/param/TorrentParam.java b/src/main/java/com/example/myproject/dto/param/TorrentParam.java
index 1ef832b..7716a50 100644
--- a/src/main/java/com/example/myproject/dto/param/TorrentParam.java
+++ b/src/main/java/com/example/myproject/dto/param/TorrentParam.java
@@ -24,6 +24,13 @@
     @Schema(description = "促销种子")
     private String free;
 
+    @Schema(description = "排序字段")
+    private String orderKey;
+
+    @Schema(description = "是否倒序")
+    private Boolean orderDesc;
+
+
     private Set<String> likeExpressions;
 
     public void buildLike() {
diff --git a/src/main/java/com/example/myproject/entity/LoginLog.java b/src/main/java/com/example/myproject/entity/LoginLog.java
new file mode 100644
index 0000000..482c399
--- /dev/null
+++ b/src/main/java/com/example/myproject/entity/LoginLog.java
@@ -0,0 +1,26 @@
+package com.example.myproject.entity;
+
+import java.time.LocalDateTime;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import lombok.Data;
+
+@Entity
+@Table(name = "login_log")
+@Data
+public class LoginLog {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    private Long userId;
+
+    @Column(columnDefinition = "datetime default now()", name = "login_time")
+    private LocalDateTime loginTime;
+
+}
diff --git a/src/main/java/com/example/myproject/entity/PaidPlaylistEntity.java b/src/main/java/com/example/myproject/entity/PaidPlaylistEntity.java
new file mode 100644
index 0000000..ae212aa
--- /dev/null
+++ b/src/main/java/com/example/myproject/entity/PaidPlaylistEntity.java
@@ -0,0 +1,42 @@
+package com.example.myproject.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.persistence.*;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Data
+@Table(name="paid_playlists")
+@Entity
+@ApiModel("付费片单实体类")
+public class PaidPlaylistEntity {
+
+    @ApiModelProperty(value = "片单ID", example = "1")
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    @ApiModelProperty(value = "片单标题", example = "热门电影合集")
+    private String title;
+
+    @ApiModelProperty(value = "封面图片URL", example = "/uploads/PlaylistCoverUrl/1.jpg")
+    private String coverUrl;
+
+    @ApiModelProperty(value = "价格,单位元", example = "1")
+    private BigDecimal price;
+
+    @ApiModelProperty(value = "片单描述", example = "包含多部热门电影的合集")
+    private String description;
+
+    @TableField(fill = FieldFill.INSERT)
+    @ApiModelProperty(value = "创建时间", example = "2024-01-01 12:00:00")
+    private LocalDateTime createdAt;
+
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    @ApiModelProperty(value = "更新时间", example = "2024-01-02 12:00:00")
+    private LocalDateTime updatedAt;
+}
diff --git a/src/main/java/com/example/myproject/entity/PaidPlaylistSeedEntity.java b/src/main/java/com/example/myproject/entity/PaidPlaylistSeedEntity.java
new file mode 100644
index 0000000..d721ce8
--- /dev/null
+++ b/src/main/java/com/example/myproject/entity/PaidPlaylistSeedEntity.java
@@ -0,0 +1,34 @@
+package com.example.myproject.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.persistence.*;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("paid_playlist_seeds")
+@Entity
+@Table(name = "paid_playlist_seeds")
+@ApiModel("付费片单与种子关联实体类")
+public class PaidPlaylistSeedEntity {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @ApiModelProperty(value = "关联ID", example = "1")
+    private Long id;
+
+    @ApiModelProperty(value = "付费片单ID", example = "1")
+    @Column(name = "playlist_id")
+    private Long playlistId;
+
+    @ApiModelProperty(value = "种子ID", example = "123")
+    @Column(name = "seed_id")
+    private Long seedId;
+
+    @TableField(fill = FieldFill.INSERT)
+    @ApiModelProperty(value = "创建时间", example = "2024-01-01 12:00:00")
+    private LocalDateTime createdAt;
+}
diff --git a/src/main/java/com/example/myproject/entity/UserPaidPlaylistEntity.java b/src/main/java/com/example/myproject/entity/UserPaidPlaylistEntity.java
new file mode 100644
index 0000000..6ea5037
--- /dev/null
+++ b/src/main/java/com/example/myproject/entity/UserPaidPlaylistEntity.java
@@ -0,0 +1,33 @@
+package com.example.myproject.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.persistence.*;
+import java.time.LocalDateTime;
+
+@Data
+@Entity
+@Table(name = "user_paid_playlists")
+@ApiModel("用户付费片单记录实体类")
+public class UserPaidPlaylistEntity {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @ApiModelProperty(value = "记录ID", example = "1")
+    private Long id;
+
+    @ApiModelProperty(value = "用户ID", example = "1001")
+    @Column(name = "user_id")
+    private Long userId;
+
+    @ApiModelProperty(value = "付费片单ID", example = "1")
+    @Column(name = "playlist_id")
+    private Long playlistId;
+
+    @TableField(fill = FieldFill.INSERT)
+    @ApiModelProperty(value = "付费时间", example = "2024-01-01 12:00:00")
+    private LocalDateTime paidAt;
+}
diff --git a/src/main/java/com/example/myproject/entity/Users.java b/src/main/java/com/example/myproject/entity/Users.java
index a9398cf..2a0e946 100644
--- a/src/main/java/com/example/myproject/entity/Users.java
+++ b/src/main/java/com/example/myproject/entity/Users.java
@@ -1,6 +1,7 @@
 package com.example.myproject.entity;
 
 import javax.persistence.*;
+import java.math.BigDecimal;
 import java.util.Date;
 
 @Entity
@@ -34,10 +35,10 @@
     private Long level;
 
     @Column(name = "upload_count")
-    private Float uploadCount;
+    private Float uploadCount= 0f;
 
     @Column(name = "download_count")
-    private Float downloadCount;
+    private Float downloadCount= 0f;
 
     @Column(name = "share_rate")
     private Float shareRate;
@@ -77,14 +78,16 @@
     @Column(name = "lastupdatetime")
     private Date lastupdatetime;
 
-    @Column(name = "money", nullable = false)
-    private Integer money;
+    @Column(nullable = false,precision = 10, scale = 2)
+    private BigDecimal money;
+    @Column(name = "status")
+    private Integer status;// 用户状态,0: 正常 1 封禁 2 怀疑用户
 
     // Getters and Setters
-    public Integer getMoney() {
+    public BigDecimal getMoney() {
         return money;
     }
-    public void setMoney(Integer money) {
+    public void setMoney(BigDecimal money) {
         this.money = money;
     }
 
@@ -264,4 +267,14 @@
     public void setLastupdatetime(Date lastupdatetime) {
         this.lastupdatetime = lastupdatetime;
     }
+
+    public String getLevelRole() {
+        return switch (this.level.intValue()) {
+            case 1 -> "candy";
+            case 2 -> "cookie";
+            case 3 -> "chocolate";
+            case 4 -> "ice-cream";
+            default -> null;
+        };
+    }
 }
\ No newline at end of file
diff --git a/src/main/java/com/example/myproject/exception/BusinessException.java b/src/main/java/com/example/myproject/exception/BusinessException.java
new file mode 100644
index 0000000..af1b8de
--- /dev/null
+++ b/src/main/java/com/example/myproject/exception/BusinessException.java
@@ -0,0 +1,19 @@
+package com.example.myproject.exception;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+public class BusinessException extends RuntimeException {
+    /**
+     * Constructs a new runtime exception with the specified detail message.
+     * The cause is not initialized, and may subsequently be initialized by a
+     * call to {@link #initCause}.
+     *
+     * @param message the detail message. The detail message is saved for
+     *                later retrieval by the {@link #getMessage()} method.
+     */
+    public BusinessException(String message) {
+        super(message);
+    }
+}
+
diff --git a/src/main/java/com/example/myproject/mapper/PaidPlaylistMapper.java b/src/main/java/com/example/myproject/mapper/PaidPlaylistMapper.java
new file mode 100644
index 0000000..74d32e9
--- /dev/null
+++ b/src/main/java/com/example/myproject/mapper/PaidPlaylistMapper.java
@@ -0,0 +1 @@
+package com.example.myproject.mapper;
\ No newline at end of file
diff --git a/src/main/java/com/example/myproject/mapper/PaidPlaylistSeedMapper.java b/src/main/java/com/example/myproject/mapper/PaidPlaylistSeedMapper.java
new file mode 100644
index 0000000..9e91975
--- /dev/null
+++ b/src/main/java/com/example/myproject/mapper/PaidPlaylistSeedMapper.java
@@ -0,0 +1,17 @@
+package com.example.myproject.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.example.myproject.entity.PaidPlaylistSeedEntity;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+@Mapper
+public interface PaidPlaylistSeedMapper extends BaseMapper<PaidPlaylistSeedEntity> {
+
+    @Select("SELECT seed_id FROM paid_playlist_seeds WHERE playlist_id = #{playlistId}")
+    List<Long> selectSeedIdsByPlaylistId(@Param("playlistId") Long playlistId);
+
+}
diff --git a/src/main/java/com/example/myproject/mapper/UserPaidPlaylistMapper.java b/src/main/java/com/example/myproject/mapper/UserPaidPlaylistMapper.java
new file mode 100644
index 0000000..5852581
--- /dev/null
+++ b/src/main/java/com/example/myproject/mapper/UserPaidPlaylistMapper.java
@@ -0,0 +1,20 @@
+package com.example.myproject.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.example.myproject.entity.UserPaidPlaylistEntity;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+@Mapper
+public interface UserPaidPlaylistMapper extends BaseMapper<UserPaidPlaylistEntity> {
+
+    @Select("SELECT playlist_id FROM user_paid_playlists WHERE user_id = #{userId}")
+    List<Long> selectPlaylistIdsByUserId(@Param("userId") Long userId);
+
+    @Select("SELECT COUNT(1) FROM user_paid_playlists WHERE user_id = #{userId} AND playlist_id = #{playlistId}")
+    int checkUserPaid(@Param("userId") Long userId, @Param("playlistId") Long playlistId);
+
+}
diff --git a/src/main/java/com/example/myproject/repository/LoginLogRepository.java b/src/main/java/com/example/myproject/repository/LoginLogRepository.java
new file mode 100644
index 0000000..0775a6a
--- /dev/null
+++ b/src/main/java/com/example/myproject/repository/LoginLogRepository.java
@@ -0,0 +1,17 @@
+package com.example.myproject.repository;
+
+import com.example.myproject.entity.LoginLog;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import java.time.LocalDateTime;
+import java.util.List;
+
+public interface LoginLogRepository extends JpaRepository<LoginLog, Long> {
+
+    @Query("SELECT DISTINCT DATE(l.loginTime) FROM LoginLog l WHERE l.userId = :userId ORDER BY DATE(l.loginTime)")
+    List<LocalDateTime> findDistinctLoginDates(@Param("userId") Long userId);
+
+    @Query("SELECT COUNT(DISTINCT DATE(l.loginTime)) FROM LoginLog l WHERE l.userId = :userId")
+    Long countTotalDistinctLoginDays(@Param("userId") Long userId);
+}
diff --git a/src/main/java/com/example/myproject/repository/PaidPlaylistEntityRepository.java b/src/main/java/com/example/myproject/repository/PaidPlaylistEntityRepository.java
new file mode 100644
index 0000000..9403247
--- /dev/null
+++ b/src/main/java/com/example/myproject/repository/PaidPlaylistEntityRepository.java
@@ -0,0 +1,12 @@
+package com.example.myproject.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+import com.example.myproject.entity.PaidPlaylistEntity;
+
+@Repository
+public interface PaidPlaylistEntityRepository
+        extends JpaRepository<PaidPlaylistEntity, Long>, JpaSpecificationExecutor<PaidPlaylistEntity> {
+}
diff --git a/src/main/java/com/example/myproject/repository/PaidPlaylistSeedEntityRepository.java b/src/main/java/com/example/myproject/repository/PaidPlaylistSeedEntityRepository.java
new file mode 100644
index 0000000..93f5339
--- /dev/null
+++ b/src/main/java/com/example/myproject/repository/PaidPlaylistSeedEntityRepository.java
@@ -0,0 +1,20 @@
+package com.example.myproject.repository;
+
+import java.util.List;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import com.example.myproject.entity.PaidPlaylistSeedEntity;
+
+@Repository
+public interface PaidPlaylistSeedEntityRepository extends JpaRepository<PaidPlaylistSeedEntity, Long> {
+    void deleteByPlaylistId(Long playlistId);
+
+    List<PaidPlaylistSeedEntity> findByPlaylistId(Long playlistId);
+
+
+    boolean existsByPlaylistIdAndSeedId(Long playlistId, Long seedId);
+
+    List<PaidPlaylistSeedEntity> findBySeedId(Long seedId);
+}
diff --git a/src/main/java/com/example/myproject/repository/TorrentReportRepository.java b/src/main/java/com/example/myproject/repository/TorrentReportRepository.java
index 7b3e89c..94b1149 100644
--- a/src/main/java/com/example/myproject/repository/TorrentReportRepository.java
+++ b/src/main/java/com/example/myproject/repository/TorrentReportRepository.java
@@ -1,13 +1,48 @@
+//package com.example.myproject.repository;
+//
+//import com.example.myproject.entity.TorrentReport;
+//import org.springframework.data.jpa.repository.JpaRepository;
+//import org.springframework.data.jpa.repository.Query;
+//import org.springframework.data.repository.query.Param;
+//
+//import java.util.Optional;
+//
+//public interface TorrentReportRepository extends JpaRepository<TorrentReport, Integer> {
+//    @Query(value = "SELECT * FROM TorrentReport WHERE peerId = :peerId AND infoHash = :infoHash ORDER BY id DESC LIMIT 1", nativeQuery = true)
+//    Optional<TorrentReport> findLatestByPeerIdAndInfoHash(@Param("peerId") String peerId, @Param("infoHash") String infoHash);
+//}
 package com.example.myproject.repository;
 
-import com.example.myproject.entity.TorrentReport;
+import java.util.List;
+
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.Query;
 import org.springframework.data.repository.query.Param;
 
+import com.example.myproject.entity.TorrentReport;
 import java.util.Optional;
 
 public interface TorrentReportRepository extends JpaRepository<TorrentReport, Integer> {
+    List<TorrentReport> findByUserId(Integer userId);
+
+    // 根据种子ID查询所有上报记录
+    List<TorrentReport> findByTorrentId(Long torrentId);
+
+    // 计算某个种子所有用户上报的总上传量
+    @Query("SELECT SUM(r.uploaded) FROM TorrentReport r WHERE r.torrentId = :torrentId")
+    Long sumUploadedByTorrentId(@Param("torrentId") Long torrentId);
+
+    // 计算某个种子所有用户上报的总下载量
+    @Query("SELECT SUM(r.downloaded) FROM TorrentReport r WHERE r.torrentId = :torrentId")
+    Long sumDownloadedByTorrentId(@Param("torrentId") Long torrentId);
+
+    // 查询某个用户在某个种子的所有上报记录,按时间排序
+    List<TorrentReport> findByTorrentIdAndUserIdOrderByReportTimeAsc(Long torrentId, Long userId);
+
+    // 获取某个种子最近活跃的Peer ID数量 (简单判断,可以优化)
+    @Query("SELECT COUNT(DISTINCT r.peerId) FROM TorrentReport r WHERE r.torrentId = :torrentId AND r.reportTime > :cutoffTime")
+    Long countRecentActivePeers(@Param("torrentId") Long torrentId,
+                                @Param("cutoffTime") java.time.LocalDateTime cutoffTime);
     @Query(value = "SELECT * FROM TorrentReport WHERE peerId = :peerId AND infoHash = :infoHash ORDER BY id DESC LIMIT 1", nativeQuery = true)
     Optional<TorrentReport> findLatestByPeerIdAndInfoHash(@Param("peerId") String peerId, @Param("infoHash") String infoHash);
 }
diff --git a/src/main/java/com/example/myproject/repository/UserPaidPlaylistEntityRepository.java b/src/main/java/com/example/myproject/repository/UserPaidPlaylistEntityRepository.java
new file mode 100644
index 0000000..68416eb
--- /dev/null
+++ b/src/main/java/com/example/myproject/repository/UserPaidPlaylistEntityRepository.java
@@ -0,0 +1,15 @@
+package com.example.myproject.repository;
+
+import java.util.Optional;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import com.example.myproject.entity.UserPaidPlaylistEntity;
+
+@Repository
+public interface UserPaidPlaylistEntityRepository extends JpaRepository<UserPaidPlaylistEntity, Long> {
+    Optional<UserPaidPlaylistEntity> findByUserIdAndPlaylistId(Long userId, Long playlistId);
+
+    boolean existsByUserIdAndPlaylistId(Long userId, Long playlistId);
+}
diff --git a/src/main/java/com/example/myproject/schedule/UserLevelSchedule.java b/src/main/java/com/example/myproject/schedule/UserLevelSchedule.java
new file mode 100644
index 0000000..a5597df
--- /dev/null
+++ b/src/main/java/com/example/myproject/schedule/UserLevelSchedule.java
@@ -0,0 +1,66 @@
+package com.example.myproject.schedule;
+
+import java.util.Arrays;
+import java.util.List;
+
+import com.example.myproject.service.TorrentService;
+import com.example.myproject.service.UserService;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.DefaultTransactionDefinition;
+
+import com.example.myproject.entity.Users;
+import com.example.myproject.repository.UserRepository;
+
+import lombok.RequiredArgsConstructor;
+
+@Component
+@RequiredArgsConstructor
+public class UserLevelSchedule {
+    private final UserRepository userRepository;
+    private final UserService userService;
+    private final TorrentService torrentService;
+    private final PlatformTransactionManager transactionManager;
+    // 各级需要的经验
+    private final List<Integer> EXPERIENCE_ARRAY = Arrays.asList(50, 200, 500, Integer.MAX_VALUE);
+
+    @Scheduled(fixedRate = 30000)
+    public void upgradeUserLevel() {
+        for (Users user : userRepository.findAll()) {
+            // check the experience
+            int level = (int) Math.max(0, Math.min(EXPERIENCE_ARRAY.size() - 1, user.getLevel()));
+            int targetExperience = EXPERIENCE_ARRAY.get(level -1 );
+            if (user.getCurrentExperience() < targetExperience) {
+                // failed to verify
+                continue;
+            }
+            boolean shouldUpgrade = false;
+            Integer userId = user.getUserId().intValue();
+            Float userSeedingHours = torrentService.getUserSeedingHours(userId);
+            Float userShareRate = torrentService.getUserShareRate(userId);
+            Long userUploadCount = torrentService.getUserUploadCount(userId);
+            shouldUpgrade = switch (user.getLevel().intValue()) {
+                case 1 -> // upgrade to cookie
+                        userService.hasConsecutiveLoginDays(user.getUserId());
+                case 2 -> // upgrade to chocolate
+                        userShareRate >= 0.5 && userUploadCount >= 10 * 1024 * 1024 && userSeedingHours >= 72;
+                case 3 -> // upgrade to ice-cream
+                        userShareRate >= 0.75 && userUploadCount >= 50 * 1024 * 1024 && userSeedingHours >= 240;
+                default -> false;
+            };
+            if (shouldUpgrade) {
+                TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
+                try {
+                    user.setLevel(user.getLevel() + 1);
+                    userRepository.save(user);
+                    transactionManager.commit(status);
+                } catch (Exception e) {
+                    transactionManager.rollback(status);
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+}
diff --git a/src/main/java/com/example/myproject/service/PlaylistService.java b/src/main/java/com/example/myproject/service/PlaylistService.java
new file mode 100644
index 0000000..cfeb453
--- /dev/null
+++ b/src/main/java/com/example/myproject/service/PlaylistService.java
@@ -0,0 +1,60 @@
+package com.example.myproject.service;
+
+import java.util.List;
+
+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 org.springframework.data.domain.Page;
+
+public interface PlaylistService {
+
+    /**
+     * 创建片单
+     *
+     * @param request 创建片单请求DTO
+     * @return 创建的片单实体
+     */
+    PaidPlaylistEntity createPlaylist(CreatePlaylistRequest request);
+
+    /**
+     * 更新片单
+     *
+     * @param request 更新片单请求DTO
+     * @return 更新后的片单实体
+     */
+    PaidPlaylistEntity updatePlaylist(UpdatedPlaylistRequest request);
+
+    /**
+     * 删除片单
+     *
+     * @param ids 片单ID列表
+     */
+    void removePlaylists(List<Long> ids);
+
+    /**
+     * 分页查询片单
+     *
+     * @param request 查询片单请求DTO
+     * @return 分页结果
+     */
+    Page<PagePlaylistDto> getPlaylists(QueryPlaylistRequest request);
+
+    /**
+     * 支付片单
+     *
+     * @param playlistId 片单ID
+     */
+    void payPlaylist(Long playlistId);
+
+    /**
+     * 获取片单详情
+     *
+     * @param playlistId 片单ID
+     * @return 片单详情DTO
+     */
+    PlaylistDetail getDetail(Long playlistId);
+}
diff --git a/src/main/java/com/example/myproject/service/TorrentService.java b/src/main/java/com/example/myproject/service/TorrentService.java
index 700b8ea..6444822 100644
--- a/src/main/java/com/example/myproject/service/TorrentService.java
+++ b/src/main/java/com/example/myproject/service/TorrentService.java
@@ -73,6 +73,15 @@
     boolean checkUserUploadRatio(Long userId);
     
 //    double calculateDownloadSize(Long torrentId, Long userId);
+
+    /**
+     * 查看该种子是否在付费片单里,不在就返回true,如果在就看该用户是否支付了该付费片单
+     *
+     * @param torrentID 种子id
+     */
+    boolean isPaid(Long torrentID);
+
+
     
 
 
@@ -93,4 +102,30 @@
     List<TorrentEntity> getTorrentsByCategory(String category);
 
     void incrementDownloadCount(Long torrentId);
+    /**
+     * 查看是否需要付费
+     */
+    boolean needPay(Long torrentID);
+    /**
+     * 获取用户的分享率
+     * @param userId 用户ID
+     * @return 分享率(上传量/下载量)
+     */
+    Float getUserShareRate(Integer userId);
+
+    /**
+     * 获取用户的上传量
+     * @param userId 用户ID
+     * @return 上传量(字节)
+     */
+    Long getUserUploadCount(Integer userId);
+
+    /**
+     * 获取用户的做种时长
+     * @param userId 用户ID
+     * @return 做种时长(小时)
+     */
+    Float getUserSeedingHours(Integer userId);
+
 }
+
diff --git a/src/main/java/com/example/myproject/service/UserService.java b/src/main/java/com/example/myproject/service/UserService.java
index 7f0b48a..ca16ddf 100644
--- a/src/main/java/com/example/myproject/service/UserService.java
+++ b/src/main/java/com/example/myproject/service/UserService.java
@@ -334,6 +334,7 @@
 import com.example.myproject.entity.Users;
 import com.example.myproject.entity.UserInviteCode;
 import com.example.myproject.repository.FriendRelationRepository;
+import com.example.myproject.repository.LoginLogRepository;
 import com.example.myproject.repository.UserRepository;
 import com.example.myproject.repository.UserInviteCodeRepository;
 import jakarta.transaction.Transactional;
@@ -344,6 +345,7 @@
 import javax.servlet.http.HttpServletRequest;
 import java.io.File;
 import java.io.IOException;
+import java.math.BigDecimal;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -363,6 +365,11 @@
 
     @Autowired
     private FriendRelationRepository friendRelationRepository;
+    private final LoginLogRepository loginLogRepository;
+
+    public UserService(LoginLogRepository loginLogRepository) {
+        this.loginLogRepository = loginLogRepository;
+    }
 
     // 生成邀请码
     public Map<String, Object> generateInviteCode(Long userId) {
@@ -445,8 +452,8 @@
             return "邀请码无效或已被使用";
         }
 
-        // 设置默认等级为2(由于邀请码有效)
-        Long level = 2L;
+        // 设置默认等级为1(由于邀请码有效)
+        Long level = 1L;
 
         // 设置默认头像 URL
         String avatarUrl = "https://example.com/default-avatar.jpg";  // 默认头像
@@ -722,13 +729,56 @@
         if (user == null) {
             return "用户不存在";
         }
-        // 累加money
-        Integer currentMoney = user.getMoney() == null ? 0 : user.getMoney();
-        user.setMoney(currentMoney + amount);
+        // 处理money为BigDecimal
+        BigDecimal currentMoney = user.getMoney() == null ? BigDecimal.ZERO : user.getMoney();
+        BigDecimal rechargeAmount = new BigDecimal(amount);
+        BigDecimal newMoney = currentMoney.add(rechargeAmount);
+
+        user.setMoney(newMoney);
         userRepository.save(user);
         return "充值成功,当前余额:" + user.getMoney();
     }
 
 
+    public Users getById(Long userId) {
+        return userRepository.findById(userId).orElse(null);
+    }
+
+
+    /**
+     * 检查用户是否有连续三天的登录记录
+     * @param userId 用户ID
+     * @return 是否有连续三天的登录记录
+     */
+    public boolean hasConsecutiveLoginDays(Long userId) {
+        // 获取用户的所有登录日期
+        List<LocalDateTime> loginDates = loginLogRepository.findDistinctLoginDates(userId);
+
+        if (loginDates.size() < 3) {
+            return false;
+        }
+
+        // 将日期转换为LocalDate并排序
+        List<LocalDate> dates = loginDates.stream()
+                .map(LocalDateTime::toLocalDate)
+                .distinct()
+                .sorted()
+                .toList();
+
+        // 检查是否有连续的三天
+        for (int i = 0; i < dates.size() - 2; i++) {
+            LocalDate date1 = dates.get(i);
+            LocalDate date2 = dates.get(i + 1);
+            LocalDate date3 = dates.get(i + 2);
+
+            if (date2.equals(date1.plusDays(1)) && date3.equals(date2.plusDays(1))) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+
 
 }
diff --git a/src/main/java/com/example/myproject/service/serviceImpl/PlaylistServiceImpl.java b/src/main/java/com/example/myproject/service/serviceImpl/PlaylistServiceImpl.java
new file mode 100644
index 0000000..d241dcf
--- /dev/null
+++ b/src/main/java/com/example/myproject/service/serviceImpl/PlaylistServiceImpl.java
@@ -0,0 +1,195 @@
+package com.example.myproject.service.serviceImpl;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import javax.persistence.criteria.Predicate;
+
+import com.example.myproject.entity.*;
+import com.example.myproject.mapper.TorrentMapper;
+import com.example.myproject.repository.UserRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.BeanUtils;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.StringUtils;
+
+import com.example.myproject.dto.CreatePlaylistRequest;
+import com.example.myproject.exception.BusinessException;
+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.repository.PaidPlaylistEntityRepository;
+import com.example.myproject.repository.PaidPlaylistSeedEntityRepository;
+import com.example.myproject.repository.UserPaidPlaylistEntityRepository;
+import com.example.myproject.service.PlaylistService;
+
+import cn.dev33.satoken.stp.StpUtil;
+
+@Service
+@RequiredArgsConstructor
+public class PlaylistServiceImpl implements PlaylistService {
+
+    private final PaidPlaylistEntityRepository paidPlaylistRepository;
+    private final UserPaidPlaylistEntityRepository userPaidPlaylistEntityRepository;
+    private final PaidPlaylistSeedEntityRepository paidPlaylistSeedEntityRepository;
+    private final TorrentMapper torrentMapper;
+    private final UserRepository userRepository;
+
+
+    @Override
+    @Transactional
+    public PaidPlaylistEntity createPlaylist(CreatePlaylistRequest request) {
+        PaidPlaylistEntity paidPlaylist = new PaidPlaylistEntity();
+        BeanUtils.copyProperties(request, paidPlaylist);
+        paidPlaylist.setCreatedAt(LocalDateTime.now());
+        paidPlaylist.setUpdatedAt(LocalDateTime.now());
+        PaidPlaylistEntity savedPlaylist = paidPlaylistRepository.save(paidPlaylist);
+
+        return addPlaylistTorrents(request, savedPlaylist);
+    }
+
+
+    @Override
+    @Transactional
+    public PaidPlaylistEntity updatePlaylist(UpdatedPlaylistRequest request) {
+        PaidPlaylistEntity existingPlaylist = paidPlaylistRepository.findById(request.getId())
+                .orElseThrow(() -> new BusinessException("片单不存在"));
+
+        BeanUtils.copyProperties(request, existingPlaylist);
+        existingPlaylist.setUpdatedAt(LocalDateTime.now());
+        PaidPlaylistEntity updatedPlaylist = paidPlaylistRepository.save(existingPlaylist);
+
+        // 删除旧的关联种子
+        paidPlaylistSeedEntityRepository.deleteByPlaylistId(request.getId());
+
+        // 添加新的关联种子
+        return addPlaylistTorrents(request, updatedPlaylist);
+    }
+
+    @Override
+    @Transactional
+    public void removePlaylists(List<Long> ids) {
+        paidPlaylistRepository.deleteAllByIdInBatch(ids);
+        ids.forEach(paidPlaylistSeedEntityRepository::deleteByPlaylistId);
+    }
+
+    @Override
+    public Page<PagePlaylistDto> getPlaylists(QueryPlaylistRequest request) {
+        // 构建查询条件
+        Specification<PaidPlaylistEntity> spec = (root, query, criteriaBuilder) -> {
+            List<Predicate> predicates = new ArrayList<>();
+            Optional.ofNullable(request.getId())
+                    .ifPresent(id -> predicates.add(criteriaBuilder.equal(root.get("id"), id)));
+            Optional.ofNullable(request.getTitle()).ifPresent(title -> {
+                if (StringUtils.hasText(title)) {
+                    predicates.add(criteriaBuilder.like(root.get("title"), "%" + title + "%"));
+                }
+            });
+            Optional.ofNullable(request.getPrice())
+                    .ifPresent(price -> predicates.add(criteriaBuilder.equal(root.get("price"), price)));
+            Optional.ofNullable(request.getDescription()).ifPresent(description -> {
+                if (StringUtils.hasText(description)) {
+                    predicates.add(criteriaBuilder.like(root.get("description"), "%" + description + "%"));
+                }
+            });
+            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
+        };
+
+        // 分页查询
+        Pageable pageable = PageRequest.of(request.getPage() - 1, request.getSize());
+        Page<PaidPlaylistEntity> playlistPage = paidPlaylistRepository.findAll(spec, pageable);
+
+        Long userId = StpUtil.getLoginIdAsLong();
+
+        return playlistPage.map(paidPlaylistEntity -> {
+            PagePlaylistDto dto = new PagePlaylistDto();
+            BeanUtils.copyProperties(paidPlaylistEntity, dto);
+            // 检查用户是否已支付
+            boolean isPaid = userPaidPlaylistEntityRepository
+                    .findByUserIdAndPlaylistId(userId, paidPlaylistEntity.getId()).isPresent();
+            dto.setIsPaid(isPaid);
+            return dto;
+        });
+    }
+
+    @Override
+    @Transactional
+    public void payPlaylist(Long playlistId) {
+        Long userId = StpUtil.getLoginIdAsLong();
+        PaidPlaylistEntity playlist = paidPlaylistRepository.findById(playlistId)
+                .orElseThrow(() -> new BusinessException("片单不存在"));
+
+        if (userPaidPlaylistEntityRepository.findByUserIdAndPlaylistId(userId, playlistId).isPresent()) {
+            throw new BusinessException("您已购买此片单");
+        }
+
+        Users u = userRepository.findById(userId)
+                .orElseThrow(() -> new BusinessException("用户不存在"));
+        if (u.getMoney().compareTo(playlist.getPrice()) < 0) {
+            throw new BusinessException("余额不足,无法支付片单");
+        }
+
+        u.setMoney(u.getMoney().subtract(playlist.getPrice()));
+        userRepository.save(u);
+
+        UserPaidPlaylistEntity userPaidPlaylist = new UserPaidPlaylistEntity();
+        userPaidPlaylist.setUserId(userId);
+        userPaidPlaylist.setPlaylistId(playlistId);
+        userPaidPlaylist.setPaidAt(LocalDateTime.now());
+        userPaidPlaylistEntityRepository.save(userPaidPlaylist);
+    }
+
+    @Override
+    public PlaylistDetail getDetail(Long playlistId) {
+        Long userId = StpUtil.getLoginIdAsLong();
+        PaidPlaylistEntity playlist = paidPlaylistRepository.findById(playlistId)
+                .orElseThrow(() -> new BusinessException("片单不存在"));
+
+        // 检查用户是否已支付
+        boolean isPaid = userPaidPlaylistEntityRepository.findByUserIdAndPlaylistId(userId, playlistId).isPresent();
+        if (!isPaid) {
+            throw new BusinessException("您未购买此片单,无法查看详情");
+        }
+
+        PlaylistDetail detail = new PlaylistDetail();
+        BeanUtils.copyProperties(playlist, detail);
+
+        // 获取关联的种子列表
+        List<Long> seedIds = paidPlaylistSeedEntityRepository.findByPlaylistId(playlistId).stream()
+                .map(PaidPlaylistSeedEntity::getSeedId)
+                .toList();
+
+        if (!seedIds.isEmpty()) {
+
+            List<TorrentEntity> torrents = torrentMapper.selectBatchIds(seedIds);
+            detail.setTorrentList(torrents);
+        }
+
+        return detail;
+    }
+
+    private PaidPlaylistEntity addPlaylistTorrents(CreatePlaylistRequest request, PaidPlaylistEntity savedPlaylist) {
+        if (request.getTorrentList() != null && !request.getTorrentList().isEmpty()) {
+            List<PaidPlaylistSeedEntity> playlistSeeds = request.getTorrentList().stream()
+                    .map(torrentId -> {
+                        PaidPlaylistSeedEntity seed = new PaidPlaylistSeedEntity();
+                        seed.setPlaylistId(savedPlaylist.getId());
+                        seed.setSeedId(torrentId);
+                        seed.setCreatedAt(LocalDateTime.now());
+                        return seed;
+                    })
+                    .collect(Collectors.toList());
+            paidPlaylistSeedEntityRepository.saveAll(playlistSeeds);
+        }
+        return savedPlaylist;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/myproject/service/serviceImpl/TorrentServiceImpl.java b/src/main/java/com/example/myproject/service/serviceImpl/TorrentServiceImpl.java
index cf97746..1900975 100644
--- a/src/main/java/com/example/myproject/service/serviceImpl/TorrentServiceImpl.java
+++ b/src/main/java/com/example/myproject/service/serviceImpl/TorrentServiceImpl.java
@@ -298,6 +298,11 @@
 package com.example.myproject.service.serviceImpl;
 import java.time.Duration;
 
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.example.myproject.entity.PaidPlaylistSeedEntity;
 import com.example.myproject.entity.TorrentEntity;
 import com.example.myproject.entity.TorrentReport;
 import com.example.myproject.entity.User;
@@ -306,6 +311,7 @@
 import com.example.myproject.mapper.TorrentMapper;
 import com.example.myproject.mapper.UserMapper;
 import com.example.myproject.repository.TorrentReportRepository;
+import com.example.myproject.repository.UserRepository;
 import com.example.myproject.service.AuditService;
 import com.example.myproject.service.TorrentService;
 import com.example.myproject.service.PromotionService;
@@ -320,7 +326,10 @@
 import com.turn.ttorrent.tracker.Tracker;
 import com.turn.ttorrent.tracker.TrackedTorrent;
 import com.example.myproject.common.base.Result;
+import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import com.example.myproject.repository.PaidPlaylistSeedEntityRepository;
+import com.example.myproject.repository.UserPaidPlaylistEntityRepository;
 
 
 import org.springframework.beans.factory.annotation.Autowired;
@@ -332,16 +341,16 @@
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.time.LocalDateTime;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
+import java.util.*;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
 @Slf4j
 @Service
+@RequiredArgsConstructor
 public class TorrentServiceImpl implements TorrentService {
+    private final PaidPlaylistSeedEntityRepository paidPlaylistSeedEntityRepository;
+    private final UserPaidPlaylistEntityRepository userPaidPlaylistEntityRepository;
     @Autowired
     private Tracker tracker;
 
@@ -355,6 +364,8 @@
 
 
     @Autowired
+    private UserRepository userRepository;
+    @Autowired
     private UserMapper userMapper;
 
     @Autowired
@@ -370,7 +381,42 @@
 
     @Override
     public List<TorrentEntity> search(TorrentParam param) {
-        return torrentMapper.search(param);
+        QueryWrapper<TorrentEntity> wrapper = new QueryWrapper<>();
+
+        // 分类筛选
+        if (StringUtils.isNotBlank(param.getCategory())) {
+            wrapper.eq("category", param.getCategory());
+        }
+
+        // 模糊查询:title like %keyword%
+        if (param.getLikeExpressions() != null && !param.getLikeExpressions().isEmpty()) {
+            wrapper.and(w -> {
+                for (String keyword : param.getLikeExpressions()) {
+                    w.or().like("title", keyword);
+                }
+            });
+        }
+
+        // 排序处理
+        String orderKey = param.getOrderKey();
+        Boolean orderDesc = param.getOrderDesc();
+
+        if (StringUtils.isNotBlank(orderKey)) {
+            String underlineKey = StrUtil.toUnderlineCase(orderKey);
+            List<String> allowedFields = param.getOrderKey(TorrentEntity.class);
+
+            if (allowedFields.contains(underlineKey)) {
+                if (Boolean.TRUE.equals(orderDesc)) {
+                    wrapper.orderByDesc(underlineKey);
+                } else {
+                    wrapper.orderByAsc(underlineKey);
+                }
+            }
+        } else {
+            wrapper.orderByDesc("create_time");
+        }
+
+        return torrentMapper.selectList(wrapper);
     }
 
     @Override
@@ -615,6 +661,162 @@
         torrentMapper.incrementDownloadCount(torrentId);
     }
 
+    @Override
+    public boolean isPaid(Long torrentID) {
+        List<PaidPlaylistSeedEntity> bySeedId = paidPlaylistSeedEntityRepository.findBySeedId(torrentID);
 
 
+        // 不是付费种子
+        if(bySeedId.isEmpty()){
+            return true;
+        }
+        // 检查用户是否购买了对应的付费片单
+        long userId = StpUtil.getLoginIdAsLong();
+        return bySeedId.stream().map(PaidPlaylistSeedEntity::getPlaylistId).anyMatch(p -> userPaidPlaylistEntityRepository.existsByUserIdAndPlaylistId(
+                userId, p)) ;
+    }
+    @Override
+    public boolean needPay(Long torrentID) {
+        return !paidPlaylistSeedEntityRepository.findBySeedId(torrentID).isEmpty();
+    }
+    @Override
+    public Float getUserSeedingHours(Integer userId) {
+        List<TorrentReport> reports = torrentReportRepository.findByUserId(userId);
+        if (reports.isEmpty()) {
+            return 0.0f;
+        }
+
+        // 按种子和peer分组
+        Map<String, List<TorrentReport>> peerReports = new HashMap<>();
+        for (TorrentReport report : reports) {
+            String key = report.getInfoHash() + "_" + report.getPeerId();
+            peerReports.computeIfAbsent(key, k -> new ArrayList<>()).add(report);
+        }
+
+        float totalHours = 0.0f;
+        LocalDateTime now = LocalDateTime.now();
+
+        // 处理每个peer的做种记录
+        for (List<TorrentReport> peerReportList : peerReports.values()) {
+            // 按时间排序
+            peerReportList.sort((a, b) -> a.getReportTime().compareTo(b.getReportTime()));
+
+            LocalDateTime seedingStart = null;
+            boolean isSeeding = false;
+
+            for (int i = 0; i < peerReportList.size(); i++) {
+                TorrentReport report = peerReportList.get(i);
+                String event = report.getEvent();
+
+                // 判断是否在做种:left为0表示下载完成,可以开始做种
+                boolean canSeed = report.getLeft() == 0;
+
+                if (canSeed) {
+                    if (!isSeeding) {
+                        // 开始做种
+                        seedingStart = report.getReportTime();
+                        isSeeding = true;
+                    }
+                } else {
+                    if (isSeeding) {
+                        // 停止做种
+                        long minutes = java.time.Duration.between(seedingStart, report.getReportTime()).toMinutes();
+                        totalHours += minutes / 60.0f;
+                        isSeeding = false;
+                        seedingStart = null;
+                    }
+                }
+
+                // 处理客户端重启的情况
+                if ("started".equals(event) && canSeed) {
+                    // 如果是新会话且可以做种,开始计时
+                    if (!isSeeding) {
+                        seedingStart = report.getReportTime();
+                        isSeeding = true;
+                    }
+                } else if ("stopped".equals(event) && isSeeding) {
+                    // 如果是停止事件且正在做种,结束计时
+                    long minutes = java.time.Duration.between(seedingStart, report.getReportTime()).toMinutes();
+                    totalHours += minutes / 60.0f;
+                    isSeeding = false;
+                    seedingStart = null;
+                }
+            }
+
+            // 如果最后一个状态是正在做种,计算到当前时间的时长
+            if (isSeeding && seedingStart != null) {
+                long minutes = java.time.Duration.between(seedingStart, now).toMinutes();
+                totalHours += minutes / 60.0f;
+            }
+        }
+
+        return totalHours;
+    }
+    @Override
+    public Long getUserUploadCount(Integer userId) {
+        // 获取用户所有种子的最新上报记录
+        List<TorrentReport> reports = torrentReportRepository.findByUserId(userId);
+        if (reports.isEmpty()) {
+            return 0L;
+        }
+
+        // 按种子分组,获取每个种子的最新记录
+        Map<String, TorrentReport> latestReports = new HashMap<>();
+        for (TorrentReport report : reports) {
+            String key = report.getInfoHash();
+            TorrentReport existing = latestReports.get(key);
+            if (existing == null || report.getReportTime().isAfter(existing.getReportTime())) {
+                latestReports.put(key, report);
+            }
+        }
+
+        // 计算总上传量
+        long totalUploaded = 0;
+        for (TorrentReport report : latestReports.values()) {
+            // 只统计活跃的种子(非stopped状态)
+            if (!"stopped".equals(report.getEvent())) {
+                totalUploaded += report.getUploaded();
+            }
+        }
+
+        return totalUploaded;
+    }
+
+    @Override
+    public Float getUserShareRate(Integer userId) {
+        // 获取用户所有种子的最新上报记录
+        List<TorrentReport> reports = torrentReportRepository.findByUserId(userId);
+        if (reports.isEmpty()) {
+            return 0.0f;
+        }
+
+        // 按种子分组,获取每个种子的最新记录
+        Map<String, TorrentReport> latestReports = new HashMap<>();
+        for (TorrentReport report : reports) {
+            String key = report.getInfoHash();
+            TorrentReport existing = latestReports.get(key);
+            if (existing == null || report.getReportTime().isAfter(existing.getReportTime())) {
+                latestReports.put(key, report);
+            }
+        }
+
+        // 计算总上传量和下载量
+        long totalUploaded = 0;
+        long totalDownloaded = 0;
+        for (TorrentReport report : latestReports.values()) {
+            // 只统计活跃的种子(非stopped状态)
+            if (!"stopped".equals(report.getEvent())) {
+                totalUploaded += report.getUploaded();
+                totalDownloaded += report.getDownloaded();
+            }
+        }
+
+        // 防止除以零
+        if (totalDownloaded == 0) {
+            return totalUploaded > 0 ? Float.POSITIVE_INFINITY : 0.0f;
+        }
+
+        return (float) totalUploaded / totalDownloaded;
+    }
+
 }
\ No newline at end of file
diff --git a/src/main/java/com/example/myproject/utils/Result.java b/src/main/java/com/example/myproject/utils/Result.java
index e46f828..9ebe740 100644
--- a/src/main/java/com/example/myproject/utils/Result.java
+++ b/src/main/java/com/example/myproject/utils/Result.java
@@ -128,4 +128,5 @@
         return result;
     }
 
+
 }