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

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