修改增加上传量下载量逻辑

Change-Id: I5dc30fd08feabca1e2acffb647966e4060d76bd3
diff --git a/pom.xml b/pom.xml
index f93819e..b24c1ea 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,6 +28,14 @@
     </repositories>
     -->
 
+    <!-- 添加 JitPack 仓库,支持 com.dampcake:bencode2 发布 -->
+    <repositories>
+        <repository>
+            <id>jitpack.io</id>
+            <url>https://jitpack.io</url>
+        </repository>
+    </repositories>
+
     <!-- 添加依赖 -->
     <dependencies>
         <!-- Apache Commons Lang3,用于 Pair 等工具类 -->
@@ -122,6 +130,12 @@
             <artifactId>simple</artifactId>
             <version>5.1.6</version>
         </dependency>
+
+        <dependency>
+            <groupId>com.dampcake</groupId>
+            <artifactId>bencode</artifactId>
+            <version>1.4.1</version>
+        </dependency>
     </dependencies>
 
     <!-- 构建配置 -->
diff --git a/src/main/java/entity/SeedHash.java b/src/main/java/entity/SeedHash.java
new file mode 100644
index 0000000..0ca07ab
--- /dev/null
+++ b/src/main/java/entity/SeedHash.java
@@ -0,0 +1,19 @@
+package entity;
+
+import javax.persistence.*;
+
+@Entity
+@Table(name = "SeedHash")
+public class SeedHash {
+    @Id
+    @Column(name = "seed_id", length = 64)
+    public String seedId;
+
+    @Column(name = "info_hash", length = 40, nullable = false)
+    public String infoHash;
+
+    // optional back‐ref
+    @ManyToOne
+    @JoinColumn(name = "seed_id", insertable = false, updatable = false)
+    public Seed seed;
+}
diff --git a/src/main/java/entity/TransRecord.java b/src/main/java/entity/TransRecord.java
index cf7ac7f..f19155e 100644
--- a/src/main/java/entity/TransRecord.java
+++ b/src/main/java/entity/TransRecord.java
@@ -4,51 +4,55 @@
 import javax.persistence.Entity;
 import javax.persistence.ForeignKey;
 import javax.persistence.Id;
-import javax.persistence.IdClass;
 import javax.persistence.JoinColumn;
 import javax.persistence.ManyToOne;
 import javax.persistence.Table;
 
 @Entity
 @Table(name = "Transport")
-@IdClass(TransportId.class)
 public class TransRecord{
     @Id
     @Column(name = "task_id", length = 64, nullable = false)
     public String taskid;
 
-    @Id
-    @Column(name = "uploader_id", length = 36, nullable = false)
+    @Column(name = "uploader_id", length = 36, nullable = true)
     public String uploaduserid;
 
-    @ManyToOne(optional = false)
+    @ManyToOne(optional = true)
     @JoinColumn(name = "uploader_id", referencedColumnName = "user_id", foreignKey = @ForeignKey(name = "fk_tr_user_up"), insertable = false, updatable = false)
     public User uploader;
 
-    @Id
-    @Column(name = "downloader_id", length = 36, nullable = false)
+    @Column(name = "downloader_id", length = 36, nullable = true)
     public String downloaduserid;
 
-    @ManyToOne(optional = false)
+    @ManyToOne(optional = true)
     @JoinColumn(name = "downloader_id", referencedColumnName = "user_id", foreignKey = @ForeignKey(name = "fk_tr_user_down"), insertable = false, updatable = false)
     public User downloader;
 
-    @Column(name = "seed_id", length = 64, nullable = false)
+    @Column(name = "seed_id", length = 64, nullable = true)
     public String seedid;
 
-    @ManyToOne(optional = false)
+    @ManyToOne(optional = true)
     @JoinColumn(name = "seed_id", referencedColumnName = "seed_id", foreignKey = @ForeignKey(name = "fk_tr_seed"), insertable = false, updatable = false)
     public Seed seed;
 
-    @Column(name = "uploaded", nullable = false)
-    public long upload;
+    @Column(name = "uploaded", nullable = true)
+    public Long upload = 0L;
 
-    @Column(name = "downloaded", nullable = false)
-    public long download;
+    @Column(name = "downloaded", nullable = true)
+    public Long download = 0L;
 
-    @Column(name = "upload_peak", nullable = false)
-    public long maxupload;
+    @Column(name = "upload_peak", nullable = true)
+    public Long maxupload = 0L;
 
-    @Column(name = "download_peak", nullable = false)
-    public long maxdownload;
+    @Column(name = "download_peak", nullable = true)
+    public Long maxdownload = 0L;
+    
+    // 默认构造函数,确保字段初始化
+    public TransRecord() {
+        this.upload = 0L;
+        this.download = 0L;
+        this.maxupload = 0L;
+        this.maxdownload = 0L;
+    }
 }
\ No newline at end of file
diff --git a/src/main/java/tracker/DataCaptureProxy.java b/src/main/java/tracker/DataCaptureProxy.java
index c2ed137..cb66feb 100644
--- a/src/main/java/tracker/DataCaptureProxy.java
+++ b/src/main/java/tracker/DataCaptureProxy.java
@@ -6,7 +6,7 @@
 import java.net.InetSocketAddress;
 import java.net.SocketAddress;
 import java.net.URL;
-
+import tracker.Tracker;
 import org.simpleframework.http.Request;
 import org.simpleframework.http.Response;
 import org.simpleframework.http.core.Container;
@@ -34,15 +34,17 @@
             String uploaded   = req.getParameter("uploaded");
             String downloaded = req.getParameter("downloaded");
             String passkey    = req.getParameter("passkey");
+            String port       = req.getParameter("port"); // qBittorrent 服务端端口
 
-            // 获取客户端IP地址
+            // 获取客户端IP地址和端口
             String clientIp;
+            int clientPort = -1;
             // 直接从 TCP 连接(socket 源地址)中读取
             SocketAddress socketAddress = req.getClientAddress();
             if (socketAddress instanceof InetSocketAddress) {
-                clientIp = ((InetSocketAddress) socketAddress)
-                               .getAddress()
-                               .getHostAddress();
+                InetSocketAddress inetSocketAddress = (InetSocketAddress) socketAddress;
+                clientIp = inetSocketAddress.getAddress().getHostAddress();
+                clientPort = inetSocketAddress.getPort();
             } else {
                 // 兜底写法,将整个 SocketAddress 转为字符串
                 clientIp = socketAddress.toString();
@@ -53,23 +55,25 @@
                 ", uploaded=" + uploaded +
                 ", downloaded=" + downloaded +
                 ", passkey=" + passkey +
-                ", client_ip=" + clientIp
+                ", client_ip=" + clientIp +
+                ", client_port=" + clientPort +
+                ", qbt_service_port=" + port
             );
 
             // 调用 Tracker 方法更新上传和下载数据
-            if (passkey != null && !passkey.isEmpty()) {
+            if (passkey != null && !passkey.isEmpty() && infoHash != null && !infoHash.isEmpty()) {
                 try {
                     if (uploaded != null && !uploaded.isEmpty()) {
                         int uploadValue = Integer.parseInt(uploaded);
                         if (uploadValue > 0) {
-                            tracker.AddUpLoad(passkey, uploadValue);
+                            tracker.AddUpLoad(passkey, uploadValue, infoHash);
                         }
                     }
                     
                     if (downloaded != null && !downloaded.isEmpty()) {
                         int downloadValue = Integer.parseInt(downloaded);
                         if (downloadValue > 0) {
-                            tracker.AddDownload(passkey, downloadValue);
+                            tracker.AddDownload(passkey, downloadValue, infoHash);
                         }
                     }
                 } catch (NumberFormatException e) {
diff --git a/src/main/java/tracker/Tracker.java b/src/main/java/tracker/Tracker.java
index 008ac4a..1ac4d94 100644
--- a/src/main/java/tracker/Tracker.java
+++ b/src/main/java/tracker/Tracker.java
@@ -4,20 +4,19 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.nio.file.StandardCopyOption;
-import java.util.HashMap;
-import java.util.Map;
-import javax.persistence.EntityManager;
-import javax.persistence.EntityManagerFactory;
-import javax.persistence.EntityTransaction;
-import javax.persistence.Persistence;
-import com.querydsl.jpa.impl.JPAUpdateClause;
-import entity.QUserPT;
-import entity.Seed;
-import entity.SeedDownload;
-import entity.TransRecord;
-import entity.config;
-import entity.TTorent;
+import java.security.MessageDigest;
 import java.time.LocalDateTime;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import javax.persistence.*;
+import com.dampcake.bencode.Bencode;
+import com.dampcake.bencode.Type;
+import com.querydsl.jpa.impl.JPAUpdateClause;
+import entity.*;
+import entity.config;
+
 public class Tracker implements TrackerInterface {
     private final EntityManagerFactory emf;
     // 默认构造:产线数据库
@@ -35,85 +34,190 @@
         this.emf = emf;
     }
     @Override
-    public boolean AddUpLoad(String userid, int upload) {
+    public boolean AddUpLoad(String userid, int upload, String infoHash) {
+        long newTotal = upload;  // convert to long
         EntityManager em = emf.createEntityManager();
         EntityTransaction tx = em.getTransaction();
+        tx.begin();
         try {
-            tx.begin();
-            QUserPT q = QUserPT.userPT;
-            long updated = new JPAUpdateClause(em, q)
-                .where(q.userid.eq(userid))
-                .set(q.upload, q.upload.add(upload))
-                .execute();
+            // 1) find the seedId by infoHash
+            String seedId = em.createQuery(
+                "SELECT s.seedId FROM SeedHash s WHERE s.infoHash = :ih", String.class)
+                .setParameter("ih", infoHash)
+                .getSingleResult();
+            // 2) sum existing uploads for this user+seed
+            Long sumSoFar = em.createQuery(
+                "SELECT COALESCE(SUM(t.upload),0) FROM TransRecord t WHERE t.uploaduserid = :uid AND t.seedid = :sid",
+                Long.class)
+                .setParameter("uid", userid)
+                .setParameter("sid", seedId)
+                .getSingleResult();
+            long delta = newTotal - sumSoFar;
+            if (delta < 0L) {
+                tx.rollback();
+                return true;   // error: newTotal less than already recorded
+            }
+            if (delta == 0L) {
+                tx.rollback();
+                return false;  // nothing to do
+            }
+            // 3) persist a new TransRecord with only the delta
+            TransRecord rd = new TransRecord();
+            rd.taskid       = UUID.randomUUID().toString();
+            rd.uploaduserid = userid;
+            rd.seedid       = seedId;
+            rd.upload       = delta;
+            rd.maxupload    = newTotal;
+            em.persist(rd);
+
+            // 4) 重新计算用户的总上传,确保与 TransRecord 完全一致
+            Long totalUpload = em.createQuery(
+                "SELECT COALESCE(SUM(t.upload),0) FROM TransRecord t WHERE t.uploaduserid = :uid",
+                Long.class
+            )
+            .setParameter("uid", userid)
+            .getSingleResult();
+            UserPT user = em.find(UserPT.class, userid);
+            user.upload = totalUpload;
+            em.merge(user);
             tx.commit();
-            // 成功时 updated>0 返回 false,失败时返回 true
-            return updated <= 0;
-        } catch(Exception e) {
+            return false;      // success
+        } catch (RuntimeException ex) {
             if (tx.isActive()) tx.rollback();
-            return true;
+            throw ex;
         } finally {
             em.close();
         }
     }
+    
     @Override
     public boolean ReduceUpLoad(String userid, int upload){
+        long uploadLong = upload;  // convert to long
         EntityManager em = emf.createEntityManager();
         EntityTransaction tx = em.getTransaction();
+        tx.begin();
         try {
-            tx.begin();
-            QUserPT q = QUserPT.userPT;
-            long updated = new JPAUpdateClause(em, q)
-                .where(q.userid.eq(userid))
-                .set(q.upload, q.upload.subtract(upload))
-                .execute();
+            // 1) fetch user and ensure enough upload to reduce
+            UserPT user = em.find(UserPT.class, userid);
+            long before = user.upload;
+            if (uploadLong > before) {
+                tx.rollback();
+                return true;   // error: cannot reduce more than current total
+            }
+            // 2) subtract
+            user.upload = before - uploadLong;
+            em.merge(user);
+            // (optional) record a negative TransRecord so sums stay in sync
+            TransRecord rd = new TransRecord();
+            rd.taskid       = UUID.randomUUID().toString();
+            rd.uploaduserid = userid;
+            rd.seedid       = null;
+            rd.upload       = -uploadLong;
+            rd.maxupload    = user.upload;
+            em.persist(rd);
             tx.commit();
-            // 成功时 updated>0 返回 false,失败时返回 true
-            return updated <= 0;
-        } catch(Exception e) {
+            return false;      // success
+        } catch (RuntimeException ex) {
             if (tx.isActive()) tx.rollback();
-            return true;
+            throw ex;
         } finally {
             em.close();
         }
     }
     @Override
-    public boolean AddDownload(String userid, int download) {
+    public boolean AddDownload(String userid, int download, String infoHash) {
+        long newTotal = download;  // convert to long
         EntityManager em = emf.createEntityManager();
         EntityTransaction tx = em.getTransaction();
         try {
-            tx.begin();
-            QUserPT q = QUserPT.userPT;
-            long updated = new JPAUpdateClause(em, q)
-                .where(q.userid.eq(userid))
-                .set(q.download, q.download.add(download))
-                .execute();
-            tx.commit();
-            return updated <= 0;
-        } catch(Exception e) {
-            if (tx.isActive()) tx.rollback();
+            // 1. 查 SeedHash
+            TypedQuery<SeedHash> qsh = em.createQuery(
+                "SELECT s FROM SeedHash s WHERE s.infoHash = :h", SeedHash.class);
+            qsh.setParameter("h", infoHash);
+            List<SeedHash> shl = qsh.getResultList();
+            if (shl.isEmpty()) {
+                System.out.println("seed没有被记录");
+                return true;
+            }
+            String seedid = shl.get(0).seedId;
+
+            // 2. 统计该用户在该种子上的已有 download
+            TypedQuery<Long> qsum = em.createQuery(
+                "SELECT COALESCE(SUM(t.download),0) FROM TransRecord t " +
+                "WHERE t.seedid = :sid AND t.downloaduserid = :uid", Long.class);
+            qsum.setParameter("sid", seedid);
+            qsum.setParameter("uid", userid);
+            long oldSeedSum = qsum.getSingleResult();
+
+            long diff = newTotal - oldSeedSum;
+            if (diff <= 0) return false;
+            
+            System.out.println("AddDownload: 该种子原有总量=" + oldSeedSum + ", 新总量=" + newTotal + ", 增量=" + diff);
+
+            try {
+                tx.begin();
+                // 1. persist 增量记录
+                TransRecord tr = new TransRecord();
+                tr.taskid         = UUID.randomUUID().toString();
+                tr.downloaduserid = userid;
+                tr.seedid         = seedid;
+                tr.download       = diff;
+                tr.maxdownload    = newTotal;
+                em.persist(tr);
+
+                // 2. 全表重新累计该用户所有种子的 download,并更新 UserPT.download
+                TypedQuery<Long> qTotal = em.createQuery(
+                    "SELECT COALESCE(SUM(t.download),0) FROM TransRecord t WHERE t.downloaduserid = :uid",
+                    Long.class
+                )
+                .setParameter("uid", userid);
+                long userTotalDownload = qTotal.getSingleResult();
+                QUserPT quser = QUserPT.userPT;
+                new JPAUpdateClause(em, quser)
+                    .where(quser.userid.eq(userid))
+                    .set(quser.download, userTotalDownload)
+                    .execute();
+
+                tx.commit();
+                return false;
+            } catch (Exception e) {
+                if (tx.isActive()) tx.rollback();
+                return true;
+            } finally {
+                em.close();
+            }
+        } catch (Exception e) {
             return true;
-        } finally {
-            em.close();
         }
     }
     @Override
     public boolean ReduceDownload(String userid, int download) {
+        long downloadLong = download;  // convert to long
         EntityManager em = emf.createEntityManager();
-        EntityTransaction tx = em.getTransaction();
         try {
+            // 1. 预检查当前值
+            TypedQuery<Long> qcurr = em.createQuery(
+                "SELECT u.download FROM UserPT u WHERE u.userid = :uid", Long.class);
+            qcurr.setParameter("uid", userid);
+            long current = qcurr.getSingleResult();
+            if (downloadLong > current) {
+                em.close();
+                return true;
+            }
+            // 2. 执行减法更新
+            EntityTransaction tx = em.getTransaction();
             tx.begin();
             QUserPT q = QUserPT.userPT;
-            long updated = new JPAUpdateClause(em, q)
+            new JPAUpdateClause(em, q)
                 .where(q.userid.eq(userid))
-                .set(q.download, q.download.subtract(download))
+                .set(q.download, q.download.subtract(downloadLong))
                 .execute();
             tx.commit();
-            return updated <= 0;
+            return false;
         } catch(Exception e) {
-            if (tx.isActive()) tx.rollback();
             return true;
         } finally {
-            em.close();
+            if (em.isOpen()) em.close();
         }
     }
     @Override
@@ -166,6 +270,26 @@
             String filename = TTorent.getName();
             Path target = storageDir.resolve(seedid + "_" + filename);
             Files.copy(TTorent.toPath(), target, StandardCopyOption.REPLACE_EXISTING);
+
+            // attempt to parse infoHash, but don’t fail if parsing fails
+            String infoHash = null;
+            try {
+                byte[] torrentData = Files.readAllBytes(target);
+                Bencode bencode = new Bencode();
+                @SuppressWarnings("unchecked")
+                Map<String,Object> meta = (Map<String,Object>) bencode.decode(torrentData, Type.DICTIONARY);
+                byte[] infoBytes = bencode.encode((Map<String,Object>) meta.get("info"));
+                MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
+                byte[] digest = sha1.digest(infoBytes);
+                StringBuilder sb = new StringBuilder();
+                for (byte b1 : digest) {
+                    sb.append(String.format("%02x", b1));
+                }
+                infoHash = sb.toString();
+            } catch (Exception e) {
+                System.err.println("Warning: could not parse torrent infoHash: " + e.getMessage());
+            }
+
             EntityManager em = emf.createEntityManager();
             EntityTransaction tx = em.getTransaction();
             try {
@@ -173,6 +297,14 @@
                 Seed seed = em.find(Seed.class, seedid);
                 seed.url = target.toString();
                 em.merge(seed);
+
+                // upsert SeedHash only if we have a valid infoHash
+                if (infoHash != null) {
+                    SeedHash sh = new SeedHash();
+                    sh.seedId = seedid;
+                    sh.infoHash = infoHash;
+                    em.merge(sh);
+                }
                 tx.commit();
                 return 0;
             } catch (Exception e) {
@@ -224,7 +356,7 @@
             tx.begin();
             em.persist(rd);
             tx.commit();
-            // 持久化成功,返回1表示插入成功
+            // 返回1表示插入成功
             return 1;
         } catch (Exception e) {
             if (tx.isActive()) tx.rollback();
diff --git a/src/main/java/tracker/TrackerInterface.java b/src/main/java/tracker/TrackerInterface.java
index b65b8be..517f100 100644
--- a/src/main/java/tracker/TrackerInterface.java
+++ b/src/main/java/tracker/TrackerInterface.java
@@ -3,12 +3,12 @@
 
 import entity.TransRecord;
 public interface TrackerInterface{
-    public boolean AddUpLoad(String userid,int upload);//给用户新增上传量,返回0成功,返回1失败;
-    public boolean ReduceUpLoad(String userid,int upload);//给用户减上传量,返回0成功,返回1失败;
-    public boolean AddDownload(String userid,int download);//给用户增加下载量,返回0成功,返回1失败;
-    public boolean ReduceDownload(String userid,int download);//给用户减少下载量,返回0成功,返回1失败;
-    public boolean AddMagic(String userid,int magic);//给用户增加魔力值,返回0成功,返回1失败;
-    public boolean ReduceMagic(String userid,int magic);//给用户减少魔力值,返回0成功,返回1失败;
+    public boolean AddUpLoad(String userid,int newTotalUploadForSeed,String infoHash);//给用户新增上传量,newTotalUploadForSeed为该种子的新总上传量,返回false成功,返回true失败;
+    public boolean ReduceUpLoad(String userid,int upload);//给用户减上传量,返回false成功,返回true失败;
+    public boolean AddDownload(String userid,int newTotalDownloadForSeed,String infoHash);//给用户增加下载量,newTotalDownloadForSeed为该种子的新总下载量,返回false成功,返回true失败;
+    public boolean ReduceDownload(String userid,int newTotalDownload);//给用户减少下载量,返回false成功,返回true失败;
+    public boolean AddMagic(String userid,int magic);//给用户增加魔力值,返回false成功,返回true失败;
+    public boolean ReduceMagic(String userid,int magic);//给用户减少魔力值,返回false成功,返回true失败;
 
     public int SaveTorrent(String seedid,File TTorent);//保存seedid对应的ttorent信息
     public File GetTTorent(String seedid,String userid);//根据种子id获得ttorent信息然后构建Ttorent文件并返回,同时记录用户的下载行为
diff --git a/src/test/java/trackertest/TrackerTest.java b/src/test/java/trackertest/TrackerTest.java
index c307ba6..2aeffdb 100644
--- a/src/test/java/trackertest/TrackerTest.java
+++ b/src/test/java/trackertest/TrackerTest.java
@@ -22,7 +22,6 @@
 
 import entity.Seed;
 import entity.TransRecord;
-import entity.TransportId;
 import entity.config;
 import tracker.Tracker;
 public class TrackerTest {
@@ -31,6 +30,7 @@
     private static List<String> userIds;
     private static Map<String, Long> originalUploads;
     private static Tracker tracker;
+    private static List<String> infoHashes;
     @BeforeAll
     static void setup() throws Exception {
         // 强制加载 MySQL 驱动,否则无法建立连接
@@ -60,6 +60,10 @@
              .getSingleResult();
             originalUploads.put(uid, up != null ? up : 0L);
         }
+        // fetch real infoHash values
+        infoHashes = em.createQuery(
+            "select s.infoHash from SeedHash s", String.class
+        ).getResultList();
         tracker = new Tracker(emf);
     }
     @AfterAll
@@ -79,116 +83,176 @@
         if (em != null && em.isOpen()) em.close();
         if (emf != null && emf.isOpen()) emf.close();
     }
+    /*
     @TestFactory
     Collection<DynamicTest> testAddUpLoad() {
         Random rnd = new Random();
+        String ih = infoHashes.get(0);
         return userIds.stream()
             .map(uid -> DynamicTest.dynamicTest("AddUpLoad for user " + uid, () -> {
-                int delta = rnd.nextInt(1000) + 1; // Ensure non-zero value
-                long before = originalUploads.get(uid);
-                
-                // AddUpLoad 前打印
-                System.out.println("Running AddUpLoad assert for user=" + uid + ", delta=" + delta);
-                Assertions.assertFalse(tracker.AddUpLoad(uid, delta), 
-                    "AddUpLoad should return false on successful operation");
-                System.out.println("AddUpLoad assert passed for user=" + uid);
-                // Clear the persistence context to ensure fresh data is fetched
-                em.clear();
-                
-                // Fetch updated value
-                Long after = em.createQuery(
-                    "select u.upload from UserPT u where u.userid = :uid", Long.class
-                ).setParameter("uid", uid)
-                 .getSingleResult();
-                // upload 值断言前打印
-                System.out.println("Running upload-value assert for user=" + uid);
-                Assertions.assertEquals(before + delta, after, 
-                    "Upload value should be increased by " + delta);
-                System.out.println("Upload-value assert passed for user=" + uid);
-                
-                // ReduceUpLoad 前打印
-                System.out.println("Running ReduceUpLoad assert for user=" + uid + ", delta=" + delta);
-                Assertions.assertFalse(tracker.ReduceUpLoad(uid, delta),
-                    "ReduceUpLoad should return false on successful operation");
-                System.out.println("ReduceUpLoad assert passed for user=" + uid);
+                EntityTransaction tx = em.getTransaction();
+                tx.begin();
+                try {
+                    // 获取该用户当前的总上传量
+                    Long currentUserUpload = em.createQuery(
+                        "SELECT COALESCE(u.upload,0) FROM UserPT u WHERE u.userid = :uid", Long.class
+                    ).setParameter("uid", uid).getSingleResult();
+                    
+                    // 获取该用户在该种子上的当前上传量
+                    Long currentSeedUpload = em.createQuery(
+                        "SELECT COALESCE(SUM(t.upload),0) FROM TransRecord t WHERE t.uploaduserid = :uid AND t.seedid = " +
+                        "(SELECT s.seedId FROM SeedHash s WHERE s.infoHash = :ih)", Long.class
+                    ).setParameter("uid", uid).setParameter("ih", ih).getSingleResult();
+                    
+                    int delta = rnd.nextInt(1000) + 1;
+                    long newSeedTotal = currentSeedUpload + delta;
+                    
+                    Assertions.assertFalse(tracker.AddUpLoad(uid, (int)newSeedTotal, ih),
+                        "AddUpLoad should return false on successful operation");
+                    em.clear();
+
+                    // commit & restart test TX so we see the data that tracker committed
+                    tx.commit();
+                    em.clear();
+                    tx.begin();
+
+                    // 验证 UserPT.upload 是否等于 TransRecord 表中该用户的实际总和
+                    Long actualTransRecordSum = em.createQuery(
+                        "SELECT COALESCE(SUM(t.upload),0) FROM TransRecord t WHERE t.uploaduserid = :uid", Long.class
+                    ).setParameter("uid", uid).getSingleResult();
+                    
+                    Long userPTUpload = em.createQuery(
+                        "SELECT u.upload FROM UserPT u WHERE u.userid = :uid", Long.class
+                    ).setParameter("uid", uid).getSingleResult();
+                    
+                    Assertions.assertEquals(actualTransRecordSum, userPTUpload, 
+                        "UserPT.upload should equal sum of TransRecord.upload for this user");
+                    Assertions.assertEquals(currentUserUpload + delta, userPTUpload.longValue(),
+                        "User total upload should increase by delta");
+                } finally {
+                    tx.rollback();
+                    em.clear();
+                }
             }))
             .collect(Collectors.toList());
     }
-    @TestFactory
-    Collection<DynamicTest> testReduceUpLoad() {
-        Random rnd = new Random();
-        return userIds.stream()
-            .map(uid -> DynamicTest.dynamicTest("ReduceUpLoad for user " + uid, () -> {
-                long before = originalUploads.get(uid);
-                int max = (int)Math.min(before, 1000);
-                int delta = max > 0 ? rnd.nextInt(max) + 1 : 0;
-                if (delta == 0) return;  // 无可减量时跳过
-                // ReduceUpLoad 前打印
-                System.out.println("Running ReduceUpLoad assert for user=" + uid + ", delta=" + delta);
-                Assertions.assertFalse(tracker.ReduceUpLoad(uid, delta));
-                System.out.println("ReduceUpLoad assert passed for user=" + uid);
-                Long after = em.createQuery(
-                    "select u.upload from UserPT u where u.userid = :uid", Long.class
-                ).setParameter("uid", uid)
-                 .getSingleResult();
-                // 减少后值断言前打印
-                System.out.println("Running post-reduce-value assert for user=" + uid);
-                Assertions.assertEquals(before - delta, after);
-                System.out.println("Post-reduce-value assert passed for user=" + uid);
-                // 回滚 AddUpLoad 前打印
-                System.out.println("Running rollback AddUpLoad assert for user=" + uid + ", delta=" + delta);
-                Assertions.assertFalse(tracker.AddUpLoad(uid, delta));
-                System.out.println("Rollback AddUpLoad assert passed for user=" + uid);
-            }))
-            .collect(Collectors.toList());
-    }
+    */
+   
+   
+    // @TestFactory
+    // Collection<DynamicTest> testReduceUpLoad() {
+    //     Random rnd = new Random();
+    //     String ih = infoHashes.get(0);  // use same infoHash as other tests
+    //     return userIds.stream()
+    //         .map(uid -> DynamicTest.dynamicTest("ReduceUpLoad for user " + uid, () -> {
+    //             long before = originalUploads.get(uid);
+    //             int max = (int)Math.min(before, 1000);
+    //             int delta = max > 0 ? rnd.nextInt(max) + 1 : 0;
+    //             if (delta == 0) return;  // 无可减量时跳过
+    //             // ReduceUpLoad 前打印
+    //             System.out.println("Running ReduceUpLoad assert for user=" + uid + ", delta=" + delta);
+    //             Assertions.assertFalse(tracker.ReduceUpLoad(uid, delta));
+    //             System.out.println("ReduceUpLoad assert passed for user=" + uid);
+    //             Long after = em.createQuery(
+    //                 "select u.upload from UserPT u where u.userid = :uid", Long.class
+    //             ).setParameter("uid", uid)
+    //              .getSingleResult();
+    //             // 减少后值断言前打印
+    //             System.out.println("Running post-reduce-value assert for user=" + uid);
+    //             Assertions.assertEquals(before - delta, after);
+    //             System.out.println("Post-reduce-value assert passed for user=" + uid);
+    //             // 回滚 AddUpLoad 前打印
+    //             System.out.println("Running rollback AddUpLoad assert for user=" + uid + ", delta=" + delta);
+    //             Assertions.assertFalse(tracker.AddUpLoad(uid, (int)before, ih));
+    //             System.out.println("Rollback AddUpLoad assert passed for user=" + uid);
+    //         }))
+    //         .collect(Collectors.toList());
+    // }
+    
+    /*
     @TestFactory
     Collection<DynamicTest> testAddDownload() {
         Random rnd = new Random();
+        String ih = infoHashes.get(0);
         return userIds.stream()
             .map(uid -> DynamicTest.dynamicTest("AddDownload for user " + uid, () -> {
-                int delta = rnd.nextInt(1000) + 1;
-                Long before = em.createQuery(
-                    "select u.download from UserPT u where u.userid = :uid", Long.class
-                ).setParameter("uid", uid).getSingleResult();
-                before = before != null ? before : 0L;
-                System.out.println("Running AddDownload assert for user=" + uid + ", delta=" + delta);
-                Assertions.assertFalse(tracker.AddDownload(uid, delta),
-                    "AddDownload should return false on successful operation");
-                em.clear();
-                Long after = em.createQuery(
-                    "select u.download from UserPT u where u.userid = :uid", Long.class
-                ).setParameter("uid", uid).getSingleResult();
-                System.out.println("Running download-value assert for user=" + uid);
-                Assertions.assertEquals(before + delta, after,
-                    "Download value should be increased by " + delta);
-                System.out.println("Running rollback ReduceDownload assert for user=" + uid + ", delta=" + delta);
-                Assertions.assertFalse(tracker.ReduceDownload(uid, delta),
-                    "Rollback ReduceDownload should return false");
+                EntityTransaction tx = em.getTransaction();
+                tx.begin();
+                try {
+                    // 获取该用户当前的总下载量
+                    Long currentUserDownload = em.createQuery(
+                        "SELECT COALESCE(u.download,0) FROM UserPT u WHERE u.userid = :uid", Long.class
+                    ).setParameter("uid", uid).getSingleResult();
+                    
+                    // 获取该用户在该种子上的当前下载量
+                    Long currentSeedDownload = em.createQuery(
+                        "SELECT COALESCE(SUM(t.download),0) FROM TransRecord t WHERE t.downloaduserid = :uid AND t.seedid = " +
+                        "(SELECT s.seedId FROM SeedHash s WHERE s.infoHash = :ih)", Long.class
+                    ).setParameter("uid", uid).setParameter("ih", ih).getSingleResult();
+                    
+                    int delta = rnd.nextInt(1000) + 1;
+                    long newSeedTotal = currentSeedDownload + delta;
+                    
+                    Assertions.assertFalse(tracker.AddDownload(uid, (int)newSeedTotal, ih),
+                        "AddDownload should return false on successful operation");
+                    em.clear();
+
+                    // commit & restart test TX so we see the data that tracker committed
+                    tx.commit();
+                    em.clear();
+                    tx.begin();
+
+                    // 验证 UserPT.download 是否等于 TransRecord 表中该用户的实际总和
+                    Long actualTransRecordSum = em.createQuery(
+                        "SELECT COALESCE(SUM(t.download),0) FROM TransRecord t WHERE t.downloaduserid = :uid", Long.class
+                    ).setParameter("uid", uid).getSingleResult();
+                    
+                    Long userPTDownload = em.createQuery(
+                        "SELECT u.download FROM UserPT u WHERE u.userid = :uid", Long.class
+                    ).setParameter("uid", uid).getSingleResult();
+                    
+                    Assertions.assertEquals(actualTransRecordSum, userPTDownload,
+                        "UserPT.download should equal sum of TransRecord.download for this user");
+                    
+                    // 验证用户总下载量增加了预期的delta
+                    Assertions.assertEquals(currentUserDownload + delta, userPTDownload.longValue(),
+                        "User total download should increase by delta");
+                } finally {
+                    tx.rollback();
+                    em.clear();
+                }
             }))
             .collect(Collectors.toList());
     }
+    */
     @TestFactory
     Collection<DynamicTest> testReduceDownload() {
         Random rnd = new Random();
         return userIds.stream()
             .map(uid -> DynamicTest.dynamicTest("ReduceDownload for user " + uid, () -> {
                 Long before = em.createQuery(
-                    "select u.download from UserPT u where u.userid = :uid", Long.class
+                    "SELECT u.download FROM UserPT u WHERE u.userid = :uid", Long.class
                 ).setParameter("uid", uid).getSingleResult();
                 before = before != null ? before : 0L;
                 int max = before.intValue();
                 int delta = max > 0 ? rnd.nextInt(max) + 1 : 0;
                 if (delta == 0) return;
+                
                 System.out.println("Running ReduceDownload assert for user=" + uid + ", delta=" + delta);
                 Assertions.assertFalse(tracker.ReduceDownload(uid, delta));
+                em.clear();
+                
                 Long after = em.createQuery(
-                    "select u.download from UserPT u where u.userid = :uid", Long.class
+                    "SELECT u.download FROM UserPT u WHERE u.userid = :uid", Long.class
                 ).setParameter("uid", uid).getSingleResult();
                 System.out.println("Running post-reduce-download-value assert for user=" + uid);
-                Assertions.assertEquals(before - delta, after);
+                Assertions.assertEquals(before - delta, after.longValue());
+                
                 System.out.println("Running rollback AddDownload assert for user=" + uid + ", delta=" + delta);
-                Assertions.assertFalse(tracker.AddDownload(uid, delta));
+                // 使用有效的 infoHash
+                Assertions.assertFalse(
+                    tracker.AddDownload(uid, before.intValue(), infoHashes.get(0))
+                );
             }))
             .collect(Collectors.toList());
     }
@@ -209,7 +273,7 @@
                     "select u.magic from UserPT u where u.userid = :uid", Integer.class
                 ).setParameter("uid", uid).getSingleResult();
                 System.out.println("Running magic-value assert for user=" + uid);
-                Assertions.assertEquals(before + delta, after.intValue());
+                Assertions.assertEquals((Integer)(before + delta), after);
                 System.out.println("Running rollback ReduceMagic assert for user=" + uid + ", delta=" + delta);
                 Assertions.assertFalse(tracker.ReduceMagic(uid, delta));
             }))
@@ -229,11 +293,12 @@
                 if (delta == 0) return;
                 System.out.println("Running ReduceMagic assert for user=" + uid + ", delta=" + delta);
                 Assertions.assertFalse(tracker.ReduceMagic(uid, delta));
+                em.clear();
                 Integer after = em.createQuery(
                     "select u.magic from UserPT u where u.userid = :uid", Integer.class
                 ).setParameter("uid", uid).getSingleResult();
                 System.out.println("Running post-reduce-magic-value assert for user=" + uid);
-                Assertions.assertEquals(before - delta, after.intValue());
+                Assertions.assertEquals((Integer)(before - delta), after);
                 System.out.println("Running rollback AddMagic assert for user=" + uid + ", delta=" + delta);
                 Assertions.assertFalse(tracker.AddMagic(uid, delta));
             }))
@@ -262,22 +327,21 @@
                 rd.uploaduserid = uploaderId;
                 rd.downloaduserid = downloaderId;
                 rd.seedid = seedId;
-                rd.upload = rnd.nextInt(10000);
-                rd.download = rnd.nextInt(10000);
-                rd.maxupload = rd.upload + rnd.nextInt(10000);
-                rd.maxdownload = rd.download + rnd.nextInt(10000);
+                rd.upload = (long) rnd.nextInt(10000);
+                rd.download = (long) rnd.nextInt(10000);
+                rd.maxupload = rd.upload + (long) rnd.nextInt(10000);
+                rd.maxdownload = rd.download + (long) rnd.nextInt(10000);
                 // 调用待测方法
                 int ret = tracker.AddRecord(rd);
                 Assertions.assertTrue(ret > 0, "返回值应为新记录主键");
                 // 验证已插入
-                TransportId pk = new TransportId(taskId, uploaderId, downloaderId);
-                TransRecord fetched = em.find(TransRecord.class, pk);
-                Assertions.assertNotNull(fetched, "应查询到新增的 TransRecord");
-                // 清理:删除测试记录
-                EntityTransaction tx = em.getTransaction();
-                tx.begin();
-                em.remove(fetched);
-                tx.commit();
+                TransRecord fetched = em.find(TransRecord.class, rd.taskid);
+
+                Assertions.assertNotNull(fetched, "查询应返回非空实体");
+                Assertions.assertEquals(rd.upload, fetched.upload);
+                Assertions.assertEquals(rd.download, fetched.download);
+                Assertions.assertEquals(rd.maxupload, fetched.maxupload);
+                Assertions.assertEquals(rd.maxdownload, fetched.maxdownload);
             }))
             .collect(Collectors.toList());
     }
@@ -349,4 +413,5 @@
             }))
             .collect(Collectors.toList());
     }
-}
\ No newline at end of file
+    
+}
diff --git a/trackerServer b/trackerServer
deleted file mode 160000
index bbe2ea1..0000000
--- a/trackerServer
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit bbe2ea1f3844c884654fc7f70b641e983c9dfe10