增加流量监测和假种检测功能

Change-Id: I808ce14b6f08565f797f4681a6f72db9c730d011
diff --git a/src/main/java/cheat/Cheat.java b/src/main/java/cheat/Cheat.java
index cb35c05..1141c5b 100644
--- a/src/main/java/cheat/Cheat.java
+++ b/src/main/java/cheat/Cheat.java
@@ -4,6 +4,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.HashMap;
 
 import javax.persistence.EntityManager;
 import javax.persistence.EntityManagerFactory;
@@ -20,6 +21,7 @@
 
 import com.querydsl.jpa.impl.JPAQueryFactory;
 
+import entity.Seed;
 import entity.Appeal;
 import entity.QAppeal;
 import entity.QSeed;
@@ -27,6 +29,8 @@
 import entity.config;
 import entity.QUser;
 import entity.TTorent;
+import entity.QTransRecord;
+import entity.TransRecord;
 
 public class Cheat implements CheatInterfnterface {
 
@@ -180,50 +184,230 @@
 
     @Override
     public void DetectTrans() {
-        // JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager);
-        // QSeed qSeed = QSeed.seed;
-        // List<Seed> seeds = queryFactory
-        //     .selectFrom(qSeed)
-        //     .fetch();
-        // for (Seed seed : seeds) {
-        //     QTransRecord qTransRecord = QTransRecord.transRecord;
-        //     List<TransRecord> tasks = queryFactory
-        //         .selectFrom(qTransRecord)
-        //         .where(qTransRecord.seedid.eq(seed.seedid))
-        //         .fetch();
+        EntityManager entityManager = emf.createEntityManager();
+        try {
+            JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager);
+            QSeed qSeed = QSeed.seed;
+            QTransRecord qTransRecord = QTransRecord.transRecord;
+            
+            // 获取所有种子
+            List<Seed> seeds = queryFactory
+                .selectFrom(qSeed)
+                .fetch();
 
-        //     int n = tasks.size();
-        //     if (n == 0) continue;
+            // 存储每个种子的delta值
+            List<Double> allDeltas = new ArrayList<>();
+            Map<String, Double> seedDeltaMap = new HashMap<>();
+            
+            // 计算每个种子的delta值(只考虑account_status不为1的用户)
+            for (Seed seed : seeds) {
+                List<TransRecord> tasks = queryFactory
+                    .selectFrom(qTransRecord)
+                    .leftJoin(qTransRecord.uploader).fetchJoin()
+                    .leftJoin(qTransRecord.downloader).fetchJoin()
+                    .where(qTransRecord.seedid.eq(seed.seedid))
+                    .fetch();
 
-        //     double[] xArr = new double[n];
-        //     for (int i = 0; i < n; i++) {
-        //         TransRecord t = tasks.get(i);
-        //         xArr[i] = Math.max(0, t.upload - t.download);
-        //     }
+                // 过滤掉account_status为1的用户的记录
+                List<TransRecord> validTasks = new ArrayList<>();
+                for (TransRecord task : tasks) {
+                    boolean shouldInclude = true;
+                    
+                    // 检查上传者
+                    if (task.uploaduserid != null) {
+                        User uploader = entityManager.find(User.class, task.uploaduserid);
+                        if (uploader != null && uploader.accountstate) {
+                            shouldInclude = false;
+                        }
+                    }
+                    
+                    // 检查下载者
+                    if (task.downloaduserid != null) {
+                        User downloader = entityManager.find(User.class, task.downloaduserid);
+                        if (downloader != null && downloader.accountstate) {
+                            shouldInclude = false;
+                        }
+                    }
+                    
+                    if (shouldInclude) {
+                        validTasks.add(task);
+                    }
+                }
 
-        //     double sum = 0;
-        //     for (double x : xArr) sum += x;
-        //     double mu = sum / n;
+                if (validTasks.isEmpty()) continue;
 
-        //     double sqSum = 0;
-        //     for (double x : xArr) sqSum += (x - mu) * (x - mu);
-        //     double sigma = Math.sqrt(sqSum / n);
+                // 计算该种子的总delta
+                double totalDelta = 0;
+                for (TransRecord task : validTasks) {
+                    long upload = task.upload != null ? task.upload : 0L;
+                    long download = task.download != null ? task.download : 0L;
+                    totalDelta += (upload - download);
+                }
 
-        //     for (int i = 0; i < n; i++) {
-        //         if (Math.abs(xArr[i] - mu) > 3 * sigma) {
-        //             User user = entityManager.find(User.class, tasks.get(i).downloaduserid);
-        //             if (user != null) {
-        //                 user.detectedCount++;
-        //                 user.lastDetectedTime = new java.util.Date();
-        //                 entityManager.merge(user);
-        //             }
-        //         }
-        //     }
-        // }
+                allDeltas.add(totalDelta);
+                seedDeltaMap.put(seed.seedid, totalDelta);
+            }
+
+            if (allDeltas.size() < 2) return; // 需要至少2个种子才能计算标准差
+
+            // 计算平均值和标准差
+            double sum = 0;
+            for (double delta : allDeltas) {
+                sum += delta;
+            }
+            double mean = sum / allDeltas.size();
+
+            double variance = 0;
+            for (double delta : allDeltas) {
+                variance += Math.pow(delta - mean, 2);
+            }
+            double stdDev = Math.sqrt(variance / allDeltas.size());
+
+            // 检测异常种子并处理参与者
+            for (Seed seed : seeds) {
+                Double seedDelta = seedDeltaMap.get(seed.seedid);
+                if (seedDelta == null) continue;
+
+                // 检查是否偏离1个标准差
+                if (Math.abs(seedDelta - mean) > stdDev) {
+                    // 获取该种子的所有传输记录参与者
+                    List<TransRecord> suspiciousTasks = queryFactory
+                        .selectFrom(qTransRecord)
+                        .where(qTransRecord.seedid.eq(seed.seedid))
+                        .fetch();
+
+                    // 更新所有参与者的detectedCount
+                    for (TransRecord task : suspiciousTasks) {
+                        // 处理上传者
+                        if (task.uploaduserid != null) {
+                            User uploader = entityManager.find(User.class, task.uploaduserid);
+                            if (uploader != null && !uploader.accountstate) {
+                                uploader.detectedCount++;
+                                uploader.lastDetectedTime = new java.util.Date();
+                                entityManager.merge(uploader);
+                            }
+                        }
+                        
+                        // 处理下载者
+                        if (task.downloaduserid != null) {
+                            User downloader = entityManager.find(User.class, task.downloaduserid);
+                            if (downloader != null && !downloader.accountstate) {
+                                downloader.detectedCount++;
+                                downloader.lastDetectedTime = new java.util.Date();
+                                entityManager.merge(downloader);
+                            }
+                        }
+                    }
+                }
+            }
+        } finally {
+            if (entityManager != null) {
+                entityManager.close();
+            }
+        }
     }
 
     @Override
+    @Transactional
     public void DetectFakeSeed(){
+        EntityManager entityManager = emf.createEntityManager();
+        try {
+            String torrentDir = entity.config.TORRENT_STORAGE_DIR;
+            String scriptPath = "./src/main/java/cheat/fakeSeed.bash";
+            
+            ProcessBuilder processBuilder = new ProcessBuilder("bash", scriptPath, torrentDir);
+            processBuilder.inheritIO();
+            
+            Process process = processBuilder.start();
+            int exitCode = process.waitFor();
+            
+            if (exitCode == 0) {
+                System.out.println("FakeSeed detection completed successfully");
+                
+                // 处理检测结果
+                String failedDir = torrentDir + "/failed_torrents";
+                java.io.File failedDirFile = new java.io.File(failedDir);
+                
+                // 获取失败种子文件列表
+                java.util.Set<String> failedTorrentNames = new java.util.HashSet<>();
+                if (failedDirFile.exists() && failedDirFile.isDirectory()) {
+                    java.io.File[] failedFiles = failedDirFile.listFiles((dir, name) -> name.endsWith(".torrent"));
+                    if (failedFiles != null) {
+                        for (java.io.File file : failedFiles) {
+                            failedTorrentNames.add(file.getName());
+                        }
+                    }
+                }
+                
+                JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager);
+                QSeed qSeed = QSeed.seed;
+                
+                // 收集假种子的拥有者
+                java.util.Set<String> fakeOwners = new java.util.HashSet<>();
+                
+                // 更新数据库中的fake_hits字段
+                List<Seed> allSeeds = queryFactory
+                    .selectFrom(qSeed)
+                    .where(qSeed.url.isNotNull())
+                    .fetch();
+                
+                for (Seed seed : allSeeds) {
+                    java.io.File seedFile = new java.io.File(seed.url);
+                    String fileName = seedFile.getName();
+                    
+                    if (failedTorrentNames.contains(fileName)) {
+                        // 失败种子,fake_hits + 1
+                        seed.faketime++;
+                        seed.lastfakecheck = new java.util.Date();
+                        fakeOwners.add(seed.seeduserid);
+                        System.out.println("Found fake seed: " + seed.seedid + " (fake_hits: " + seed.faketime + ")");
+                    } else {
+                        // 正常种子,fake_hits 清零
+                        if (seed.faketime > 0) {
+                            seed.faketime = 0;
+                            seed.lastfakecheck = new java.util.Date();
+                            System.out.println("Reset fake_hits for seed: " + seed.seedid);
+                        }
+                    }
+                    entityManager.merge(seed);
+                }
+                
+                // 更新所有用户的 fakeDetectedCount 和 fakeLastDetectedTime
+                entity.QUser qUser = entity.QUser.user;
+                List<User> allUsers = queryFactory
+                    .selectFrom(qUser)
+                    .fetch();
+                
+                java.util.Date now = new java.util.Date();
+                for (User user : allUsers) {
+                    if (fakeOwners.contains(user.userid)) {
+                        // 假种子拥有者,fakeDetectedCount + 1
+                        user.fakeDetectedCount++;
+                        System.out.println("Increased fake detection count for user: " + user.userid + " (count: " + user.fakeDetectedCount + ")");
+                    } else {
+                        // 其他用户,fakeDetectedCount 清零
+                        if (user.fakeDetectedCount > 0) {
+                            user.fakeDetectedCount = 0;
+                            System.out.println("Reset fake detection count for user: " + user.userid);
+                        }
+                    }
+                    // 更新所有用户的检测时间
+                    user.fakeLastDetectedTime = now;
+                    entityManager.merge(user);
+                }
+                
+                System.out.println("Fake seed detection and database update completed");
+            } else {
+                System.err.println("FakeSeed detection failed with exit code: " + exitCode);
+            }
+        } catch (Exception e) {
+            System.err.println("Error running FakeSeed detection script: " + e.getMessage());
+            e.printStackTrace();
+        } finally {
+            if (entityManager != null) {
+                entityManager.close();
+            }
+        }
     }
 
     @Override
diff --git a/src/main/java/cheat/fakeSeed.bash b/src/main/java/cheat/fakeSeed.bash
new file mode 100644
index 0000000..a2aae52
--- /dev/null
+++ b/src/main/java/cheat/fakeSeed.bash
@@ -0,0 +1,182 @@
+#!/usr/bin/env bash
+#
+# batch_test_torrents.sh — 批量检测 .torrent 文件是否能正确启动下载
+#
+# 依赖:curl, jq
+#
+# 用法:./batch_test_torrents.sh /path/to/torrent_dir
+
+set -euo pipefail
+
+QB_URL="http://127.0.0.1:8080"
+QB_USER="admin"
+QB_PASS="9H6k8VpcM"
+COOKIE_JAR="$(mktemp)"
+TORRENT_DIR="${1:-.}"
+TIMEOUT_SECS=60     # 最长等多久才判超时
+POLL_INTERVAL=2     # 每次轮询间隔
+FAILED_DIR="${TORRENT_DIR}/failed_torrents"  # 失败种子存放目录
+
+
+# 登录
+_login() {
+  curl -s -c "$COOKIE_JAR" \
+       -d "username=$QB_USER&password=$QB_PASS" \
+       "$QB_URL/api/v2/auth/login" \
+    | grep -q "Ok." || {
+      echo "❌ 登录失败" >&2
+      exit 1
+    }
+}
+
+# 登出
+_logout() {
+  curl -s -b "$COOKIE_JAR" "$QB_URL/api/v2/auth/logout" >/dev/null
+  rm -f "$COOKIE_JAR"
+}
+
+# 添加 torrent,返回 infoHash
+# $1 = .torrent 文件路径
+_add_torrent() {
+  local file="$1"
+  # 丢弃 “Ok.”,只留下后续 info-hash
+  curl -s -b "$COOKIE_JAR" -X POST \
+       -F "torrents=@${file}" \
+       "$QB_URL/api/v2/torrents/add" >/dev/null
+
+  # 等待 qBittorrent 收到任务
+  sleep 3
+
+  # 取最新添加的那个 torrent(按 added_on 降序,limit=1)
+  local info
+  info=$(curl -s -b "$COOKIE_JAR" \
+           "$QB_URL/api/v2/torrents/info?limit=1&sort=added_on&reverse=true")
+
+  # 检查是否获取到有效的 JSON 响应
+  if ! echo "$info" | jq empty 2>/dev/null; then
+    echo "ERROR: Invalid JSON response from qBittorrent API" >&2
+    echo "Response: $info" >&2
+    return 1
+  fi
+
+  # 检查是否有 torrent 记录
+  local count
+  count=$(echo "$info" | jq 'length')
+  if [[ "$count" == "0" ]]; then
+    echo "ERROR: No torrents found after adding" >&2
+    return 1
+  fi
+
+  # 只输出 hash
+  echo "$info" | jq -r '.[0].hash'
+}
+
+# 删除 torrent,同时删除已下载的文件
+# $1 = infoHash
+_delete_torrent() {
+  local hash="$1"
+  curl -s -b "$COOKIE_JAR" \
+       -G --data-urlencode "hashes=${hash}" \
+       "$QB_URL/api/v2/torrents/delete?deleteFiles=true" >/dev/null
+}
+
+# 等待并检测状态
+# $1 = infoHash
+_wait_for_progress() {
+  local hash="$1"
+  local waited=0
+
+  while (( waited < TIMEOUT_SECS )); do
+    # 获取特定种子信息
+    local info
+    info=$(curl -s -b "$COOKIE_JAR" \
+               -G --data-urlencode "hashes=${hash}" \
+               "$QB_URL/api/v2/torrents/info")
+
+    # 检查 JSON 响应
+    if ! echo "$info" | jq empty 2>/dev/null; then
+      echo "⚠️  ${hash}: Invalid API response, retrying..."
+      sleep $POLL_INTERVAL
+      waited=$(( waited + POLL_INTERVAL ))
+      continue
+    fi
+
+    # 检查是否返回了数据
+    local count
+    count=$(echo "$info" | jq 'length')
+    if [[ "$count" == "0" ]]; then
+      echo "⚠️  ${hash}: Torrent not found, retrying..."
+      sleep $POLL_INTERVAL
+      waited=$(( waited + POLL_INTERVAL ))
+      continue
+    fi
+
+    local state progress
+    state=$(echo "$info" | jq -r '.[0].state // "unknown"')
+    progress=$(echo "$info" | jq -r '.[0].progress // 0')
+
+    # 成功开始下载(progress > 0)
+    if awk "BEGIN {exit !($progress > 0)}"; then
+      local progress_percent
+      progress_percent=$(awk "BEGIN {printf \"%.2f\", $progress * 100}")
+      echo "✅ ${hash}: started downloading (progress=${progress_percent}%)"
+      return 0
+    fi
+
+    # 出错状态
+    if [[ "$state" == "error" ]]; then
+      echo "❌ ${hash}: entered error state"
+      return 1
+    fi
+
+    sleep $POLL_INTERVAL
+    waited=$(( waited + POLL_INTERVAL ))
+  done
+
+  echo "⚠️  ${hash}: no progress after ${TIMEOUT_SECS}s timeout"
+  return 2
+}
+
+main() {
+  if [[ ! -d "$TORRENT_DIR" ]]; then
+    echo "Usage: $0 /path/to/torrent_dir" >&2
+    exit 1
+  fi
+
+  # 创建失败种子目录(如果不存在)
+  mkdir -p "$FAILED_DIR"
+  
+  # 清空失败种子目录
+  if [[ -d "$FAILED_DIR" ]]; then
+    rm -f "$FAILED_DIR"/*.torrent 2>/dev/null || true
+    echo "已清空失败种子目录:$FAILED_DIR"
+  fi
+
+  _login
+  echo "开始批量测试目录:$TORRENT_DIR"
+  echo "失败的种子将被复制到:$FAILED_DIR"
+
+  for file in "$TORRENT_DIR"/*.torrent; do
+    [[ -e "$file" ]] || { echo "目录中没有 .torrent 文件"; break; }
+
+    echo "---- 测试 $file ----"
+    hash=$(_add_torrent "$file")
+    echo "添加成功,infoHash=$hash"
+
+    if _wait_for_progress "$hash"; then
+      echo ">>> $file 下载检测通过"
+    else
+      echo ">>> $file 下载检测失败"
+      cp "$file" "$FAILED_DIR/"
+      echo "已将失败种子复制到:$FAILED_DIR/$(basename "$file")"
+    fi
+
+    _delete_torrent "$hash"
+    echo
+  done
+
+  _logout
+  echo "全部完成。失败的种子文件已保存在:$FAILED_DIR"
+}
+
+main "$@"
diff --git a/src/main/java/cheat/faketest.py b/src/main/java/cheat/faketest.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/main/java/cheat/faketest.py
diff --git a/src/main/java/entity/config.java b/src/main/java/entity/config.java
index 0230066..c77f73d 100644
--- a/src/main/java/entity/config.java
+++ b/src/main/java/entity/config.java
@@ -85,4 +85,7 @@
         begVote = 3;
         cheatTime = 5;
     }
+    // public static final String trackerHost="0.0.0.0";
+    // public static final int trackerPort=6969;
+    // public static final int capturePort=6670;
 }
diff --git a/src/main/java/tracker/DataCaptureProxy.java b/src/main/java/tracker/DataCaptureProxy.java
index cb66feb..1749421 100644
--- a/src/main/java/tracker/DataCaptureProxy.java
+++ b/src/main/java/tracker/DataCaptureProxy.java
@@ -66,14 +66,22 @@
                     if (uploaded != null && !uploaded.isEmpty()) {
                         int uploadValue = Integer.parseInt(uploaded);
                         if (uploadValue > 0) {
-                            tracker.AddUpLoad(passkey, uploadValue, infoHash);
+                            try {
+                                tracker.AddUpLoad(passkey, uploadValue, infoHash);
+                            } catch (javax.persistence.NoResultException e) {
+                                System.out.println("Skipping upload update: info_hash not found in database - " + infoHash);
+                            }
                         }
                     }
                     
                     if (downloaded != null && !downloaded.isEmpty()) {
                         int downloadValue = Integer.parseInt(downloaded);
                         if (downloadValue > 0) {
-                            tracker.AddDownload(passkey, downloadValue, infoHash);
+                            try {
+                                tracker.AddDownload(passkey, downloadValue, infoHash);
+                            } catch (javax.persistence.NoResultException e) {
+                                System.out.println("Skipping download update: info_hash not found in database - " + infoHash);
+                            }
                         }
                     }
                 } catch (NumberFormatException e) {
diff --git a/src/main/java/tracker/Tracker.java b/src/main/java/tracker/Tracker.java
index 1ac4d94..1b38806 100644
--- a/src/main/java/tracker/Tracker.java
+++ b/src/main/java/tracker/Tracker.java
@@ -16,7 +16,8 @@
 import com.querydsl.jpa.impl.JPAUpdateClause;
 import entity.*;
 import entity.config;
-
+import java.util.Scanner;
+import java.io.IOException;
 public class Tracker implements TrackerInterface {
     private final EntityManagerFactory emf;
     // 默认构造:产线数据库
@@ -55,7 +56,7 @@
             long delta = newTotal - sumSoFar;
             if (delta < 0L) {
                 tx.rollback();
-                return true;   // error: newTotal less than already recorded
+                return false;   // error: newTotal less than already recorded
             }
             if (delta == 0L) {
                 tx.rollback();
@@ -69,7 +70,7 @@
             rd.upload       = delta;
             rd.maxupload    = newTotal;
             em.persist(rd);
-
+            em.flush();
             // 4) 重新计算用户的总上传,确保与 TransRecord 完全一致
             Long totalUpload = em.createQuery(
                 "SELECT COALESCE(SUM(t.upload),0) FROM TransRecord t WHERE t.uploaduserid = :uid",
@@ -77,10 +78,29 @@
             )
             .setParameter("uid", userid)
             .getSingleResult();
+
+            Long PTuploadbefor = em.createQuery(
+                "SELECT t.upload FROM UserPT t WHERE t.userid = :uid",
+                Long.class
+            ).setParameter("uid", userid)
+            .getSingleResult();
+            
+        
             UserPT user = em.find(UserPT.class, userid);
             user.upload = totalUpload;
             em.merge(user);
+            em.flush();
             tx.commit();
+
+            Long PTuploadafter = em.createQuery(
+                "SELECT t.upload FROM UserPT t WHERE t.userid = :uid",
+                Long.class
+            ).setParameter("uid", userid)
+            .getSingleResult();
+
+            System.out.println("按回车继续...");
+            System.out.printf("thisadd:%d userptsofar:%d userptafter:%d totaluploadnow:%d delta:%d%n",upload, PTuploadbefor,PTuploadafter, totalUpload,delta);
+            System.out.println("按回车继续...");
             return false;      // success
         } catch (RuntimeException ex) {
             if (tx.isActive()) tx.rollback();
@@ -137,7 +157,7 @@
             List<SeedHash> shl = qsh.getResultList();
             if (shl.isEmpty()) {
                 System.out.println("seed没有被记录");
-                return true;
+                return false;
             }
             String seedid = shl.get(0).seedId;
 
@@ -202,7 +222,7 @@
             long current = qcurr.getSingleResult();
             if (downloadLong > current) {
                 em.close();
-                return true;
+                return false;
             }
             // 2. 执行减法更新
             EntityTransaction tx = em.getTransaction();
@@ -271,14 +291,16 @@
             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
+            // 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"));
+                Map<String,Object> meta = bencode.decode(torrentData, Type.DICTIONARY);
+                @SuppressWarnings("unchecked")
+                Map<String,Object> info = (Map<String,Object>) meta.get("info");
+                byte[] infoBytes = bencode.encode(info);
                 MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
                 byte[] digest = sha1.digest(infoBytes);
                 StringBuilder sb = new StringBuilder();
diff --git a/src/test/java/trackertest/TrackerTest.java b/src/test/java/trackertest/TrackerTest.java
index 2aeffdb..0d324db 100644
--- a/src/test/java/trackertest/TrackerTest.java
+++ b/src/test/java/trackertest/TrackerTest.java
@@ -83,60 +83,64 @@
         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, () -> {
-                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();
+    
+//     @TestFactory
+//     Collection<DynamicTest> testAddUpLoad() {
+//     Random rnd = new Random();
+//     String ih = infoHashes.get(0);
+//     return userIds.stream()
+//         .map(uid -> DynamicTest.dynamicTest("AddUpLoad for user " + uid, () -> {
+//             // 1) 获取该用户当前的总上传量
+//             Long currentUserUpload = em.createQuery(
+//                 "SELECT u.upload FROM UserPT u WHERE u.userid = :uid", Long.class)
+//                 .setParameter("uid", uid)
+//                 .getSingleResult();
 
-                    // commit & restart test TX so we see the data that tracker committed
-                    tx.commit();
-                    em.clear();
-                    tx.begin();
+//             // 2) 获取该用户在该种子上的当前上传量
+//             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();
 
-                    // 验证 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());
-    }
-    */
+//             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();
+
+//             // 验证 TransRecord 表中的实际总和
+//             Long actualTransRecordSum = em.createQuery(
+//                 "SELECT COALESCE(SUM(t.upload),0) FROM TransRecord t WHERE t.uploaduserid = :uid", Long.class)
+//                 .setParameter("uid", uid)
+//                 .getSingleResult();
+//             // 验证 UserPT.upload
+//             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 应等于 TransRecord 中的总和"
+//             );
+//             Assertions.assertEquals(
+//                 currentUserUpload + delta, userPTUpload.longValue(),
+//                 "UserPT.upload 应增加 delta"
+//             );
+
+//             // 恢复原始上传量,避免测试污染
+//             Assertions.assertFalse(tracker.ReduceUpLoad(uid, delta));
+//         }))
+//         .collect(Collectors.toList());
+// }
+    
    
    
     // @TestFactory