增加付费片单,修复种子列表搜索排序
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;
}
+
}