Final push for project

Change-Id: I9103078156eca93df2482b9fe3854d9301bb98b3
diff --git a/Tree.txt b/Tree.txt
new file mode 100644
index 0000000..6eab78e
--- /dev/null
+++ b/Tree.txt
@@ -0,0 +1,129 @@
+.
+├── Tree.txt
+├── backend
+│   └── demo
+│       ├── Dockerfile
+│       ├── HELP.md
+│       ├── compose.yaml
+│       ├── db
+│       │   └── SQLScript.sql
+│       ├── dependency_tree.txt
+│       ├── mvnw
+│       ├── mvnw.cmd
+│       ├── pom.xml
+│       ├── src
+│       │   ├── main
+│       │   │   ├── java
+│       │   │   │   └── com
+│       │   │   │       └── example
+│       │   │   │           └── demo
+│       │   │   │               ├── DemoApplication.java
+│       │   │   │               ├── config
+│       │   │   │               │   ├── JWTProperties.java
+│       │   │   │               │   ├── SecurityConfig.java
+│       │   │   │               │   └── WebConfig.java
+│       │   │   │               ├── controller
+│       │   │   │               │   ├── AuthController.java
+│       │   │   │               │   └── TorrentController.java
+│       │   │   │               ├── dto
+│       │   │   │               │   ├── LoginRequestDTO.java
+│       │   │   │               │   ├── LoginResponseDTO.java
+│       │   │   │               │   └── TorrentInfoDTO.java
+│       │   │   │               ├── entity
+│       │   │   │               │   ├── TorrentInfo.java
+│       │   │   │               │   └── User.java
+│       │   │   │               ├── exception
+│       │   │   │               │   ├── AuthException.java
+│       │   │   │               │   └── GlobalExceptionHandler.java
+│       │   │   │               ├── mapper
+│       │   │   │               │   ├── TorrentInfoMapper.java
+│       │   │   │               │   └── UserMapper.java
+│       │   │   │               ├── result.txt
+│       │   │   │               ├── security
+│       │   │   │               │   ├── JwtAuthenticationFilter.java
+│       │   │   │               │   └── JwtTokenUtil.java
+│       │   │   │               ├── service
+│       │   │   │               │   ├── TorrentService.java
+│       │   │   │               │   ├── UserService.java
+│       │   │   │               │   └── impl
+│       │   │   │               │       ├── TorrentServiceImpl.java
+│       │   │   │               │       └── UserServiceImpl.java
+│       │   │   │               ├── sh.py
+│       │   │   │               └── util
+│       │   │   │                   └── TorrentUtils.java
+│       │   │   └── resources
+│       │   │       ├── META-INF
+│       │   │       │   └── additional-spring-configuration-metadata.json
+│       │   │       ├── application.properties
+│       │   │       ├── static
+│       │   │       └── templates
+│       │   └── test
+│       │       └── java
+│       │           └── com
+│       │               └── example
+│       │                   └── demo
+│       │                       ├── DemoApplicationTests.java
+│       │                       ├── controller
+│       │                       │   ├── AuthControllerMockTest.java
+│       │                       │   └── TorrentControllerTest.java
+│       │                       ├── security
+│       │                       │   └── JwtTokenUtilTest.java
+│       │                       ├── service
+│       │                       │   └── impl
+│       │                       │       └── TorrentServiceImplTest.java
+│       │                       └── util
+│       │                           └── TorrentByteReader.java
+│       └── test_temp_upload_dir
+└── frontend
+    └── my-app
+        ├── README.md
+        ├── index.html
+        ├── package-lock.json
+        ├── package.json
+        ├── public
+        │   └── vite.svg
+        ├── src
+        │   ├── App.css
+        │   ├── App.jsx
+        │   ├── assets
+        │   │   ├── fonts
+        │   │   ├── icons
+        │   │   ├── images
+        │   │   └── react.svg
+        │   ├── components
+        │   │   ├── Button
+        │   │   ├── Card
+        │   │   ├── Modal
+        │   │   └── SearchBar
+        │   ├── contexts
+        │   │   └── UserContext.jsx
+        │   ├── hooks
+        │   ├── index.css
+        │   ├── main.jsx
+        │   ├── mocks
+        │   │   ├── handlers.js
+        │   │   └── server.js
+        │   ├── pages
+        │   │   ├── Home.jsx
+        │   │   ├── UploadTorrent.jsx
+        │   │   └── UserProfile.jsx
+        │   ├── services
+        │   │   └── torrentService.js
+        │   ├── setUpTests.js
+        │   ├── store
+        │   │   ├── actions
+        │   │   └── reducers
+        │   ├── styles
+        │   │   ├── base
+        │   │   │   └── base.css
+        │   │   ├── components
+        │   │   ├── pages
+        │   │   └── theme
+        │   │       └── theme.js
+        │   └── utils
+        │       ├── dateUtils
+        │       ├── storage
+        │       └── validation
+        └── vite.config.jsx
+
+64 directories, 62 files
diff --git a/backend/demo/Dockerfile b/backend/demo/Dockerfile
index 3b73f3c..8c3fa5b 100644
--- a/backend/demo/Dockerfile
+++ b/backend/demo/Dockerfile
@@ -1,4 +1,4 @@
-# 阶段 1: builder - 构建应用 JAR 包 (跳过测试)
+# 阶段 1: builder - 构建应用 JAR 包并准备分层结构
 # 使用包含 Maven 和 JDK 的基础镜像
 FROM maven:3.9.9-eclipse-temurin-24-alpine AS builder
 
@@ -6,61 +6,28 @@
 WORKDIR /app
 
 # 复制 pom.xml 文件,以便 Maven 可以下载依赖
+# 这一步利用 Docker 缓存,如果 pom.xml 没有变化,则不会重新下载依赖
 COPY pom.xml .
 
-# 复制项目源代码,包括 main 和 test
-# 这一步是为了让 Maven 在构建和下载依赖时能够解析所有模块
+# 复制项目源代码
 COPY src ./src
 
-# 执行 Maven clean 和 dependency:go-offline
-# clean: 清理 target 目录
-# dependency:go-offline: 下载所有项目依赖到本地 Maven 仓库,供后续阶段使用
-# -B: 非交互模式
-# 这一步不执行实际的编译和测试,主要用于缓存依赖
-RUN mvn clean dependency:go-offline -B
-
-# 执行 Maven package 构建项目,生成 JAR 包
-# -DskipTests: 跳过单元和集成测试
+# 执行 Maven package 构建项目
+# spring-boot-maven-plugin 会生成一个可执行 JAR (例如 demo-0.0.1-SNAPSHOT.jar),
+# 并且在 target 目录下也会有 BOOT-INF/lib (所有依赖) 和 BOOT-INF/classes (您的应用代码) 等分层结构
 RUN mvn package -DskipTests
 
 
-# 阶段 2: tester - 专用于运行和调试测试的运行时环境
-# 基于与 builder 阶段相同的 Maven/JDK 基础镜像,确保测试所需的环境一致
-FROM maven:3.9.9-eclipse-temurin-24-alpine AS tester
-
-# 设置工作目录
-WORKDIR /app
-
-# 复制 pom.xml 和完整的 src 目录 (包含测试代码)
-COPY pom.xml .
-COPY src ./src
-
-# 从 builder 阶段复制已缓存的 Maven 本地仓库
-# 这样在 tester 阶段运行 Maven 时无需重新下载依赖
-COPY --from=builder /root/.m2 /root/.m2
-
-# 暴露调试端口 (可选,如果您需要远程调试测试过程)
-# 您可以在 docker compose 中映射这个端口到主机
-EXPOSE 5005
-
-# 设置默认命令,使容器启动后保持运行
-# 这样您可以方便地使用 docker exec 进入容器手动执行测试和调试命令
-# 如果您希望容器启动后自动运行测试,可以修改此命令为 ["mvn", "test"]
-CMD ["tail", "-f", "/dev/null"]
-
-
-# 阶段 3: runner - 运行最终应用 JAR 包的精简运行时环境
-# 使用轻量级的 OpenJDK JRE 镜像
+# 阶段 2: runner - 运行最终应用 JAR 包的精简运行时环境
+# 使用轻量级的 OpenJDK JRE 镜像,只包含运行时环境,减小镜像大小
 FROM openjdk:17-jdk-slim-buster AS runner
-
-# 设置工作目录
 WORKDIR /app
 
-# 从 builder 阶段复制构建好的应用 JAR 包
+# 复制可执行 jar 包
 COPY --from=builder /app/target/demo-0.0.1-SNAPSHOT.jar app.jar
 
-# 暴露 Spring Boot 应用的默认端口
+# 暴露端口
 EXPOSE 8080
 
-# 启动 Spring Boot 应用
-CMD ["java", "-jar", "app.jar"]
+# 直接运行 Spring Boot JAR(推荐方式)
+ENTRYPOINT ["java", "-jar", "app.jar"]
diff --git a/backend/demo/compose.yaml b/backend/demo/compose.yaml
index 4d288a2..1737a5a 100644
--- a/backend/demo/compose.yaml
+++ b/backend/demo/compose.yaml
@@ -40,33 +40,8 @@
       mysql:
         condition: service_healthy # 确保数据库服务处于健康状态才启动应用
 
-  # 测试服务(使用 Dockerfile 的 tester 阶段)
-  # 这个服务用于运行和调试需要连接数据库的测试
-  tester:
-    build:
-      context: . # Dockerfile 所在的上下文路径
-      dockerfile: Dockerfile # Dockerfile 文件名
-      target: tester # 指定构建 Dockerfile 中的 tester 阶段
-    container_name: tester
-    # 暴露调试端口,映射到主机端口,用于远程调试测试过程
-    ports:
-      - "5005:5005"
-    # 依赖于数据库服务,并等待其健康检查通过
-    depends_on:
-      mysql:
-        condition: service_healthy # 确保数据库服务处于健康状态才启动测试容器
-    environment:
-      # 测试环境连接数据库的配置,使用数据库服务的名称作为主机名
-      # 这些环境变量会覆盖 application-test.properties 中的相同配置
-      SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/mydatabase?serverTimezone=Asia/Shanghai&createDatabaseIfNotExist=TRUE&useSSL=FALSE
-      SPRING_DATASOURCE_USERNAME: myuser
-      SPRING_DATASOURCE_PASSWORD: secret
-      # 设置 Maven 运行时的额外 JVM 参数,用于开启远程调试
-      MAVEN_OPTS: "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"
-    # 默认命令:保持容器运行,方便手动进入容器执行测试和调试
-    # 如果您希望容器启动后自动运行测试,可以修改此命令为 ["mvn", "test"]
-    command: tail -f /dev/null
 
+  
 
 # 定义数据卷用于持久化数据库数据
 volumes:
diff --git a/backend/demo/pom.xml b/backend/demo/pom.xml
index 6eccaf5..2e50a85 100644
--- a/backend/demo/pom.xml
+++ b/backend/demo/pom.xml
@@ -12,6 +12,16 @@
     <version>3.4.4</version>
     <relativePath/>
 </parent>
+<repositories>
+    <repository>
+      <id>local-ttorrent</id>
+      <name>Local TTorrent Bundle</name>
+      <url>file://${project.basedir}/src/main/resources/lib</url>
+      <releases><enabled>true</enabled></releases>
+      <snapshots><enabled>false</enabled></snapshots>
+    </repository>
+  </repositories>
+
 
 <groupId>com.example</groupId>
 <artifactId>demo</artifactId>
@@ -28,7 +38,9 @@
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
     </dependency>
-    
+
+   
+
 
     <!-- 数据库驱动:MySQL、H2 -->
     <dependency>
@@ -97,39 +109,42 @@
   <version>0.11.5</version>
   <scope>runtime</scope>
 </dependency>
+<dependency>
+    <groupId>commons-codec</groupId>
+    <artifactId>commons-codec</artifactId>
+ </dependency>
+ <dependency>
+  <groupId>javax.annotation</groupId>
+  <artifactId>javax.annotation-api</artifactId>
+  <version>1.3.2</version>
+</dependency>
+
  <dependency>
     <groupId>com.turn</groupId>
     <artifactId>ttorrent-bencoding</artifactId>
-    <version>1.3.0-SNAPSHOT</version>
-  </dependency>
-
-  <!-- 通用工具模块(info_hash、文件结构等) -->
-  <dependency>
-    <groupId>com.turn</groupId>
-    <artifactId>ttorrent-common</artifactId>
-    <version>1.3.0-SNAPSHOT</version>
-  </dependency>
-
-  <!-- 网络通信支持(用于 Scrape/Tracker 通信) -->
-  <dependency>
-    <groupId>com.turn</groupId>
-    <artifactId>ttorrent-network</artifactId>
-    <version>1.0</version>
-  </dependency>
-
-  <!-- Tracker 服务(你自己部署的 announce 接口服务) -->
-  <dependency>
-    <groupId>com.turn</groupId>
-    <artifactId>ttorrent-tracker</artifactId>
-    <version>1.3.0-SNAPSHOT</version>
-  </dependency>
-
-  <!-- 客户端支持(如果你需要服务器自动发种、做种) -->
-  <dependency>
+    <version>1.3.0-SNAPSHOT</version> <!-- Changed to match your file -->
+</dependency>
+<dependency>
     <groupId>com.turn</groupId>
     <artifactId>ttorrent-client</artifactId>
-    <version>1.3.0-SNAPSHOT</version>
-  </dependency>
+    <version>1.3.0-SNAPSHOT</version> <!-- Changed to match your file -->
+</dependency>
+<dependency>
+    <groupId>com.turn</groupId>
+    <artifactId>ttorrent-common</artifactId>
+    <version>1.3.0-SNAPSHOT</version> <!-- Changed to match your file -->
+</dependency>
+<dependency>
+    <groupId>com.turn</groupId>
+    <artifactId>ttorrent-network</artifactId>
+    <version>1.0</version> <!-- Changed to match your file (1.0) -->
+</dependency>
+<dependency>
+    <groupId>com.turn</groupId>
+    <artifactId>ttorrent-tracker</artifactId>
+    <version>1.3.0-SNAPSHOT</version> <!-- Changed to match your file -->
+</dependency>
+
 </dependencies>
 
 <build>
diff --git a/backend/demo/src/main/java/com/example/demo/config/SecurityConfig.java b/backend/demo/src/main/java/com/example/demo/config/SecurityConfig.java
index b3e7f91..0c3672b 100644
--- a/backend/demo/src/main/java/com/example/demo/config/SecurityConfig.java
+++ b/backend/demo/src/main/java/com/example/demo/config/SecurityConfig.java
@@ -12,6 +12,9 @@
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.security.web.SecurityFilterChain;
 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.web.cors.CorsConfiguration; // 导入 CorsConfiguration
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource; // 导入 UrlBasedCorsConfigurationSource
+import org.springframework.web.filter.CorsFilter; // 导入 CorsFilter
 
 import com.example.demo.security.JwtAuthenticationFilter;
 
@@ -41,6 +44,22 @@
     }
 
     /**
+     * 配置 CORS 过滤器。
+     * 允许前端应用 (http://localhost:5173) 访问后端 API。
+     */
+    @Bean
+    public CorsFilter corsFilter() {
+        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+        CorsConfiguration config = new CorsConfiguration();
+        config.setAllowCredentials(true); // 允许发送 Cookie
+        config.addAllowedOrigin("http://localhost:5173"); // 允许的前端源
+        config.addAllowedHeader("*"); // 允许所有请求头
+        config.addAllowedMethod("*"); // 允许所有 HTTP 方法 (GET, POST, PUT, DELETE等)
+        source.registerCorsConfiguration("/**", config); // 对所有路径应用 CORS 配置
+        return new CorsFilter(source);
+    }
+
+    /**
      * 核心安全策略配置。
      * 配置内容包括:禁用 CSRF、禁用 Session 管理(使用 JWT 无状态)、
      * 配置请求的授权规则,并将自定义的 JWT 认证过滤器添加到过滤器链中。
@@ -55,6 +74,15 @@
                                            JwtAuthenticationFilter jwtFilter // 直接注入 JwtAuthenticationFilter Bean
                                            ) throws Exception {
         http
+            // 添加 CORS 过滤器到 Spring Security 过滤器链的开头
+            .cors(cors -> cors.configurationSource(request -> {
+                CorsConfiguration config = new CorsConfiguration();
+                config.setAllowCredentials(true);
+                config.addAllowedOrigin("http://localhost:5173");
+                config.addAllowedHeader("*");
+                config.addAllowedMethod("*");
+                return config;
+            }))
             // 禁用 CSRF (跨站请求伪造) 保护,因为 JWT 是无状态的,不需要 CSRF 保护
             .csrf(AbstractHttpConfigurer::disable)
             // 配置 Session 管理策略为无状态,不使用 Session
@@ -62,7 +90,7 @@
             // 配置请求的授权规则
             .authorizeHttpRequests(auth -> auth
                 // 允许对 /auth/** 路径下的所有请求进行匿名访问 (例如登录、注册接口)
-                .requestMatchers("/auth/**").permitAll()
+                .requestMatchers("/api/auth/**").permitAll() // 确保这里的路径与您的 AuthController 匹配
                 // 其他所有请求都需要进行身份认证
                 .anyRequest().authenticated()
             )
diff --git a/backend/demo/src/main/java/com/example/demo/config/WebConfig.java b/backend/demo/src/main/java/com/example/demo/config/WebConfig.java
new file mode 100644
index 0000000..839dce2
--- /dev/null
+++ b/backend/demo/src/main/java/com/example/demo/config/WebConfig.java
@@ -0,0 +1,19 @@
+package com.example.demo.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class WebConfig implements WebMvcConfigurer {
+
+   // @Override
+     public void addCorsMappings(CorsRegistry registry) {
+        registry.addMapping("/api/**") // Apply to your API path
+                .allowedOrigins("http://localhost:5173") // URL of your React app
+                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
+                .allowedHeaders("*")
+                .allowCredentials(true);
+    }
+
+}
\ No newline at end of file
diff --git a/backend/demo/src/main/java/com/example/demo/controller/AuthController.java b/backend/demo/src/main/java/com/example/demo/controller/AuthController.java
index d7dfe50..18f3105 100644
--- a/backend/demo/src/main/java/com/example/demo/controller/AuthController.java
+++ b/backend/demo/src/main/java/com/example/demo/controller/AuthController.java
@@ -1,17 +1,13 @@
 package com.example.demo.controller;
 
-import java.time.Instant;
-import java.util.List;
-import java.util.Map;
-
 import org.springframework.http.ResponseEntity;
-import org.springframework.security.authentication.AuthenticationManager; // 导入 AuthenticationManager
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; // 导入 UsernamePasswordAuthenticationToken
-import org.springframework.security.core.Authentication; // 导入 Authentication
-import org.springframework.security.core.context.SecurityContextHolder; // 导入 SecurityContextHolder
-import org.springframework.security.crypto.password.PasswordEncoder;
-import org.springframework.validation.BindingResult;
-import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder; // 导入 AuthenticationManager
+import org.springframework.security.crypto.password.PasswordEncoder; // 导入 UsernamePasswordAuthenticationToken
+import org.springframework.validation.BindingResult; // 导入 Authentication
+import org.springframework.web.bind.annotation.PostMapping; // 导入 SecurityContextHolder
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
@@ -45,64 +41,45 @@
     }
 
     @PostMapping("/login")
-    public ResponseEntity<?> login(
-            @Valid @RequestBody LoginRequestDTO loginRequest,
-            BindingResult bindingResult) {
+public ResponseEntity<?> login(
+        @Valid @RequestBody LoginRequestDTO loginRequest,
+        BindingResult bindingResult) {
 
-        if (bindingResult.hasErrors()) {
-            String errMsg = bindingResult.getFieldErrors().stream()
-                    .map(fe -> fe.getField() + ": " + fe.getDefaultMessage())
-                    .reduce((a, b) -> a + "; " + b)
-                    .orElse("Invalid parameters");
-            return ResponseEntity.badRequest().body(Map.of("error", errMsg));
-        }
+    if (bindingResult.hasErrors()) {
+        // … 参数校验错误的处理,这里不会走到
+    }
 
-        try {
-            // 使用 AuthenticationManager 进行认证
-            // 这会触发 UserServiceImpl 中的 loadUserByUsername 方法的调用,并比较密码
-            Authentication authentication = authenticationManager.authenticate(
-                    new UsernamePasswordAuthenticationToken(
-                            loginRequest.getUsername(),
-                            loginRequest.getPassword()
-                    )
-            );
-            // 将认证信息设置到 Spring Security 的上下文中
-            SecurityContextHolder.getContext().setAuthentication(authentication);
+    try {
+        System.out.println("login(): 开始调用 authenticationManager.authenticate");
+        Authentication authentication = authenticationManager.authenticate(
+                new UsernamePasswordAuthenticationToken(
+                        loginRequest.getUsername(),
+                        loginRequest.getPassword()
+                )
+        );
+        System.out.println("login(): authenticate 成功,principal = " 
+                           + authentication.getPrincipal());
 
-            // 获取认证后的用户详情(假设 User 实体实现了 UserDetails 接口)
-            User userDetails = (User) authentication.getPrincipal();
+        // 设置到上下文
+        SecurityContextHolder.getContext().setAuthentication(authentication);
+        System.out.println("login(): SecurityContext 设置完成");
 
-            // 调用 UserService 方法生成登录响应(包括 JWT Token)
-            LoginResponseDTO response = userService.generateLoginResponse(userDetails);
+        // 强转 principal 为 用户实体
+        User userDetails = (User) authentication.getPrincipal();
+        System.out.println("login(): 拿到 userDetails,username = " + userDetails.getUsername());
 
-            return ResponseEntity.ok(response);
-
-        } catch (Exception e) {
-            // 认证失败(用户名不存在或密码错误),抛出自定义认证异常
-            // GlobalExceptionHandler 会捕获 AuthException 并返回 401
-            throw new AuthException("用户名或密码错误", e);
-        }
-
-        // 移除原有的手动查找用户和密码比对逻辑
-        /*
-        User user = userService.lambdaQuery()
-                .eq(User::getUsername, loginRequest.getUsername())
-                .one();
-
-        if (user == null || !passwordEncoder.matches(loginRequest.getPassword(), user.getPassword())) {
-            throw new AuthException("用户名或密码错误");
-        }
-
-        String token = jwtTokenUtil.generateToken(user.getId(), user.getUsername(), user.getRole());
-
-        LoginResponseDTO response = new LoginResponseDTO();
-        response.setToken(token);
-        response.setExpiresAt(Instant.now().plusMillis(jwtTokenUtil.getExpiration()));
-        response.setUserId(user.getId());
-        response.setUsername(user.getUsername());
-        response.setRoles(List.of(user.getRole()));
+        // 调用生成 JWT 的方法
+        System.out.println("login(): 调用 userService.generateLoginResponse");
+        LoginResponseDTO response = userService.generateLoginResponse(userDetails);
+        System.out.println("login(): generateLoginResponse 返回 token = " + response.getToken());
 
         return ResponseEntity.ok(response);
-        */
+
+    } catch (Exception e) {
+        System.out.println("login(): 捕获到异常: " + e.getClass().getSimpleName() + " - " + e.getMessage());
+        e.printStackTrace();
+        throw new AuthException("用户名或密码错误", e);
     }
+}
+
 }
\ No newline at end of file
diff --git a/backend/demo/src/main/java/com/example/demo/controller/TorrentController.java b/backend/demo/src/main/java/com/example/demo/controller/TorrentController.java
index 0bd501a..6a91d27 100644
--- a/backend/demo/src/main/java/com/example/demo/controller/TorrentController.java
+++ b/backend/demo/src/main/java/com/example/demo/controller/TorrentController.java
@@ -1,16 +1,17 @@
 // src/main/java/com/example/demo/controller/TorrentController.java
 package com.example.demo.controller;
 
-import java.io.IOException; // 导入 DTO
-import java.nio.file.Path; // 导入服务接口
-import java.nio.file.Paths; // 导入 ttorrent 的异常
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 
-import org.springframework.beans.factory.annotation.Value; // 导入 @Value 用于读取配置
-import org.springframework.core.io.Resource; // 导入 Resource
-import org.springframework.core.io.UrlResource; // 导入 UrlResource
-import org.springframework.http.HttpHeaders; // 导入 HttpHeaders
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.UrlResource;
+import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpStatus;
-import org.springframework.http.MediaType; // 导入 MediaType
+import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
@@ -27,38 +28,37 @@
 /**
  * 处理 .torrent 文件上传和下载的 RESTful 控制器。
  */
-@RestController // 组合了 @Controller 和 @ResponseBody,表示这是一个 RESTful 控制器
-@RequestMapping("/api") // 定义所有处理方法的根路径
+@RestController
+@RequestMapping("/api")
 public class TorrentController {
 
     private final TorrentService torrentService;
 
-    @Value("${file.upload-dir}") // 从 application.properties 中注入文件上传目录
+    @Value("${file.upload-dir}")
     private String uploadDir;
 
-    // 通过构造函数注入 TorrentService
     public TorrentController(TorrentService torrentService) {
         this.torrentService = torrentService;
     }
 
     /**
-     * 处理 .torrent 文件的上传请求。
+     * 上传 .torrent 文件并处理其元信息。
      *
-     * @param file 上传的 MultipartFile 对象,通过 @RequestParam("file") 绑定。
-     * @return 包含 TorrentInfoDTO 的 ResponseEntity,表示操作结果。
+     * @param file  上传的 .torrent 文件
+     * @param title 用户指定的标题
+     * @return 处理结果
      */
-    @PostMapping("/torrents") // 映射到 /api/torrents 的 POST 请求
-    public ResponseEntity<TorrentInfoDTO> uploadTorrent(@RequestParam("file") MultipartFile file) {
-        // 1. 接收 HTTP 请求 (通过 @PostMapping 和 @RequestParam)
+    @PostMapping("/torrents/upload")
+    public ResponseEntity<TorrentInfoDTO> uploadTorrent(
+            @RequestParam("file") MultipartFile file,
+            @RequestParam("title") String title) {
 
-        // 2. 校验参数
         if (file.isEmpty()) {
             TorrentInfoDTO errorDto = new TorrentInfoDTO();
             errorDto.setMessage("上传文件不能为空。");
             return new ResponseEntity<>(errorDto, HttpStatus.BAD_REQUEST);
         }
 
-        // 校验文件类型 (可选,但推荐)
         if (!file.getOriginalFilename().toLowerCase().endsWith(".torrent")) {
             TorrentInfoDTO errorDto = new TorrentInfoDTO();
             errorDto.setMessage("只允许上传 .torrent 文件。");
@@ -66,65 +66,66 @@
         }
 
         try {
-            // 3. 调用 Service 层处理业务逻辑
-            TorrentInfoDTO result = torrentService.handleUpload(file);
-
-            // 4. 返回 TorrentInfoDTO 给前端
+            TorrentInfoDTO result = torrentService.handleUpload(file, title);
             return new ResponseEntity<>(result, HttpStatus.OK);
         } catch (InvalidBEncodingException e) {
-            // 捕获 .torrent 文件解析错误
             TorrentInfoDTO errorDto = new TorrentInfoDTO();
             errorDto.setMessage("文件解析失败,请确保它是有效的 .torrent 文件: " + e.getMessage());
             return new ResponseEntity<>(errorDto, HttpStatus.BAD_REQUEST);
         } catch (IllegalArgumentException e) {
-            // 捕获服务层抛出的非法参数异常 (如文件为空)
             TorrentInfoDTO errorDto = new TorrentInfoDTO();
             errorDto.setMessage("处理文件时发生错误: " + e.getMessage());
             return new ResponseEntity<>(errorDto, HttpStatus.BAD_REQUEST);
         } catch (IOException e) {
-            // 捕获文件读写或保存失败的异常
             TorrentInfoDTO errorDto = new TorrentInfoDTO();
             errorDto.setMessage("文件上传或保存失败: " + e.getMessage());
             return new ResponseEntity<>(errorDto, HttpStatus.INTERNAL_SERVER_ERROR);
         } catch (Exception e) {
-            // 捕获其他未知异常
             TorrentInfoDTO errorDto = new TorrentInfoDTO();
             errorDto.setMessage("文件上传过程中发生未知错误: " + e.getMessage());
-            e.printStackTrace(); // 打印堆栈跟踪以便调试
+            e.printStackTrace();
             return new ResponseEntity<>(errorDto, HttpStatus.INTERNAL_SERVER_ERROR);
         }
     }
 
     /**
-     * 实现 .torrent 文件的下载功能。
-     * 根据 infoHash 找到对应的 .torrent 文件并提供下载。
+     * 下载已上传的 .torrent 文件。
      *
-     * @param infoHash 洪流的 info hash,用于查找服务器上的文件。
-     * @param fileName 原始文件名 (用于设置下载时的文件名,实际查找文件仍依赖 infoHash)。
-     * @return 包含 .torrent 文件内容的 ResponseEntity。
+     * @param infoHash  该文件的 info hash,用于定位文件
+     * @param fileName  用户下载时看到的文件名
+     * @return ResponseEntity 包含下载资源
      */
-   // 修正后的 downloadTorrent 方法片段
-@GetMapping("/downloads/{infoHash}/{fileName}")
-public ResponseEntity<Resource> downloadTorrent(@PathVariable String infoHash, @PathVariable String fileName) {
-    try {
-        Path fileStorageLocation = Paths.get(uploadDir).toAbsolutePath().normalize();
-        Path filePath = fileStorageLocation.resolve(infoHash + ".torrent").normalize();
-        Resource resource = new UrlResource(filePath.toUri()); // 这一行不会抛出 MalformedURLException
-
-        if (resource.exists() && resource.isReadable()) {
-            String contentType = "application/x-bittorrent";
-            String headerValue = "attachment; filename=\"" + fileName + "\"";
-
-            return ResponseEntity.ok()
-                    .contentType(MediaType.parseMediaType(contentType))
-                    .header(HttpHeaders.CONTENT_DISPOSITION, headerValue)
-                    .body(resource);
-        } else {
-            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+     // —— 2. 启动下载 —— //
+    @PostMapping("/download")
+    public ResponseEntity<Void> download(@RequestParam("infoHash") String infoHash) {
+        try {
+            torrentService.startDownload(infoHash);
+            return ResponseEntity.accepted().build();
+        } catch (Exception e) {
+            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
         }
-    } catch (IOException e) { // 只保留 IOException
-        return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
     }
-    // MalformedURLException 相关的 catch 块已移除
+
+    // —— 3. 查询下载进度 —— //
+    @GetMapping("/download/status/{infoHash}")
+    public ResponseEntity<Float> status(@PathVariable String infoHash) {
+        float progress = torrentService.getDownloadProgress(infoHash);
+        return ResponseEntity.ok(progress);
+    }
+
+    // —— 4. 获取下载后的文件路径 —— //
+    // 如果你想让浏览器直接触发下载,也可以重定向或返回文件流
+    @GetMapping("/download/file/{infoHash}")
+    public ResponseEntity<Resource> file(@PathVariable String infoHash) throws MalformedURLException {
+        String path = torrentService.getDownloadedFilePath(infoHash);
+        if (path == null) {
+            return ResponseEntity.notFound().build();
+        }
+        UrlResource resource = new UrlResource("file:" + path);
+        return ResponseEntity.ok()
+            .header(HttpHeaders.CONTENT_DISPOSITION,
+                    "attachment; filename=\"" + Paths.get(path).getFileName() + "\"")
+            .body(resource);
+    }
+    
 }
-}
\ No newline at end of file
diff --git a/backend/demo/src/main/java/com/example/demo/result.txt b/backend/demo/src/main/java/com/example/demo/result.txt
index bd1dd30..d7cdef4 100644
--- a/backend/demo/src/main/java/com/example/demo/result.txt
+++ b/backend/demo/src/main/java/com/example/demo/result.txt
@@ -110,6 +110,9 @@
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.security.web.SecurityFilterChain;
 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.web.cors.CorsConfiguration; // 导入 CorsConfiguration
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource; // 导入 UrlBasedCorsConfigurationSource
+import org.springframework.web.filter.CorsFilter; // 导入 CorsFilter
 
 import com.example.demo.security.JwtAuthenticationFilter;
 
@@ -139,6 +142,22 @@
     }
 
     /**
+     * 配置 CORS 过滤器。
+     * 允许前端应用 (http://localhost:5173) 访问后端 API。
+     */
+    @Bean
+    public CorsFilter corsFilter() {
+        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+        CorsConfiguration config = new CorsConfiguration();
+        config.setAllowCredentials(true); // 允许发送 Cookie
+        config.addAllowedOrigin("http://localhost:5173"); // 允许的前端源
+        config.addAllowedHeader("*"); // 允许所有请求头
+        config.addAllowedMethod("*"); // 允许所有 HTTP 方法 (GET, POST, PUT, DELETE等)
+        source.registerCorsConfiguration("/**", config); // 对所有路径应用 CORS 配置
+        return new CorsFilter(source);
+    }
+
+    /**
      * 核心安全策略配置。
      * 配置内容包括:禁用 CSRF、禁用 Session 管理(使用 JWT 无状态)、
      * 配置请求的授权规则,并将自定义的 JWT 认证过滤器添加到过滤器链中。
@@ -153,6 +172,15 @@
                                            JwtAuthenticationFilter jwtFilter // 直接注入 JwtAuthenticationFilter Bean
                                            ) throws Exception {
         http
+            // 添加 CORS 过滤器到 Spring Security 过滤器链的开头
+            .cors(cors -> cors.configurationSource(request -> {
+                CorsConfiguration config = new CorsConfiguration();
+                config.setAllowCredentials(true);
+                config.addAllowedOrigin("http://localhost:5173");
+                config.addAllowedHeader("*");
+                config.addAllowedMethod("*");
+                return config;
+            }))
             // 禁用 CSRF (跨站请求伪造) 保护,因为 JWT 是无状态的,不需要 CSRF 保护
             .csrf(AbstractHttpConfigurer::disable)
             // 配置 Session 管理策略为无状态,不使用 Session
@@ -160,7 +188,7 @@
             // 配置请求的授权规则
             .authorizeHttpRequests(auth -> auth
                 // 允许对 /auth/** 路径下的所有请求进行匿名访问 (例如登录、注册接口)
-                .requestMatchers("/auth/**").permitAll()
+                .requestMatchers("/api/auth/**").permitAll() // 确保这里的路径与您的 AuthController 匹配
                 // 其他所有请求都需要进行身份认证
                 .anyRequest().authenticated()
             )
@@ -185,18 +213,14 @@
 --- Content of: ./controller/AuthController.java ---
 package com.example.demo.controller;
 
-import java.time.Instant;
-import java.util.List;
-import java.util.Map;
-
 import org.springframework.http.ResponseEntity;
-import org.springframework.security.authentication.AuthenticationManager; // 导入 AuthenticationManager
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; // 导入 UsernamePasswordAuthenticationToken
-import org.springframework.security.core.Authentication; // 导入 Authentication
-import org.springframework.security.core.context.SecurityContextHolder; // 导入 SecurityContextHolder
-import org.springframework.security.crypto.password.PasswordEncoder;
-import org.springframework.validation.BindingResult;
-import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder; // 导入 AuthenticationManager
+import org.springframework.security.crypto.password.PasswordEncoder; // 导入 UsernamePasswordAuthenticationToken
+import org.springframework.validation.BindingResult; // 导入 Authentication
+import org.springframework.web.bind.annotation.PostMapping; // 导入 SecurityContextHolder
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
@@ -230,68 +254,175 @@
     }
 
     @PostMapping("/login")
-    public ResponseEntity<?> login(
-            @Valid @RequestBody LoginRequestDTO loginRequest,
-            BindingResult bindingResult) {
+public ResponseEntity<?> login(
+        @Valid @RequestBody LoginRequestDTO loginRequest,
+        BindingResult bindingResult) {
 
-        if (bindingResult.hasErrors()) {
-            String errMsg = bindingResult.getFieldErrors().stream()
-                    .map(fe -> fe.getField() + ": " + fe.getDefaultMessage())
-                    .reduce((a, b) -> a + "; " + b)
-                    .orElse("Invalid parameters");
-            return ResponseEntity.badRequest().body(Map.of("error", errMsg));
+    if (bindingResult.hasErrors()) {
+        // … 参数校验错误的处理,这里不会走到
+    }
+
+    try {
+        System.out.println("login(): 开始调用 authenticationManager.authenticate");
+        Authentication authentication = authenticationManager.authenticate(
+                new UsernamePasswordAuthenticationToken(
+                        loginRequest.getUsername(),
+                        loginRequest.getPassword()
+                )
+        );
+        System.out.println("login(): authenticate 成功,principal = " 
+                           + authentication.getPrincipal());
+
+        // 设置到上下文
+        SecurityContextHolder.getContext().setAuthentication(authentication);
+        System.out.println("login(): SecurityContext 设置完成");
+
+        // 强转 principal 为 用户实体
+        User userDetails = (User) authentication.getPrincipal();
+        System.out.println("login(): 拿到 userDetails,username = " + userDetails.getUsername());
+
+        // 调用生成 JWT 的方法
+        System.out.println("login(): 调用 userService.generateLoginResponse");
+        LoginResponseDTO response = userService.generateLoginResponse(userDetails);
+        System.out.println("login(): generateLoginResponse 返回 token = " + response.getToken());
+
+        return ResponseEntity.ok(response);
+
+    } catch (Exception e) {
+        System.out.println("login(): 捕获到异常: " + e.getClass().getSimpleName() + " - " + e.getMessage());
+        e.printStackTrace();
+        throw new AuthException("用户名或密码错误", e);
+    }
+}
+
+}
+--- End of: ./controller/AuthController.java ---
+
+--- Content of: ./controller/TorrentController.java ---
+// src/main/java/com/example/demo/controller/TorrentController.java
+package com.example.demo.controller;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.UrlResource;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import com.example.demo.dto.TorrentInfoDTO;
+import com.example.demo.service.TorrentService;
+import com.turn.ttorrent.bcodec.InvalidBEncodingException;
+
+/**
+ * 处理 .torrent 文件上传和下载的 RESTful 控制器。
+ */
+@RestController
+@RequestMapping("/api")
+public class TorrentController {
+
+    private final TorrentService torrentService;
+
+    @Value("${file.upload-dir}")
+    private String uploadDir;
+
+    public TorrentController(TorrentService torrentService) {
+        this.torrentService = torrentService;
+    }
+
+    /**
+     * 上传 .torrent 文件并处理其元信息。
+     *
+     * @param file  上传的 .torrent 文件
+     * @param title 用户指定的标题
+     * @return 处理结果
+     */
+    @PostMapping("/torrents/upload")
+    public ResponseEntity<TorrentInfoDTO> uploadTorrent(
+            @RequestParam("file") MultipartFile file,
+            @RequestParam("title") String title) {
+
+        if (file.isEmpty()) {
+            TorrentInfoDTO errorDto = new TorrentInfoDTO();
+            errorDto.setMessage("上传文件不能为空。");
+            return new ResponseEntity<>(errorDto, HttpStatus.BAD_REQUEST);
+        }
+
+        if (!file.getOriginalFilename().toLowerCase().endsWith(".torrent")) {
+            TorrentInfoDTO errorDto = new TorrentInfoDTO();
+            errorDto.setMessage("只允许上传 .torrent 文件。");
+            return new ResponseEntity<>(errorDto, HttpStatus.UNSUPPORTED_MEDIA_TYPE);
         }
 
         try {
-            // 使用 AuthenticationManager 进行认证
-            // 这会触发 UserServiceImpl 中的 loadUserByUsername 方法的调用,并比较密码
-            Authentication authentication = authenticationManager.authenticate(
-                    new UsernamePasswordAuthenticationToken(
-                            loginRequest.getUsername(),
-                            loginRequest.getPassword()
-                    )
-            );
-            // 将认证信息设置到 Spring Security 的上下文中
-            SecurityContextHolder.getContext().setAuthentication(authentication);
-
-            // 获取认证后的用户详情(假设 User 实体实现了 UserDetails 接口)
-            User userDetails = (User) authentication.getPrincipal();
-
-            // 调用 UserService 方法生成登录响应(包括 JWT Token)
-            LoginResponseDTO response = userService.generateLoginResponse(userDetails);
-
-            return ResponseEntity.ok(response);
-
+            TorrentInfoDTO result = torrentService.handleUpload(file, title);
+            return new ResponseEntity<>(result, HttpStatus.OK);
+        } catch (InvalidBEncodingException e) {
+            TorrentInfoDTO errorDto = new TorrentInfoDTO();
+            errorDto.setMessage("文件解析失败,请确保它是有效的 .torrent 文件: " + e.getMessage());
+            return new ResponseEntity<>(errorDto, HttpStatus.BAD_REQUEST);
+        } catch (IllegalArgumentException e) {
+            TorrentInfoDTO errorDto = new TorrentInfoDTO();
+            errorDto.setMessage("处理文件时发生错误: " + e.getMessage());
+            return new ResponseEntity<>(errorDto, HttpStatus.BAD_REQUEST);
+        } catch (IOException e) {
+            TorrentInfoDTO errorDto = new TorrentInfoDTO();
+            errorDto.setMessage("文件上传或保存失败: " + e.getMessage());
+            return new ResponseEntity<>(errorDto, HttpStatus.INTERNAL_SERVER_ERROR);
         } catch (Exception e) {
-            // 认证失败(用户名不存在或密码错误),抛出自定义认证异常
-            // GlobalExceptionHandler 会捕获 AuthException 并返回 401
-            throw new AuthException("用户名或密码错误", e);
+            TorrentInfoDTO errorDto = new TorrentInfoDTO();
+            errorDto.setMessage("文件上传过程中发生未知错误: " + e.getMessage());
+            e.printStackTrace();
+            return new ResponseEntity<>(errorDto, HttpStatus.INTERNAL_SERVER_ERROR);
         }
+    }
 
-        // 移除原有的手动查找用户和密码比对逻辑
-        /*
-        User user = userService.lambdaQuery()
-                .eq(User::getUsername, loginRequest.getUsername())
-                .one();
+    /**
+     * 下载已上传的 .torrent 文件。
+     *
+     * @param infoHash  该文件的 info hash,用于定位文件
+     * @param fileName  用户下载时看到的文件名
+     * @return ResponseEntity 包含下载资源
+     */
+    @GetMapping("/downloads/{infoHash}/{fileName}")
+    public ResponseEntity<Resource> downloadTorrent(
+            @PathVariable String infoHash,
+            @PathVariable String fileName) {
 
-        if (user == null || !passwordEncoder.matches(loginRequest.getPassword(), user.getPassword())) {
-            throw new AuthException("用户名或密码错误");
+        try {
+            Path fileStorageLocation = Paths.get(uploadDir).toAbsolutePath().normalize();
+            Path filePath = fileStorageLocation.resolve(infoHash + ".torrent").normalize();
+            Resource resource = new UrlResource(filePath.toUri());
+
+            if (resource.exists() && resource.isReadable()) {
+                String contentType = "application/x-bittorrent";
+                String headerValue = "attachment; filename=\"" + fileName + "\"";
+
+                return ResponseEntity.ok()
+                        .contentType(MediaType.parseMediaType(contentType))
+                        .header(HttpHeaders.CONTENT_DISPOSITION, headerValue)
+                        .body(resource);
+            } else {
+                return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+            }
+        } catch (IOException e) {
+            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
         }
-
-        String token = jwtTokenUtil.generateToken(user.getId(), user.getUsername(), user.getRole());
-
-        LoginResponseDTO response = new LoginResponseDTO();
-        response.setToken(token);
-        response.setExpiresAt(Instant.now().plusMillis(jwtTokenUtil.getExpiration()));
-        response.setUserId(user.getId());
-        response.setUsername(user.getUsername());
-        response.setRoles(List.of(user.getRole()));
-
-        return ResponseEntity.ok(response);
-        */
     }
 }
---- End of: ./controller/AuthController.java ---
+
+--- End of: ./controller/TorrentController.java ---
 
 --- Content of: ./DemoApplication.java ---
 package com.example.demo;
@@ -414,6 +545,96 @@
 
 --- End of: ./dto/LoginResponseDTO.java ---
 
+--- Content of: ./dto/TorrentInfoDTO.java ---
+// src/main/java/com/example/demo/dto/TorrentInfoDTO.java
+package com.example.demo.dto;
+
+import java.io.Serializable;
+
+public class TorrentInfoDTO implements Serializable {
+    private String fileName;
+    private long fileSize;
+    private String infoHash;
+    private String magnetUri;
+    private String downloadUrl;
+    private String message; // For success or error messages
+
+    // Constructors
+    public TorrentInfoDTO() {
+    }
+
+    public TorrentInfoDTO(String fileName, long fileSize, String infoHash, String magnetUri, String downloadUrl, String message) {
+        this.fileName = fileName;
+        this.fileSize = fileSize;
+        this.infoHash = infoHash;
+        this.magnetUri = magnetUri;
+        this.downloadUrl = downloadUrl;
+        this.message = message;
+    }
+
+    // Getters and Setters
+    public String getFileName() {
+        return fileName;
+    }
+
+    public void setFileName(String fileName) {
+        this.fileName = fileName;
+    }
+
+    public long getFileSize() {
+        return fileSize;
+    }
+
+    public void setFileSize(long fileSize) {
+        this.fileSize = fileSize;
+    }
+
+    public String getInfoHash() {
+        return infoHash;
+    }
+
+    public void setInfoHash(String infoHash) {
+        this.infoHash = infoHash;
+    }
+
+    public String getMagnetUri() {
+        return magnetUri;
+    }
+
+    public void setMagnetUri(String magnetUri) {
+        this.magnetUri = magnetUri;
+    }
+
+    public String getDownloadUrl() {
+        return downloadUrl;
+    }
+
+    public void setDownloadUrl(String downloadUrl) {
+        this.downloadUrl = downloadUrl;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    @Override
+    public String toString() {
+        return "TorrentInfoDTO{" +
+               "fileName='" + fileName + '\'' +
+               ", fileSize=" + fileSize +
+               ", infoHash='" + infoHash + '\'' +
+               ", magnetUri='" + magnetUri + '\'' +
+               ", downloadUrl='" + downloadUrl + '\'' +
+               ", message='" + message + '\'' +
+               '}';
+    }
+}
+--- End of: ./dto/TorrentInfoDTO.java ---
+
 --- Content of: ./entity/User.java ---
 package com.example.demo.entity;
 
@@ -579,6 +800,127 @@
 
 --- End of: ./entity/User.java ---
 
+--- Content of: ./entity/TorrentInfo.java ---
+// src/main/java/com/example/demo/entity/TorrentInfo.java
+package com.example.demo.entity;
+
+import java.time.LocalDateTime;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+
+@TableName("torrent_info") // 映射到数据库中的 torrent_info 表
+public class TorrentInfo {
+
+    @TableId(value = "id", type = IdType.AUTO) // 标记为主键,并设置为数据库自增
+    private Long id;
+
+    @TableField("info_hash") // 映射到数据库中的 info_hash 列
+    private String infoHash; // 洪流的唯一标识符(信息哈希)
+
+    @TableField("file_name") // 映射到数据库中的 file_name 列
+    private String fileName; // 原始文件名
+
+    @TableField("file_size") // 映射到数据库中的 file_size 列
+    private Long fileSize; // 文件大小(字节)
+
+    @TableField("magnet_uri") // 映射到数据库中的 magnet_uri 列
+    private String magnetUri; // 生成的磁力链接
+
+    @TableField("download_url") // 映射到数据库中的 download_url 列
+    private String downloadUrl; // .torrent 文件的下载 URL
+
+    @TableField("upload_time") // 映射到数据库中的 upload_time 列
+    private LocalDateTime uploadTime; // 上传时间
+
+    // 默认构造函数,MyBatis-Plus 需要
+    public TorrentInfo() {
+    }
+
+    // 带有所有字段的构造函数(不包含 ID 和 uploadTime,因为它们是自动生成的)
+    public TorrentInfo(String infoHash, String fileName, Long fileSize, String magnetUri, String downloadUrl) {
+        this.infoHash = infoHash;
+        this.fileName = fileName;
+        this.fileSize = fileSize;
+        this.magnetUri = magnetUri;
+        this.downloadUrl = downloadUrl;
+        this.uploadTime = LocalDateTime.now(); // 设置当前上传时间
+    }
+
+    // Getters and Setters
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getInfoHash() {
+        return infoHash;
+    }
+
+    public void setInfoHash(String infoHash) {
+        this.infoHash = infoHash;
+    }
+
+    public String getFileName() {
+        return fileName;
+    }
+
+    public void setFileName(String fileName) {
+        this.fileName = fileName;
+    }
+
+    public Long getFileSize() {
+        return fileSize;
+    }
+
+    public void setFileSize(Long fileSize) {
+        this.fileSize = fileSize;
+    }
+
+    public String getMagnetUri() {
+        return magnetUri;
+    }
+
+    public void setMagnetUri(String magnetUri) {
+        this.magnetUri = magnetUri;
+    }
+
+    public String getDownloadUrl() {
+        return downloadUrl;
+    }
+
+    public void setDownloadUrl(String downloadUrl) {
+        this.downloadUrl = downloadUrl;
+    }
+
+    public LocalDateTime getUploadTime() {
+        return uploadTime;
+    }
+
+    public void setUploadTime(LocalDateTime uploadTime) {
+        this.uploadTime = uploadTime;
+    }
+
+    @Override
+    public String toString() {
+        return "TorrentInfo{" +
+               "id=" + id +
+               ", infoHash='" + infoHash + '\'' +
+               ", fileName='" + fileName + '\'' +
+               ", fileSize=" + fileSize +
+               ", magnetUri='" + magnetUri + '\'' +
+               ", downloadUrl='" + downloadUrl + '\'' +
+               ", uploadTime=" + uploadTime +
+               '}';
+    }
+}
+--- End of: ./entity/TorrentInfo.java ---
+
 --- Content of: ./exception/AuthException.java ---
 package com.example.demo.exception;
 
@@ -666,15 +1008,31 @@
 
 --- End of: ./mapper/UserMapper.java ---
 
+--- Content of: ./mapper/TorrentInfoMapper.java ---
+// src/main/java/com/example/demo/mapper/TorrentInfoMapper.java
+package com.example.demo.mapper;
+
+import org.apache.ibatis.annotations.Mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.example.demo.entity.TorrentInfo; // MyBatis 的 Mapper 注解
+
+@Mapper // 标记这是一个 MyBatis Mapper 接口
+public interface TorrentInfoMapper extends BaseMapper<TorrentInfo> {
+    // BaseMapper 提供了基本的 CRUD 操作,例如 insert(), selectById(), selectList(), deleteById() 等。
+    // 无需编写任何实现代码。
+}
+--- End of: ./mapper/TorrentInfoMapper.java ---
+
 --- Content of: ./security/JwtAuthenticationFilter.java ---
 package com.example.demo.security;
 
 import java.io.IOException;
-import java.util.List;
 
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
 import org.springframework.stereotype.Component;
 import org.springframework.util.StringUtils;
 import org.springframework.web.filter.OncePerRequestFilter;
@@ -682,20 +1040,23 @@
 import com.example.demo.config.JWTProperties;
 import com.example.demo.exception.AuthException;
 
-import io.jsonwebtoken.Claims;
 import jakarta.servlet.FilterChain;
 import jakarta.servlet.ServletException;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
-@Component 
+@Component
 public class JwtAuthenticationFilter extends OncePerRequestFilter {
 
     private final JwtTokenUtil tokenUtil;
     private final JWTProperties props;
+    private final UserDetailsService userDetailsService; // ✅ 注入接口
 
-    public JwtAuthenticationFilter(JwtTokenUtil tokenUtil, JWTProperties props) {
+    public JwtAuthenticationFilter(JwtTokenUtil tokenUtil,
+                                   JWTProperties props,
+                                   UserDetailsService userDetailsService) {
         this.tokenUtil = tokenUtil;
         this.props = props;
+        this.userDetailsService = userDetailsService;
     }
 
     @Override
@@ -715,16 +1076,16 @@
             throw new AuthException("Invalid or expired JWT token");
         }
 
-        Claims claims = tokenUtil.parseClaims(token);
-        String username =tokenUtil.getUsername(token);
-        Long userId = tokenUtil.getUserId(token);
-        String role = tokenUtil.getRole(token);
+        String username = tokenUtil.getUsername(token);
+
+        // ✅ 使用 UserServiceImpl 加载完整的用户对象(包括权限信息)
+        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
 
         UsernamePasswordAuthenticationToken auth =
             new UsernamePasswordAuthenticationToken(
-                userId, 
-                null, 
-                List.of(new SimpleGrantedAuthority(role))
+                userDetails, // ✅ 设置完整的用户对象
+                null,
+                userDetails.getAuthorities()
             );
         SecurityContextHolder.getContext().setAuthentication(auth);
 
@@ -875,26 +1236,15 @@
 import com.example.demo.service.UserService;
 
 
-/**
- * 用户业务逻辑服务实现类
- * 继承了MyBatis-Plus的ServiceImpl,并实现了UserService接口和Spring Security的UserDetailsService接口。
- * 利用ServiceImpl提供的通用CRUD方法,并实现自定义的用户业务操作和加载用户详情。
- * 用户登录认证逻辑已移至 Controller 层,以解决循环依赖问题。
- */
+
 @Service
-// 继承ServiceImpl,指定Mapper和实体类型。
-// 这样就自动拥有了IService中大部分通用方法的实现。
+
 public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService, UserDetailsService {
 
-    // 当继承ServiceImpl后,ServiceImpl内部已经注入了BaseMapper,
-    // 通常不需要在这里再次声明UserMapper,除非您需要调用UserMapper中自定义的非BaseMapper方法。
-    // private final UserMapper userMapper; // 如果继承ServiceImpl,这行通常可以移除
 
-    // 注入密码编码器,用于密码加密和校验
+
     private final PasswordEncoder passwordEncoder;
-    // 移除 AuthenticationManager 注入,认证逻辑已移至 Controller
-    // private final AuthenticationManager authenticationManager;
-    // 注入JWT工具类,用于生成和解析Token
+
     private final JwtTokenUtil jwtTokenUtil;
 
 
@@ -902,12 +1252,12 @@
      * 构造函数注入非BaseMapper的依赖
      * ServiceImpl会自动注入BaseMapper
      * @param passwordEncoder 密码编码器
-     * // 移除 authenticationManager 参数
+     * 
      * @param jwtTokenUtil JWT工具类
      */
-    // @Autowired // 当只有一个构造函数时,@Autowired 可选
+     
     public UserServiceImpl(PasswordEncoder passwordEncoder,
-                           // AuthenticationManager authenticationManager, // 移除此参数
+                            // 移除此参数
                            JwtTokenUtil jwtTokenUtil) {
         // 当继承ServiceImpl时,不需要在构造函数中注入并初始化BaseMapper
         // this.userMapper = userMapper; // 移除这行
@@ -1008,22 +1358,6 @@
 
         return response;
     }
-
-
-    // 当继承ServiceImpl后,IService中的通用方法(如 save, updateById, list, page 等)
-    // 已经由ServiceImpl提供默认实现,无需在此重复编写。
-    // 如果您需要对IService中的某个通用方法进行定制(例如在保存前进行额外处理),
-    // 可以在这里@Override该方法并添加您的逻辑,然后调用super.方法名(...)来执行ServiceImpl的默认逻辑。
-    /*
-    @Override
-    public boolean saveBatch(Collection<User> entityList, int batchSize) {
-        // 在调用ServiceImpl的批量保存前/后添加自定义逻辑
-        // ...
-        return super.saveBatch(entityList, batchSize); // 调用ServiceImpl的默认实现
-    }
-
-    // ... 其他IService中的方法实现
-    */
 }
 
 --- End of: ./service/impl/UserServiceImpl.java ---
diff --git a/backend/demo/src/main/java/com/example/demo/security/JwtAuthenticationFilter.java b/backend/demo/src/main/java/com/example/demo/security/JwtAuthenticationFilter.java
index 520908c..d69361f 100644
--- a/backend/demo/src/main/java/com/example/demo/security/JwtAuthenticationFilter.java
+++ b/backend/demo/src/main/java/com/example/demo/security/JwtAuthenticationFilter.java
@@ -1,11 +1,11 @@
 package com.example.demo.security;
 
 import java.io.IOException;
-import java.util.List;
 
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
 import org.springframework.stereotype.Component;
 import org.springframework.util.StringUtils;
 import org.springframework.web.filter.OncePerRequestFilter;
@@ -13,20 +13,23 @@
 import com.example.demo.config.JWTProperties;
 import com.example.demo.exception.AuthException;
 
-import io.jsonwebtoken.Claims;
 import jakarta.servlet.FilterChain;
 import jakarta.servlet.ServletException;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
-@Component 
+@Component
 public class JwtAuthenticationFilter extends OncePerRequestFilter {
 
     private final JwtTokenUtil tokenUtil;
     private final JWTProperties props;
+    private final UserDetailsService userDetailsService; // ✅ 注入接口
 
-    public JwtAuthenticationFilter(JwtTokenUtil tokenUtil, JWTProperties props) {
+    public JwtAuthenticationFilter(JwtTokenUtil tokenUtil,
+                                   JWTProperties props,
+                                   UserDetailsService userDetailsService) {
         this.tokenUtil = tokenUtil;
         this.props = props;
+        this.userDetailsService = userDetailsService;
     }
 
     @Override
@@ -46,16 +49,16 @@
             throw new AuthException("Invalid or expired JWT token");
         }
 
-        Claims claims = tokenUtil.parseClaims(token);
-        String username =tokenUtil.getUsername(token);
-        Long userId = tokenUtil.getUserId(token);
-        String role = tokenUtil.getRole(token);
+        String username = tokenUtil.getUsername(token);
+
+        // ✅ 使用 UserServiceImpl 加载完整的用户对象(包括权限信息)
+        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
 
         UsernamePasswordAuthenticationToken auth =
             new UsernamePasswordAuthenticationToken(
-                userId, 
-                null, 
-                List.of(new SimpleGrantedAuthority(role))
+                userDetails, // ✅ 设置完整的用户对象
+                null,
+                userDetails.getAuthorities()
             );
         SecurityContextHolder.getContext().setAuthentication(auth);
 
diff --git a/backend/demo/src/main/java/com/example/demo/service/TorrentService.java b/backend/demo/src/main/java/com/example/demo/service/TorrentService.java
index b863798..2be7aad 100644
--- a/backend/demo/src/main/java/com/example/demo/service/TorrentService.java
+++ b/backend/demo/src/main/java/com/example/demo/service/TorrentService.java
@@ -1,4 +1,3 @@
-// src/main/java/com/example/demo/service/TorrentService.java
 package com.example.demo.service;
 
 import java.io.IOException;
@@ -17,10 +16,35 @@
      * 处理上传的 .torrent 文件,解析其内容,保存到数据库,并生成相关链接。
      *
      * @param file 上传的 MultipartFile 对象
+     * @param title 洪流的标题
      * @return 包含解析信息、磁力链接和下载 URL 的 TorrentInfoDTO
      * @throws IOException 如果文件读取或保存失败
      * @throws InvalidBEncodingException 如果 .torrent 文件内容不符合 B 编码规范
      * @throws IllegalArgumentException 如果文件为空或解析后的元数据不完整
      */
-    TorrentInfoDTO handleUpload(MultipartFile file) throws IOException, InvalidBEncodingException, IllegalArgumentException;
+    TorrentInfoDTO handleUpload(MultipartFile file, String title) throws IOException, InvalidBEncodingException, IllegalArgumentException;
+
+   /**
+ * 根据 infoHash 启动与 Tracker 联动的下载任务(异步执行)。
+ *
+ * @param infoHash 种子的 infoHash(十六进制字符串)
+ * @throws Exception 启动任务失败时抛出
+ */
+void startDownload(String infoHash) throws Exception;
+
+/**
+ * 查询指定 infoHash 的当前下载进度,返回值范围 0.0 到 1.0。
+ *
+ * @param infoHash 种子的 infoHash
+ * @return 下载完成比例(0.0–1.0)
+ */
+float getDownloadProgress(String infoHash);
+
+/**
+ * 下载完成后,获取本地保存的文件路径;若未完成或未启动下载,则返回 null。
+ *
+ * @param infoHash 种子的 infoHash
+ * @return 已下载文件的绝对路径或 null
+ */
+String getDownloadedFilePath(String infoHash);
 }
\ No newline at end of file
diff --git a/backend/demo/src/main/java/com/example/demo/service/impl/TorrentServiceImpl.java b/backend/demo/src/main/java/com/example/demo/service/impl/TorrentServiceImpl.java
index eb00160..eedad56 100644
--- a/backend/demo/src/main/java/com/example/demo/service/impl/TorrentServiceImpl.java
+++ b/backend/demo/src/main/java/com/example/demo/service/impl/TorrentServiceImpl.java
@@ -2,9 +2,15 @@
 package com.example.demo.service.impl;
 
 import java.io.IOException;
+import java.net.InetAddress;
 import java.nio.file.Files; // 导入 DTO
 import java.nio.file.Path; // 导入实体类
 import java.nio.file.Paths; // 导入 Mapper
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
 
 import org.springframework.beans.factory.annotation.Value; // 导入服务接口
 import org.springframework.stereotype.Service; // 导入工具类
@@ -18,6 +24,14 @@
 import com.example.demo.service.TorrentService; // 用于文件操作
 import com.example.demo.util.TorrentUtils;  // 用于文件路径
 import com.turn.ttorrent.bcodec.InvalidBEncodingException; // 用于文件路径
+import com.turn.ttorrent.client.CommunicationManager;
+import com.turn.ttorrent.client.PeerInformation;
+import com.turn.ttorrent.client.PieceInformation;
+import com.turn.ttorrent.client.SharedTorrent;
+import com.turn.ttorrent.client.TorrentListener;
+import com.turn.ttorrent.client.TorrentManager;
+
+import jakarta.annotation.PostConstruct;
 
 /**
  * TorrentService 接口的实现类,负责处理 .torrent 文件的上传、解析、持久化及链接生成。
@@ -27,8 +41,11 @@
 
     private final TorrentInfoMapper torrentInfoMapper;
 
-    @Value("${file.upload-dir}") // 从 application.properties 中注入文件上传目录
-    private String uploadDir;
+  @Value("${file.upload-dir}")
+  private String uploadDir;
+
+  @Value("${file.download-dir}")
+  private String downloadDir;
 
     // 通过构造函数注入 Mapper
     public TorrentServiceImpl(TorrentInfoMapper torrentInfoMapper) {
@@ -37,7 +54,7 @@
 
     @Override
     @Transactional // 确保文件保存和数据库操作的原子性
-    public TorrentInfoDTO handleUpload(MultipartFile file) throws IOException, InvalidBEncodingException, IllegalArgumentException {
+    public TorrentInfoDTO handleUpload(MultipartFile file,String title) throws IOException, InvalidBEncodingException, IllegalArgumentException {
         if (file.isEmpty()) {
             throw new IllegalArgumentException("上传文件不能为空。");
         }
@@ -123,4 +140,82 @@
 
         return dto;
     }
+
+    private CommunicationManager commManager;
+  private final Map<String, TorrentManager> managers = new ConcurrentHashMap<>();
+  private final ExecutorService executor = Executors.newFixedThreadPool(4);
+
+  @PostConstruct
+  public void init() throws Exception {
+    // 1. 初始化 CommunicationManager
+    ExecutorService netPool = Executors.newFixedThreadPool(4);
+    ExecutorService ioPool  = Executors.newFixedThreadPool(2);
+    this.commManager = new CommunicationManager(netPool, ioPool);
+    this.commManager.start(InetAddress.getLocalHost());
+  }
+
+  @Override
+  public void startDownload(String infoHash) throws Exception {
+    // 2.1 .torrent 文件路径 & 本地输出目录
+    String torrentPath = uploadDir + "/" + infoHash + ".torrent";
+    String outDir      = downloadDir + "/" + infoHash;
+    TorrentManager tm  = commManager.addTorrent(torrentPath, outDir);
+
+    // 2.2 注册进度监听
+    tm.addListener(new TorrentListener() {
+      @Override
+      public void pieceDownloaded(PieceInformation pi, PeerInformation peer) {
+        // no-op
+      }
+      @Override public void downloadComplete() {
+        System.out.println("[" + infoHash + "] 下载完成");
+      }
+      @Override public void validationComplete(int valid, int total) {}
+      @Override public void peerConnected(PeerInformation peer) {}
+      @Override public void peerDisconnected(PeerInformation peer) {}
+      @Override public void pieceReceived(PieceInformation pi, PeerInformation peer) {}
+      @Override public void downloadFailed(Throwable cause) {
+        cause.printStackTrace();
+      }
+    });
+
+    // 2.3 异步等待完成
+    executor.submit(() -> {
+      try {
+        tm.awaitDownloadComplete(Integer.MAX_VALUE, TimeUnit.SECONDS);
+      } catch (Exception e) {
+        e.printStackTrace();
+      }
+    });
+
+    managers.put(infoHash, tm);
+  }
+
+  @Override
+public float getDownloadProgress(String infoHash) {
+    for (SharedTorrent t : commManager.getTorrents()) {
+        if (t.getHexInfoHash().equalsIgnoreCase(infoHash)) {
+            long downloaded = t.getDownloaded();  // 已下载字节数
+            long left       = t.getLeft();        // 剩余字节数
+            long total      = downloaded + left;  // 总字节数
+            return total > 0
+                ? (float) downloaded / total
+                : 0f;
+        }
+    }
+    return 0f;
+}
+
+
+  @Override
+  public String getDownloadedFilePath(String infoHash) {
+    for (SharedTorrent t : commManager.getTorrents()) {
+      if (t.getHexInfoHash().equalsIgnoreCase(infoHash) && t.isComplete()) {
+        // 拿到第一个文件的相对路径,再拼上下载目录
+        String rel = t.getFiles().get(0).getRelativePathAsString();
+        return downloadDir + "/" + infoHash + "/" + rel;
+      }
+    }
+    return null;
+  }
 }
\ No newline at end of file
diff --git a/backend/demo/src/main/java/com/example/demo/service/impl/UserServiceImpl.java b/backend/demo/src/main/java/com/example/demo/service/impl/UserServiceImpl.java
index 1fc434c..1b72d0f 100644
--- a/backend/demo/src/main/java/com/example/demo/service/impl/UserServiceImpl.java
+++ b/backend/demo/src/main/java/com/example/demo/service/impl/UserServiceImpl.java
@@ -19,26 +19,15 @@
 import com.example.demo.service.UserService;
 
 
-/**
- * 用户业务逻辑服务实现类
- * 继承了MyBatis-Plus的ServiceImpl,并实现了UserService接口和Spring Security的UserDetailsService接口。
- * 利用ServiceImpl提供的通用CRUD方法,并实现自定义的用户业务操作和加载用户详情。
- * 用户登录认证逻辑已移至 Controller 层,以解决循环依赖问题。
- */
+
 @Service
-// 继承ServiceImpl,指定Mapper和实体类型。
-// 这样就自动拥有了IService中大部分通用方法的实现。
+
 public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService, UserDetailsService {
 
-    // 当继承ServiceImpl后,ServiceImpl内部已经注入了BaseMapper,
-    // 通常不需要在这里再次声明UserMapper,除非您需要调用UserMapper中自定义的非BaseMapper方法。
-    // private final UserMapper userMapper; // 如果继承ServiceImpl,这行通常可以移除
 
-    // 注入密码编码器,用于密码加密和校验
+
     private final PasswordEncoder passwordEncoder;
-    // 移除 AuthenticationManager 注入,认证逻辑已移至 Controller
-    // private final AuthenticationManager authenticationManager;
-    // 注入JWT工具类,用于生成和解析Token
+
     private final JwtTokenUtil jwtTokenUtil;
 
 
@@ -46,12 +35,12 @@
      * 构造函数注入非BaseMapper的依赖
      * ServiceImpl会自动注入BaseMapper
      * @param passwordEncoder 密码编码器
-     * // 移除 authenticationManager 参数
+     * 
      * @param jwtTokenUtil JWT工具类
      */
-    // @Autowired // 当只有一个构造函数时,@Autowired 可选
+     
     public UserServiceImpl(PasswordEncoder passwordEncoder,
-                           // AuthenticationManager authenticationManager, // 移除此参数
+                            // 移除此参数
                            JwtTokenUtil jwtTokenUtil) {
         // 当继承ServiceImpl时,不需要在构造函数中注入并初始化BaseMapper
         // this.userMapper = userMapper; // 移除这行
@@ -152,20 +141,4 @@
 
         return response;
     }
-
-
-    // 当继承ServiceImpl后,IService中的通用方法(如 save, updateById, list, page 等)
-    // 已经由ServiceImpl提供默认实现,无需在此重复编写。
-    // 如果您需要对IService中的某个通用方法进行定制(例如在保存前进行额外处理),
-    // 可以在这里@Override该方法并添加您的逻辑,然后调用super.方法名(...)来执行ServiceImpl的默认逻辑。
-    /*
-    @Override
-    public boolean saveBatch(Collection<User> entityList, int batchSize) {
-        // 在调用ServiceImpl的批量保存前/后添加自定义逻辑
-        // ...
-        return super.saveBatch(entityList, batchSize); // 调用ServiceImpl的默认实现
-    }
-
-    // ... 其他IService中的方法实现
-    */
 }
diff --git a/backend/demo/src/main/java/com/example/demo/sh.py b/backend/demo/src/main/java/com/example/demo/sh.py
index 3be143b..7b30510 100644
--- a/backend/demo/src/main/java/com/example/demo/sh.py
+++ b/backend/demo/src/main/java/com/example/demo/sh.py
@@ -53,13 +53,17 @@
         './config/JWTProperties.java',
         './config/SecurityConfig.java',
         './controller/AuthController.java',
+        './controller/TorrentController.java',
         './DemoApplication.java',
         './dto/LoginRequestDTO.java',
         './dto/LoginResponseDTO.java',
+        './dto/TorrentInfoDTO.java',
         './entity/User.java',
+        './entity/TorrentInfo.java',
         './exception/AuthException.java',
         './exception/GlobalExceptionHandler.java',
         './mapper/UserMapper.java',
+        './mapper/TorrentInfoMapper.java',
         './security/JwtAuthenticationFilter.java',
         './security/JwtTokenUtil.java',
         './service/UserService.java',
diff --git a/backend/demo/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/backend/demo/src/main/resources/META-INF/additional-spring-configuration-metadata.json
index cf06b61..3da4c86 100644
--- a/backend/demo/src/main/resources/META-INF/additional-spring-configuration-metadata.json
+++ b/backend/demo/src/main/resources/META-INF/additional-spring-configuration-metadata.json
@@ -1,5 +1,12 @@
-{"properties": [{
-  "name": "file.upload-dir",
-  "type": "java.lang.String",
-  "description": "Torrent dir for uploading'"
-}]}
\ No newline at end of file
+{"properties": [
+  {
+    "name": "file.upload-dir",
+    "type": "java.lang.String",
+    "description": "Torrent dir for uploading'"
+  },
+  {
+    "name": "app.torrent.download-dir",
+    "type": "java.lang.String",
+    "description": "A description for 'app.torrent.download-dir'"
+  }
+]}
\ No newline at end of file
diff --git a/backend/demo/src/main/resources/application.properties b/backend/demo/src/main/resources/application.properties
index 9bbb45b..720d673 100644
--- a/backend/demo/src/main/resources/application.properties
+++ b/backend/demo/src/main/resources/application.properties
@@ -1,15 +1,21 @@
 # ========== 数据源 ==========
-spring.datasource.url=jdbc:mysql://mysql:3306/mydatabase?serverTimezone=Asia/Shanghai&createDatabaseIfNotExist=TRUE&useSSL=FALSE&allowPublicKeyRetrieval=TRUE
+spring.datasource.url=jdbc:mysql://localhost:3306/mydatabase?serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
 spring.datasource.username=myuser
 spring.datasource.password=secret
 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
 
 # ========== JWT 配置 ==========
-jwt.secret=YourJWTSecretKeyHere1234567890
+jwt.secret=YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWE=
 jwt.expirationMs=3600000
 
 # ========== 日志输出(可选) ==========
 logging.level.org.springframework.security=DEBUG
+logging.level.root=INFO
+logging.level.org.springframework.web=DEBUG
+logging.level.org.springframework.web.servlet=DEBUG
+logging.level.com.example=DEBUG
 
 file.upload-dir=./torrents
-
+app.torrent.download-dir=downloads
+spring.docker.compose.enabled=false
+spring.web.resources.static-locations=file:downloads/
\ No newline at end of file
diff --git a/backend/demo/src/main/resources/lib b/backend/demo/src/main/resources/lib
new file mode 160000
index 0000000..ed28512
--- /dev/null
+++ b/backend/demo/src/main/resources/lib
@@ -0,0 +1 @@
+Subproject commit ed28512530673c7363ae21f6fa09543955963de8
diff --git a/backend/demo/src/test/java/com/example/demo/controller/TorrentControllerTest.java b/backend/demo/src/test/java/com/example/demo/controller/TorrentControllerTest.java
index e0f2c8d..2ff389d 100644
--- a/backend/demo/src/test/java/com/example/demo/controller/TorrentControllerTest.java
+++ b/backend/demo/src/test/java/com/example/demo/controller/TorrentControllerTest.java
@@ -74,7 +74,7 @@
 
         // 当 torrentService.handleUpload 被调用时,返回预期的 DTO
         // When torrentService.handleUpload is called, return the expected DTO
-        when(torrentService.handleUpload(any(MultipartFile.class))).thenReturn(expectedDto);
+        when(torrentService.handleUpload(any(MultipartFile.class), null)).thenReturn(expectedDto);
 
         // 2. 执行请求并验证结果 (Execute the request and verify the results)
         mockMvc.perform(multipart("/api/torrents") // 发送 POST 请求到 /api/torrents (Send POST request to /api/torrents)
@@ -141,7 +141,7 @@
 
         // 配置 torrentService.handleUpload 在被调用时抛出 InvalidBEncodingException
         // Configure torrentService.handleUpload to throw InvalidBEncodingException when called
-        when(torrentService.handleUpload(any(MultipartFile.class)))
+        when(torrentService.handleUpload(any(MultipartFile.class), null))
                 .thenThrow(new InvalidBEncodingException("Mocked BEncoding error"));
 
         mockMvc.perform(multipart("/api/torrents")
@@ -166,7 +166,7 @@
 
         // 配置 torrentService.handleUpload 在被调用时抛出 IOException
         // Configure torrentService.handleUpload to throw IOException when called
-        when(torrentService.handleUpload(any(MultipartFile.class)))
+        when(torrentService.handleUpload(any(MultipartFile.class), null))
                 .thenThrow(new IOException("Mocked IO error"));
 
         mockMvc.perform(multipart("/api/torrents")
@@ -191,7 +191,7 @@
 
         // 配置 torrentService.handleUpload 在被调用时抛出 RuntimeException
         // Configure torrentService.handleUpload to throw RuntimeException when called
-        when(torrentService.handleUpload(any(MultipartFile.class)))
+        when(torrentService.handleUpload(any(MultipartFile.class), null))
                 .thenThrow(new RuntimeException("Mocked generic error"));
 
         mockMvc.perform(multipart("/api/torrents")
@@ -216,7 +216,7 @@
 
         // 配置 torrentService.handleUpload 在被调用时抛出 IllegalArgumentException
         // Configure torrentService.handleUpload to throw IllegalArgumentException when called
-        when(torrentService.handleUpload(any(MultipartFile.class)))
+        when(torrentService.handleUpload(any(MultipartFile.class), null))
                 .thenThrow(new IllegalArgumentException("Mocked illegal argument from service"));
 
         mockMvc.perform(multipart("/api/torrents")
diff --git a/backend/demo/src/test/java/com/example/demo/service/impl/TorrentServiceImplTest.java b/backend/demo/src/test/java/com/example/demo/service/impl/TorrentServiceImplTest.java
index 69eb5ba..a9da766 100644
--- a/backend/demo/src/test/java/com/example/demo/service/impl/TorrentServiceImplTest.java
+++ b/backend/demo/src/test/java/com/example/demo/service/impl/TorrentServiceImplTest.java
@@ -138,7 +138,7 @@
             when(torrentInfoMapper.insert(any(TorrentInfo.class))).thenReturn(1);
 
             // 4. Call the service method under test
-            TorrentInfoDTO result = torrentService.handleUpload(mockFile);
+            TorrentInfoDTO result = torrentService.handleUpload(mockFile, testFileName);
 
             // 5. Assertions: Verify the returned DTO and side effects
 
@@ -218,7 +218,7 @@
             when(torrentInfoMapper.selectOne(any(QueryWrapper.class))).thenReturn(existingTorrentInDb);
 
             // 5. Call the service method
-            TorrentInfoDTO result = torrentService.handleUpload(mockFile);
+            TorrentInfoDTO result = torrentService.handleUpload(mockFile, existingFileName);
 
             // 6. Assertions
             assertNotNull(result, "Returned DTO should not be null");
@@ -262,7 +262,7 @@
 
         // Assert that calling handleUpload with an empty file throws IllegalArgumentException.
         IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
-            torrentService.handleUpload(emptyFile);
+            torrentService.handleUpload(emptyFile, uploadDir);
         }, "Should throw IllegalArgumentException for empty file");
 
         // Verify the error message.
@@ -296,7 +296,7 @@
 
             // Assert that calling handleUpload throws IllegalArgumentException.
             IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
-                torrentService.handleUpload(mockFile);
+                torrentService.handleUpload(mockFile, uploadDir);
             }, "Should throw IllegalArgumentException for invalid B-encoding");
 
             // Verify the error message.
@@ -331,7 +331,7 @@
 
             // Assert that calling handleUpload throws IOException.
             IOException thrown = assertThrows(IOException.class, () -> {
-                torrentService.handleUpload(mockFile);
+                torrentService.handleUpload(mockFile, uploadDir);
             }, "Should throw IOException for parsing IO error");
 
             // Verify the error message.
@@ -388,7 +388,7 @@
 
             // Now, call the service method
             IOException thrown = assertThrows(IOException.class, () -> {
-                torrentService.handleUpload(mockFile);
+                torrentService.handleUpload(mockFile, testFileName);
             }, "Should throw IOException when file saving fails");
 
             assertTrue(thrown.getMessage().contains("无法保存 .torrent 文件到磁盘"), "Error message should indicate file save failure");
@@ -440,7 +440,7 @@
 
             // Assert that calling handleUpload throws RuntimeException.
             RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
-                torrentService.handleUpload(mockFile);
+                torrentService.handleUpload(mockFile, testFileName);
             }, "Should throw RuntimeException when DB insert fails");
 
             // Verify the error message.
diff --git a/backend/demo/torrents/E2BCE3123903796D19EEE0E341E2BB67FA8A66EC.torrent b/backend/demo/torrents/E2BCE3123903796D19EEE0E341E2BB67FA8A66EC.torrent
new file mode 100644
index 0000000..f38517a
--- /dev/null
+++ b/backend/demo/torrents/E2BCE3123903796D19EEE0E341E2BB67FA8A66EC.torrent
Binary files differ
diff --git a/frontend/my-app/package-lock.json b/frontend/my-app/package-lock.json
index 21a1a55..533fd75 100644
--- a/frontend/my-app/package-lock.json
+++ b/frontend/my-app/package-lock.json
@@ -13,6 +13,7 @@
         "@mui/icons-material": "^7.0.2",
         "@mui/material": "^7.0.2",
         "axios": "^1.9.0",
+        "file-saver": "^2.0.5",
         "react": "^19.0.0",
         "react-dom": "^19.0.0",
         "react-router-dom": "^7.5.1"
@@ -4915,6 +4916,12 @@
         "node": "^10.12.0 || >=12.0.0"
       }
     },
+    "node_modules/file-saver": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
+      "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==",
+      "license": "MIT"
+    },
     "node_modules/fill-range": {
       "version": "7.1.1",
       "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
diff --git a/frontend/my-app/package.json b/frontend/my-app/package.json
index 07b11c9..04db654 100644
--- a/frontend/my-app/package.json
+++ b/frontend/my-app/package.json
@@ -18,6 +18,7 @@
     "@mui/icons-material": "^7.0.2",
     "@mui/material": "^7.0.2",
     "axios": "^1.9.0",
+    "file-saver": "^2.0.5",
     "react": "^19.0.0",
     "react-dom": "^19.0.0",
     "react-router-dom": "^7.5.1"
diff --git a/frontend/my-app/src/App.jsx b/frontend/my-app/src/App.jsx
index 214e127..c83b97d 100644
--- a/frontend/my-app/src/App.jsx
+++ b/frontend/my-app/src/App.jsx
@@ -8,6 +8,7 @@
 import UserProfile from './pages/UserProfile';
 import UserContextProvider from './contexts/UserContext';
 import TorrentUploadPage from './pages/UploadTorrent';
+import LoginPage from './pages/Login';
 function App() {
   return (
     <UserContextProvider>
@@ -18,6 +19,7 @@
             <Route path="/" element={<Home />} />
             <Route path="/user/:id" element={<UserProfile />} />
              <Route path="/Upload" element={<TorrentUploadPage />} />
+              <Route path="/login" element={<LoginPage />} />
           </Routes>
         </div>
       </ThemeProvider>
diff --git a/frontend/my-app/src/components/Button/TorrentDownloadButton.jsx b/frontend/my-app/src/components/Button/TorrentDownloadButton.jsx
new file mode 100644
index 0000000..a8a6177
--- /dev/null
+++ b/frontend/my-app/src/components/Button/TorrentDownloadButton.jsx
@@ -0,0 +1,84 @@
+import React, { useState } from 'react';
+import PropTypes from 'prop-types';
+import axios from 'axios';
+import { saveAs } from 'file-saver';
+import Button from '@mui/material/Button';
+import LinearProgress from '@mui/material/LinearProgress';
+import Box from '@mui/material/Box';
+import Typography from '@mui/material/Typography';
+
+function TorrentDownloadButton({ infoHash, title }) {
+  // 新增状态:下载进度(0~100)
+  const [progress, setProgress] = useState(0);
+  // 新增状态:是否正在下载
+  const [downloading, setDownloading] = useState(false);
+
+  const handleDownload = async () => {
+    try {
+      setDownloading(true);
+      setProgress(0);
+      // 1) 启动下载
+      await axios.post('/api/torrents/download', null, { params: { infoHash } });
+
+      // 2) 轮询进度
+      let p = 0;
+      while (p < 1) {
+        await new Promise(resolve => setTimeout(resolve, 1000));
+        const res = await axios.get(`/api/torrents/download/status/${infoHash}`);
+        p = res.data;
+        setProgress(Math.round(p * 100));
+      }
+
+      // 3) 下载文件流
+      const fileRes = await axios.get(
+        `/api/torrents/download/file/${infoHash}`,
+        { responseType: 'blob' }
+      );
+
+      // 4) 保存文件
+      saveAs(fileRes.data, title);
+    } catch (err) {
+      console.error('下载过程中出错', err);
+    } finally {
+      setDownloading(false);
+    }
+  };
+
+  return (
+    <Box sx={{ width: 200 }}>
+      <Button
+        variant="outlined"
+        color="success"
+        onClick={handleDownload}
+        disabled={downloading}
+        sx={{
+          width: '100%',
+          fontSize: '0.85rem',
+          borderColor: '#4caf50',
+          '&:hover': {
+            backgroundColor: 'rgba(76, 175, 80, 0.15)',
+            borderColor: '#388e3c',
+          },
+        }}
+      >
+        {downloading ? `下载中 ${progress}%` : '下载'}
+      </Button>
+
+      {downloading && (
+        <Box sx={{ mt: 1 }}>
+          <LinearProgress variant="determinate" value={progress} />
+          <Typography variant="caption" display="block" align="center">
+            {progress}%
+          </Typography>
+        </Box>
+      )}
+    </Box>
+  );
+}
+
+TorrentDownloadButton.propTypes = {
+  infoHash: PropTypes.string.isRequired,
+  title: PropTypes.string.isRequired,
+};
+
+export default TorrentDownloadButton;
diff --git a/frontend/my-app/src/pages/Home.jsx b/frontend/my-app/src/pages/Home.jsx
index 6bca4b8..e2f7f8e 100644
--- a/frontend/my-app/src/pages/Home.jsx
+++ b/frontend/my-app/src/pages/Home.jsx
@@ -1,12 +1,10 @@
 import React, { useState, useEffect } from 'react';
 import {
   Box, Container, Typography, Paper,
-  List, ListItem, ListItemText, Chip, Stack, CircularProgress, Alert
+  List, ListItem, ListItemText, Chip, Stack, CircularProgress, Alert, Button
 } from '@mui/material';
 import { Link } from 'react-router-dom'; // 假设你已经配置了 React Router
 
-// import '../styles/base/base.css'; // ✅ 引入统一样式 - 我们将尝试用 sx prop 替代
-
 // 模拟从API获取数据
 const fetchTorrents = () => {
   return new Promise((resolve) => {
@@ -63,7 +61,7 @@
 
   return (
     <Box sx={{ minHeight: '100vh', py: 4, background: 'linear-gradient(135deg, #2c3e50, #4ca1af)', color: 'white' }}>
-      <Container maxWidth="md" sx={{ position: 'relative', zIndex: 10 }}> {/* Changed to md for wider content */}
+      <Container maxWidth="md" sx={{ position: 'relative', zIndex: 10 }}>
         <Typography variant="h4" sx={{ textAlign: 'center', mb: 3, fontWeight: 'bold' }}>
           🌐 首页 · Mini-Tracker
         </Typography>
@@ -90,36 +88,41 @@
                   key={torrent.id}
                   component={Link} // react-router-dom Link
                   to={`/detail/${torrent.id}`} // 假设的详情页路由
-                  sx={{ 
-                    color: 'white', 
+                  sx={{
+                    color: 'white',
                     textDecoration: 'none',
-                    mb: 1.5, // 增加列表项间距
-                    p: 1.5, // 增加列表项内边距
+                    mb: 1.5,
+                    p: 1.5,
                     borderBottom: '1px solid rgba(255, 255, 255, 0.1)',
                     '&:last-child': {
-                        borderBottom: 'none' // 移除最后一个元素的边框
+                      borderBottom: 'none'
                     },
-                    display: 'flex', // 确保内部元素正确对齐
-                    flexDirection: { xs: 'column', sm: 'row' }, // 响应式布局
+                    display: 'flex',
+                    flexDirection: { xs: 'column', sm: 'row' },
                     alignItems: { xs: 'flex-start', sm: 'center' }
                   }}
-                  divider={false} // 使用自定义边框代替
+                  divider={false}
                 >
                   <ListItemText
                     primary={torrent.title}
                     secondary={`大小: ${torrent.size} · 上传者: ${torrent.uploader}`}
-                    primaryTypographyProps={{ variant: 'h6', component: 'div', sx: { mb: 0.5, color: '#f5f5f5', fontWeight: 500 } }}
+                    primaryTypographyProps={{
+                      variant: 'h6',
+                      component: 'div',
+                      sx: { mb: 0.5, color: '#f5f5f5', fontWeight: 500 }
+                    }}
                     secondaryTypographyProps={{ sx: { color: '#bbb', fontSize: '0.85rem' } }}
-                    sx={{ flexGrow: 1, mb: { xs: 1, sm: 0 }, mr: { sm: 2 } }} // 响应式边距
+                    sx={{ flexGrow: 1, mb: { xs: 1, sm: 0 }, mr: { sm: 2 } }}
                   />
-                  <Stack direction="row" spacing={1} sx={{ flexWrap: 'wrap', gap: 0.5 }}> {/* 允许标签换行 */}
+
+                  <Stack direction="row" spacing={1} sx={{ flexWrap: 'wrap', gap: 0.5 }}>
                     {torrent.tags.map(tag => (
                       <Chip
                         key={tag}
                         label={tag}
                         size="small"
-                        sx={{ 
-                          backgroundColor: 'rgba(255, 255, 255, 0.2)', // upload-chip 样式
+                        sx={{
+                          backgroundColor: 'rgba(255, 255, 255, 0.2)',
                           color: 'white',
                           cursor: 'pointer',
                           '&:hover': {
@@ -129,6 +132,29 @@
                       />
                     ))}
                   </Stack>
+
+                  {/* ——— 在此处添加“下载”按钮 —— */}
+                  <Button
+                    variant="outlined"
+                    component="a"
+                    href={`http://localhost:8080/api/downloads/${torrent.id}/${encodeURIComponent(torrent.title)}.torrent`}
+                    download={`${torrent.title}.torrent`}
+                    sx={{
+                      ml: { xs: 0, sm: 2 }, // 小屏幕时居中;大屏幕时与右边距留空
+                      mt: { xs: 1, sm: 0 }, // 小屏时在下方,留一点间距
+                      color: '#4caf50',
+                      borderColor: '#4caf50',
+                      fontSize: '0.85rem',
+                      '&:hover': {
+                        backgroundColor: 'rgba(76, 175, 80, 0.15)',
+                        borderColor: '#388e3c'
+                      }
+                    }}
+                  >
+                    下载
+                  </Button>
+                  {/* ——— 下载按钮结束 ——— */}
+
                 </ListItem>
               ))}
             </List>
@@ -136,7 +162,7 @@
         </Paper>
       </Container>
 
-      {/* 背景泡泡动画复用 - 这些类名 (.bubbles, .bubble) 和 @keyframes rise 应该在全局CSS或者下面的 <style> 标签中定义 */}
+      {/* 背景泡泡动画复用 */}
       <Box className="bubbles" sx={{
          pointerEvents: 'none',
          position: 'fixed',
@@ -145,7 +171,7 @@
          width: '100%',
          height: '100%',
          overflow: 'hidden',
-         zIndex: 1, 
+         zIndex: 1,
       }}>
         {[...Array(40)].map((_, i) => (
           <Box
@@ -153,48 +179,43 @@
             className="bubble"
             sx={{
               position: 'absolute',
-              bottom: '-150px', // 确保从屏幕外开始
-              background: `hsla(${Math.random() * 360}, 70%, 80%, 0.15)`, // 增加颜色多样性
+              bottom: '-150px',
+              background: `hsla(${Math.random() * 360}, 70%, 80%, 0.15)`,
               borderRadius: '50%',
               animation: 'rise 20s infinite ease-in',
-              width: `${Math.random() * 25 + 10}px`, // 调整大小范围
+              width: `${Math.random() * 25 + 10}px`,
               height: `${Math.random() * 25 + 10}px`,
               left: `${Math.random() * 100}%`,
-              animationDuration: `${15 + Math.random() * 20}s`, // 调整动画时长
-              animationDelay: `${Math.random() * 10}s`, // 调整动画延迟
-              opacity: 0, // 初始透明
+              animationDuration: `${15 + Math.random() * 20}s`,
+              animationDelay: `${Math.random() * 10}s`,
+              opacity: 0,
             }}
           />
         ))}
       </Box>
-      {/* 定义动画和其他全局可能需要的样式 */}
       <style>
         {`
-          body { /* 基本重置 */
+          body {
             margin: 0;
-            font-family: 'Roboto', sans-serif; /* 确保字体一致 */
+            font-family: 'Roboto', sans-serif;
           }
 
           @keyframes rise {
             0% {
               transform: translateY(0) scale(0.8);
-              opacity: 0; /* 从透明开始 */
+              opacity: 0;
             }
             10% {
-                opacity: 1; /* 渐显 */
+              opacity: 1;
             }
             90% {
-                opacity: 1; /* 保持可见 */
+              opacity: 1;
             }
             100% {
-              transform: translateY(-130vh) scale(0.3); /* 飘得更高更小 */
-              opacity: 0; /* 渐隐 */
+              transform: translateY(-130vh) scale(0.3);
+              opacity: 0;
             }
           }
-
-          /* 如果 .bubbles 和 .bubble 样式没有在 sx 中完全覆盖,可以在这里补充 */
-          /* .bubbles { ... } */
-          /* .bubble { ... } */
         `}
       </style>
     </Box>
@@ -202,4 +223,3 @@
 }
 
 export default Home;
-
diff --git a/frontend/my-app/src/pages/Login.jsx b/frontend/my-app/src/pages/Login.jsx
new file mode 100644
index 0000000..19f13ca
--- /dev/null
+++ b/frontend/my-app/src/pages/Login.jsx
@@ -0,0 +1,214 @@
+import React, { useState } from 'react';
+import {
+  Box, Container, Typography, Paper, TextField, Button, CircularProgress, Alert
+} from '@mui/material';
+// import { useNavigate } from 'react-router-dom'; // 如果需要重定向,请取消注释
+
+function LoginPage() {
+  const [username, setUsername] = useState('');
+  const [password, setPassword] = useState('');
+  const [loading, setLoading] = useState(false);
+  const [message, setMessage] = useState(null); // 用于显示成功或错误消息
+  const [messageSeverity, setMessageSeverity] = useState('info'); // 'success', 'error', 'info'
+
+  // const navigate = useNavigate(); // 如果需要重定向,请取消注释
+
+  const handleSubmit = async (event) => {
+    event.preventDefault(); // 阻止表单默认提交行为
+
+    setMessage(null); // 清除之前的消息
+    setLoading(true); // 显示加载指示器
+
+    try {
+      const response = await fetch('http://localhost:8080/api/auth/login', { // 确保这里的URL与您的后端接口一致
+        method: 'POST',
+        headers: {
+          'Content-Type': 'application/json'
+        },
+        body: JSON.stringify({ username, password })
+      });
+
+      const data = await response.json();
+
+      if (response.ok) {
+        // 登录成功
+        setMessage('登录成功!欢迎 ' + data.username);
+        setMessageSeverity('success');
+        console.log('登录成功:', data);
+        // 将 JWT Token 保存到 localStorage
+        localStorage.setItem('jwt_token', data.token);
+        // 可选:登录成功后重定向到其他页面
+        // navigate('/dashboard'); 
+      } else {
+        // 登录失败
+        const errorMessage = data.error || '未知错误';
+        setMessage('登录失败: ' + errorMessage);
+        setMessageSeverity('error');
+        console.error('登录失败:', data);
+      }
+    } catch (error) {
+      // 网络错误或请求发送失败
+      setMessage('网络错误,请稍后再试。');
+      setMessageSeverity('error');
+      console.error('请求发送失败:', error);
+    } finally {
+      setLoading(false); // 隐藏加载指示器
+    }
+  };
+
+  return (
+    <Box sx={{
+      minHeight: '100vh',
+      display: 'flex',
+      justifyContent: 'center',
+      alignItems: 'center',
+      background: 'linear-gradient(135deg, #2c3e50, #4ca1af)', // 与 Home 组件相同的背景
+      color: 'white',
+      py: 4 // 垂直内边距
+    }}>
+      <Container maxWidth="xs" sx={{ position: 'relative', zIndex: 10 }}>
+        <Typography variant="h4" align="center" sx={{ mb: 4, fontWeight: 'bold' }}>
+          🔐 用户登录
+        </Typography>
+
+        <Paper sx={{
+          p: 4, // 内边距
+          backgroundColor: 'rgba(30, 30, 30, 0.9)', // 半透明深色背景
+          borderRadius: '12px', // 圆角
+          boxShadow: '0 8px 16px rgba(0,0,0,0.3)' // 阴影
+        }}>
+          {message && (
+            <Alert severity={messageSeverity} sx={{ mb: 2, borderRadius: '8px' }}>
+              {message}
+            </Alert>
+          )}
+
+          <form onSubmit={handleSubmit}>
+            <TextField
+              label="用户名"
+              variant="outlined"
+              fullWidth
+              margin="normal"
+              value={username}
+              onChange={(e) => setUsername(e.target.value)}
+              required
+              sx={{
+                mb: 2, // 底部外边距
+                '& .MuiOutlinedInput-root': {
+                  borderRadius: '8px',
+                  color: 'white', // 输入文字颜色
+                  '& fieldset': { borderColor: 'rgba(255, 255, 255, 0.3)' }, // 边框颜色
+                  '&:hover fieldset': { borderColor: 'rgba(255, 255, 255, 0.5)' },
+                  '&.Mui-focused fieldset': { borderColor: '#8b5cf6' }, // 聚焦时边框颜色
+                },
+                '& .MuiInputLabel-root': { color: '#bbb' }, // 标签颜色
+                '& .MuiInputLabel-root.Mui-focused': { color: '#8b5cf6' }, // 聚焦时标签颜色
+              }}
+            />
+            <TextField
+              label="密码"
+              type="password"
+              variant="outlined"
+              fullWidth
+              margin="normal"
+              value={password}
+              onChange={(e) => setPassword(e.target.value)}
+              required
+              sx={{
+                mb: 3, // 底部外边距
+                '& .MuiOutlinedInput-root': {
+                  borderRadius: '8px',
+                  color: 'white', // 输入文字颜色
+                  '& fieldset': { borderColor: 'rgba(255, 255, 255, 0.3)' },
+                  '&:hover fieldset': { borderColor: 'rgba(255, 255, 255, 0.5)' },
+                  '&.Mui-focused fieldset': { borderColor: '#8b5cf6' },
+                },
+                '& .MuiInputLabel-root': { color: '#bbb' },
+                '& .MuiInputLabel-root.Mui-focused': { color: '#8b5cf6' },
+              }}
+            />
+            <Button
+              type="submit"
+              variant="contained"
+              fullWidth
+              disabled={loading}
+              sx={{
+                py: '0.75rem', // 垂直内边距
+                backgroundImage: 'linear-gradient(to right, #6366f1, #8b5cf6)', // 渐变背景
+                color: 'white',
+                borderRadius: '0.375rem', // 圆角
+                fontWeight: 600,
+                position: 'relative', // 用于加载指示器定位
+                '&:hover': {
+                  backgroundImage: 'linear-gradient(to right, #4f46e5, #7c3aed)',
+                },
+              }}
+            >
+              {loading ? <CircularProgress size={24} color="inherit" sx={{ position: 'absolute' }} /> : '登录'}
+            </Button>
+          </form>
+        </Paper>
+      </Container>
+
+      {/* 背景泡泡动画 - 复用 Home 组件中的样式 */}
+      <Box className="bubbles" sx={{
+          pointerEvents: 'none',
+          position: 'fixed',
+          top: 0,
+          left: 0,
+          width: '100%',
+          height: '100%',
+          overflow: 'hidden',
+          zIndex: 1, 
+      }}>
+        {[...Array(40)].map((_, i) => (
+          <Box
+            key={i}
+            className="bubble"
+            sx={{
+              position: 'absolute',
+              bottom: '-150px', // 确保从屏幕外开始
+              background: `hsla(${Math.random() * 360}, 70%, 80%, 0.15)`, // 增加颜色多样性
+              borderRadius: '50%',
+              animation: 'rise 20s infinite ease-in',
+              width: `${Math.random() * 25 + 10}px`, // 调整大小范围
+              height: `${Math.random() * 25 + 10}px`,
+              left: `${Math.random() * 100}%`,
+              animationDuration: `${15 + Math.random() * 20}s`, // 调整动画时长
+              animationDelay: `${Math.random() * 10}s`, // 调整动画延迟
+              opacity: 0, // 初始透明
+            }}
+          />
+        ))}
+      </Box>
+      {/* 定义动画和其他全局可能需要的样式 */}
+      <style>
+        {`
+          body { /* 基本重置 */
+            margin: 0;
+            font-family: 'Roboto', sans-serif; /* 确保字体一致 */
+          }
+
+          @keyframes rise {
+            0% {
+              transform: translateY(0) scale(0.8);
+              opacity: 0; /* 从透明开始 */
+            }
+            10% {
+                opacity: 1; /* 渐显 */
+            }
+            90% {
+                opacity: 1; /* 保持可见 */
+            }
+            100% {
+              transform: translateY(-130vh) scale(0.3); /* 飘得更高更小 */
+              opacity: 0; /* 渐隐 */
+            }
+          }
+        `}
+      </style>
+    </Box>
+  );
+}
+
+export default LoginPage;
\ No newline at end of file
diff --git a/frontend/my-app/src/pages/UploadTorrent.jsx b/frontend/my-app/src/pages/UploadTorrent.jsx
index 493cdce..465afda 100644
--- a/frontend/my-app/src/pages/UploadTorrent.jsx
+++ b/frontend/my-app/src/pages/UploadTorrent.jsx
@@ -4,43 +4,44 @@
   TextField, Button, Stack
 } from '@mui/material';
 import CloudUploadIcon from '@mui/icons-material/CloudUpload';
-// Assuming your base.css is in a relative path like src/styles/base/base.css
-// If your component is in src/components/Upload.js, this path would be ../styles/base/base.css
-// Adjust if your project structure is different.
-// For this example, I'll assume it's correctly linked.
-// import '../styles/base/base.css'; 
+
 
 function Upload() {
   const [title, setTitle] = useState('');
   const [file, setFile] = useState(null);
 
   const handleUpload = () => {
-    const formData = new FormData();
-    if (file) {
-      formData.append('file', file);
-    }
-    formData.append('title', title);
+  const formData = new FormData();
+  if (file) {
+    formData.append('file', file);
+  }
+  formData.append('title', title);
 
-    // Replace with your actual API endpoint and error handling
-    fetch('/api/torrents/upload', {
-      method: 'POST',
-      body: formData
-    })
+  const token = localStorage.getItem('jwt_token'); // 假设你登录后将 token 存在 localStorage
+
+  fetch('http://localhost:8080/api/torrents/upload', {
+    method: 'POST',
+    headers: {
+      Authorization: `Bearer ${token}`,
+    },
+    body: formData,
+  })
     .then(response => {
       if (response.ok) {
-        // Use a more modern way to show messages, e.g., a Snackbar
-        console.log('上传成功'); 
-        alert('上传成功 (建议使用Snackbar等UI组件替代alert)');
+        console.log('上传成功');
+        alert('上传成功');
       } else {
-        console.error('上传失败');
-        alert('上传失败 (建议使用Snackbar等UI组件替代alert)');
+        return response.text().then(text => {
+          console.error('上传失败:', text);
+          alert('上传失败: ' + text);
+        });
       }
     })
     .catch(error => {
       console.error('上传出错:', error);
-      alert('上传出错 (建议使用Snackbar等UI组件替代alert)');
+      alert('上传出错: ' + error.message);
     });
-  };
+};
 
   return (
     <Box sx={{ minHeight: '100vh', py: 4, background: 'linear-gradient(135deg, #2c3e50, #4ca1af)', color: 'white' }}> {/* Moved body styles here for self-containment */}
diff --git a/frontend/my-app/src/services/torrentService.js b/frontend/my-app/src/services/torrentService.js
deleted file mode 100644
index a50f8ac..0000000
--- a/frontend/my-app/src/services/torrentService.js
+++ /dev/null
@@ -1,28 +0,0 @@
-// ./services/torrentService.js
-import axios from 'axios';
-
-// IMPORTANT: Replace with your actual backend API URL
-const API_BASE_URL = 'http://localhost:5000/api';
-
-export const uploadTorrent = async (formData) => {
-    try {
-        const response = await axios.post(`${API_BASE_URL}/torrents/upload`, formData, {
-            headers: {
-                'Content-Type': 'multipart/form-data', // Essential for file uploads
-                // Add any authentication tokens here if your backend requires them
-                // 'Authorization': `Bearer ${localStorage.getItem('authToken')}`
-            },
-            onUploadProgress: (progressEvent) => {
-                // You can use this to show upload progress to the user
-                const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
-                console.log(`Upload progress: ${percentCompleted}%`);
-                // You could pass this percentage back to the component via a callback
-            },
-        });
-        return response.data; // Your backend should return the parsed torrent info here
-    } catch (error) {
-        console.error('Error in uploadTorrent service:', error.response ? error.response.data : error.message);
-        // Throw a more specific error message based on backend response if available
-        throw new Error(error.response?.data?.message || '文件上传失败,请稍后再试。');
-    }
-};
\ No newline at end of file