Merge "添加Comment相关文件"
diff --git a/lib/ttorrent-bencoding-1.3.0-SNAPSHOT.jar b/lib/ttorrent-bencoding-1.3.0-SNAPSHOT.jar
new file mode 100644
index 0000000..3234e88
--- /dev/null
+++ b/lib/ttorrent-bencoding-1.3.0-SNAPSHOT.jar
Binary files differ
diff --git a/lib/ttorrent-cli-1.3.0-SNAPSHOT.jar b/lib/ttorrent-cli-1.3.0-SNAPSHOT.jar
new file mode 100644
index 0000000..1b51e32
--- /dev/null
+++ b/lib/ttorrent-cli-1.3.0-SNAPSHOT.jar
Binary files differ
diff --git a/lib/ttorrent-client-1.3.0-SNAPSHOT.jar b/lib/ttorrent-client-1.3.0-SNAPSHOT.jar
new file mode 100644
index 0000000..4fb3ba9
--- /dev/null
+++ b/lib/ttorrent-client-1.3.0-SNAPSHOT.jar
Binary files differ
diff --git a/lib/ttorrent-common-1.3.0-SNAPSHOT.jar b/lib/ttorrent-common-1.3.0-SNAPSHOT.jar
new file mode 100644
index 0000000..40e6ff1
--- /dev/null
+++ b/lib/ttorrent-common-1.3.0-SNAPSHOT.jar
Binary files differ
diff --git a/lib/ttorrent-network-1.0.jar b/lib/ttorrent-network-1.0.jar
new file mode 100644
index 0000000..d27eb9f
--- /dev/null
+++ b/lib/ttorrent-network-1.0.jar
Binary files differ
diff --git a/lib/ttorrent-tracker-1.3.0-SNAPSHOT.jar b/lib/ttorrent-tracker-1.3.0-SNAPSHOT.jar
new file mode 100644
index 0000000..4e5006a
--- /dev/null
+++ b/lib/ttorrent-tracker-1.3.0-SNAPSHOT.jar
Binary files differ
diff --git a/pom.xml b/pom.xml
index 22f8f1c..ea99d5b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,6 +11,7 @@
     <groupId>com.pt5</groupId>
     <artifactId>PT-houduan</artifactId>
     <version>0.0.1-SNAPSHOT</version>
+<!--    <packaging>pom</packaging>-->
     <name>PT-houduan</name>
     <description>PT-houduan</description>
     <url/>
@@ -30,13 +31,191 @@
         <java.version>17</java.version>
     </properties>
     <dependencies>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-data-jpa</artifactId>
-        </dependency>
+<!--        <dependency>-->
+<!--            <groupId>com.turn</groupId>-->
+<!--            <artifactId>ttorrent-client</artifactId>-->
+<!--            <version>1.3.0-SNAPSHOT</version>-->
+<!--        </dependency>-->
+<!--        <dependency>-->
+<!--            <groupId>com.turn</groupId>-->
+<!--            <artifactId>ttorrent-tracker</artifactId>-->
+<!--            <version>1.3.0-SNAPSHOT</version>-->
+<!--        </dependency>-->
+<!--        <dependency>-->
+<!--            <groupId>com.turn</groupId>-->
+<!--            <artifactId>ttorrent</artifactId>-->
+<!--            <version>1.3.0-SNAPSHOT</version>-->
+<!--        </dependency>-->
+
+
+<!--        <dependency>-->
+<!--            <groupId>org.springframework.boot</groupId>-->
+<!--            <artifactId>spring-boot-starter-data-jpa</artifactId>-->
+<!--        </dependency>-->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.springframework.boot</groupId>
+                    <artifactId>spring-boot-starter-logging</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-logging</artifactId>
+        </dependency>
+
+        <!--        这边开始是tracker的-->
+        <dependency>
+            <groupId>com.turn</groupId>
+            <artifactId>tracker</artifactId>
+            <version>1.3.0-SNAPSHOT</version>
+            <scope>system</scope>
+            <systemPath>${project.basedir}/lib/ttorrent-tracker-1.3.0-SNAPSHOT.jar</systemPath>
+            <exclusions>
+                <!-- 排除旧版本的 simpleframework -->
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>*</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>log4j</groupId>
+                    <artifactId>*</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>ch.qos.logback</groupId>
+                    <artifactId>*</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>com.turn</groupId>
+            <artifactId>client</artifactId>
+            <version>1.3.0-SNAPSHOT</version>
+            <scope>system</scope>
+            <systemPath>${project.basedir}/lib/ttorrent-client-1.3.0-SNAPSHOT.jar</systemPath>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>*</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>log4j</groupId>
+                    <artifactId>*</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>ch.qos.logback</groupId>
+                    <artifactId>*</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+<!--        <dependency>-->
+<!--            <groupId>com.turn</groupId>-->
+<!--            <artifactId>cli</artifactId>-->
+<!--            <version>1.3.0-SNAPSHOT</version>-->
+<!--            <scope>system</scope>-->
+<!--            <systemPath>${project.basedir}/lib/ttorrent-cli-1.3.0-SNAPSHOT.jar</systemPath>-->
+<!--            <exclusions>-->
+<!--                <exclusion>-->
+<!--                    <groupId>org.slf4j</groupId>-->
+<!--                    <artifactId>*</artifactId>-->
+<!--                </exclusion>-->
+<!--                <exclusion>-->
+<!--                    <groupId>log4j</groupId>-->
+<!--                    <artifactId>*</artifactId>-->
+<!--                </exclusion>-->
+<!--                <exclusion>-->
+<!--                    <groupId>ch.qos.logback</groupId>-->
+<!--                    <artifactId>*</artifactId>-->
+<!--                </exclusion>-->
+<!--            </exclusions>-->
+<!--        </dependency>-->
+        <dependency>
+            <groupId>com.turn</groupId>
+            <artifactId>bencode</artifactId>
+            <version>1.3.0-SNAPSHOT</version>
+            <scope>system</scope>
+            <systemPath>${project.basedir}/lib/ttorrent-bencoding-1.3.0-SNAPSHOT.jar</systemPath>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>*</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>log4j</groupId>
+                    <artifactId>*</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>ch.qos.logback</groupId>
+                    <artifactId>*</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>com.turn</groupId>
+            <artifactId>commom</artifactId>
+            <version>1.3.0-SNAPSHOT</version>
+            <scope>system</scope>
+            <systemPath>${project.basedir}/lib/ttorrent-common-1.3.0-SNAPSHOT.jar</systemPath>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>*</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>log4j</groupId>
+                    <artifactId>*</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>ch.qos.logback</groupId>
+                    <artifactId>*</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>com.turn</groupId>
+            <artifactId>network</artifactId>
+            <version>1.3.0-SNAPSHOT</version>
+            <scope>system</scope>
+            <systemPath>${project.basedir}/lib/ttorrent-network-1.0.jar</systemPath>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>*</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>log4j</groupId>
+                    <artifactId>*</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>ch.qos.logback</groupId>
+                    <artifactId>*</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+<!--        <dependency>-->
+<!--            <groupId>org.simpleframework</groupId>-->
+<!--            <artifactId>simple-http</artifactId>-->
+<!--            <version>6.0.1</version>-->
+<!--        </dependency>-->
+<!--        <dependency>-->
+<!--            <groupId>org.simpleframework</groupId>-->
+<!--            <artifactId>simple-transport</artifactId>-->
+<!--            <version>6.0.1</version> &lt;!&ndash; 使用最新稳定版本 &ndash;&gt;-->
+<!--        </dependency>-->
+        <!-- 替换现有的 simple-transport 依赖 -->
+        <dependency>
+            <groupId>org.simpleframework</groupId>
+            <artifactId>simple</artifactId>
+            <version>5.1.6</version> <!-- 与 ttorrent 兼容的版本 -->
+            <exclusions>
+                <exclusion>
+                    <groupId>*</groupId>
+                    <artifactId>*</artifactId>
+                </exclusion>
+            </exclusions>
         </dependency>
 
         <!-- MyBatis-Plus -->
@@ -51,6 +230,7 @@
             <version>3.0.3</version>
         </dependency>
 
+
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-devtools</artifactId>
@@ -77,11 +257,51 @@
             <artifactId>h2</artifactId>
             <scope>test</scope>
         </dependency>
+<!--        <dependency>-->
+<!--            <groupId>log4j</groupId>-->
+<!--            <artifactId>log4j</artifactId>-->
+<!--            <version>1.2.17</version>-->
+<!--        </dependency>-->
     </dependencies>
 
     <build>
         <plugins>
             <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <includeSystemScope>true</includeSystemScope> <!-- 包含system范围的依赖 -->
+                    <executable>true</executable>
+                    <layers>
+                        <enabled>true</enabled>
+                    </layers>
+                    <excludes>
+                        <exclude>
+                            <groupId>org.projectlombok</groupId>
+                            <artifactId>lombok</artifactId>
+                        </exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>copy-dependencies</id>
+                        <phase>prepare-package</phase>
+                        <goals>
+                            <goal>copy-dependencies</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${project.build.directory}/lib</outputDirectory>
+                            <includeScope>system</includeScope> <!-- 专门复制system范围的依赖 -->
+                            <excludeTransitive>true</excludeTransitive>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <configuration>
@@ -93,19 +313,24 @@
                     </annotationProcessorPaths>
                 </configuration>
             </plugin>
-            <plugin>
-                <groupId>org.springframework.boot</groupId>
-                <artifactId>spring-boot-maven-plugin</artifactId>
-                <configuration>
-                    <excludes>
-                        <exclude>
-                            <groupId>org.projectlombok</groupId>
-                            <artifactId>lombok</artifactId>
-                        </exclude>
-                    </excludes>
-                </configuration>
-            </plugin>
         </plugins>
+        <resources>
+            <resource>
+                <directory>src/main/resources</directory>
+                <filtering>true</filtering>
+            </resource>
+            <resource>
+                <directory>lib</directory>
+                <targetPath>BOOT-INF/lib/</targetPath>
+                <includes>
+                    <include>**/*.jar</include>
+                </includes>
+            </resource>
+        </resources>
     </build>
 
+<!--    <modules>-->
+<!--        <module>ttorrent</module>-->
+<!--    </modules>-->
+
 </project>
diff --git a/src/main/java/com/pt5/pthouduan/config/TorrentClientMain.java b/src/main/java/com/pt5/pthouduan/config/TorrentClientMain.java
new file mode 100644
index 0000000..08216f0
--- /dev/null
+++ b/src/main/java/com/pt5/pthouduan/config/TorrentClientMain.java
@@ -0,0 +1,35 @@
+package com.pt5.pthouduan.config;
+
+import com.turn.ttorrent.client.SimpleClient;
+
+import java.net.InetAddress;
+
+public class TorrentClientMain {
+
+    public static void main(String[] args) {
+        try {
+            // 1. 创建客户端对象
+            SimpleClient client = new SimpleClient();
+
+            // 2. 获取本地监听 IP(注意不能用 127.0.0.1,如果其他 peer 不在同一机器)
+            InetAddress address = InetAddress.getLocalHost();
+
+            // 3. 开始下载
+            client.downloadTorrent(
+                    "D:\\torrenttest\\doenload\\video.mp4.torrent",           // torrent 文件路径
+                    "D://torrenttest/111111",              // 下载输出目录
+                    address                     // 本机地址
+            );
+
+            System.out.println("Download completed.");
+
+            // 4. 如果不想继续做 Seeder,可以停止客户端
+            client.stop();
+
+        } catch (Exception e) {
+            System.err.println("Download failed:");
+            e.printStackTrace();
+        }
+    }
+}
+
diff --git a/src/main/java/com/pt5/pthouduan/config/TorrentInfoHashPrinter.java b/src/main/java/com/pt5/pthouduan/config/TorrentInfoHashPrinter.java
new file mode 100644
index 0000000..73d0d23
--- /dev/null
+++ b/src/main/java/com/pt5/pthouduan/config/TorrentInfoHashPrinter.java
@@ -0,0 +1,24 @@
+package com.pt5.pthouduan.config;
+
+import com.turn.ttorrent.tracker.TrackedTorrent;
+import java.io.File;
+
+public class TorrentInfoHashPrinter {
+    public static void main(String[] args) throws Exception {
+        File torrentFile = new File("D:\\torrenttest\\111111\\output.torrent");
+        TrackedTorrent torrent = TrackedTorrent.load(torrentFile);
+        byte[] infoHash = torrent.getInfoHash();
+
+        // 打印十六进制形式的 info_hash
+        System.out.println("info_hash (hex): " + bytesToHex(infoHash));
+    }
+
+    private static String bytesToHex(byte[] bytes) {
+        StringBuilder sb = new StringBuilder();
+        for (byte b : bytes) {
+            sb.append(String.format("%02X", b));
+        }
+        return sb.toString();
+    }
+}
+
diff --git a/src/main/java/com/pt5/pthouduan/config/TrackerConfig.java b/src/main/java/com/pt5/pthouduan/config/TrackerConfig.java
new file mode 100644
index 0000000..93784b5
--- /dev/null
+++ b/src/main/java/com/pt5/pthouduan/config/TrackerConfig.java
@@ -0,0 +1,93 @@
+package com.pt5.pthouduan.config;
+
+import com.pt5.pthouduan.entity.StatsRecorder;
+import com.pt5.pthouduan.entity.TrackerInitializer;
+import com.pt5.pthouduan.entity.TrackeredTorrentWithStats;
+import com.pt5.pthouduan.service.impl.updatePeerStatsService;
+import com.turn.ttorrent.tracker.TorrentsRepository;
+import com.turn.ttorrent.tracker.TrackedTorrent;
+import com.turn.ttorrent.tracker.Tracker;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+
+import org.springframework.beans.factory.annotation.Value;
+
+import javax.sql.DataSource;
+
+@Configuration
+public class TrackerConfig {
+
+    @Value("${tracker.port:6969}")
+    private int trackerPort;
+
+    @Value("${tracker.accept.foreign:false}")
+    private boolean acceptForeignTorrents;
+
+    @Autowired
+    private DataSource dataSource;
+    @Autowired
+    private TrackerInitializer trackerInitializer;
+    @Autowired
+    private updatePeerStatsService statsService;
+
+    @Bean
+    public Tracker tracker() throws IOException {
+        // 1. 创建 Tracker 实例
+        //Tracker tracker = new Tracker(6969);
+        //Tracker tracker = new Tracker(6969,"127.0.0.1");
+        Tracker tracker = trackerInitializer.createTracker();
+        // 4. 启动 Tracker(true 表示作为守护线程运行)
+        tracker.start(true);
+
+        // 2. 自动加载目录下的所有种子文件
+        File torrentsDirectory = new File("D:\\torrenttest\\111111");
+        if (torrentsDirectory.exists() && torrentsDirectory.isDirectory()) {
+            FilenameFilter filter = (dir, name) -> name.endsWith(".torrent");
+
+            for (File torrentFile : torrentsDirectory.listFiles(filter)) {
+                try {
+                    TrackedTorrent baseTorrent = TrackedTorrent.load(torrentFile);
+                    //获取HashInfo
+                    byte[] infoHash = baseTorrent.getInfoHash();
+                    System.out.println("Registered torrent hash: " + infoHash);
+                    System.out.println("Registered torrent hash: " + baseTorrent.getHexInfoHash());
+                    TrackeredTorrentWithStats trackedTorrentWithStats = new TrackeredTorrentWithStats(infoHash,dataSource,"",statsService);
+                    tracker.announce(trackedTorrentWithStats);
+                    //tracker.announce(TrackedTorrent.load(torrentFile));
+                    System.out.println("Registered torrent: " + torrentFile.getName());
+                } catch (IOException e) {
+                    System.err.println("Failed to load torrent: " + torrentFile.getName());
+                    e.printStackTrace();
+                }
+            }
+        }
+
+        // 3. 设置是否接受未知种子
+        tracker.setAcceptForeignTorrents(true);
+
+
+
+        System.out.println("Tracker started on port " + trackerPort +
+                ", accepting foreign torrents: " + acceptForeignTorrents);
+
+        // 添加JVM关闭钩子确保Tracker正常停止
+        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+            tracker.stop();
+            System.out.println("Tracker stopped gracefully");
+        }));
+
+        System.out.println("All registered torrents:");
+        for (TrackedTorrent t : tracker.getTrackedTorrents()) {
+            System.out.println(t.getHexInfoHash());
+        }
+
+
+        return tracker;
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/pt5/pthouduan/config/WebMvcConfig.java b/src/main/java/com/pt5/pthouduan/config/WebMvcConfig.java
new file mode 100644
index 0000000..85145f4
--- /dev/null
+++ b/src/main/java/com/pt5/pthouduan/config/WebMvcConfig.java
@@ -0,0 +1,23 @@
+package com.pt5.pthouduan.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class WebMvcConfig implements WebMvcConfigurer {
+
+    @Override
+    public void addResourceHandlers(ResourceHandlerRegistry registry) {
+        // 帖子图片:D:/postuploads/  →  /images/**
+        registry.addResourceHandler("/images/**")
+                .addResourceLocations("file:D:/postuploads/");//路径改一下
+
+        // ✅ 求助帖图片:D:/uploads/  →  /uploads/**
+        registry.addResourceHandler("/uploads/**")
+                .addResourceLocations("file:D:/uploads/");
+    }
+
+
+}
+
diff --git a/src/main/java/com/pt5/pthouduan/controller/TorrentController.java b/src/main/java/com/pt5/pthouduan/controller/TorrentController.java
new file mode 100644
index 0000000..71cc76f
--- /dev/null
+++ b/src/main/java/com/pt5/pthouduan/controller/TorrentController.java
@@ -0,0 +1,301 @@
+package com.pt5.pthouduan.controller;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.pt5.pthouduan.entity.PeerInfo;
+import com.pt5.pthouduan.entity.Torrent;
+import com.pt5.pthouduan.entity.TrackeredTorrentWithStats;
+import com.pt5.pthouduan.entity.User;
+import com.pt5.pthouduan.exception.TorrentNotFoundException;
+import com.pt5.pthouduan.mapper.UserMapper;
+import com.pt5.pthouduan.service.TorrentService;
+//import com.pt5.pthouduan.service.TorrentStatsService;
+//import com.pt5.pthouduan.service.TrackeredTorrentService;
+import com.pt5.pthouduan.service.impl.PeerService;
+//import com.pt5.pthouduan.service.impl.UserServiceImpl;
+import com.turn.ttorrent.bcodec.BDecoder;
+import com.turn.ttorrent.bcodec.BEValue;
+import com.turn.ttorrent.bcodec.BEncoder;
+import com.turn.ttorrent.tracker.Tracker;
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.*;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.*;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+//@Controller
+@RestController
+@CrossOrigin // 允许前端跨域访问
+@RequestMapping("/torrent")
+public class TorrentController {
+    @Autowired
+    private TorrentService torrentService;
+    @Autowired
+    private UserMapper userMapper;
+//    @Autowired
+//    private UserServiceImpl userServiceImpl;
+    @Autowired
+    private Tracker tracker;
+    @Autowired
+    private PeerService peerService;
+//    @Autowired
+//    private TorrentStatsService statsService;
+//    @Autowired
+//    private TrackeredTorrentService trackeredTorrentService;
+
+
+    @GetMapping("/{infoHash}/seeders")
+    public ResponseEntity<List<PeerInfo>> getSeedersByInfoHash(@PathVariable String infoHash) {
+        List<PeerInfo> seeders = peerService.getSeedersByInfoHash(infoHash.toUpperCase());
+        System.out.println("Response: " + seeders);
+        return ResponseEntity.ok(seeders);
+    }
+    //添加搜索
+    @GetMapping("/search")
+    public ResponseEntity<List<Torrent>> searchTorrents(@RequestParam("keyword") String keyword) {
+        List<Torrent> result = torrentService.searchByKeyword(keyword);
+        return ResponseEntity.ok(result);
+    }
+
+   //显示所有种子
+    @GetMapping("/list")
+    public List<Torrent> getAllTorrents() {
+        return torrentService.getAllTorrents();
+    }
+    // 按分类获取种子(带 category 参数)
+    @GetMapping("/listByCategory")
+    public List<Torrent> getTorrentsByCategory(@RequestParam(required = false) Integer categoryid) {
+        if (categoryid == null) {
+            return torrentService.getAllTorrents();
+        }
+        return torrentService.getTorrentsByCategory(categoryid); // 否则按分类过滤
+    }
+    @GetMapping("/listByCategorywithfilter")
+    public List<Torrent> getTorrentsByCategorywithFilter(
+            @RequestParam Integer categoryid,
+            @RequestParam Map<String, String> filters
+    ){
+        filters.remove("categoryid");
+        List<Torrent> torrents = torrentService.getTorrentsByCategorywithfilters(categoryid,filters);
+        return torrents;
+    }
+    // 获取单个种子详情
+    @GetMapping("/{id}")
+    public ResponseEntity<?> getTorrentById(@PathVariable Long id) {
+        try {
+            Torrent torrent = torrentService.getTorrentById(id);
+            if (torrent == null) {
+                return ResponseEntity.notFound().build(); // 如果种子不存在,返回 404
+            }
+            System.out.println(torrent);
+            return ResponseEntity.ok(torrent); // 返回种子详情
+        } catch (Exception e) {
+            return ResponseEntity.badRequest().body("获取种子详情失败: " + e.getMessage());
+        }
+    }
+//未更新前端前
+//    @PostMapping("/upload")
+//    public ResponseEntity<?> uploadTorrent(
+//            @RequestParam("file") MultipartFile torrentFile,
+//            @RequestParam("title") String title,
+//            @RequestParam("description") String description
+//            //@AuthenticationPrincipal User user
+//            //User user
+//    ){
+//        // 创建临时用户对象
+//        User user = new User(1L,"testuser");
+//        //user.setUserid(1L); // 设置测试用户ID
+//        try{
+//            //torrentService.Upload(torrentFile,title,description,user);
+//            return torrentService.Upload(torrentFile, title, description, user);
+//        }catch(Exception e){
+//            return ResponseEntity.badRequest().body("Upload failed:" + e.getMessage());
+//        }
+//    }
+//@PostMapping("/upload")
+//public ResponseEntity<?> uploadTorrent(
+//        @RequestParam("file") MultipartFile torrentFile,
+//        @RequestParam("title") String title,
+//        @RequestParam("description") String description,
+//        @RequestParam("categoryId") Integer categoryId,
+//        @RequestParam(value = "dpi",required = false) String dpi,
+//        @RequestParam(value = "caption", required = false) String caption){
+//    // 创建临时用户对象
+//    User user = new User(1L,"testuser");
+//    //user.setUserid(1L); // 设置测试用户ID
+//    try{
+//        //torrentService.Upload(torrentFile,title,description,user);
+//        return torrentService.Upload(torrentFile, title, description, categoryId, dpi,caption,user);
+//    }catch(Exception e){
+//        return ResponseEntity.badRequest().body("Upload failed:" + e.getMessage());
+//    }
+//}
+@PostMapping("/upload")
+public ResponseEntity<?> uploadTorrent(
+        @RequestParam("file") MultipartFile torrentFile,
+        @RequestParam("title") String title,
+        @RequestParam("description") String description,
+        @RequestParam("categoryId") Integer categoryId,
+        // 以下为通用扩展字段(根据类型选择性使用)
+        @RequestParam(value = "dpi",required = false) String dpi,
+        @RequestParam(value = "caption", required = false) String caption,
+        @RequestParam(value = "region", required = false) String region,
+        @RequestParam(value = "year", required = false) Integer year,
+        @RequestParam(value = "genre", required = false) String genre,
+        @RequestParam(value = "format", required = false) String format,
+        @RequestParam(value = "resolution", required = false) String resolution,
+        @RequestParam(value = "codecFormat", required = false) String codecFormat,
+        @RequestParam(value = "platform", required = false) String platform,
+        @RequestParam(value = "language", required = false) String language,
+        @RequestParam(value = "eventType", required = false) String eventType,
+        @RequestParam(value = "dataType", required = false) String dataType,
+        @RequestParam(value = "source", required = false) String source,
+        @RequestParam(value = "style", required = false) String style,
+        @RequestParam(value = "isMainland", required = false) Boolean isMainland){
+    // 创建临时用户对象
+    User user = new User(1L,"testuser");
+    
+    // 构建扩展参数Map
+    Map<String, String> extraParams = new HashMap<>();
+
+    // 通用参数
+    putIfNotNull(extraParams, "dpi", dpi);
+    putIfNotNull(extraParams, "caption", caption);
+    putIfNotNull(extraParams, "region", region);
+    putIfNotNull(extraParams, "year", year != null ? year.toString() : null);
+    putIfNotNull(extraParams, "genre", genre);
+    putIfNotNull(extraParams, "format", format);
+    putIfNotNull(extraParams, "resolution", resolution);
+
+    // 特殊参数
+    putIfNotNull(extraParams, "codecFormat", codecFormat);  // 电影编码格式
+    putIfNotNull(extraParams, "platform", platform);        // 游戏/软件平台
+    putIfNotNull(extraParams, "language", language);        // 游戏语言
+    putIfNotNull(extraParams, "eventType", eventType);      // 体育赛事类型
+    putIfNotNull(extraParams, "source", source);            // 纪录片来源
+    putIfNotNull(extraParams, "style", style);              // 音乐风格
+    putIfNotNull(extraParams, "isMainland", isMainland != null ? isMainland.toString() : null); // 综艺是否大陆
+    putIfNotNull(extraParams, "dataType", dataType);
+//    extraParams.put("region", "CN");
+//    extraParams.put("year", "2023");
+//    extraParams.put("genre", "Action");
+//    extraParams.put("encodeFormat", "H.264");
+//    extraParams.put("resolution", "1080p");
+
+
+    //user.setUserid(1L); // 设置测试用户ID
+    try{
+        //torrentService.Upload(torrentFile,title,description,user);
+        //return torrentService.Upload(torrentFile, title, description, categoryId, dpi,caption,user);
+
+//        return torrentService.uploadWithCategory(
+//                torrentFile, title, description, categoryId, user, dpi, caption,
+//                region, year, genre, format, resolution, codecFormat,
+//                platform, language, eventType, source, style, isMainland
+//        );
+        // 调用Service
+        return torrentService.uploadWithCategory(
+                torrentFile,
+                title,
+                description,
+                categoryId,
+                user,
+                extraParams
+        );
+    }catch(Exception e){
+        return ResponseEntity.badRequest().body("Upload failed:" + e.getMessage());
+    }
+}
+    // 辅助方法:避免重复的空值判断
+    private void putIfNotNull(Map<String, String> map, String key, String value) {
+        if (value != null) {
+            map.put(key, value);
+        }
+    }
+
+//    @GetMapping("/download/{id}")
+//    public ResponseEntity<Resource> downloadTorrent(@PathVariable Long id, HttpServletRequest request){
+//        try{
+//            //获取种子文件资源
+//            Torrent torrentFile = torrentService.getTorrentFile(id);            //确定文件内容类型
+//            String filePath = torrentFile.getFilePath();
+//            File file = new File(filePath);
+//            if(!file.exists()){
+//                return ResponseEntity.notFound().build();  //如果文件不存在就返回异常
+//            }
+//            String contentType = request.getServletContext().getMimeType(torrentFile.getFilePath());
+//            if(contentType == null){
+//                contentType = "application/octet-stream";  //如果没有文件类型,默认使用二进制流
+//            }
+//            //构建resource对象
+//            Resource resource = torrentService.loadTorrentFileAsResource(torrentFile.getFilepath());  //将文件加载为Spring的resource对象
+//            return ResponseEntity.ok()
+//                    .contentType(MediaType.parseMediaType(contentType))
+//                    .header(HttpHeaders.CONTENT_DISPOSITION,"attachment;filename=\"" + resource.getFilename() + "\"")
+//                    .body(resource);
+//        }catch(TorrentNotFoundException e){
+//            return ResponseEntity.notFound().build();
+//        }catch(Exception e){
+//            return ResponseEntity.internalServerError().build();
+//        }
+//    }
+@GetMapping("/download/{id}")
+public ResponseEntity<byte[]> downloadTorrent(@PathVariable Long id, HttpServletRequest request){
+    try{
+        //需要加入passkey
+        User user = new User(1L,"testuser");
+        user.setPasskey("111111");
+        String passkey = "111111";  // 先模拟一下,到时候记得改
+        //获取种子文件资源
+        Torrent torrentFile = torrentService.getTorrentFile(id);            //确定文件内容类型
+        // 2. 原子性更新下载次数(推荐直接通过 SQL 递增)
+        torrentService.incrementDownloadCount(id);  // 在 Service 层实现原子更新
+        System.out.println("Requested ID: " + id);
+        System.out.println("Returned Torrent: " + torrentFile);
+        String filePath = torrentFile.getFilePath();
+        // 解码.torrent文件
+        FileInputStream fis = new FileInputStream(filePath);
+        Map<String, BEValue> decoded = BDecoder.bdecode(fis).getMap();
+        fis.close();
+
+        //替换announce字段
+        String announceUrl = "http://127.0.0.1:6969/announce?passkey=" + passkey;
+        decoded.put("announce", new BEValue(announceUrl));
+        System.out.println("Modified announce: " + decoded.get("announce"));
+
+        //重新编码为.torrent字节数组
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        BEncoder.bencode(new BEValue(decoded), baos);
+        byte[] modifiedTorrent = baos.toByteArray();
+
+        String outputPath = "D:/torrenttest/" + torrentFile.getFilename();
+        try(FileOutputStream fos = new FileOutputStream(outputPath)){
+            fos.write(modifiedTorrent);
+        }
+        System.out.println("Modified torrent bytes: " + Arrays.toString(modifiedTorrent));
+
+        // 构建响应,下载.torrent文件
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
+        headers.setContentDisposition(ContentDisposition.attachment()
+                .filename(torrentFile.getFilename())
+                .build());
+
+        return new ResponseEntity<>(modifiedTorrent, headers, HttpStatus.OK);
+    }catch (TorrentNotFoundException e) {
+        return ResponseEntity.notFound().build();
+    } catch (Exception e) {
+        e.printStackTrace();
+        return ResponseEntity.internalServerError().build();
+    }
+}
+
+
+
+}
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
diff --git a/src/main/java/com/pt5/pthouduan/exception/TorrentNotFoundException.java b/src/main/java/com/pt5/pthouduan/exception/TorrentNotFoundException.java
new file mode 100644
index 0000000..14a7334
--- /dev/null
+++ b/src/main/java/com/pt5/pthouduan/exception/TorrentNotFoundException.java
@@ -0,0 +1,8 @@
+package com.pt5.pthouduan.exception;
+
+
+public class TorrentNotFoundException extends RuntimeException {
+    public TorrentNotFoundException(String message) {
+        super(message);
+    }
+}
diff --git a/src/main/java/com/pt5/pthouduan/mapper/TorrentMapper.java b/src/main/java/com/pt5/pthouduan/mapper/TorrentMapper.java
index 9c7b608..e7ba2a3 100644
--- a/src/main/java/com/pt5/pthouduan/mapper/TorrentMapper.java
+++ b/src/main/java/com/pt5/pthouduan/mapper/TorrentMapper.java
@@ -3,6 +3,11 @@
 import com.pt5.pthouduan.entity.Torrent;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+import java.util.Map;
 
 /**
  * <p>
@@ -14,5 +19,23 @@
  */
 @Mapper
 public interface TorrentMapper extends BaseMapper<Torrent> {
+    // 仅保留方法声明,移除所有SQL注解
+    List<Torrent> selectByUploaderId(@Param("uploaderId") Long uploaderId);
+    List<Torrent> searchByName(@Param("keyword") String keyword);
+    List<Torrent> selectTopPopular();
+    List<Torrent> getAllTorrents();
+    Integer existsByInfoHash(@Param("infoHash") String infoHash);
+    void incrementDownloadCount(@Param("id") Long id);
+    void save(Torrent entity);
+    List<Torrent> selectTorrentsByCategory(Integer category);
+    Torrent selectById(@Param("torrentid") Long id);
+    List<Torrent> searchByKeyword(String keyword);
 
+    void setFreePromotion();       // 免费下载
+    void setDoubleUpload();        // 上传加倍
+    void setHalfDownload();        // 下载减半
+    void clearPromotion();         // 恢复促销
+    Torrent selectByinfohash(@Param("infohash") String infohash);
+
+    List<Torrent> listByCategoryWithFilters(Integer categoryid, Map<String, String> filters, String extendTable);
 }
diff --git a/src/main/java/com/pt5/pthouduan/mapper/UserMapper.java b/src/main/java/com/pt5/pthouduan/mapper/UserMapper.java
index 89c37ab..2f96180 100644
--- a/src/main/java/com/pt5/pthouduan/mapper/UserMapper.java
+++ b/src/main/java/com/pt5/pthouduan/mapper/UserMapper.java
@@ -1,11 +1,21 @@
 package com.pt5.pthouduan.mapper;
 
+import com.pt5.pthouduan.entity.PeerInfo;
 import com.pt5.pthouduan.entity.User;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import org.apache.ibatis.annotations.*;
 import org.springframework.stereotype.Repository;
 
+import java.util.List;
 
+/**
+ * <p>
+ *  Mapper 接口
+ * </p>
+ *
+ * @author ljx
+ * @since 2025-04-14
+ */
 @Repository
 public interface UserMapper extends BaseMapper<User> {
     @Insert("INSERT INTO user(username, password, email, passkey) VALUES(#{username}, #{password}, #{email}, #{passkey})")
@@ -17,6 +27,15 @@
     @Select("SELECT * FROM User WHERE email = #{email}")
     User selectByEmail(String email);
 
+    @Select("SELECT credit FROM User WHERE username = #{username}")
+    int getcreditByUsername(String username);
+
+    @Update("UPDATE user SET credit = credit - #{price} WHERE username = #{username}")
+    int deductCreditByUsername(@Param("username") String username, @Param("price") int price);
+
+    @Update("UPDATE user SET upload = upload + #{upload} WHERE username = #{username}")
+    int increaseUploadByUsername(@Param("username") String username, @Param("upload") Integer upload);
+
     @Update("UPDATE user SET password = #{password} WHERE username = #{username}")
     int updatePassword(@Param("username") String username, @Param("password") String password);
 
@@ -28,4 +47,14 @@
 
     @Update("UPDATE user SET gradeId = #{gradeId} WHERE username = #{username}")
     int updateGrade(@Param("username") String username, @Param("gradeId") Integer gradeId);
+
+    @Update("UPDATE user SET decoration = CONCAT(IFNULL(decoration, ''), ' ', #{newDecoration}) WHERE username = #{username}")
+    int appendUserDecoration(@Param("username") String username, @Param("newDecoration") String newDecoration);
+
+    boolean existsByPasskey(String passkey);
+    void incrementUserTraffic( @Param("info_hash") String infoHash,@Param("passkey") String passkey, @Param("user_upload") long uploaded, @Param("user_download") long downloaded);
+
+    String getUsernameByPasskey(String passkey);
+
+    List<PeerInfo> findSeedersByInfoHash(@Param("infoHash") String infoHash);
 }
diff --git a/src/main/java/com/pt5/pthouduan/service/PasskeyValidator.java b/src/main/java/com/pt5/pthouduan/service/PasskeyValidator.java
new file mode 100644
index 0000000..a4b4d0f
--- /dev/null
+++ b/src/main/java/com/pt5/pthouduan/service/PasskeyValidator.java
@@ -0,0 +1,5 @@
+package com.pt5.pthouduan.service;
+
+public interface PasskeyValidator {
+    boolean isValid(String passkey);
+}
\ No newline at end of file
diff --git a/src/main/java/com/pt5/pthouduan/service/TorrentService.java b/src/main/java/com/pt5/pthouduan/service/TorrentService.java
new file mode 100644
index 0000000..0d887da
--- /dev/null
+++ b/src/main/java/com/pt5/pthouduan/service/TorrentService.java
@@ -0,0 +1,30 @@
+package com.pt5.pthouduan.service;
+
+import com.pt5.pthouduan.entity.Torrent;
+import com.pt5.pthouduan.entity.TrackeredTorrentWithStats;
+import com.pt5.pthouduan.entity.User;
+import org.springframework.core.io.Resource;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
+import java.util.Map;
+
+public interface TorrentService {
+//    public void upload(File file);
+
+
+    ////未连接口ResponseEntity<Resource> Upload(MultipartFile torrentFile, String title, String description, User user) throws Exception;
+    ResponseEntity<Resource> Upload(MultipartFile torrentFile, String title, String description, Integer catrgoryId, String dpi, String caption, User user) throws Exception;
+    Torrent getTorrentFile(Long torrentid);
+    Resource loadTorrentFileAsResource(String filepath) throws Exception;
+    void incrementDownloadCount(Long torrentid);
+    List<Torrent> getAllTorrents();
+    List<Torrent> getTorrentsByCategory(Integer category);
+    Torrent getTorrentById(Long id);
+    List<Torrent> searchByKeyword(String keyword);
+    ResponseEntity<Resource> uploadWithCategory(MultipartFile torrentFile, String title, String description, Integer categoryId, User user, Map<String, String> extraParams) throws Exception;
+
+    List<Torrent> getTorrentsByCategorywithfilters(Integer categoryid, Map<String, String> filters);
+    //TrackeredTorrentWithStats.TorrentStats getTorrentStats(String infoHash);
+}
diff --git a/src/main/java/com/pt5/pthouduan/service/impl/DatabasePasskeyValidator.java b/src/main/java/com/pt5/pthouduan/service/impl/DatabasePasskeyValidator.java
new file mode 100644
index 0000000..2c7b3df
--- /dev/null
+++ b/src/main/java/com/pt5/pthouduan/service/impl/DatabasePasskeyValidator.java
@@ -0,0 +1,21 @@
+package com.pt5.pthouduan.service.impl;
+
+import com.pt5.pthouduan.service.PasskeyValidator;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Service;
+
+// 示例实现(使用Spring JDBC)
+@Service
+public class DatabasePasskeyValidator implements PasskeyValidator {
+
+    @Autowired
+    private JdbcTemplate jdbcTemplate;
+
+    @Override
+    public boolean isValid(String passkey) {
+        String sql = "SELECT COUNT(*) FROM user WHERE passkey = ?";
+        int count = jdbcTemplate.queryForObject(sql, Integer.class, passkey);
+        return count > 0;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/pt5/pthouduan/service/impl/PeerService.java b/src/main/java/com/pt5/pthouduan/service/impl/PeerService.java
new file mode 100644
index 0000000..27faa92
--- /dev/null
+++ b/src/main/java/com/pt5/pthouduan/service/impl/PeerService.java
@@ -0,0 +1,20 @@
+package com.pt5.pthouduan.service.impl;
+
+import com.pt5.pthouduan.entity.PeerInfo;
+import com.pt5.pthouduan.entity.User;
+import com.pt5.pthouduan.mapper.UserMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class PeerService {
+    @Autowired
+    private UserMapper userMapper;
+
+    public List<PeerInfo> getSeedersByInfoHash(String infoHash){
+        return userMapper.findSeedersByInfoHash(infoHash);
+    }
+
+}
diff --git a/src/main/java/com/pt5/pthouduan/service/impl/TorrentServiceImpl.java b/src/main/java/com/pt5/pthouduan/service/impl/TorrentServiceImpl.java
new file mode 100644
index 0000000..93aad92
--- /dev/null
+++ b/src/main/java/com/pt5/pthouduan/service/impl/TorrentServiceImpl.java
@@ -0,0 +1,434 @@
+package com.pt5.pthouduan.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.pt5.pthouduan.entity.*;
+import com.pt5.pthouduan.exception.TorrentNotFoundException;
+import com.pt5.pthouduan.mapper.*;
+import com.pt5.pthouduan.service.TorrentService;
+import com.pt5.pthouduan.util.TorrentParser;
+import com.turn.ttorrent.bcodec.BDecoder;
+import com.turn.ttorrent.bcodec.BEValue;
+import com.turn.ttorrent.bcodec.BEncoder;
+import com.turn.ttorrent.tracker.TrackedTorrent;
+import com.turn.ttorrent.tracker.Tracker;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.io.ByteArrayResource;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.UrlResource;
+import org.springframework.http.*;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.*;
+import java.net.MalformedURLException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+
+@Service
+@Transactional
+public class TorrentServiceImpl implements TorrentService {
+
+    @Value("${tracker.url}")  //等等看看这个有没有传进来
+    private String trackerUrl;
+    @Autowired
+    private TorrentMapper torrentMapper;
+    @Autowired  // 确保使用接口类型(推荐)
+    private Tracker tracker;
+    @Autowired
+    private MovieInfoMapper movieInfoMapper;
+    // 🎵 Music 音乐
+    @Autowired
+    private MusicInfoMapper musicInfoMapper;
+
+    // 🎮 Game 游戏
+    @Autowired
+    private GameInfoMapper gameInfoMapper;
+
+    // 📺 TV 剧集
+    @Autowired
+    private TvInfoMapper tvInfoMapper;
+
+    // 🧸 Anime 动漫
+    @Autowired
+    private AnimeInfoMapper animeInfoMapper;
+
+    // 📚 Learning 学习
+    @Autowired
+    private EduInfoMapper eduInfoMapper;
+
+    // � Software 软件
+    @Autowired
+    private SoftwareInfoMapper softwareInfoMapper;
+
+    // 🎤 Variety 综艺
+    @Autowired
+    private ShowInfoMapper showInfoMapper;
+
+    // ⚽ Sports 体育
+    @Autowired
+    private SportInfoMapper sportInfoMapper;
+
+    // 🎬 Documentary 纪录片
+    @Autowired
+    private DocumentaryInfoMapper documentaryInfoMapper;
+
+    // 📦 Other 其他
+    @Autowired
+    private OtherInfoMapper otherInfoMapper;
+
+
+    public List<Torrent> getAllTorrents() {
+        return torrentMapper.getAllTorrents();
+    }
+    public List<Torrent> getTorrentsByCategory(Integer category) {
+        return torrentMapper.selectTorrentsByCategory(category);
+    }
+    public Torrent getTorrentById(Long id) {
+        return torrentMapper.selectById(id);
+    }
+
+
+
+//    @Override
+//    public void Upload(MultipartFile torrentFile, String title, String description, User user) throws Exception {
+//        //读取torrent文件
+//        InputStream inputStream = torrentFile.getInputStream();
+//        //解析torrent文件
+//        byte[] torrentData = torrentFile.getBytes();
+//        Torrent torrent = TorrentParser.parse(torrentData);
+//        //验证info_hash是否存在
+//        Integer result;
+//        result = torrentMapper.existsByInfoHash(torrent.getInfoHash());
+//        if(result >= 1){
+//            throw new IllegalArgumentException("Torrent already exists");
+//        }
+//        String fileSavePath = "D:/torrenttest/" + torrentFile.getOriginalFilename();
+//        torrentFile.transferTo(new File(fileSavePath));
+//        // 4. 注册到Tracker(关键步骤!)
+//        TrackedTorrent trackedTorrent = TrackedTorrent.load(new File(fileSavePath));
+//        tracker.announce(trackedTorrent);
+//        //存入数据库
+//        Torrent entity = new Torrent();
+//        entity.setInfoHash(torrent.getInfoHash());
+//        entity.setUploader_id(user.getUserid());
+//        entity.setTorrentTitle(title);
+//        entity.setDescription(description);
+//        entity.setTorrentSize(torrent.getTorrentSize());
+//        entity.setFilename(torrentFile.getOriginalFilename());
+//        entity.setPath(fileSavePath); // 保存路径到数据库
+//        torrentMapper.save(entity);
+//    }
+@Override
+public ResponseEntity<Resource> Upload(MultipartFile torrentFile, String title, String description, Integer categoryId, String dpi, String caption,User user) throws Exception {
+    //读取torrent文件
+    InputStream inputStream = torrentFile.getInputStream();
+    // === 1. 基础校验 ===
+    if (torrentFile.isEmpty()) {
+        throw new IllegalArgumentException("Torrent file cannot be empty");
+    }
+    // === 2. 注入passkey ===
+    byte[] modifiedData = addPasskeyToTorrent(torrentFile.getBytes(), "111111");
+    Torrent torrent = TorrentParser.parse(modifiedData);
+    // === 4. 检查重复 ===
+    if (torrentMapper.existsByInfoHash(torrent.getInfoHash()) >= 1) {
+        throw new IllegalArgumentException("Torrent already exists");
+    }
+    String fileSavePath = "D:/torrenttest/" + torrentFile.getOriginalFilename();
+    //torrentFile.transferTo(new File(fileSavePath));
+    Files.write(new File(fileSavePath).toPath(), modifiedData);
+    // 4. 注册到Tracker(关键步骤!)
+    TrackedTorrent trackedTorrent = TrackedTorrent.load(new File(fileSavePath));
+    tracker.announce(trackedTorrent);
+    //存入数据库
+    Torrent entity = new Torrent();
+    entity.setInfoHash(torrent.getInfoHash());
+    entity.setUploader_id(user.getUserid());
+    entity.setTorrentTitle(title);
+    entity.setDescription(description);
+    entity.setTorrentSize(torrent.getTorrentSize());
+    entity.setFilename(torrentFile.getOriginalFilename());
+    entity.setCategoryid(categoryId);
+    entity.setCaption(caption);
+    entity.setDpi(dpi);
+    entity.setUploadTime(LocalDateTime.now());
+    entity.setPath(fileSavePath); // 保存路径到数据库
+    torrentMapper.save(entity);
+    // 返回修改后的.torrent文件给用户下载
+    ByteArrayResource resource = new ByteArrayResource(modifiedData);
+    // 2. 设置正确的Content-Type
+    // 构建响应,下载.torrent文件
+    // 构建响应,下载.torrent文件
+//    HttpHeaders headers = new HttpHeaders();
+//    headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
+//    headers.setContentDisposition(ContentDisposition.attachment()
+//            .filename(torrentFile.getOriginalFilename())
+//            .build());
+//    return new ResponseEntity<>(resource, headers, HttpStatus.OK);
+    return ResponseEntity.ok()
+            .header(HttpHeaders.CONTENT_DISPOSITION,
+                    "attachment; filename=\"" + torrentFile.getOriginalFilename() + "\"")
+            .contentType(MediaType.parseMediaType("application/x-bittorrent"))
+            .body(new ByteArrayResource(modifiedData));
+}
+    @Override
+    public ResponseEntity<Resource> uploadWithCategory(MultipartFile torrentFile, String title, String description, Integer categoryId, User user, Map<String, String> extraParams ) throws Exception {
+        //读取torrent文件
+        InputStream inputStream = torrentFile.getInputStream();
+        // === 1. 基础校验 ===
+        if (torrentFile.isEmpty()) {
+            throw new IllegalArgumentException("Torrent file cannot be empty");
+        }
+        // === 2. 注入passkey ===
+        byte[] modifiedData = addPasskeyToTorrent(torrentFile.getBytes(), "111111");
+        Torrent torrent = TorrentParser.parse(modifiedData);
+        // === 4. 检查重复 ===
+        if (torrentMapper.existsByInfoHash(torrent.getInfoHash()) >= 1) {
+            throw new IllegalArgumentException("Torrent already exists");
+        }
+        String fileSavePath = "D:/torrenttest/" + torrentFile.getOriginalFilename();
+        //torrentFile.transferTo(new File(fileSavePath));
+        Files.write(new File(fileSavePath).toPath(), modifiedData);
+        // 4. 注册到Tracker(关键步骤!)
+        TrackedTorrent trackedTorrent = TrackedTorrent.load(new File(fileSavePath));
+        tracker.announce(trackedTorrent);
+        //存入数据库
+        Torrent entity = new Torrent();
+        entity.setInfoHash(torrent.getInfoHash());
+        entity.setUploader_id(user.getUserid());
+        entity.setTorrentTitle(title);
+        entity.setDescription(description);
+        entity.setTorrentSize(torrent.getTorrentSize());
+        entity.setFilename(torrentFile.getOriginalFilename());
+        entity.setCategoryid(categoryId);
+        entity.setUploadTime(LocalDateTime.now());
+        entity.setPath(fileSavePath); // 保存路径到数据库
+        torrentMapper.save(entity);
+        switch (categoryId) {
+            case 1: // 🎬 Movie 电影
+                MovieInfo movie = new MovieInfo();
+                movie.setTorrentid(entity.getTorrentid());
+                System.out.println(entity.getTorrentid());
+                movie.setRegion(extraParams.get("region"));
+                movie.setYear(extraParams.get("year") != null ? Integer.parseInt(extraParams.get("year")) : null);
+                movie.setGenre(extraParams.get("genre"));
+                movie.setCodecFormat(extraParams.get("codecFormat"));
+                movie.setResolution(extraParams.get("resolution"));
+                movieInfoMapper.insert(movie);
+                break;
+
+            case 3: // 🎵 Music 音乐
+                MusicInfo music = new MusicInfo();
+                music.setTorrentid(entity.getTorrentid());
+                music.setGenre(extraParams.get("genre"));
+                music.setRegion(extraParams.get("region"));
+                music.setStyle(extraParams.get("style"));
+                music.setFormat(extraParams.get("format"));
+                musicInfoMapper.insert(music);
+                break;
+
+            case 5: // 🎮 Game 游戏
+                GameInfo game = new GameInfo();
+                game.setTorrentid(entity.getTorrentid());
+                game.setPlatform(extraParams.get("platform"));
+                game.setGenre(extraParams.get("genre"));
+                game.setFormat(extraParams.get("dataType"));
+                game.setLanguage(extraParams.get("language"));
+                gameInfoMapper.insert(game);
+                break;
+
+            case 2: // 📺 TV 剧集
+                TvInfo tv = new TvInfo();
+                tv.setTorrentId(entity.getTorrentid());
+                tv.setRegion(extraParams.get("region"));
+                tv.setFormat(extraParams.get("format"));
+                tv.setGenre(extraParams.get("genre"));
+                tvInfoMapper.insert(tv);
+                break;
+
+            case 4: // 🧸 Anime 动漫
+                AnimeInfo anime = new AnimeInfo();
+                anime.setTorrentid(entity.getTorrentid());
+                anime.setGenre(extraParams.get("genre"));
+                anime.setFormat(extraParams.get("format"));
+                anime.setResolution(extraParams.get("resolution"));
+                animeInfoMapper.insert(anime);
+                break;
+
+            case 9: // 📚 Learning 学习
+                EduInfo learning = new EduInfo();
+                learning.setTorrentid(entity.getTorrentid());
+                learning.setGenre(extraParams.get("genre"));
+                learning.setFormat(extraParams.get("format"));
+                eduInfoMapper.insert(learning);
+                break;
+
+            case 8: // 🧰 Software 软件
+                SoftwareInfo software = new SoftwareInfo();
+                software.setTorrentid(entity.getTorrentid());
+                software.setPlatform(extraParams.get("platform"));
+                software.setGenre(extraParams.get("genre"));
+                software.setFormat(extraParams.get("format"));
+                softwareInfoMapper.insert(software);
+                break;
+
+            case 6: // 🎤 Variety 综艺
+                ShowInfo variety = new ShowInfo();
+                variety.setTorrentid(entity.getTorrentid());
+                variety.setIsMainland(Boolean.valueOf(extraParams.get("mainland")));  // 是否大陆综艺
+                variety.setGenre(extraParams.get("genre"));
+                variety.setFormat(extraParams.get("format"));
+                showInfoMapper.insert(variety);
+                break;
+
+            case 7: // ⚽ Sports 体育
+                SportInfo sports = new SportInfo();
+                sports.setTorrentid(entity.getTorrentid());
+                sports.setGenre(extraParams.get("genre"));
+                sports.setEventType(extraParams.get("eventType"));
+                sports.setFormat(extraParams.get("format"));
+                sportInfoMapper.insert(sports);
+                break;
+
+            case 10: // 🎬 Documentary 纪录片
+                DocumentaryInfo doc = new DocumentaryInfo();
+                doc.setTorrentid(entity.getTorrentid());
+                doc.setYear(extraParams.get("year") != null ? Integer.parseInt(extraParams.get("year")) : null);
+                doc.setSource(extraParams.get("source"));  // 节目源
+                doc.setFormat(extraParams.get("format"));
+                documentaryInfoMapper.insert(doc);
+                break;
+
+            case 11: // 📦 Other 其他
+                OtherInfo other = new OtherInfo();
+                other.setTorrentid(entity.getTorrentid());
+                other.setGenre(extraParams.get("genre"));
+                otherInfoMapper.insert(other);
+                break;
+
+            default:
+                System.out.println("不支持的分类,或无扩展表记录");
+        }
+
+        // 返回修改后的.torrent文件给用户下载
+        ByteArrayResource resource = new ByteArrayResource(modifiedData);
+        // 2. 设置正确的Content-Type
+        // 构建响应,下载.torrent文件
+        // 构建响应,下载.torrent文件
+//    HttpHeaders headers = new HttpHeaders();
+//    headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
+//    headers.setContentDisposition(ContentDisposition.attachment()
+//            .filename(torrentFile.getOriginalFilename())
+//            .build());
+//    return new ResponseEntity<>(resource, headers, HttpStatus.OK);
+        System.out.println("运行到这里了");
+        return ResponseEntity.ok()
+                .header(HttpHeaders.CONTENT_DISPOSITION,
+                        "attachment; filename=\"" + torrentFile.getOriginalFilename() + "\"")
+                .contentType(MediaType.parseMediaType("application/x-bittorrent"))
+                .body(new ByteArrayResource(modifiedData));
+    }
+    // 专用方法:向torrent文件注入passkey
+    private byte[] addPasskeyToTorrent(byte[] originalData, String passkey) throws Exception {
+        // 1. 解析原始torrent
+        BEValue decoded = BDecoder.bdecode(new ByteArrayInputStream(originalData));
+        Map<String, BEValue> torrentMap = decoded.getMap();
+
+        // 2. 修改announce URL
+        String originalAnnounce = torrentMap.get("announce").getString();
+        String newAnnounce = originalAnnounce.contains("?")
+                ? originalAnnounce + "&passkey=" + passkey
+                : originalAnnounce + "?passkey=" + passkey;
+
+        // 3. 重建torrent文件
+        torrentMap.put("announce", new BEValue(newAnnounce));
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        BEncoder.bencode(torrentMap, baos);
+        return baos.toByteArray();
+    }
+
+    @Override
+    public List<Torrent> getTorrentsByCategorywithfilters(Integer categoryid, Map<String, String> filters) {
+        String extendTable = getExtendTableByCategoryId(categoryid);
+        if(extendTable == null) {
+            System.out.println("未知表");
+        }
+        return torrentMapper.listByCategoryWithFilters(categoryid,filters,extendTable);
+    }
+
+    @Override
+    public List<Torrent> searchByKeyword(String keyword) {
+        return torrentMapper.searchByKeyword(keyword);
+    }
+
+    private String getExtendTableByCategoryId(Integer categoryid) {
+        switch(categoryid) {
+            case 1:return "movie_info";
+            case 2:return "tvseries_info";
+            case 3:return "music_info";
+            case 4:return "anime_info";
+            case 5:return "game_info";
+            case 6:return "variety_info";
+            case 7:return "sport_info";
+            case 8:return "software_info";
+            case 9:return "edu_info";
+            case 10:return "documentary_info";
+            case 11: return "other_info";
+        }
+        return null;
+    }
+
+    //    @Override
+//    public Torrent getTorrentFile(Long torrentid) {
+//        System.out.println("查询结果:" + torrentid);
+//        Torrent result = torrentMapper.selectById(torrentid);
+//        if(result != null){
+//            System.out.println("not null:" + torrentid);
+//        }else{
+//            System.out.println("null:" + torrentid);
+//        }
+//        return torrentMapper.selectById(torrentid);
+//    }
+@Override
+public Torrent getTorrentFile(Long torrentid) {
+    System.out.println("查询参数:" + torrentid);
+    try {
+        Torrent result = torrentMapper.selectById(torrentid);
+        System.out.println("第一次查询结果:" + (result != null ? "非空" : "空"));
+        return result;
+    } catch (Exception e) {
+        System.out.println("查询异常:" + e.getMessage());
+        e.printStackTrace();
+        return null;
+    }
+}
+
+
+    @Override
+    public Resource loadTorrentFileAsResource(String filepath) throws Exception{
+        try{
+            Path file = Paths.get(filepath).normalize();
+            Resource resource = new UrlResource(file.toUri());
+
+            if(resource.exists() || resource.isReadable()){
+                return resource;
+            }else{
+                throw new Exception("Could not read file: " + filepath);
+            }
+        }catch(MalformedURLException e){
+            throw new Exception("Could not read file: " + filepath,e);
+        }
+    }
+
+    @Override
+    public void incrementDownloadCount(Long torrentid) {
+        torrentMapper.incrementDownloadCount(torrentid);
+    }
+}
diff --git a/src/main/java/com/pt5/pthouduan/service/impl/updatePeerStatsService.java b/src/main/java/com/pt5/pthouduan/service/impl/updatePeerStatsService.java
new file mode 100644
index 0000000..bdcdc65
--- /dev/null
+++ b/src/main/java/com/pt5/pthouduan/service/impl/updatePeerStatsService.java
@@ -0,0 +1,141 @@
+package com.pt5.pthouduan.service.impl;
+
+import com.pt5.pthouduan.mapper.UserMapper;
+import com.turn.ttorrent.tracker.Tracker;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.sql.DataSource;
+import java.nio.charset.StandardCharsets;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
+import static com.pt5.pthouduan.util.TorrentUtils.hexStringToByteArray;
+
+@Service
+public class updatePeerStatsService {
+
+    @Autowired
+    private DataSource dataSource;
+
+    @Autowired
+    private UserMapper userMapper;
+
+
+    public void updatePeerStatsInDB(String infoHash, String ip, int port, String peerId,
+                                    long uploaded, long downloaded, long deltaUpload,
+                                    long deltaDownload, String passkey, String event,
+                                    long left) {
+        double shareRatio = downloaded == 0 ? 0.0 : (double) uploaded / downloaded;
+
+        //记得在config里面写一下
+        int intervalSeconds = 100;
+        String username = userMapper.getUsernameByPasskey(passkey); // ← 获取用户名
+        String client = parseClientFromPeerId(peerId);              // ← 解析客户端
+        System.out.println("剩下的:" + left);
+        boolean isCompleted = left == 0;
+        double uploadSpeed = intervalSeconds > 0 ? (double) deltaUpload / intervalSeconds : 0.0;
+        double downloadSpeed = intervalSeconds > 0 ? (double) deltaDownload / intervalSeconds : 0.0;
+
+//        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 = VALUES(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, passkey);
+//
+//            stmt.executeUpdate();
+        String sql = """
+    INSERT INTO peer_stats (
+        info_hash, ip, port, peer_id, uploaded, downloaded,
+        delta_upload, delta_download, share_ratio, passkey,
+        username, last_event, last_updated, client, created_at,
+        completed_time, upload_speed, download_speed
+    )
+    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, ?, CURRENT_TIMESTAMP, ?, ?, ?)
+    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,
+        last_event = VALUES(last_event),
+        username = VALUES(username),
+        client = VALUES(client),
+        passkey = VALUES(passkey),
+        completed_time = IF(VALUES(completed_time) IS NOT NULL, VALUES(completed_time), completed_time),
+        upload_speed = VALUES(upload_speed),
+        download_speed = VALUES(download_speed);
+""";
+
+        try (Connection conn = dataSource.getConnection();
+             PreparedStatement stmt = conn.prepareStatement(sql)) {
+
+            stmt.setString(1, infoHash);               // info_hash
+            stmt.setString(2, ip);                     // ip
+            stmt.setInt(3, port);                      // port
+            stmt.setString(4, peerId);                 // peer_id
+            stmt.setLong(5, uploaded);                 // uploaded
+            stmt.setLong(6, downloaded);               // downloaded
+            stmt.setLong(7, deltaUpload);              // delta_upload
+            stmt.setLong(8, deltaDownload);            // delta_download
+            stmt.setDouble(9, shareRatio);             // share_ratio
+            stmt.setString(10, passkey);               // passkey
+            stmt.setString(11, username);              // username
+            stmt.setString(12, event);                 // last_event
+            // last_updated = CURRENT_TIMESTAMP (in SQL)
+            stmt.setString(13, client);                // client
+            // created_at = CURRENT_TIMESTAMP (in SQL)
+            stmt.setTimestamp(14, isCompleted ? new java.sql.Timestamp(System.currentTimeMillis()) : null);  // completed_time
+            stmt.setDouble(15, uploadSpeed);           // upload_speed
+            stmt.setDouble(16, downloadSpeed);         // download_speed
+//completetime还要再检查一下
+            stmt.executeUpdate();
+
+            // ✅ 成功写入后更新 user 表
+            userMapper.incrementUserTraffic(infoHash.toLowerCase(),passkey, deltaUpload, deltaDownload);
+
+        } catch (SQLException e) {
+            e.printStackTrace(); // 建议替换为日志系统
+        }
+    }
+    public static String parseClientFromPeerId(String peerId) {
+        String peerIdHex = peerId;
+        byte[] peerIdBytes = hexStringToByteArray(peerIdHex);
+        String decodedPeerId = new String(peerIdBytes, StandardCharsets.UTF_8);
+        System.out.println("Decoded Peer ID: " + decodedPeerId);
+        System.out.println("客户端:"+ peerId);
+        if (peerId == null || peerId.length() < 6) return "Unknown";
+
+        if (decodedPeerId.startsWith("-UT")) return "uTorrent";
+        if (decodedPeerId.startsWith("-TR")) return "Transmission";
+        if (decodedPeerId.startsWith("-AZ")) return "Azureus";
+        if (decodedPeerId.startsWith("-LT")) return "libtorrent";
+        if (decodedPeerId.startsWith("-qB")) return "qBittorrent";
+
+        return "Other";
+    }
+}
+
diff --git a/src/main/java/com/pt5/pthouduan/util/TorrentParser.java b/src/main/java/com/pt5/pthouduan/util/TorrentParser.java
new file mode 100644
index 0000000..d2b8f3f
--- /dev/null
+++ b/src/main/java/com/pt5/pthouduan/util/TorrentParser.java
@@ -0,0 +1,62 @@
+package com.pt5.pthouduan.util;
+
+
+import com.pt5.pthouduan.entity.Torrent;
+import com.turn.ttorrent.bcodec.BDecoder;
+import com.turn.ttorrent.bcodec.BEValue;
+import com.turn.ttorrent.bcodec.BEncoder;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.security.MessageDigest;
+import java.util.Map;
+
+public class TorrentParser {
+public static Torrent parse(byte[] torrentBytes) throws Exception {
+    InputStream in = new ByteArrayInputStream(torrentBytes);
+    BEValue decoded = BDecoder.bdecode(in);
+    Map<String, BEValue> torrentMap = decoded.getMap();
+    BEValue infoValue = torrentMap.get("info");
+    if (infoValue == null) {
+        throw new IllegalArgumentException("Invalid torrent file: missing 'info' dictionary");
+    }
+
+    // 提取 info 的 Map 并编码
+    Map<String, BEValue> infoMap = infoValue.getMap();
+    ByteBuffer buffer = BEncoder.bencode(infoMap);  // 返回 ByteBuffer
+    byte[] infoBencoded = new byte[buffer.remaining()];
+    buffer.get(infoBencoded);  // 转换为 byte[]
+
+    // 计算 SHA-1 hash
+    MessageDigest digest = MessageDigest.getInstance("SHA-1");
+    byte[] hashBytes = digest.digest(infoBencoded);
+    StringBuilder sb = new StringBuilder();
+    for (byte b : hashBytes) {
+        sb.append(String.format("%02x", b));
+    }
+    String infoHash = sb.toString();
+
+    // 计算种子大小
+    long torrentSize = 0;
+    BEValue lengthValue = infoMap.get("length");
+    if (lengthValue != null) {
+        torrentSize = lengthValue.getLong();
+    } else {
+        BEValue filesValue = infoMap.get("files");
+        if (filesValue != null) {
+            for (BEValue fileValue : filesValue.getList()) {
+                BEValue fileLength = fileValue.getMap().get("length");
+                if (fileLength != null) {
+                    torrentSize += fileLength.getLong();
+                }
+            }
+        }
+    }
+
+    Torrent torrent = new Torrent();
+    torrent.setInfoHash(infoHash);
+    torrent.setTorrentSize(torrentSize);
+    return torrent;
+}
+}
diff --git a/src/main/java/com/pt5/pthouduan/util/TorrentUtils.java b/src/main/java/com/pt5/pthouduan/util/TorrentUtils.java
new file mode 100644
index 0000000..6da7e73
--- /dev/null
+++ b/src/main/java/com/pt5/pthouduan/util/TorrentUtils.java
@@ -0,0 +1,40 @@
+package com.pt5.pthouduan.util;
+
+import java.util.HexFormat;
+
+public class TorrentUtils {
+    /**
+     * 16进制字符串转byte[]
+     * @param hexString 不带前缀的16进制字符串(如 "7a2b3c")
+     */
+    public static byte[] hexStringToByteArray(String hexString) {
+        // Java 17+ 推荐方式
+        return HexFormat.of().parseHex(hexString);
+
+        /* Java 8 兼容方案:
+        int len = hexString.length();
+        byte[] data = new byte[len / 2];
+        for (int i = 0; i < len; i += 2) {
+            data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4)
+                                + Character.digit(hexString.charAt(i + 1), 16));
+        }
+        return data;
+        */
+    }
+
+    /**
+     * byte[] 转16进制字符串
+     */
+    public static String byteArrayToHexString(byte[] bytes) {
+        // Java 17+ 推荐方式
+        return HexFormat.of().formatHex(bytes);
+
+        /* Java 8 兼容方案:
+        StringBuilder sb = new StringBuilder();
+        for (byte b : bytes) {
+            sb.append(String.format("%02x", b));
+        }
+        return sb.toString();
+        */
+    }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 34a3a7e..749faf7 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1,14 +1,37 @@
 spring.application.name=PT-houduan
 spring.datasource.url=jdbc:mysql://localhost:3306/pt?useSSL=false&serverTimezone=Asia/Shanghai
+#spring.datasource.url=jdbc:mysql://host.docker.internal:3306/pt?useSSL=false&serverTimezone=Asia/Shanghai
 spring.datasource.username=root
-spring.datasource.password=123456
+spring.datasource.password=12345
 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
-
+# application.properties
+#spring.datasource.url=jdbc:mysql://202.205.102.121:3306/1group5?useSSL=false&serverTimezone=Asia/Shanghai
+#spring.datasource.username=team5
+#spring.datasource.password=Team5001#
+#spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
+hikari.maximum-pool-size = 20
+hikari.minimum-idle = 5
+hikari.idle-timeout = 30000
 # MyBatis-Plus ??
 mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
 mybatis-plus.global-config.db-config.id-type=auto
 mybatis-plus.global-config.db-config.logic-delete-value=1
 mybatis-plus.global-config.db-config.logic-not-delete-value=0
-mybatis-plus.mapper-locations=classpath:/mapper/*.xml
+#logging.level.root=DEBUG
+#logging.level.org.springframework.web=DEBUG
+#logging.level.com.pt5=TRACE
+#logging.level.root=DEBUG
+mybatis-plus.mapper-locations=classpath:mapper/xml/*.xml
 mybatis-plus.type-aliases-package=com.pt5.pthouduan.entity
 
+uploadDirectory= ./uploads/files    # ????????
+torrent-dir= ./uploads/torrents # ????????
+# ??????
+pt.storage.torrent-path=/var/pt/torrents
+pt.storage.temp-path=/var/pt/temp
+# application.properties ??
+tracker.url=http://localhost:6969/announce
+
+
+
+
diff --git a/src/main/resources/mapper/xml/TorrentMapper.xml b/src/main/resources/mapper/xml/TorrentMapper.xml
new file mode 100644
index 0000000..609ec46
--- /dev/null
+++ b/src/main/resources/mapper/xml/TorrentMapper.xml
@@ -0,0 +1,228 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="com.pt5.pthouduan.mapper.TorrentMapper">
+
+    <!-- 按上传者查询 -->
+    <select id="selectByUploaderId" resultType="com.pt5.pthouduan.entity.Torrent">
+        SELECT * FROM torrent
+        WHERE userid = #{uploaderId}
+    </select>
+
+    <!-- 按名称模糊搜索 -->
+    <select id="searchByName" resultType="com.pt5.pthouduan.entity.Torrent">
+        SELECT * FROM torrent
+        WHERE torrent_title LIKE CONCAT('%', #{keyword}, '%')
+    </select>
+
+    <select id="searchByKeyword" resultType="com.pt5.pthouduan.entity.Torrent">
+        SELECT * FROM torrent
+        WHERE torrent_title LIKE CONCAT('%', #{keyword}, '%')
+    </select>
+
+    <!-- 获取热门种子 -->
+    <select id="selectTopPopular" resultType="com.pt5.pthouduan.entity.Torrent">
+        SELECT * FROM torrent
+        ORDER BY download_count DESC
+            LIMIT 10
+    </select>
+
+    <!-- 检查infoHash是否存在 -->
+    <select id="existsByInfoHash" resultType="int" parameterType="string">
+        SELECT COUNT(*) FROM torrent WHERE info_hash = #{infoHash}
+    </select>
+
+
+    <!-- 下载计数+1 -->
+    <update id="incrementDownloadCount">
+        UPDATE torrent
+        SET download_count = download_count + 1
+        WHERE torrentid = #{id}
+    </update>
+
+    <!-- 复杂查询示例(带动态SQL) -->
+    <select id="selectByCondition" resultType="com.pt5.pthouduan.entity.Torrent">
+        SELECT * FROM torrent
+        <where>
+            <if test="condition.categoryId != null">
+                AND categoryid = #{condition.categoryId}
+            </if>
+            <if test="condition.minSize != null">
+                AND torrent_size >= #{condition.minSize}
+            </if>
+        </where>
+        ORDER BY ${condition.orderBy} DESC
+    </select>
+
+<!--    <select id="selectById" resultType="com.pt5.pthouduan.entity.Torrent">-->
+<!--        SELECT-->
+<!--            t.torrentid,-->
+<!--            t.torrent_title,-->
+<!--            t.description,-->
+<!--            t.uploader_id,-->
+<!--            t.upload_time,-->
+<!--            t.download_count,-->
+<!--            t.torrent_size,-->
+<!--            t.filename,-->
+<!--            t.path,-->
+<!--            t.categoryid,-->
+<!--            t.caption,-->
+<!--            t.dpi-->
+<!--        FROM torrent t-->
+<!--        WHERE t.torrentid = #{torrentid}-->
+<!--    </select>-->
+    <select id="selectById" resultType="com.pt5.pthouduan.entity.Torrent">
+        SELECT
+            t.torrentid,
+            t.torrent_title,
+            t.description,
+            t.uploader_id,
+            t.upload_time,
+            t.download_count,
+            t.torrent_size,
+            t.filename,
+            t.path,
+            t.categoryid,
+            t.caption,
+            t.dpi,
+            t.last_seed,
+            t.info_hash
+        FROM torrent t
+        WHERE t.torrentid = #{torrentid}
+    </select>
+
+    <select id="getAllTorrents" resultType="com.pt5.pthouduan.entity.Torrent">
+        SELECT
+            torrentid,
+            torrent_title,
+            filename,
+            description,
+            path,
+            uploader_id,
+            upload_time,
+            download_count,
+            categoryid,
+            promotionid,
+            dpi,
+            caption
+        FROM
+            torrent
+    </select>
+
+    <insert id="save" parameterType="com.pt5.pthouduan.entity.Torrent"
+            useGeneratedKeys="true" keyProperty="torrentid" keyColumn="torrentid">
+        INSERT INTO torrent (
+            uploader_id,
+            promotionid,
+            categoryid,
+            info_hash,
+            torrent_title,
+            dpi,
+            caption,
+            torrent_size,
+            upload_time,
+            download_count,
+            description,
+            path,
+            filename
+        ) VALUES (
+                     #{uploaderid},
+                     #{promotionid},
+                     #{categoryid},
+                     #{infohash},
+                     #{torrenttitle},
+                     #{dpi},
+                     #{caption},
+                     #{torrentsize},
+                     #{uploadtime},
+                     #{downloadCount},
+                     #{description},
+                     #{path},
+                     #{filename}
+                 )
+    </insert>
+    <!-- 按分类查询种子 -->
+    <select id="selectTorrentsByCategory" resultType="com.pt5.pthouduan.entity.Torrent">
+        SELECT * FROM torrent WHERE categoryid = #{category}
+    </select>
+
+    <!-- 免费下载 -->
+    <update id="setFreePromotion">
+            <![CDATA[
+        UPDATE torrent t
+        LEFT JOIN (
+            SELECT info_hash, MAX(last_updated) AS last_time
+            FROM peer_stats
+            GROUP BY info_hash
+        ) ps ON t.info_hash = ps.info_hash
+        SET t.promotionid = 3
+        WHERE ps.last_time IS NULL OR ps.last_time < NOW() - INTERVAL 7 DAY
+        ]]>
+    </update>
+
+
+    <!-- 上传加倍 -->
+    <update id="setDoubleUpload">
+            <![CDATA[
+        UPDATE torrent t
+        LEFT JOIN (
+            SELECT info_hash, MAX(last_updated) AS last_time,
+                   SUM(delta_upload) AS total_upload
+            FROM peer_stats
+            GROUP BY info_hash
+        ) ps ON t.info_hash = ps.info_hash
+        SET t.promotionid = 1
+        WHERE ps.last_time < NOW() - INTERVAL 5 DAY
+          AND (ps.total_upload IS NULL OR ps.total_upload = 0)
+        ]]>
+    </update>
+
+
+    <!-- 下载减半 -->
+    <update id="setHalfDownload">
+            <![CDATA[
+        UPDATE torrent t
+        LEFT JOIN (
+            SELECT info_hash, MAX(last_updated) AS last_time,
+                   SUM(delta_download) AS total_download
+            FROM peer_stats
+            GROUP BY info_hash
+        ) ps ON t.info_hash = ps.info_hash
+        SET t.promotionid = 2
+        WHERE ps.last_time < NOW() - INTERVAL 5 DAY
+          AND (ps.total_download IS NULL OR ps.total_download = 0)
+        ]]>
+    </update>
+
+
+    <!-- 取消促销 -->
+    <update id="clearPromotion">
+            <![CDATA[
+        UPDATE torrent t
+        LEFT JOIN (
+            SELECT info_hash, MAX(last_updated) AS last_time
+            FROM peer_stats
+            GROUP BY info_hash
+        ) ps ON t.info_hash = ps.info_hash
+        SET t.promotionid = NULL
+        WHERE ps.last_time >= NOW() - INTERVAL 3 DAY
+        ]]>
+    </update>
+
+    <select id="listByCategoryWithFilters" resultType="com.pt5.pthouduan.entity.Torrent">
+        SELECT * FROM torrent t
+        WHERE t.categoryid = #{categoryid}
+        <if test="filters != null and !filters.isEmpty()">
+            AND t.torrentid IN (
+            SELECT e.torrentid FROM
+            ${extendTable} e
+            WHERE
+            <foreach collection="filters" index="key" item="value" separator=" AND ">
+                (e.${key} = #{value})
+            </foreach>
+            )
+        </if>
+    </select>
+
+</mapper>
\ No newline at end of file
diff --git a/src/main/resources/mapper/xml/UserMapper.xml b/src/main/resources/mapper/xml/UserMapper.xml
new file mode 100644
index 0000000..229f3c0
--- /dev/null
+++ b/src/main/resources/mapper/xml/UserMapper.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.pt5.pthouduan.mapper.UserMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.pt5.pthouduan.entity.User">
+        <id column="userid" property="userid" />
+        <result column="username" property="username" />
+        <result column="password" property="password" />
+        <result column="user_upload" property="userUpload" />
+        <result column="user_download" property="userDownload" />
+        <result column="credit" property="credit" />
+        <result column="image" property="image" />
+        <result column="sex" property="sex" />
+        <result column="grade_id" property="gradeId" />
+        <result column="passkey" property="passkey" />
+        <result column="user_created_time" property="userCreatedTime" />
+        <result column="ratio" property="ratio" />
+        <result column="age" property="age" />
+        <result column="privacy" property="privacy" />
+    </resultMap>
+
+    <!-- 通用查询结果列 -->
+    <sql id="Base_Column_List">
+        username, password, user_upload, user_download, credit, image, sex, userid, grade_id, passkey, user_created_time, ratio, age, privacy
+    </sql>
+    <!-- 根据 passkey 查询用户名 -->
+    <select id="getUsernameByPasskey" parameterType="java.lang.String" resultType="java.lang.String">
+        SELECT username
+        FROM user
+        WHERE passkey = #{passkey}
+    </select>
+
+    <select id="findSeedersByInfoHash" resultType="com.pt5.pthouduan.entity.PeerInfo">
+        SELECT
+            ps.ip,
+            ps.port,
+            ps.peer_id,
+            ps.uploaded,
+            ps.upload_speed,
+            ps.downloaded,
+            ps.download_speed,
+            ps.last_event,
+            ps.last_updated,
+            ps.created_at,
+            ps.client,
+            ps.username,
+            ps.created_at,
+            ps.completed_time,
+            ps.port
+        FROM peer_stats ps
+        WHERE ps.info_hash = #{infoHash}
+    </select>
+
+
+
+    <!-- 根据 passkey 检查用户是否存在 -->
+    <select id="existsByPasskey" resultType="boolean">
+        SELECT COUNT(*) > 0
+        FROM user
+        WHERE passkey = #{passkey}
+    </select>
+
+<!--    <update id="incrementUserTraffic">-->
+<!--        UPDATE user-->
+<!--        SET user_upload = user_upload + #{user_upload},-->
+<!--            user_download = user_download + #{user_download}-->
+<!--        WHERE passkey = #{passkey}-->
+<!--    </update>-->
+    <update id="incrementUserTraffic">
+        UPDATE user u
+            JOIN torrent t ON t.info_hash = #{info_hash} AND u.passkey = #{passkey}
+            SET
+                u.user_upload = u.user_upload + (
+                CASE
+                WHEN t.promotionid = 1 THEN #{user_upload} * 2      -- 上传加倍
+                ELSE #{user_upload}
+                END
+                ),
+                u.user_download = u.user_download + (
+                CASE
+                WHEN t.promotionid = 2 THEN #{user_download} / 2    -- 下载减半
+                WHEN t.promotionid = 3 THEN 0                       -- 免费下载
+                ELSE #{user_download}
+                END
+                )
+    </update>
+
+
+
+</mapper>
diff --git a/src/test/java/com/pt5/pthouduan/ControllerTest/TorrentUploadControllerTest.java b/src/test/java/com/pt5/pthouduan/ControllerTest/TorrentUploadControllerTest.java
new file mode 100644
index 0000000..c0c9cbf
--- /dev/null
+++ b/src/test/java/com/pt5/pthouduan/ControllerTest/TorrentUploadControllerTest.java
@@ -0,0 +1,247 @@
+package com.pt5.pthouduan.ControllerTest;
+
+import com.pt5.pthouduan.controller.TorrentController;
+import com.pt5.pthouduan.entity.User;
+import com.pt5.pthouduan.service.TorrentService;
+import org.apache.tomcat.util.net.openssl.ciphers.Authentication;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.mock.web.MockMultipartFile;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+public class TorrentUploadControllerTest {
+
+    @Mock
+    private TorrentService torrentService;
+
+    @InjectMocks
+    private TorrentController torrentController;
+
+    @BeforeEach
+    void setUp() {
+        MockitoAnnotations.openMocks(this);
+        setupSecurityContext();
+    }
+
+    private void setupSecurityContext() {
+        // 设置模拟用户
+        User user = new User(1L, "testuser");
+        Authentication authentication = mock(Authentication.class);
+    }
+
+    @Test
+    void uploadTorrent_Success() throws Exception {
+        // 准备测试数据
+        MockMultipartFile mockFile = new MockMultipartFile(
+                "file", "test.torrent", "application/x-bittorrent", "mock torrent data".getBytes()
+        );
+
+        // 准备参数
+        String title = "Test Torrent";
+        String description = "Test Description";
+        Integer categoryId = 1;
+        String dpi = "1080";
+        String caption = "Test Caption";
+        String region = "CN";
+        Integer year = 2023;
+        String genre = "Action";
+
+        // 准备预期的extraParams
+        Map<String, String> expectedExtraParams = new HashMap<>();
+        expectedExtraParams.put("dpi", dpi);
+        expectedExtraParams.put("caption", caption);
+        expectedExtraParams.put("region", region);
+        expectedExtraParams.put("year", year.toString());
+        expectedExtraParams.put("genre", genre);
+
+        // 模拟Service行为
+        when(torrentService.uploadWithCategory(
+                any(MultipartFile.class), anyString(), anyString(),
+                anyInt(), any(User.class), anyMap())
+        ).thenReturn(ResponseEntity.ok().build());
+
+        // 执行测试
+        ResponseEntity<?> response = torrentController.uploadTorrent(
+                mockFile, title, description, categoryId,
+                dpi, caption,
+                region, year, genre,
+                null, null, null, null, null, null, null, null, null, null
+        );
+
+        // 验证结果
+        assertEquals(HttpStatus.OK, response.getStatusCode());
+
+        // 使用ArgumentCaptor捕获参数
+        ArgumentCaptor<Map<String, String>> captor = ArgumentCaptor.forClass(Map.class);
+        verify(torrentService).uploadWithCategory(
+                eq(mockFile), eq(title), eq(description),
+                eq(categoryId), any(User.class), captor.capture()
+        );
+
+        // 验证Map内容
+        Map<String, String> actualParams = captor.getValue();
+        assertEquals(expectedExtraParams, actualParams);
+    }
+
+    @Test
+    void uploadTorrent_WithAllOptionalParams() throws Exception {
+        // 准备测试数据
+        MockMultipartFile mockFile = new MockMultipartFile(
+                "file", "test.torrent", "application/x-bittorrent", "mock torrent data".getBytes()
+        );
+
+        // 准备所有可能的参数
+        String title = "Complete Torrent";
+        String description = "Complete Description";
+        Integer categoryId = 2;
+
+        // 准备所有可选参数
+        String dpi = "4K";
+        String caption = "Complete Caption";
+        String region = "US";
+        Integer year = 2022;
+        String genre = "Sci-Fi";
+        String format = "MP4";
+        String resolution = "2160p";
+        String codecFormat = "H.265";
+        String platform = "PC";
+        String language = "English";
+        String eventType = "Championship";
+        String dataType = "RAW";
+        String source = "Blu-ray";
+        String style = "Rock";
+        Boolean isMainland = false;
+
+        // 准备预期的extraParams
+        Map<String, String> expectedExtraParams = new HashMap<>();
+        expectedExtraParams.put("dpi", dpi);
+        expectedExtraParams.put("caption", caption);
+        expectedExtraParams.put("region", region);
+        expectedExtraParams.put("year", year.toString());
+        expectedExtraParams.put("genre", genre);
+        expectedExtraParams.put("format", format);
+        expectedExtraParams.put("resolution", resolution);
+        expectedExtraParams.put("codecFormat", codecFormat);
+        expectedExtraParams.put("platform", platform);
+        expectedExtraParams.put("language", language);
+        expectedExtraParams.put("eventType", eventType);
+        expectedExtraParams.put("dataType", dataType);
+        expectedExtraParams.put("source", source);
+        expectedExtraParams.put("style", style);
+        expectedExtraParams.put("isMainland", isMainland.toString());
+
+        // 模拟Service行为
+        when(torrentService.uploadWithCategory(
+                any(MultipartFile.class), anyString(), anyString(),
+                anyInt(), any(User.class), anyMap())
+        ).thenReturn(ResponseEntity.ok().build());
+
+        // 执行测试
+        ResponseEntity<?> response = torrentController.uploadTorrent(
+                mockFile, title, description, categoryId,
+                dpi, caption,
+                region, year, genre,
+                format, resolution, codecFormat,
+                platform, language, eventType,
+                dataType, source, style, isMainland
+        );
+
+        // 验证结果
+        assertEquals(HttpStatus.OK, response.getStatusCode());
+
+        // 验证参数传递
+        ArgumentCaptor<Map<String, String>> captor = ArgumentCaptor.forClass(Map.class);
+        verify(torrentService).uploadWithCategory(
+                eq(mockFile), eq(title), eq(description),
+                eq(categoryId), any(User.class), captor.capture()
+        );
+
+        // 验证所有参数都被正确传递
+        Map<String, String> actualParams = captor.getValue();
+        assertEquals(expectedExtraParams, actualParams);
+    }
+
+    @Test
+    void uploadTorrent_WithNullOptionalParams() throws Exception {
+        // 准备测试数据
+        MockMultipartFile mockFile = new MockMultipartFile(
+                "file", "test.torrent", "application/x-bittorrent", "mock torrent data".getBytes()
+        );
+
+        String title = "Minimal Torrent";
+        String description = "Minimal Description";
+        Integer categoryId = 3;
+
+        // 模拟Service行为
+        when(torrentService.uploadWithCategory(
+                any(MultipartFile.class), anyString(), anyString(),
+                anyInt(), any(User.class), anyMap())
+        ).thenReturn(ResponseEntity.ok().build());
+
+        // 执行测试 - 只传必填参数,可选参数都为null
+        ResponseEntity<?> response = torrentController.uploadTorrent(
+                mockFile, title, description, categoryId,
+                null, null, null, null, null,
+                null, null, null, null, null,
+                null, null, null, null, null
+        );
+
+        // 验证结果
+        assertEquals(HttpStatus.OK, response.getStatusCode());
+
+        // 验证参数传递
+        ArgumentCaptor<Map<String, String>> captor = ArgumentCaptor.forClass(Map.class);
+        verify(torrentService).uploadWithCategory(
+                eq(mockFile), eq(title), eq(description),
+                eq(categoryId), any(User.class), captor.capture()
+        );
+
+        // 验证extraParams为空
+        Map<String, String> actualParams = captor.getValue();
+        assertTrue(actualParams.isEmpty());
+    }
+
+    @Test
+    void uploadTorrent_ServiceThrowsException() throws Exception {
+        // 准备测试数据
+        MockMultipartFile mockFile = new MockMultipartFile(
+                "file", "test.torrent", "application/x-bittorrent", "mock torrent data".getBytes()
+        );
+
+        String title = "Failing Torrent";
+        String description = "Failing Description";
+        Integer categoryId = 4;
+
+        // 模拟Service抛出异常
+        when(torrentService.uploadWithCategory(
+                any(MultipartFile.class), anyString(), anyString(),
+                anyInt(), any(User.class), anyMap())
+        ).thenThrow(new RuntimeException("Upload failed"));
+
+        // 执行测试
+        ResponseEntity<?> response = torrentController.uploadTorrent(
+                mockFile, title, description, categoryId,
+                null, null, null, null, null,
+                null, null, null, null, null,
+                null, null, null, null, null
+        );
+
+        // 验证结果
+        assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
+        assertTrue(response.getBody().toString().contains("Upload failed"));
+    }
+
+}
\ No newline at end of file