作弊&促销

Change-Id: Ibdee947370e11c3a98912569e1a290b5e0968fbe
diff --git a/src/main/java/com/example/myproject/service/AuditService.java b/src/main/java/com/example/myproject/service/AuditService.java
new file mode 100644
index 0000000..22d7124
--- /dev/null
+++ b/src/main/java/com/example/myproject/service/AuditService.java
@@ -0,0 +1,46 @@
+package com.example.myproject.service;
+
+import com.example.myproject.entity.AuditRecord;
+import com.example.myproject.entity.BannedUser;
+import com.example.myproject.entity.SuspiciousUser;
+import com.example.myproject.mapper.AuditRecordMapper;
+import com.example.myproject.mapper.BannedUserMapper;
+import com.example.myproject.mapper.SuspiciousUserMapper;
+import org.checkerframework.checker.units.qual.A;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+@Service
+public class AuditService {
+    @Autowired
+    AuditRecordMapper auditRecordMapper;
+    @Autowired
+    SuspiciousUserMapper suspiciousUserMapper;
+    @Autowired
+    BannedUserMapper bannedUserMapper;
+    public void flagTorrentForReview(Long torrentId, double upload, double download) {
+        AuditRecord record = new AuditRecord();
+        record.setTorrentId(torrentId);
+        record.setUpload(upload);
+        record.setDownload(download);
+        record.setCreateTime(LocalDateTime.now());
+
+        auditRecordMapper.insert(record);
+    }
+    public void addSuspiciousUser(Long userId, String reason, double speedKBs) {
+        SuspiciousUser user = new SuspiciousUser();
+        user.setUserId(userId);
+        user.setReason(reason);
+        user.setSpeedKBs(speedKBs);
+        suspiciousUserMapper.insert(user);
+    }
+
+
+    public void banUser(Long userId, String reason) {
+        BannedUser bannedUser = new BannedUser();
+        bannedUser.setUserId(userId);
+        bannedUser.setReason(reason);
+        bannedUserMapper.insert(bannedUser);
+    }
+}
diff --git a/src/main/java/com/example/myproject/service/PromotionService.java b/src/main/java/com/example/myproject/service/PromotionService.java
index 087c13b..b83bce5 100644
--- a/src/main/java/com/example/myproject/service/PromotionService.java
+++ b/src/main/java/com/example/myproject/service/PromotionService.java
@@ -13,5 +13,9 @@
     
     void deletePromotion(Long promotionId);
     
-    double getCurrentDiscount(Long torrentId);
+//    double getCurrentDiscount(Long torrentId);
+    double getUploadBonus(Long torrentId);
+    double getDownloadDiscount(Long torrentId);
+
+
 } 
\ No newline at end of file
diff --git a/src/main/java/com/example/myproject/service/TorrentService.java b/src/main/java/com/example/myproject/service/TorrentService.java
index 36891d2..e0a4f41 100644
--- a/src/main/java/com/example/myproject/service/TorrentService.java
+++ b/src/main/java/com/example/myproject/service/TorrentService.java
@@ -57,7 +57,9 @@
     byte[] fetch(Long seedId, String passkey) throws IOException;
     
     Result favorite(Long seedId, Long userId);
-    
+    Result getMyfavorite(Long userId);
+
+
     void deleteTorrent(Long seedId);
     
     void updateTorrent(Long seedId, TorrentUpdateDTO updateDTO);
@@ -68,9 +70,12 @@
     
     boolean checkUserUploadRatio(Long userId);
     
-    double calculateDownloadSize(Long torrentId, Long userId);
+//    double calculateDownloadSize(Long torrentId, Long userId);
     
-    void recordDownload(Long torrentId, Long userId, double downloadSize);
+
 
     TorrentEntity selectByInfoHash(String infoHash);
+
+    void processUploadDownload(Long userId, String peerId, String infoHash, Long torrentId, double uploaded, double downloaded);
+
 }
diff --git a/src/main/java/com/example/myproject/service/serviceImpl/PromotionServiceImpl.java b/src/main/java/com/example/myproject/service/serviceImpl/PromotionServiceImpl.java
index 9d34cbc..1cac3a4 100644
--- a/src/main/java/com/example/myproject/service/serviceImpl/PromotionServiceImpl.java
+++ b/src/main/java/com/example/myproject/service/serviceImpl/PromotionServiceImpl.java
@@ -1,10 +1,14 @@
 package com.example.myproject.service.serviceImpl;
 
 import com.example.myproject.entity.Promotion;
+import com.example.myproject.entity.TorrentEntity;
 import com.example.myproject.mapper.PromotionMapper;
 import com.example.myproject.service.PromotionService;
 import com.example.myproject.dto.PromotionCreateDTO;
 
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.swagger.models.auth.In;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -40,10 +44,18 @@
         promotion.setDiscountPercentage(promotionDTO.getDiscountPercentage());
 
         // 把List<Long>转换成逗号分隔字符串
-        String applicableTorrentIdsStr = promotionDTO.getApplicableTorrentIds().stream()
-                .map(String::valueOf)
-                .collect(Collectors.joining(","));
-        promotion.setApplicableTorrentIds(applicableTorrentIdsStr);
+//        String applicableTorrentIdsStr = promotionDTO.getApplicableTorrentIds().stream()
+//                .map(String::valueOf)
+//                .collect(Collectors.joining(","));
+//        promotion.setApplicableTorrentIds(applicableTorrentIdsStr);
+        // 把List<Long>转换成JSON字符串
+        ObjectMapper objectMapper = new ObjectMapper();
+        try {
+            String applicableTorrentIdsJson = objectMapper.writeValueAsString(promotionDTO.getApplicableTorrentIds());
+            promotion.setApplicableTorrentIds(applicableTorrentIdsJson);
+        } catch (JsonProcessingException e) {
+            throw new RuntimeException("适用种子ID序列化失败", e);
+        }
 
         promotion.setCreateTime(now);
         promotion.setUpdateTime(now);
@@ -82,17 +94,17 @@
         promotionMapper.updateById(promotion);
     }
 
-    @Override
-    public double getCurrentDiscount(Long torrentId) {
-        LocalDateTime now = LocalDateTime.now();
-        List<Promotion> activePromotions = promotionMapper.findActivePromotionsForTorrent(torrentId, now);
-
-        // 如果有多个促销活动,取折扣最大的
-        return activePromotions.stream()
-                .mapToDouble(Promotion::getDiscountPercentage)
-                .max()
-                .orElse(0.0);
-    }
+//    @Override
+//    public double getCurrentDiscount(Long torrentId) {
+//        LocalDateTime now = LocalDateTime.now();
+//        List<Promotion> activePromotions = promotionMapper.findActivePromotionsForTorrent(torrentId, now);
+//
+//        // 如果有多个促销活动,取折扣最大的
+//        return activePromotions.stream()
+//                .mapToDouble(Promotion::getDiscountPercentage)
+//                .max()
+//                .orElse(0.0);
+//    }
 
     /**
      * 验证种子ID是否存在
@@ -111,4 +123,48 @@
             throw new RuntimeException("以下种子ID不存在: " + invalidIds);
         }
     }
+    @Override
+    public double getUploadBonus(Long torrentId) {
+        double uploadBonusPercentage = getUploadBonusPercentage(torrentId); // 例如 26.4
+        return 1.0 + (uploadBonusPercentage / 100.0); // 转换为倍数,例如 1.264
+    }
+
+    @Override
+    public double getDownloadDiscount(Long torrentId) {
+        double downloadDiscountPercentage = getDownloadDiscountPercentage(torrentId); // 例如 -20.0
+        return 1.0 + (downloadDiscountPercentage / 100.0); // -20.0 → 0.8
+    }
+
+
+    /**
+     * 获取当前种子适用的上传加成百分比(正数),若无返回 0.0
+     */
+    private double getUploadBonusPercentage(Long torrentId) {
+        LocalDateTime now = LocalDateTime.now();
+        List<Promotion> activePromotions = promotionMapper.findActivePromotionsForTorrent(torrentId, now);
+
+        // 取最大的正值加成百分比
+        return activePromotions.stream()
+                .mapToDouble(Promotion::getDiscountPercentage)
+                .filter(p -> p > 0)
+                .max()
+                .orElse(0.0);
+    }
+
+    /**
+     * 获取当前种子适用的下载折扣百分比(负数),若无返回 0.0
+     */
+    private double getDownloadDiscountPercentage(Long torrentId) {
+        LocalDateTime now = LocalDateTime.now();
+        List<Promotion> activePromotions = promotionMapper.findActivePromotionsForTorrent(torrentId, now);
+
+        // 取最小的负值折扣百分比(更大的折扣)
+        return activePromotions.stream()
+                .mapToDouble(Promotion::getDiscountPercentage)
+                .filter(p -> p < 0)
+                .min()
+                .orElse(0.0);
+    }
+
+
 }
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 4870fd5..210db93 100644
--- a/src/main/java/com/example/myproject/service/serviceImpl/TorrentServiceImpl.java
+++ b/src/main/java/com/example/myproject/service/serviceImpl/TorrentServiceImpl.java
@@ -296,14 +296,19 @@
 // }
 
 package com.example.myproject.service.serviceImpl;
+import java.time.Duration;
 
 import cn.dev33.satoken.stp.StpUtil;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.example.myproject.entity.SuspiciousUser;
 import com.example.myproject.entity.TorrentEntity;
+import com.example.myproject.entity.TorrentReport;
 import com.example.myproject.entity.User;
+import com.example.myproject.mapper.BannedUserMapper;
+import com.example.myproject.mapper.SuspiciousUserMapper;
 import com.example.myproject.mapper.TorrentMapper;
 import com.example.myproject.mapper.UserMapper;
+import com.example.myproject.repository.TorrentReportRepository;
+import com.example.myproject.service.AuditService;
 import com.example.myproject.service.TorrentService;
 import com.example.myproject.service.PromotionService;
 import com.example.myproject.dto.param.TorrentParam;
@@ -315,13 +320,14 @@
 import com.turn.ttorrent.client.SimpleClient;
 import com.turn.ttorrent.common.TorrentMetadata;
 import com.turn.ttorrent.common.TorrentParser;
-import com.turn.ttorrent.common.creation.MetadataBuilder;
 import com.turn.ttorrent.tracker.Tracker;
 import com.turn.ttorrent.tracker.TrackedTorrent;
 import com.example.myproject.common.base.Result;
+import lombok.extern.slf4j.Slf4j;
 
-import com.turn.ttorrent.tracker.TrackedTorrent;
+
 import org.apache.commons.codec.binary.Hex;
+import org.checkerframework.checker.units.qual.A;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -331,19 +337,18 @@
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.net.InetAddress;
-import java.nio.file.Files;
 import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.StandardCopyOption;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
-import java.time.LocalTime;
+import java.time.LocalDateTime;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
+@Slf4j
 @Service
 public class TorrentServiceImpl implements TorrentService {
     @Autowired
@@ -352,6 +357,9 @@
     @Autowired
     private TorrentMapper torrentMapper;
 
+    @Autowired
+    private TorrentReportRepository torrentReportRepository;
+
     private final Map<String, TrackedTorrent> torrentRegistry = new HashMap<>();
 
 
@@ -360,6 +368,12 @@
 
     @Autowired
     private PromotionService promotionService;
+    @Autowired
+    SuspiciousUserMapper suspiciousUserMapper;
+    @Autowired
+    BannedUserMapper bannedUserMapper;
+    @Autowired
+    AuditService auditService;
 
     private static final double MIN_UPLOAD_RATIO = 0.5; // 最小上传比例要求
 
@@ -372,6 +386,7 @@
     public TorrentEntity selectBySeedId(Long seedId) {
         return torrentMapper.selectById(seedId);
     }
+
     private final ExecutorService seederExecutor = Executors.newCachedThreadPool();
 
     @Override
@@ -389,7 +404,7 @@
         Long size = metadata.getFiles().stream().map(f -> f.size).reduce(0L, Long::sum);
 
         // 保存种子信息
-        TorrentEntity entity= new TorrentEntity();
+        TorrentEntity entity = new TorrentEntity();
         entity.setUploader(param.getUploader());
         entity.setFileName(file.getOriginalFilename());
         entity.setSize(size);
@@ -404,7 +419,6 @@
     }
 
 
-
     @Override
     public byte[] fetch(Long seedId, String passkey) {
         TorrentEntity torrent = selectBySeedId(seedId);
@@ -422,6 +436,7 @@
 // TODO: 对应本机应用地址
 //            String announce = "http://127.0.0.1:5011/seeds/announce?passkey="+passkey +"&userId=" + StpUtil.getLoginIdAsString();
             String announce = "http://192.168.5.149:5011/seeds/announce?passkey=" + passkey + "&userId=" + StpUtil.getLoginIdAsString();
+//            String announce = "http://172.23.80.1:5011/seeds/announce?passkey=" + passkey + "&userId=" + 1;
 
             decoded.put("announce", new BEValue(announce));
             // 4. 编码成新的 .torrent 文件字节数组
@@ -452,6 +467,12 @@
     }
 
     @Override
+    public Result getMyfavorite(Long userId) {
+        List<TorrentEntity> favorites = torrentMapper.selectMyFavorite(userId);
+        return Result.ok(favorites);
+    }
+
+    @Override
     @Transactional
     public void deleteTorrent(Long seedId) {
         torrentMapper.deleteById(seedId);
@@ -507,58 +528,78 @@
         double uploadRatio = user.getUploaded() / (double) user.getDownloaded();
         return uploadRatio >= MIN_UPLOAD_RATIO;
     }
-    /**
-     * 启动做种客户端
-     */
-    private void startSeeding(Path torrentPath, Path dataDir) throws Exception {
-        SimpleClient seederClient = new SimpleClient();
-        seederClient.downloadTorrent(
-                torrentPath.toString(),
-                dataDir.toString(),
-                InetAddress.getLocalHost());
-        // 保持做种状态(阻塞线程)
-        while (true) {
-            Thread.sleep(60000); // 每60秒检查一次
-        }
-    }
 
 
     @Override
-    public double calculateDownloadSize(Long torrentId, Long userId) {
-        TorrentEntity torrent = selectBySeedId(torrentId);
-        if (torrent == null) {
-            throw new RuntimeException("种子不存在");
-        }
-
-        // 获取当前有效的促销活动
-        double discount = promotionService.getCurrentDiscount(torrentId);
-
-        // 计算实际下载量
-        return torrent.getSize() * (1 - discount / 100.0);
-    }
-
-    @Override
-    @Transactional
-    public void recordDownload(Long torrentId, Long userId, double downloadSize) {
-        // 更新用户下载量
-        userMapper.increaseDownloaded(userId, downloadSize);
-
-        // 更新种子下载次数
-        torrentMapper.increaseDownloads(torrentId);
-    }
-
-    @Override
     public TorrentEntity selectByInfoHash(String infoHash) {
         return torrentMapper.selectByInfoHash(infoHash);
     }
 
-    /**
-     * 计算种子文件的infoHash
-     */
-    private String calculateInfoHash(byte[] torrentData) throws NoSuchAlgorithmException  {
-        MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
-        sha1.update(torrentData);
-        byte[] hashBytes = sha1.digest();
-        return Hex.encodeHexString(hashBytes);
+    @Override
+    public void processUploadDownload(Long userId, String peerId, String infoHash, Long torrentId, double uploaded, double downloaded) {
+        // 查找该peerId和infoHash的最近一条TorrentReport
+        log.info("开始处理上传和下载");
+        Optional<TorrentReport> lastReportOpt = torrentReportRepository.findLatestByPeerIdAndInfoHash(peerId, infoHash);
+
+
+        double lastUploaded = 0;
+        double lastDownloaded = 0;
+        LocalDateTime lastTime = null;
+
+        if (lastReportOpt.isPresent()) {
+            lastUploaded = lastReportOpt.get().getUploaded();
+            lastDownloaded = lastReportOpt.get().getDownloaded();
+            lastTime = lastReportOpt.get().getReportTime();
+        }
+
+        double uploadedOffset = uploaded - lastUploaded;
+        double downloadedOffset = downloaded - lastDownloaded;
+
+        // 防止客户端重启或作弊导致负数,重置为0
+        if (uploadedOffset < 0) uploadedOffset = 0;
+        if (downloadedOffset < 0) downloadedOffset = 0;
+
+        /**
+         * 作弊——速度检测
+         */
+        if (lastTime != null) {
+            long seconds = Duration.between(lastTime, LocalDateTime.now()).getSeconds();
+            if (seconds > 0) {
+                double speedKBs = uploadedOffset / 1024.0 / seconds; // KB/s
+
+                double MAX_SPEED_KB = 10 * 1024;      // 10 MB/s
+                double SUSPICIOUS_SPEED_KB = 2 * 1024; // 2 MB/s
+                int MIN_EXPECTED_LEECHERS = 5;
+
+                //int leechers = torrentReportRepository.countActiveLeechers(infoHash);
+
+                if (speedKBs > MAX_SPEED_KB) {
+                    log.warn("用户 {} 上传速度 {} KB/s 超过限制,执行封禁处理", userId, speedKBs);
+                    auditService.banUser(userId, "上传速度超过最大限速");
+                    return; // 不再处理其他操作
+                }
+//                else if (speedKBs > SUSPICIOUS_SPEED_KB && leechers < MIN_EXPECTED_LEECHERS) {
+//                    log.warn("用户 {} 上传速度异常 {} KB/s,下载人数 {},加入怀疑名单", userId, speedKBs, leechers);
+//                    abnormalUserMapper.insertSuspicious(userId, speedKBs, leechers); // 你也需要实现
+//                }
+                else if (speedKBs > SUSPICIOUS_SPEED_KB) {
+                    log.warn("用户 {} 上传速度异常 {} KB/s, 加入怀疑名单", userId, speedKBs);
+                    auditService.addSuspiciousUser(userId, "上传速度异常:" ,speedKBs );
+                }
+            }
+
+            // 获取促销加成和下载折扣
+            double uploadBonus = promotionService.getUploadBonus(torrentId);       // 比如 1.2
+            log.info("上传优惠比例" + uploadBonus);
+            double downloadDiscount = promotionService.getDownloadDiscount(torrentId); // 比如 0.8
+
+            double bonusUploaded = uploadedOffset * uploadBonus;
+            double bonusDownloaded = downloadedOffset * downloadDiscount;
+
+            // 调用Mapper更新数据库
+            userMapper.updateUserUploadDownload(userId, bonusUploaded, bonusDownloaded);
+            torrentMapper.updateTorrentUploadDownload(torrentId, uploadedOffset, downloadedOffset);
+
+        }
     }
 }
\ No newline at end of file