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