upload功能
Change-Id: Iad725ce3e2edd913531bf11705bf51000dde010d
diff --git a/src/main/java/com/pt5/pthouduan/entity/PasskeyContext.java b/src/main/java/com/pt5/pthouduan/entity/PasskeyContext.java
new file mode 100644
index 0000000..6a3229f
--- /dev/null
+++ b/src/main/java/com/pt5/pthouduan/entity/PasskeyContext.java
@@ -0,0 +1,17 @@
+package com.pt5.pthouduan.entity;
+
+public class PasskeyContext {
+ private static final ThreadLocal<String> passkeyHolder = new ThreadLocal<>();
+
+ public static void setPasskey(String passkey) {
+ passkeyHolder.set(passkey);
+ }
+
+ public static String getPasskey() {
+ return passkeyHolder.get();
+ }
+
+ public static void clear() {
+ passkeyHolder.remove();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/pt5/pthouduan/entity/PasskeyTrackerProcessor.java b/src/main/java/com/pt5/pthouduan/entity/PasskeyTrackerProcessor.java
new file mode 100644
index 0000000..555dbd5
--- /dev/null
+++ b/src/main/java/com/pt5/pthouduan/entity/PasskeyTrackerProcessor.java
@@ -0,0 +1,162 @@
+package com.pt5.pthouduan.entity;
+
+import com.pt5.pthouduan.service.PasskeyValidator;
+import com.turn.ttorrent.bcodec.BEValue;
+import com.turn.ttorrent.bcodec.InvalidBEncodingException;
+import com.turn.ttorrent.common.protocol.TrackerMessage;
+import com.turn.ttorrent.common.protocol.http.HTTPAnnounceRequestMessage;
+import com.turn.ttorrent.common.protocol.http.HTTPTrackerErrorMessage;
+import com.turn.ttorrent.tracker.TrackerRequestProcessor;
+import com.turn.ttorrent.tracker.TorrentsRepository;
+//import org.simpleframework.http.Status;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+
+import java.io.IOException;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+public class PasskeyTrackerProcessor extends TrackerRequestProcessor {
+
+ private final PasskeyValidator passkeyValidator;
+ private static final String[] NUMERIC_REQUEST_FIELDS = new String[]{
+ "port", "uploaded", "downloaded", "left", "compact", "no_peer_id", "numwant"
+ };
+
+ public PasskeyTrackerProcessor(TorrentsRepository torrentsRepository,
+ PasskeyValidator passkeyValidator) {
+ super(torrentsRepository);
+ this.passkeyValidator = passkeyValidator;
+ }
+
+ @Override
+ public void process(String uri, String hostAddress, RequestHandler requestHandler)
+ throws IOException {
+ try {
+ // 1. 使用自定义解析方法(已包含passkey检查)
+ ParsedAnnounceRequest parsedRequest = parseQueryWithPasskey(uri, hostAddress);
+ System.out.println(uri);
+
+ // 2. 直接获取passkey(已在parseQueryWithPasskey中确保存在)
+ String passkey = parsedRequest.getPasskey();
+
+ // 3. 验证passkey
+ if (!passkeyValidator.isValid(passkey)) {
+ System.out.println("Invalid passkey: " + passkey);
+ return ;
+ }
+ else {
+ PasskeyContext.setPasskey(passkey);
+ System.out.println("Valid passkey: " + passkey);
+ }
+ // 如果需要,可以将passkey存储在请求上下文中,供后续处理使用
+ // 例如,通过ThreadLocal或其他方式
+ // 4. 调用父类处理
+ // 注意:这里假设父类的process方法不需要直接访问passkey
+ // 如果需要,可能需要重构父类或传递passkey
+ super.process(uri, hostAddress, requestHandler);
+ } catch (Exception e) {
+ System.out.println(e.getMessage());
+ throw new RuntimeException("Failed to update peer stats", e); // 可根据需求决定是否抛出异常
+ }
+ }
+
+
+
+ /**
+ * 自定义解析逻辑,强制要求passkey参数,并返回解析后的请求对象
+ */
+ private ParsedAnnounceRequest parseQueryWithPasskey(String uri, String hostAddress)
+ throws IOException, TrackerMessage.MessageValidationException {
+
+ Map<String, BEValue> params = new HashMap<>();
+
+ try {
+ // 分割查询参数
+ String query = uri.contains("?") ? uri.split("\\?")[1] : "";
+ String[] pairs = query.split("&");
+
+ for (String pair : pairs) {
+ if (pair.isEmpty()) continue;
+
+ String[] kv = pair.split("=", 2);
+ String key = URLDecoder.decode(kv[0], StandardCharsets.ISO_8859_1.toString());
+ String value = kv.length > 1 ? URLDecoder.decode(kv[1], StandardCharsets.ISO_8859_1.toString()) : "";
+
+ // 处理数字型参数
+ if (Arrays.asList(NUMERIC_REQUEST_FIELDS).contains(key)) {
+ try {
+ params.put(key, new BEValue(Long.parseLong(value)));
+ } catch (NumberFormatException e) {
+ throw new TrackerMessage.MessageValidationException(
+ "Invalid numeric value for field: " + key);
+ }
+ } else {
+ params.put(key, new BEValue(value, StandardCharsets.ISO_8859_1.toString()));
+ }
+ }
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw new TrackerMessage.MessageValidationException("Invalid request format");
+ }
+
+ // 强制检查passkey
+ if (!params.containsKey("passkey")) {
+ throw new TrackerMessage.MessageValidationException("passkey is required");
+ }
+
+ // 如果未提供IP,使用客户端IP
+ if (!params.containsKey("ip")) {
+ params.put("ip", new BEValue(hostAddress, StandardCharsets.ISO_8859_1.toString()));
+ }
+
+ // 创建并返回自定义的ParsedAnnounceRequest对象
+ return new ParsedAnnounceRequest(params);
+ }
+// private void serveError(HttpStatus status, HTTPTrackerErrorMessage error, RequestHandler requestHandler) throws IOException {
+// requestHandler.serveResponse(status.getCode(), status.getDescription(), error.getData());
+// }
+//
+// private void serveError(Status status, String error, RequestHandler requestHandler) throws IOException {
+// this.serveError(status, HTTPTrackerErrorMessage.craft(error), requestHandler);
+// }
+//
+// private void serveError(Status status, TrackerMessage.ErrorMessage.FailureReason reason, RequestHandler requestHandler) throws IOException {
+// this.serveError(status, reason.getMessage(), requestHandler);
+// }
+
+
+ /**
+ * 自定义类用于存储解析后的请求参数
+ */
+ private static class ParsedAnnounceRequest {
+ private final Map<String, BEValue> params;
+
+ public ParsedAnnounceRequest(Map<String, BEValue> params) {
+ this.params = params;
+ }
+
+ /**
+ * 获取passkey参数
+ */
+ public String getPasskey() throws InvalidBEncodingException {
+ BEValue passkeyValue = params.get("passkey");
+ if (passkeyValue == null) {
+ throw new IllegalStateException("passkey is missing");
+ }
+ return passkeyValue.getString();
+ }
+
+ /**
+ * 获取其他参数(如果需要)
+ */
+ public BEValue getParam(String key) {
+ return params.get(key);
+ }
+
+ // 可根据需要添加更多方法
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/pt5/pthouduan/entity/PeerInfo.java b/src/main/java/com/pt5/pthouduan/entity/PeerInfo.java
new file mode 100644
index 0000000..440d870
--- /dev/null
+++ b/src/main/java/com/pt5/pthouduan/entity/PeerInfo.java
@@ -0,0 +1,109 @@
+package com.pt5.pthouduan.entity;
+
+
+import java.sql.Timestamp;
+import java.time.LocalDateTime;
+
+public class PeerInfo {
+ private String username;
+ private long uploaded;
+ private long uploadSpeed;
+ private long downloaded;
+ private long downloadSpeed;
+ private String lastEvent;
+ private Timestamp lastUpdated;
+ private Timestamp createdAt;
+ private LocalDateTime completedtime;
+ private String client;
+ private int port;
+
+ // Getters and Setters
+ public int getPort() {
+ return port;
+ }
+
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public long getUploaded() {
+ return uploaded;
+ }
+
+ public void setUploaded(long uploaded) {
+ this.uploaded = uploaded;
+ }
+
+ public long getUploadSpeed() {
+ return uploadSpeed;
+ }
+
+ public void setUploadSpeed(long uploadSpeed) {
+ this.uploadSpeed = uploadSpeed;
+ }
+
+ public long getDownloaded() {
+ return downloaded;
+ }
+
+ public void setDownloaded(long downloaded) {
+ this.downloaded = downloaded;
+ }
+
+ public long getDownloadSpeed() {
+ return downloadSpeed;
+ }
+
+ public void setDownloadSpeed(long downloadSpeed) {
+ this.downloadSpeed = downloadSpeed;
+ }
+
+ public String getLastEvent() {
+ return lastEvent;
+ }
+
+ public void setLastEvent(String lastEvent) {
+ this.lastEvent = lastEvent;
+ }
+
+ public Timestamp getLastUpdated() {
+ return lastUpdated;
+ }
+
+ public void setLastUpdated(Timestamp lastUpdated) {
+ this.lastUpdated = lastUpdated;
+ }
+
+ public Timestamp getCreatedAt() {
+ return createdAt;
+ }
+
+ public void setCreatedAt(Timestamp createdAt) {
+ this.createdAt = createdAt;
+ }
+
+ public LocalDateTime getCompleted_time() {
+ return completedtime;
+ }
+
+ public void setCompleted_time(LocalDateTime completed_time) {
+ this.completedtime = completed_time;
+ }
+
+ public String getClient() {
+ return client;
+ }
+
+ public void setClient(String client) {
+ this.client = client;
+ }
+}
+
diff --git a/src/main/java/com/pt5/pthouduan/entity/StatsRecorder.java b/src/main/java/com/pt5/pthouduan/entity/StatsRecorder.java
new file mode 100644
index 0000000..eb59b15
--- /dev/null
+++ b/src/main/java/com/pt5/pthouduan/entity/StatsRecorder.java
@@ -0,0 +1,22 @@
+package com.pt5.pthouduan.entity;
+
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.sql.DataSource;
+import java.sql.*;
+
+public interface StatsRecorder{
+ void recordStats(
+ String passkey,
+ String infoHash,
+ String ip,
+ int port,
+ String peerId,
+ long uploaded,
+ long downloaded
+ );
+}
diff --git a/src/main/java/com/pt5/pthouduan/entity/Torrent.java b/src/main/java/com/pt5/pthouduan/entity/Torrent.java
index 5c85587..fa078c8 100644
--- a/src/main/java/com/pt5/pthouduan/entity/Torrent.java
+++ b/src/main/java/com/pt5/pthouduan/entity/Torrent.java
@@ -3,7 +3,14 @@
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
import java.io.Serializable;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.security.NoSuchAlgorithmException;
+import java.time.LocalDateTime;
/**
* <p>
@@ -21,19 +28,129 @@
@TableId("torrentid")
private Long torrentid;
+ private Long uploaderid;
+
private Long promotionid;
private Integer categoryid;
- private String infoHash;
+ private String infohash;
- private String torrentTitle;
+ private String torrenttitle;
private String dpi;
private String caption;
- private byte[] torrentSize;
+ private Long torrentsize;
+
+ //private String announce;
+
+ private String description;
+
+ private LocalDateTime uploadtime;
+
+ private LocalDateTime lastseed;
+
+ private Long downloadCount = 0L;
+
+ private String filename;
+
+ private String path;
+
+
+
+
+ public void setFilename(String filename) {
+ this.filename = filename;
+ }
+ public String getFilepath() {
+ return path;
+ }
+ public void setPath(String path) {
+ this.path = path;
+ }
+
+ public LocalDateTime getLastseed(){
+ return lastseed;
+ }
+ public void setLastseed(LocalDateTime lastseed){
+ this.lastseed = lastseed;
+ }
+
+
+
+ //private String createdBy;
+
+ public LocalDateTime getUploadTime() {
+ return uploadtime;
+ }
+ public void setUploadTime(LocalDateTime uploadTime) {
+ this.uploadtime = uploadTime;
+ }
+ public Long getDownloadCount() {
+ return downloadCount;
+ }
+ public void setDownloadCount(Long downloadCount) {
+ this.downloadCount = downloadCount;
+ }
+
+ public String getDescription(){
+ return description;
+ }
+ public void setDescription(String description){
+ this.description = description;
+ }
+
+
+ public Long getUploader_id(){
+ return uploaderid;
+ }
+
+ public void setUploader_id(Long uploader_id){
+ this.uploaderid = uploader_id;
+ }
+
+
+// public String getAnnounce() {
+// return announce;
+// }
+//
+// public void setAnnounce(String announce) {
+// this.announce = announce;
+// }
+
+// public static Torrent create(File source, URI announce, String createdBy)throws NoSuchAlgorithmException, IOException {
+// if (source == null || !source.exists()) {
+// throw new IllegalArgumentException("源文件不存在");
+// }
+// if (announce == null) {
+// throw new IllegalArgumentException("Announce URL不能为空");
+// }
+//
+// Torrent torrent = Torrent.create(source, announce, createdBy != null ? createdBy : "PT站点");
+//
+// //torrent.save(outputFile);
+// return torrent;
+// }
+// public void save(File outputFile) throws IOException{
+// if (outputFile == null ) {
+// throw new IllegalArgumentException("输出文件不能为空");
+// }
+// //确保目录存在
+// File parent = outputFile.getParentFile();
+// if (!parent.exists() || parent != null) {
+// parent.mkdirs();
+// }
+// //序列化并写入文件
+// try(FileOutputStream fos = new FileOutputStream(outputFile);){
+// byte[] data = torrent.encode();
+// fos.write(buffer.array(),buffer.arrayOffset(),buffer.remaining());
+// }
+// if(!outputFile.setReadable(true, false)){
+// System.err.println("警告:无法设置文件可读权限");
+// }
+// }
public Long getTorrentid() {
return torrentid;
@@ -60,19 +177,19 @@
}
public String getInfoHash() {
- return infoHash;
+ return infohash;
}
public void setInfoHash(String infoHash) {
- this.infoHash = infoHash;
+ this.infohash = infoHash;
}
public String getTorrentTitle() {
- return torrentTitle;
+ return torrenttitle;
}
public void setTorrentTitle(String torrentTitle) {
- this.torrentTitle = torrentTitle;
+ this.torrenttitle = torrentTitle;
}
public String getDpi() {
@@ -91,12 +208,12 @@
this.caption = caption;
}
- public byte[] getTorrentSize() {
- return torrentSize;
+ public Long getTorrentSize() {
+ return torrentsize;
}
- public void setTorrentSize(byte[] torrentSize) {
- this.torrentSize = torrentSize;
+ public void setTorrentSize(Long torrentSize) {
+ this.torrentsize = torrentSize;
}
@Override
@@ -105,11 +222,24 @@
"torrentid = " + torrentid +
", promotionid = " + promotionid +
", categoryid = " + categoryid +
- ", infoHash = " + infoHash +
- ", torrentTitle = " + torrentTitle +
+ ", infoHash = " + infohash +
+ ", torrentTitle = " + torrenttitle +
", dpi = " + dpi +
", caption = " + caption +
- ", torrentSize = " + torrentSize +
+ ", torrentSize = " + torrentsize +
"}";
}
+
+
+ public String getFilePath() {
+ return path;
+ }
+
+
+ public String getFilename() {
+ return filename;
+ }
+ public void setfilename(String filename) {
+ this.filename = filename;
+ }
}
diff --git a/src/main/java/com/pt5/pthouduan/entity/TrackerInitializer.java b/src/main/java/com/pt5/pthouduan/entity/TrackerInitializer.java
new file mode 100644
index 0000000..b9632ea
--- /dev/null
+++ b/src/main/java/com/pt5/pthouduan/entity/TrackerInitializer.java
@@ -0,0 +1,40 @@
+package com.pt5.pthouduan.entity;
+
+import com.pt5.pthouduan.service.PasskeyValidator;
+import com.pt5.pthouduan.service.UserService;
+import com.turn.ttorrent.tracker.Tracker;
+import com.turn.ttorrent.tracker.TorrentsRepository;
+import com.turn.ttorrent.tracker.TrackerRequestProcessor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.io.IOException;
+
+@Service
+public class TrackerInitializer {
+
+ @Autowired
+ private PasskeyValidator passkeyValidator;
+
+ public Tracker createTracker() throws IOException {
+ TorrentsRepository repository = new TorrentsRepository(1000);
+
+ // 1. 创建自定义的Processor
+ TrackerRequestProcessor processor = new PasskeyTrackerProcessor(
+ repository,
+ passkeyValidator
+ );
+
+ // 2. 使用四参数构造器
+ Tracker tracker = new Tracker(
+ 6969, // 端口(与Nginx配置一致)
+ "http://127.0.0.1/announce", // 对外公告地址
+ processor, // 自定义的Processor
+ repository // Torrent仓库
+ );
+ tracker.setAnnounceInterval(100);
+
+ tracker.setAcceptForeignTorrents(false); // 禁止未注册的种子
+ return tracker;
+ }
+}
diff --git a/src/main/java/com/pt5/pthouduan/entity/TrackeredTorrentWithStats.java b/src/main/java/com/pt5/pthouduan/entity/TrackeredTorrentWithStats.java
new file mode 100644
index 0000000..6f015d2
--- /dev/null
+++ b/src/main/java/com/pt5/pthouduan/entity/TrackeredTorrentWithStats.java
@@ -0,0 +1,346 @@
+package com.pt5.pthouduan.entity;
+
+import com.pt5.pthouduan.mapper.UserMapper;
+import com.pt5.pthouduan.service.impl.updatePeerStatsService;
+import com.turn.ttorrent.common.PeerUID;
+import com.turn.ttorrent.common.protocol.AnnounceRequestMessage;
+import com.turn.ttorrent.tracker.TrackedPeer;
+import com.turn.ttorrent.tracker.TrackedTorrent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.sql.DataSource;
+import java.io.UnsupportedEncodingException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+//import static com.pt5.pthouduan.entity.CustomTorrentsRepository.hexToBytes;
+
+public class TrackeredTorrentWithStats extends TrackedTorrent{
+ private static final Logger logger = LoggerFactory.getLogger(TrackeredTorrentWithStats.class);
+ private final ConcurrentMap<PeerUID,long[]> lastStats = new ConcurrentHashMap<>();
+ private final DataSource dataSource;
+ private final String passkey;
+ private final updatePeerStatsService statsService;
+ public TrackeredTorrentWithStats(byte[] infoHash, DataSource datasource, String passkey, updatePeerStatsService statsService){
+ super(infoHash);
+ this.dataSource = datasource;
+ this.passkey = passkey;
+ this.statsService = statsService;
+ }
+
+ @Override
+ public TrackedPeer update(AnnounceRequestMessage.RequestEvent event, ByteBuffer peerId, String hexPeerId, String ip, int port, long uploaded, long downloaded, long left)
+ throws UnsupportedEncodingException{
+ System.out.println("Peer update triggered!");
+ String passkey = PasskeyContext.getPasskey();
+ System.out.println("AcceptedPasskey: " + passkey);
+ PeerUID peerUID = new PeerUID(new InetSocketAddress(ip, port),this.getHexInfoHash());
+ long[] last = lastStats.getOrDefault(peerUID, new long[]{0L,0L});
+ long deltaUpload = Math.max(0,uploaded - last[0]);
+ long deltaDownloaded = Math.max(0,downloaded - last[1]);
+ lastStats.put(peerUID,new long[]{uploaded,downloaded});
+ //updatePeerStatsInDB(this.getHexInfoHash(),ip,port,hexPeerId,uploaded,downloaded,deltaUpload,deltaDownloaded,passkey);
+ try {
+ // 调用服务层更新统计
+ statsService.updatePeerStatsInDB(
+ this.getHexInfoHash(),
+ ip,
+ port,
+ hexPeerId,
+ uploaded,
+ downloaded,
+ deltaUpload,
+ deltaDownloaded,
+ passkey,
+ event.name(), // 加上 event 类型
+ left // 用于判断是否完成下载
+ );
+ // 新增:更新 Torrent 的最晚做种时间
+ updateLastSeedTimeInDB(this.getHexInfoHash().toLowerCase());
+ // 2. 插入流水表记录
+ String sql = "INSERT INTO user_traffic_log " +
+ "(info_hash, ip, port, hex_peer_id, uploaded, downloaded, delta_upload, delta_download, passkey, event, lefted) " +
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
+ try (Connection conn = dataSource.getConnection();
+ PreparedStatement ps = conn.prepareStatement(sql)) {
+ ps.setString(1, this.getHexInfoHash());
+ ps.setString(2, ip);
+ ps.setInt(3, port);
+ ps.setString(4, hexPeerId);
+ ps.setLong(5, uploaded);
+ ps.setLong(6, downloaded);
+ ps.setLong(7, deltaUpload);
+ ps.setLong(8, deltaDownloaded);
+ ps.setString(9, passkey);
+ ps.setString(10, event.name());
+ ps.setLong(11, left);
+ ps.executeUpdate();
+ }
+ } catch (Exception e) {
+ // 使用日志框架记录错误
+ logger.error("Failed to update peer stats", e);
+ throw new RuntimeException("Failed to update peer stats", e); // 可根据需求决定是否抛出异常
+ }
+ System.out.println("接收到 peer announce: " + ip + ":" + port + ", uploaded=" + uploaded + ", downloaded=" + downloaded);
+ logger.debug("Peer updated: {}:{} (up={}, down={}, Δup={}, Δdown={})",
+ ip, port, uploaded, downloaded, deltaUpload, deltaDownloaded);
+ return super.update(event,peerId,hexPeerId,ip,port,uploaded,downloaded,left);
+ }
+ // 新增方法:更新 Torrent 的最晚做种时间
+ private void updateLastSeedTimeInDB(String infoHash) {
+ String sql = "UPDATE torrent SET last_seed = now() WHERE info_hash = ?";
+ try (Connection conn = dataSource.getConnection();
+ PreparedStatement stmt = conn.prepareStatement(sql)) {
+ System.out.println("Current info_hash: " + infoHash);
+
+ stmt.setString(1, infoHash);
+ stmt.executeUpdate();
+ System.out.println("更新最后做种时间");
+
+ } catch (SQLException e) {
+ logger.error("Failed to update last_seed_time for info_hash: {}", infoHash, e);
+ }
+ }
+
+//package com.pt5.pthouduan.entity;
+//
+//import com.pt5.pthouduan.mapper.UserMapper;
+//import com.pt5.pthouduan.service.impl.updatePeerStatsService;
+//import com.turn.ttorrent.common.PeerUID;
+//import com.turn.ttorrent.common.protocol.AnnounceRequestMessage;
+//import com.turn.ttorrent.tracker.TrackedPeer;
+//import com.turn.ttorrent.tracker.TrackedTorrent;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//
+//import javax.sql.DataSource;
+//import java.io.UnsupportedEncodingException;
+//import java.net.InetSocketAddress;
+//import java.nio.ByteBuffer;
+//import java.sql.Connection;
+//import java.sql.PreparedStatement;
+//import java.sql.SQLException;
+//import java.util.concurrent.ConcurrentHashMap;
+//import java.util.concurrent.ConcurrentMap;
+//
+////import static com.pt5.pthouduan.entity.CustomTorrentsRepository.hexToBytes;
+//
+//public class TrackeredTorrentWithStats extends TrackedTorrent{
+// private static final Logger logger = LoggerFactory.getLogger(TrackeredTorrentWithStats.class);
+// private final ConcurrentMap<PeerUID,long[]> lastStats = new ConcurrentHashMap<>();
+// private final DataSource dataSource;
+// private final String passkey;
+// private final updatePeerStatsService statsService;
+// public TrackeredTorrentWithStats(byte[] infoHash, DataSource datasource, String passkey, updatePeerStatsService statsService){
+// super(infoHash);
+// this.dataSource = datasource;
+// this.passkey = passkey;
+// this.statsService = statsService;
+// }
+//
+// @Override
+// public TrackedPeer update(AnnounceRequestMessage.RequestEvent event, ByteBuffer peerId, String hexPeerId, String ip, int port, long uploaded, long downloaded, long left)
+// throws UnsupportedEncodingException{
+// System.out.println("Peer update triggered!");
+// String passkey = PasskeyContext.getPasskey();
+// System.out.println("AcceptedPasskey: " + passkey);
+// PeerUID peerUID = new PeerUID(new InetSocketAddress(ip, port),this.getHexInfoHash());
+// long[] last = lastStats.getOrDefault(peerUID, new long[]{0L,0L});
+// long deltaUpload = Math.max(0,uploaded - last[0]);
+// long deltaDownloaded = Math.max(0,downloaded - last[1]);
+// lastStats.put(peerUID,new long[]{uploaded,downloaded});
+// //updatePeerStatsInDB(this.getHexInfoHash(),ip,port,hexPeerId,uploaded,downloaded,deltaUpload,deltaDownloaded,passkey);
+// try {
+// // 调用服务层更新统计
+// statsService.updatePeerStatsInDB(
+// this.getHexInfoHash(),
+// ip,
+// port,
+// hexPeerId,
+// uploaded,
+// downloaded,
+// deltaUpload,
+// deltaDownloaded,
+// passkey
+// );
+// // 新增:更新 Torrent 的最晚做种时间
+// updateLastSeedTimeInDB(this.getHexInfoHash().toLowerCase());
+// } catch (Exception e) {
+// // 使用日志框架记录错误
+// logger.error("Failed to update peer stats", e);
+// throw new RuntimeException("Failed to update peer stats", e); // 可根据需求决定是否抛出异常
+// }
+// System.out.println("接收到 peer announce: " + ip + ":" + port + ", uploaded=" + uploaded + ", downloaded=" + downloaded);
+// logger.debug("Peer updated: {}:{} (up={}, down={}, Δup={}, Δdown={})",
+// ip, port, uploaded, downloaded, deltaUpload, deltaDownloaded);
+// return super.update(event,peerId,hexPeerId,ip,port,uploaded,downloaded,left);
+// }
+// // 新增方法:更新 Torrent 的最晚做种时间
+// private void updateLastSeedTimeInDB(String infoHash) {
+// String sql = "UPDATE torrent SET last_seed = now() WHERE info_hash = ?";
+// try (Connection conn = dataSource.getConnection();
+// PreparedStatement stmt = conn.prepareStatement(sql)) {
+// System.out.println("Current info_hash: " + infoHash);
+//
+// stmt.setString(1, infoHash);
+// stmt.executeUpdate();
+// System.out.println("更新最后做种时间");
+//
+// } catch (SQLException e) {
+// logger.error("Failed to update last_seed_time for info_hash: {}", infoHash, e);
+// }
+// }
+
+// private void updatePeerStatsInDB(String infoHash, String ip, int port, String peerId, long uploaded, long downloaded,long deltaUpload, long deltaDownload,String passkey) {
+// double shareRatio = downloaded == 0 ? 0.0 : (double) uploaded / downloaded;
+// UserMapper userMapper;
+// String sql = """
+// INSERT INTO peer_stats (info_hash, ip, port, peer_id, uploaded, downloaded, delta_upload, delta_download,share_ratio,passkey)
+// VALUES (?, ?, ?, ?, ?, ?, ?,?,?,?)
+// ON DUPLICATE KEY UPDATE
+// uploaded = VALUES(uploaded),
+// downloaded = VALUES(downloaded),
+// delta_upload = VALUES(delta_upload),
+// delta_download = VALUES(delta_download),
+// share_ratio = VALUES(share_ratio),
+// last_updated = CURRENT_TIMESTAMP,
+// passkey = passkey;
+// """;
+// try (Connection conn = dataSource.getConnection();
+// PreparedStatement stmt = conn.prepareStatement(sql)) {
+//
+// stmt.setString(1, infoHash);
+// stmt.setString(2, ip);
+// stmt.setInt(3, port);
+// stmt.setString(4, peerId);
+// stmt.setLong(5, uploaded);
+// stmt.setLong(6, downloaded);
+// stmt.setLong(7, deltaUpload);
+// stmt.setLong(8, deltaDownload);
+// stmt.setDouble(9, shareRatio);
+// //stmt.setString(10,this.passkey);
+// stmt.setString(10,passkey);
+// stmt.executeUpdate();
+// userMapper.incrementUserTraffic(passkey, deltaUpload, deltaDownload);
+//
+// } catch (SQLException e) {
+// e.printStackTrace(); // 建议换成日志记录
+// }
+//
+// }
+}
+//package com.pt5.pthouduan.entity;
+//
+//import com.turn.ttorrent.common.PeerUID;
+//import com.turn.ttorrent.common.protocol.AnnounceRequestMessage;
+//import com.turn.ttorrent.tracker.TrackedPeer;
+//import com.turn.ttorrent.tracker.TrackedTorrent;
+//import org.slf4j.Logger;
+//import org.slf4j.LoggerFactory;
+//
+//import javax.sql.DataSource;
+//import java.io.UnsupportedEncodingException;
+//import java.net.InetSocketAddress;
+//import java.nio.ByteBuffer;
+//import java.sql.Connection;
+//import java.sql.PreparedStatement;
+//import java.sql.SQLException;
+//import java.util.concurrent.ConcurrentHashMap;
+//import java.util.concurrent.ConcurrentMap;
+//
+//public class TrackeredTorrentWithStats extends TrackedTorrent {
+// private static final Logger logger = LoggerFactory.getLogger(TrackeredTorrentWithStats.class);
+//
+// private final ConcurrentMap<PeerUID, long[]> lastStats = new ConcurrentHashMap<>();
+// private final DataSource dataSource;
+// private final String passkey; // 新增passkey字段
+//
+// // 修改构造函数,增加passkey参数
+// public TrackeredTorrentWithStats(byte[] infoHash, DataSource datasource, String passkey) {
+// super(infoHash);
+// this.dataSource = datasource;
+// this.passkey = passkey;
+// }
+//
+// @Override
+// public TrackedPeer update(AnnounceRequestMessage.RequestEvent event,
+// ByteBuffer peerId, String hexPeerId,
+// String ip, int port,
+// long uploaded, long downloaded, long left)
+// throws UnsupportedEncodingException {
+// PeerUID peerUID = new PeerUID(new InetSocketAddress(ip, port), this.getHexInfoHash());
+//
+// // 计算增量流量(防止负数)
+// long[] last = lastStats.getOrDefault(peerUID, new long[]{0L, 0L});
+// long deltaUpload = Math.max(0, uploaded - last[0]);
+// long deltaDownload = Math.max(0, downloaded - last[1]);
+//
+// // 更新内存中的最新值
+// lastStats.put(peerUID, new long[]{uploaded, downloaded});
+//
+// // 记录到数据库(新增passkey字段)
+// updatePeerStatsInDB(
+// this.getHexInfoHash(),
+// ip, port, hexPeerId,
+// uploaded, downloaded,
+// deltaUpload, deltaDownload
+// );
+//
+// logger.debug("Peer updated: {}:{} (up={}, down={}, Δup={}, Δdown={})",
+// ip, port, uploaded, downloaded, deltaUpload, deltaDownload);
+//
+// return super.update(event, peerId, hexPeerId, ip, port, uploaded, downloaded, left);
+// }
+//
+// private void updatePeerStatsInDB(String infoHash, String ip, int port,
+// String peerId, long uploaded, long downloaded,
+// long deltaUpload, long deltaDownload) {
+// double shareRatio = (downloaded == 0) ? 0.0 : (double) uploaded / downloaded;
+//
+// // 修改SQL,增加passkey字段
+// String sql = """
+// INSERT INTO peer_stats (
+// info_hash, ip, port, peer_id,
+// uploaded, downloaded, delta_upload, delta_download,
+// share_ratio, passkey
+// ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+// ON DUPLICATE KEY UPDATE
+// uploaded = VALUES(uploaded),
+// downloaded = VALUES(downloaded),
+// delta_upload = VALUES(delta_upload),
+// delta_download = VALUES(delta_download),
+// share_ratio = VALUES(share_ratio),
+// last_updated = CURRENT_TIMESTAMP
+// """;
+//
+// try (Connection conn = dataSource.getConnection();
+// PreparedStatement stmt = conn.prepareStatement(sql)) {
+//
+// // 设置参数(新增passkey)
+// stmt.setString(1, infoHash);
+// stmt.setString(2, ip);
+// stmt.setInt(3, port);
+// stmt.setString(4, peerId);
+// stmt.setLong(5, uploaded);
+// stmt.setLong(6, downloaded);
+// stmt.setLong(7, deltaUpload);
+// stmt.setLong(8, deltaDownload);
+// stmt.setDouble(9, shareRatio);
+// stmt.setString(10, this.passkey); // 新增passkey
+//
+// stmt.executeUpdate();
+//
+// } catch (SQLException e) {
+// logger.error("Failed to update peer stats: {}", e.getMessage());
+// }
+// }
+//}
\ No newline at end of file