完善tracker对服务器的适配
Change-Id: I0e74985cf50dc0683c48a69e4478b24d66453920
diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml
index 4944c97..5e1a872 100644
--- a/ruoyi-admin/pom.xml
+++ b/ruoyi-admin/pom.xml
@@ -21,7 +21,11 @@
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional> <!-- 表示依赖不会传递 -->
</dependency>
-
+ <dependency>
+ <groupId>commons-codec</groupId>
+ <artifactId>commons-codec</artifactId>
+ <version>1.15</version>
+ </dependency>
<!-- spring-doc -->
<dependency>
<groupId>org.springdoc</groupId>
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/Server/AnnounceService.java b/ruoyi-admin/src/main/java/com/ruoyi/web/Server/AnnounceService.java
index 37e2135..3420fd3 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/Server/AnnounceService.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/Server/AnnounceService.java
@@ -1,6 +1,7 @@
package com.ruoyi.web.Server;
import com.alibaba.fastjson.JSON;
+import com.ruoyi.web.Server.BT.TorrentService;
import com.ruoyi.web.dao.sys.UserDao;
import com.ruoyi.web.domain.BT.TorrentEntity;
import com.ruoyi.web.domain.BT.TorrentPeerEntity;
@@ -18,6 +19,7 @@
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
+import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -31,218 +33,213 @@
final UserDao userDao;
final UserService userService;
-
final TorrentPeerService torrentPeerService;
-
-
final ValidationManager validationManager;
+ final TorrentService torrentService;
public Map<String, Object> announce(AnnounceRequest request) {
-
- validationManager.validate(request);
-
- //TODO 处理短时间内重复的请求
-
- TorrentEntity torrent = request.getTorrent();
-
- //省略peer id
boolean noPeerId = request.isNoPeerId();
+ byte[] infoHash = request.getInfoHash();
+ TorrentEntity torrent = torrentService.getByInfoHash(infoHash);
+ request.setTorrent(torrent);
+ UserEntity user = userDao.findUserByPasskey(request.getPasskey());
+ request.setUser(user);
+ // 先更新peer状态再获取列表
+ updatePeer(request);
List<Map<String, Object>> peerList = getPeerList(request, 200);
-
- updatePeer(request, null);
-
updateUserInfo(request);
- // 返回peer列表给客户端
+ // 使用动态广播间隔
Integer interval = getAnnounceInterval(request);
- TrackerResponse trackerResponse = TrackerResponse.build(interval, 60, torrent.getSeeders(), torrent.getLeechers(), peerList);
+ TrackerResponse trackerResponse = TrackerResponse.build(
+ interval, 60,
+ torrent.getSeeders(),
+ torrent.getLeechers(),
+ peerList
+ );
return trackerResponse.toResultMap();
}
private void updateUserInfo(AnnounceRequest request) {
UserEntity user = request.getUser();
TorrentEntity torrent = request.getTorrent();
- Integer userId = request.getUser().getUser_id().intValue();
- Integer torrentId = request.getTorrent().getId();
+ Integer userId = user.getUserId().intValue();
+ Integer torrentId = torrent.getId();
+ byte[] peerId = request.getPeerId();
- TorrentPeerEntity peer = torrentPeerService.getPeer(userId, torrentId, request.getPeerId());
+ // 确保peer记录存在
+ TorrentPeerEntity peer = torrentPeerService.getPeer(userId, torrentId, peerId);
if (peer == null) {
- //TODO
peer = tryInsertOrUpdatePeer(request);
+ if (peer == null) return; // 创建失败则终止
}
+ // 处理负增量问题
long lastUploaded = peer.getUploaded();
- long lastDownload = peer.getDownloaded();
- long uploadedOffset = request.getUploaded() - lastUploaded;
- long downloadedOffset = request.getDownloaded() - lastDownload;
+ long lastDownloaded = peer.getDownloaded();
+ long currentUploaded = request.getUploaded();
+ long currentDownloaded = request.getDownloaded();
- LocalDateTime updateTime = torrent.getUpdateTime();
- if (uploadedOffset < 0) {
- uploadedOffset = request.getUploaded();
- }
- if (downloadedOffset < 0) {
- downloadedOffset = request.getDownloaded();
- }
+ long uploadedOffset = Math.max(currentUploaded - lastUploaded, 0);
+ long downloadedOffset = Math.max(currentDownloaded - lastDownloaded, 0);
- user.setReal_downloaded(Objects.nonNull(user.getReal_downloaded())? user.getReal_downloaded()+lastDownload:lastDownload);
- user.setReal_uploaded(Objects.nonNull(user.getReal_uploaded())?user.getReal_uploaded()+lastUploaded: lastUploaded);
-
- //TODO 优惠
- user.setUpload(user.getUpload() + uploadedOffset);
- user.setDownload(user.getDownload() + downloadedOffset);
- user.setSeedtime(user.getSeedtime() + (Instant.now().toEpochMilli() - updateTime.toInstant(ZoneOffset.UTC).toEpochMilli()));
- userService.updateById(user);
-
- //TODO 最后处理删除
- //TODO 校正BEP标准的计算方式
-
-
+ // 使用原子操作更新用户数据
+ userService.updateUserStats(
+ userId,
+ uploadedOffset,
+ downloadedOffset,
+ calculateSeedTimeIncrement(peer, request)
+ );
}
- public void updatePeer(AnnounceRequest request, TorrentPeerEntity peerSelf) {
+ // 精确计算做种时间增量(毫秒)
+ private long calculateSeedTimeIncrement(TorrentPeerEntity peer, AnnounceRequest request) {
+ if (!request.getSeeder()) return 0;
+
+ long now = System.currentTimeMillis();
+ long lastAnnounceTime = peer.getLastAnnounce().atZone(ZoneOffset.UTC).toInstant().toEpochMilli();
+ return now - lastAnnounceTime;
+ }
+
+ // 简化事件处理逻辑
+ public void updatePeer(AnnounceRequest request) {
String event = StringUtils.trimToEmpty(request.getEvent());
log.info("Peer event {}: {}", event, JSON.toJSONString(request, true));
- Integer userId = request.getUser().getUser_id().intValue();
+
+ Integer userId = userDao.findUserByPasskey(request.getPasskey()).getUserId().intValue();
Integer torrentId = request.getTorrent().getId();
- //TODO 加入分布式锁
-
- // 任务停止 删除peer
- if (AnnounceRequest.EventType.stopped.equalsIgnoreCase(event)) {
-
- // 只有当有peer存在的时候才执行删除操作
- if (torrentPeerService.peerExists(userId, torrentId, request.getPeerId())) {
- torrentPeerService.delete(userId, torrentId, request.getPeerIdHex());
- }
-
- return;
- } else if (AnnounceRequest.EventType.started.equalsIgnoreCase(event)) {
- tryInsertOrUpdatePeer(request);
-
- } else if (AnnounceRequest.EventType.completed.equalsIgnoreCase(event)) {
- torrentPeerService.delete(userId, torrentId, request.getPeerIdHex());
- tryInsertOrUpdatePeer(request);
-
- } else {
- torrentPeerService.delete(userId, torrentId, request.getPeerIdHex());
- tryInsertOrUpdatePeer(request);
- }
- }
-
- /**
- * 获取 peer 列表
- *
- * @param peerNumWant 这个参数表明你希望从方法返回多少个 peers。如果当前的系统中现有的 peer 数量小于你想要的 peerNumWant,那么就返回所有的
- * peers;否则,只返回你想要的 peerNumWant 数量的 peers。
- * @return
- */
- private List<Map<String, Object>> getPeerList(AnnounceRequest request, Integer peerNumWant) {
-
- Integer torrentId = request.getTorrent().getId();
- Boolean seeder = request.getSeeder();
- boolean noPeerId = request.isNoPeerId();
- Integer userId = request.getUser().getUser_id().intValue();
String peerIdHex = request.getPeerIdHex();
- //TODO 从数据库获取peer列表
- //TODO 根据 seeder peerNumWant 参数限制peer
- //如果当前用户是 seeder,那么这段代码将寻找 leecher;如果当前用户不是 seeder(或者不确定是否是 seeder),那么就不对 peer 的类型进行过滤。
- List<TorrentPeerEntity> list = torrentPeerService.listByTorrent(torrentId, seeder, peerNumWant);
+ // 处理停止事件
+ if (AnnounceRequest.EventType.stopped.equalsIgnoreCase(event)) {
+ if (torrentPeerService.peerExists(userId, torrentId, peerIdHex.getBytes())) {
+ torrentPeerService.delete(userId, torrentId, peerIdHex);
+ }
+ return;
+ }
- List<Map<String, Object>> result = list.stream()
- .map(peer -> {
-
- // 当前 Peer 自己不返回
- if (peer.getUserId().equals(userId) && peer.getPeerId().equalsIgnoreCase(peerIdHex)) {
- return null;
- }
-
- Map<String, Object> dataMap = new HashMap<>();
- // 处理ipv4
- if (StringUtils.isNotBlank(peer.getIp())) {
- dataMap.put("ip", peer.getIp());
- dataMap.put("port", peer.getPort());
- if (!noPeerId) {
- dataMap.put("peer id", peer.getPeerId());
- }
- }
- //TODO 支持ipv6
- //TODO 支持压缩
- return dataMap.isEmpty() ? null : dataMap;
- })
- .filter(peer -> peer != null)
- .collect(Collectors.toList());
- return result;
+ // 其他事件统一更新
+ TorrentPeerEntity updatedPeer = tryInsertOrUpdatePeer(request);
+ if (updatedPeer != null && AnnounceRequest.EventType.completed.equalsIgnoreCase(event)) {
+ // 标记种子完成
+ updatedPeer.setSeeder(true);
+ torrentPeerService.updateById(updatedPeer);
+ }
}
+ // 优化peer列表获取
+ // 优化peer列表获取
+ private List<Map<String, Object>> getPeerList(AnnounceRequest request, Integer peerNumWant) {
+ Integer torrentId = request.getTorrent().getId();
+ boolean isSeeder = request.getSeeder();
+ boolean noPeerId = request.isNoPeerId();
+ Integer userId = userDao.findUserByPasskey(request.getPasskey()).getUserId().intValue();
+
+ String peerIdHex = request.getPeerIdHex();
+
+ // 动态调整获取数量
+ int actualPeerNum = Math.min(peerNumWant, 200);
+
+ List<TorrentPeerEntity> peers = torrentPeerService.listByTorrent(
+ torrentId,
+ isSeeder,
+ actualPeerNum
+ );
+
+ return peers.stream()
+ .filter(peerEntity -> !isSelfPeer(peerEntity, userId, peerIdHex)) // 修复变量冲突
+ .map(peerEntity -> buildPeerMap(peerEntity, noPeerId)) // 修复变量冲突
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+ }
+
+ // 新增:检查是否是自身peer
+ private boolean isSelfPeer(TorrentPeerEntity peer, Integer userId, String peerIdHex) {
+ return peer.getUserId().equals(userId) && peer.getPeerId().equals(peerIdHex);
+ }
+ // 构建peer返回数据
+ private Map<String, Object> buildPeerMap(TorrentPeerEntity peer, boolean noPeerId) {
+ Map<String, Object> dataMap = new HashMap<>();
+
+ if (StringUtils.isNotBlank(peer.getIp())) {
+ dataMap.put("ip", peer.getIp());
+ dataMap.put("port", peer.getPort());
+ if (!noPeerId) {
+ dataMap.put("peer id", peer.getPeerId());
+ }
+ }
+ // IPv6支持
+ if (StringUtils.isNotBlank(peer.getIpv6())) {
+ dataMap.put("ipv6", peer.getIpv6());
+ dataMap.put("port", peer.getPort()); // 端口复用
+ }
+
+ return dataMap.isEmpty() ? null : dataMap;
+ }
+
+ // 修复peer更新逻辑
private TorrentPeerEntity tryInsertOrUpdatePeer(AnnounceRequest request) {
+ Integer userId = userDao.findUserByPasskey(request.getPasskey()).getUserId().intValue();
+
+ Integer torrentId = request.getTorrent().getId();
+ String peerIdHex = request.getPeerIdHex();
+
try {
+ // 先尝试获取现有记录
+ TorrentPeerEntity peer = torrentPeerService.getPeer(userId, torrentId, peerIdHex.getBytes());
+ boolean isNew = false;
- TorrentPeerEntity peerEntity = new TorrentPeerEntity();
- peerEntity.setUserId(request.getUser().getUser_id().intValue());
- peerEntity.setTorrentId(request.getTorrent().getId());
- peerEntity.setPeerId(request.getPeer_id());
- peerEntity.setPeerIdHex(request.getPeerIdHex());
- peerEntity.setPort(request.getPort());
- peerEntity.setDownloaded(request.getDownloaded());
- peerEntity.setUploaded(request.getUploaded());
- peerEntity.setRemaining(request.getLeft());
- peerEntity.setSeeder(request.getSeeder());
- peerEntity.setUserAgent(request.getUserAgent());
- peerEntity.setPasskey(request.getPasskey());
- peerEntity.setCreateTime(LocalDateTime.now());
- peerEntity.setLastAnnounce(LocalDateTime.now());
-
- peerEntity.setIp(request.getIp());
- peerEntity.setIpv6(request.getIpv6());
-
- if (StringUtils.isBlank(peerEntity.getIp())) {
- peerEntity.setIp(request.getRemoteAddr());
+ if (peer == null) {
+ peer = new TorrentPeerEntity();
+ peer.setCreateTime(LocalDateTime.now());
+ isNew = true;
}
- torrentPeerService.save(peerEntity);
- return peerEntity;
+ // 更新关键字段
+ peer.setUserId(userId);
+ peer.setTorrentId(torrentId);
+ peer.setPeerId(request.getPeer_id());
+ peer.setPeerIdHex(peerIdHex);
+ peer.setPort(request.getPort());
+ peer.setDownloaded(request.getDownloaded());
+ peer.setUploaded(request.getUploaded());
+ peer.setRemaining(request.getLeft());
+ peer.setSeeder(request.getSeeder());
+ peer.setUserAgent(request.getUserAgent());
+ peer.setPasskey(request.getPasskey());
+ peer.setLastAnnounce(LocalDateTime.now());
+ peer.setIp(StringUtils.defaultIfBlank(request.getIp(), request.getRemoteAddr()));
+ peer.setIpv6(request.getIpv6());
- } catch (Exception exception) {
- log.error("Peer update error update: ", exception);
+ if (isNew) {
+ torrentPeerService.insertOrUpdate(peer);
+ } else {
+ torrentPeerService.updateById(peer);
+ }
+ return peer;
+
+ } catch (Exception e) {
+ log.error("Peer update error: ", e);
+ return null;
}
- return null;
}
- /**
- * 广播间隔
- * 策略:
- * 基于种子发布的时间长度来调整广播间隔:类似NP
- * 如 种子发布7天内,广播间隔为 600s
- * 种子发布大于7天,小于30天, 广播间隔1800s
- * 种子发布30天以上,广播间隔3600s
- * <p>
- * <p>
- * 基于活跃度调整广播间隔:你可以根据种子的活跃度(例如活跃peer的数量或者下载/上传速度)来调整广播间隔。
- * 如果一个种子的活跃度高,说明它需要更频繁地更新和广播peer列表。
- * 如果活跃度低,可以增加广播间隔以减少服务器负载。
- * <p>
- * 基于服务器负载调整广播间隔:如果你的服务器负载高(例如CPU
- * 使用率高,内存使用量高,或者网络带宽使用高),可以增加广播间隔以减少负载。
- * 如果服务器负载低,可以减小广播间隔以提高文件分享的效率。
- * <p>
- * 动态调整广播间隔:你可以实时监控你的网络状况、服务器状况、以及种子的活跃度,然后动态调整广播间隔。
- * 例如,如果你发现某个时间段用户数量增多,可以临时减小广播间隔。如果发现某个时间段用户数量减少,可以增加广播间隔。
- * <p>
- * <p>
- * 综合策略:
- * <p>
- * 种子发布7天内或活跃peer数大于1000,广播间隔为 600s
- * 种子发布大于7天,小于30天或活跃peer数在100-1000之间,广播间隔为 1800s
- * 种子发布30天以上或活跃peer数小于100,广播间隔为 3600s
- *
- * @param request
- * @return
- */
+ // 实现动态广播间隔策略
private Integer getAnnounceInterval(AnnounceRequest request) {
- //TODO 广播间隔
+ TorrentEntity torrent = request.getTorrent();
+ int activePeers = torrent.getSeeders() + torrent.getLeechers();
+ long daysActive = ChronoUnit.DAYS.between(torrent.getCreateTime(), LocalDateTime.now());
- return 60;
+ // 动态调整策略
+ if (daysActive < 7 || activePeers > 1000) {
+ return 600; // 高频更新(10分钟)
+ } else if ((daysActive >= 7 && daysActive < 30) ||
+ (activePeers >= 100 && activePeers <= 1000)) {
+ return 1800; // 中频更新(30分钟)
+ } else {
+ return 3600; // 低频更新(60分钟)
+ }
}
}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/Server/BT/TorrentCommentService.java b/ruoyi-admin/src/main/java/com/ruoyi/web/Server/BT/TorrentCommentService.java
index fbc0045..de8612c 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/Server/BT/TorrentCommentService.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/Server/BT/TorrentCommentService.java
@@ -48,7 +48,7 @@
// 查询用户信息
UserEntity user = userService.getUserById(entity.getUserId());
if (user != null) {
- vo.setUsername(user.getUser_name());
+ vo.setUsername(user.getUserName());
vo.setAvatar(user.getAvatar());
}
@@ -76,7 +76,7 @@
// 设置用户信息
UserEntity user = userService.getUserById(entity.getUserId());
if (user != null) {
- vo.setUsername(user.getUser_name());
+ vo.setUsername(user.getUserName());
vo.setAvatar(user.getAvatar());
}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/Server/BT/TorrentPeerService.java b/ruoyi-admin/src/main/java/com/ruoyi/web/Server/BT/TorrentPeerService.java
index af59128..7c5522e 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/Server/BT/TorrentPeerService.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/Server/BT/TorrentPeerService.java
@@ -16,6 +16,11 @@
@Slf4j
public class TorrentPeerService extends ServiceImpl<TorrentPeerDao, TorrentPeerEntity> {
+ final TorrentPeerDao torrentPeerDao;
+
+ public TorrentPeerService(TorrentPeerDao torrentPeerDao) {
+ this.torrentPeerDao = torrentPeerDao;
+ }
/**
* 从数据库获取peer列表
@@ -68,5 +73,9 @@
.eq(TorrentPeerEntity::getPeerIdHex, peerIdHex)
);
}
+
+ public int insertOrUpdate(TorrentPeerEntity entity){
+ return torrentPeerDao.insertOrUpdate(entity);
+ }
}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/Server/BT/TorrentService.java b/ruoyi-admin/src/main/java/com/ruoyi/web/Server/BT/TorrentService.java
index 497b1ca..35fc9e3 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/Server/BT/TorrentService.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/Server/BT/TorrentService.java
@@ -88,7 +88,7 @@
byte[] transformedBytes = torrentManager.transform(bytes);
- byte[] infoHash = torrentManager.infoHash(transformedBytes);
+ byte[] infoHash = torrentManager.infoHash(bytes);
System.out.println(infoHash);
long count = count(Wrappers.<TorrentEntity>lambdaQuery()
@@ -110,6 +110,11 @@
}
+ public int getTorrentIdByHash(byte[] infoHash){
+ return torrentDao.getTorrentIdByHash(infoHash);
+ }
+
+
public byte[] fetch(Integer torrentId, String passkey) {
byte[] fileBytes = torrentStorageService.read(torrentId);
@@ -340,16 +345,16 @@
if (userCredentialService.getById(userId) == null){
UserEntity userEntity = userService.getUserById(userId);
UserCredentialEntity userCredentialEntity = new UserCredentialEntity();
- userCredentialEntity.setUserid(userEntity.getUser_id().intValue());
- userCredentialEntity.setUsername(userEntity.getUser_name());
+ userCredentialEntity.setUserid(userEntity.getUserId().intValue());
+ userCredentialEntity.setUsername(userEntity.getUserName());
userCredentialEntity.setRegIp(IPUtils.getIpAddr());
- userCredentialEntity.setRegType(userEntity.getReg_type());
- String checkCode = passkeyManager.generate(userEntity.getUser_id().intValue());
+ userCredentialEntity.setRegType(userEntity.getRegType());
+ String checkCode = passkeyManager.generate(userEntity.getUserId().intValue());
userCredentialEntity.setCheckCode(checkCode);
// 生成随机盐和密码
String salt = RandomUtil.randomString(8);
- String passkey = passkeyManager.generate(userEntity.getUser_id().intValue());
+ String passkey = passkeyManager.generate(userEntity.getUserId().intValue());
userCredentialEntity.setSalt(salt);
userCredentialEntity.setPasskey(passkey);
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/Server/BT/TrackerURLService.java b/ruoyi-admin/src/main/java/com/ruoyi/web/Server/BT/TrackerURLService.java
index d28c7e2..68bd665 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/Server/BT/TrackerURLService.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/Server/BT/TrackerURLService.java
@@ -23,16 +23,13 @@
* 获取当前的tracker Announce 地址
*/
public String getAnnounce(String passkey) {
- try {
- InetAddress localhost = InetAddress.getLocalHost();
- String hostname = localhost.getHostName();
+
+ String hostname = "team4.10813352.xyz";
String string = Constants.Announce.PROTOCOL + "://" + hostname + ":" + Constants.Announce.PORT + "/tracker/announce?passkey=" + passkey;
return string;
- }catch (UnknownHostException e){
- return "Server error: can not get host";
- }
+
}
}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/Server/sys/UserService.java b/ruoyi-admin/src/main/java/com/ruoyi/web/Server/sys/UserService.java
index f5b57ce..333b194 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/Server/sys/UserService.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/Server/sys/UserService.java
@@ -126,6 +126,10 @@
//// long total = new PageInfo(userEntities).getTotal();
//// return new PageDTO<>(userEntities, total);
//// }
+public void updateUserStats(Integer userId, long uploaded, long downloaded, long seedTime) {
+ // 使用SQL原子操作保证数据一致性
+ baseMapper.updateUserStats(userId, uploaded, downloaded, (long)(seedTime/3600));
+}
//
// public Result findUsers(UserParam param) {
// PageHelper.startPage(param.getPage(), param.getSize());
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/BT/TrackerController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/BT/TrackerController.java
index c4c0152..359629b 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/BT/TrackerController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/BT/TrackerController.java
@@ -3,7 +3,6 @@
import cn.hutool.core.util.HexUtil;
import com.ruoyi.web.Server.AnnounceService;
-import com.ruoyi.web.controller.common.exception.TrackerException;
import com.ruoyi.web.dao.BT.AnnounceRequest;
import com.ruoyi.web.Tool.BT.BencodeUtil;
import com.ruoyi.web.Tool.BT.BinaryFieldUtil;
@@ -14,7 +13,6 @@
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -48,57 +46,24 @@
String queryStrings = request.getQueryString();
log.info("收到announce汇报:" + queryStrings);
+ String ipAddr = IPUtils.getIpAddr();
+ byte[] peerId = request.getRemoteAddr().getBytes();
- // 获取客户端真实IP
- String ipAddr = request.getRemoteAddr();
- String xffHeader = request.getHeader("X-Forwarded-For");
- if (xffHeader != null) {
- ipAddr = xffHeader.split(",")[0];
- }
- log.debug("客户端IP: {}", ipAddr);
+ String peerIdHex = HexUtil.encodeHexStr(peerId);
- // 解析并验证peer_id
- try {
- byte[] peerId = BinaryFieldUtil.matchPeerId(queryStrings);
- if (peerId.length != 20) {
- throw new TrackerException("Invalid peer_id length: " + peerId.length);
- }
- String peerIdHex = HexUtil.encodeHexStr(peerId);
+ announceRequest.setSeeder(announceRequest.getLeft().equals(0L));
+ announceRequest.setInfoHash(BinaryFieldUtil.matchInfoHash(queryStrings));
+ announceRequest.setPeerId(peerId);
+ announceRequest.setPeerIdHex(peerIdHex);
+ announceRequest.setRemoteAddr(ipAddr);
+ announceRequest.setWantDigest(wantDigest);
+ announceRequest.setUserAgent(ua);
- announceRequest.setSeeder(announceRequest.getLeft().equals(0L));
- announceRequest.setInfoHash(BinaryFieldUtil.matchInfoHash(queryStrings));
- announceRequest.setPeerId(peerId);
- announceRequest.setPeerIdHex(peerIdHex);
- announceRequest.setRemoteAddr(ipAddr);
- announceRequest.setWantDigest(wantDigest);
- announceRequest.setUserAgent(ua);
- // 验证passkey
- String passkey = extractPasskey(queryStrings);
- announceRequest.setPasskey(passkey);
+ Map<String, Object> response = announceService.announce(announceRequest);
- // 处理请求
- Map<String, Object> response = announceService.announce(announceRequest);
- return BencodeUtil.encode(response);
-
- } catch (Exception e) {
- log.error("处理announce请求失败", e);
- Map<String, Object> errorResponse = new HashMap<>();
- errorResponse.put("failure reason", e.getMessage());
- return BencodeUtil.encode(errorResponse);
- }
- }
-
- // 从queryString中提取passkey的辅助方法
- private String extractPasskey(String queryString) {
- if (queryString == null) return null;
- String[] params = queryString.split("&");
- for (String param : params) {
- if (param.startsWith("passkey=")) {
- return param.substring("passkey=".length());
- }
- }
- return null;
+ String responseStr = BencodeUtil.encode(response);
+ return responseStr;
}
/**
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/dao/BT/AnnounceRequest.java b/ruoyi-admin/src/main/java/com/ruoyi/web/dao/BT/AnnounceRequest.java
index 819e68a..3324ab2 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/dao/BT/AnnounceRequest.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/dao/BT/AnnounceRequest.java
@@ -109,6 +109,11 @@
//}
/**
+ * The purpose of the method:
+ * torrent_id
+ */
+ private Integer torrentId;
+ /**
* passkey
*/
private String passkey;
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/dao/BT/TorrentDao.java b/ruoyi-admin/src/main/java/com/ruoyi/web/dao/BT/TorrentDao.java
index 31f1fff..56c8704 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/dao/BT/TorrentDao.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/dao/BT/TorrentDao.java
@@ -88,4 +88,6 @@
@Update("UPDATE bt_torrent SET leechers = leechers + 1 WHERE id = #{id}")
int incrementLeechers(@Param("id") Integer id);
+ @Select("select id from bt_torrent where info_hash=#{infoHash} ")
+ int getTorrentIdByHash(byte[] infoHash);
}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/dao/BT/TorrentPeerDao.java b/ruoyi-admin/src/main/java/com/ruoyi/web/dao/BT/TorrentPeerDao.java
index 163471a..9158eb8 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/dao/BT/TorrentPeerDao.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/dao/BT/TorrentPeerDao.java
@@ -2,7 +2,9 @@
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.web.domain.BT.TorrentPeerEntity;
+import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Options;
/**
* 种子Peer
@@ -10,5 +12,25 @@
*/
@Mapper
public interface TorrentPeerDao extends BaseMapper<TorrentPeerEntity> {
-
+ @Insert("INSERT INTO bt_torrent_peer (" +
+ "torrent_id, user_id, peer_id, peer_id_hex, ip, port, " +
+ "uploaded, downloaded, remaining, seeder, user_agent, passkey, create_time, last_announce" +
+ ") " +
+ "VALUES (" +
+ "#{torrentId}, #{userId}, #{peerId}, #{peerIdHex}, #{ip}, #{port}, " +
+ "#{uploaded}, #{downloaded}, #{remaining}, #{seeder}, #{userAgent}, #{passkey}, " +
+ "#{createTime}, #{lastAnnounce}" +
+ ") " +
+ "ON DUPLICATE KEY UPDATE " +
+ "ip = VALUES(ip), " +
+ "port = VALUES(port), " +
+ "uploaded = VALUES(uploaded), " +
+ "downloaded = VALUES(downloaded), " +
+ "remaining = VALUES(remaining), " +
+ "seeder = VALUES(seeder), " +
+ "user_agent = VALUES(user_agent), " +
+ "passkey = VALUES(passkey), " +
+ "last_announce = VALUES(last_announce)")
+ @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
+ int insertOrUpdate(TorrentPeerEntity entity);
}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/dao/sys/UserDao.java b/ruoyi-admin/src/main/java/com/ruoyi/web/dao/sys/UserDao.java
index ad641c0..af13e26 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/dao/sys/UserDao.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/dao/sys/UserDao.java
@@ -4,6 +4,8 @@
import com.ruoyi.web.domain.sys.UserEntity;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.Update;
+import org.springframework.data.repository.query.Param;
import java.util.List;
import java.util.Set;
@@ -34,5 +36,14 @@
@Select("SELECT * FROM sys_user WHERE user_id=#{id}")
UserEntity findUserById(Integer id);
-
+ @Update("UPDATE sys_user SET " +
+ "real_uploaded = real_uploaded + #{uploaded}, " +
+ "real_downloaded = real_downloaded + #{downloaded}, " +
+ "bonus = bonus + #{seedTime} " +
+ "WHERE user_id = #{userId}")
+ void updateUserStats(
+ @Param("userId") Integer userId,
+ @Param("uploaded") long uploaded,
+ @Param("downloaded") long downloaded,
+ @Param("seedTime") long seedTime);
}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/domain/BT/TorrentEntity.java b/ruoyi-admin/src/main/java/com/ruoyi/web/domain/BT/TorrentEntity.java
index 0592102..af4f79f 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/domain/BT/TorrentEntity.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/domain/BT/TorrentEntity.java
@@ -13,151 +13,117 @@
@TableName("bt_torrent")
public class TorrentEntity {
- /**
- *
- */
@TableId
private Integer id;
- /**
- * 种子哈希
- */
+
private byte[] infoHash;
- /**
- * 名称
- */
private String name;
- /**
- * 上传文件名
- */
private String filename;
- /**
- * 标题
- */
private String title;
- /**
- * 简介副标题
- */
private String subheading;
- /**
- * 封面
- */
private String cover;
- /**
- * 描述
- */
private String description;
+ // === 为所有数字统计字段添加默认值 ===
+
/**
* 类别
*/
- private Integer category;
+ private Integer category = 0; // 默认类别为0
/**
* 状态
- *
* @see Status
*/
- private Integer status;
+ private Integer status = Status.CANDIDATE; // 默认状态为候选中
/**
* 文件状态 0 未上传 1 已上传
*/
- private Integer fileStatus;
- /**
- * 审核人
- */
- private Integer reviewer;
-
+ private Integer fileStatus = 0; // 默认文件未上传
/**
- * 添加日期
+ * 审核人 - 允许为null
*/
+ private Integer reviewer; // 未审核时为null
+
private LocalDateTime createTime;
-
-
- /**
- * 修改日期
- */
private LocalDateTime updateTime;
/**
- * 拥有者
+ * 拥有者 - 必须设置,不需要默认值
*/
private Integer owner;
+
/**
* 文件大小
*/
- private Long size;
+ private Long size = 0L; // 默认0
+
/**
- * 类型
- * single(1)
- * multi(2)
+ * 类型 single(1)/multi(2)
*/
- private Integer type;
+ private Integer type = 1; // 默认单文件
+
/**
* 文件数量
*/
- private Integer fileCount;
+ private Integer fileCount = 1; // 默认1个文件
/**
* 评论数
*/
- private Integer comments;
+ private Integer comments = 0; // 默认0评论
+
/**
* 浏览次数
*/
- private Integer views;
+ private Integer views = 0; // 默认0次浏览
+
/**
* 点击次数
*/
- private Integer hits;
+ private Integer hits = 0; // 默认0点击
/**
* 可见性
*/
- private Integer visible;
+ private Integer visible = 1; // 默认可见
/**
* 是否匿名
*/
- private Integer anonymous;
-
+ private Integer anonymous = 0; // 默认不匿名
/**
* 下载数
*/
- private Integer leechers;
+ private Integer leechers = 0; // 关键: 默认0下载
+
/**
* 做种数
*/
- private Integer seeders;
+ private Integer seeders = 0; // 关键: 默认0做种
/**
* 做种完成次数
*/
- private Integer completions;
+ private Integer completions = 0; // 默认0完成
/**
- *
+ * 备注 - 允许为null
*/
private String remark;
/**
* 种子状态
- * 0 候选中 1 已发布 2 审核不通过 3 已上架修改重审中 10 已下架
*/
public interface Status {
-
int CANDIDATE = 0;
-
int PUBLISHED = 1;
-
int AUDIT_NOT_PASSED = 2;
-
int RETRIAL = 3;
-
int REMOVED = 10;
-
}
@RequiredArgsConstructor
@@ -171,7 +137,15 @@
}
public boolean isStatusOK() {
- return status == 1;
+ return status == Status.PUBLISHED;
}
-}
+ // === 添加getter方法确保空值安全 ===
+ public Integer getLeechers() {
+ return leechers != null ? leechers : 0;
+ }
+
+ public Integer getSeeders() {
+ return seeders != null ? seeders : 0;
+ }
+}
\ No newline at end of file
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/domain/sys/UserEntity.java b/ruoyi-admin/src/main/java/com/ruoyi/web/domain/sys/UserEntity.java
index aa85880..e759333 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/domain/sys/UserEntity.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/domain/sys/UserEntity.java
@@ -18,10 +18,10 @@
public class UserEntity implements Serializable {
@TableId(value = "user_id", type = IdType.AUTO)
- private Long user_id;
- private String user_name;
- private String nick_name;
- private String user_type;
+ private Long userId;
+ private String userName;
+ private String nickName;
+ private String userType;
private String email;
private String phonenumber;
@@ -36,69 +36,69 @@
@TableField("status")
private Integer status;
- private String login_ip;
+ private String loginIp;
// 日期类型统一使用LocalDateTime
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
- private LocalDateTime login_date;
+ private LocalDateTime loginDate;
- private String create_by;
+ private String createBy;
// 修正字段名与数据库一致
@TableField("create_time")
- private LocalDateTime create_time;
+ private LocalDateTime createTime;
- private String update_by;
+ private String updateBy;
// 修正字段名与数据库一致
@TableField("update_time")
- private LocalDateTime update_time;
+ private LocalDateTime updateTime;
private String remark;
- private String full_name;
+ private String fullName;
// 使用Integer而非自定义枚举,与数据库字段类型一致
private Integer state;
private LocalDateTime added;
- private LocalDateTime last_login;
- private LocalDateTime last_access;
- private LocalDateTime last_home;
- private LocalDateTime last_offer;
- private LocalDateTime forum_access;
- private LocalDateTime last_staffmsg;
- private LocalDateTime last_pm;
- private LocalDateTime last_comment;
- private LocalDateTime last_post;
- private LocalDateTime last_active;
+ private LocalDateTime lastLogin;
+ private LocalDateTime lastAccess;
+ private LocalDateTime lastHome;
+ private LocalDateTime lastOffer;
+ private LocalDateTime forumAccess;
+ private LocalDateTime lastStaffmsg;
+ private LocalDateTime lastPm;
+ private LocalDateTime lastComment;
+ private LocalDateTime lastPost;
+ private LocalDateTime lastActive;
private Integer privacy;
- private String reg_ip;
+ private String regIp;
private Integer level;
private Long seedtime;
private Long leechtime;
@Schema(description = "真实上传量")
- private Long real_uploaded;
+ private Long realUploaded;
@Schema(description = "真实下载量")
- private Long real_downloaded;
+ private Long realDownloaded;
private String modcomment;
- private Long warning_by;
- private Integer warning_times;
+ private Long warningBy;
+ private Integer warningTimes;
private Boolean warning;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
- private LocalDateTime warning_until;
+ private LocalDateTime warningUntil;
private Long download;
private Long upload;
- private Integer invited_by;
+ private Integer invitedBy;
private Long bonus;
private Long exp;
- private String check_code;
- private Integer reg_type;
+ private String checkCode;
+ private Integer regType;
// 状态辅助方法
public boolean isUserLocked() {