增加付费片单,修复种子列表搜索排序
Change-Id: Ib645906c0f240f954676790daf2ff0e5f16f6e0a
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