作弊&促销
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