Backend update on Torrent upload with unit test complete

Change-Id: Ie8a90c82e414df32d079524d3ff3035b823b69c3
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 bfd7b1b..b3e7f91 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
@@ -5,6 +5,8 @@
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
 import org.springframework.security.config.http.SessionCreationPolicy;
 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 import org.springframework.security.crypto.password.PasswordEncoder;
@@ -12,13 +14,17 @@
 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
 
 import com.example.demo.security.JwtAuthenticationFilter;
-import com.example.demo.security.JwtTokenUtil;
 
+/**
+ * Spring Security 配置类,用于定义安全策略和相关的 Bean。
+ */
 @Configuration
+@EnableWebSecurity // 启用 Spring Security 的 Web 安全功能
 public class SecurityConfig {
 
     /**
-     * 密码加密器,用于注册用户时对密码加密、登录时校验
+     * 密码加密器,用于注册用户时对密码加密、登录时校验。
+     * 使用 BCryptPasswordEncoder,一种安全的密码哈希算法。
      */
     @Bean
     public PasswordEncoder passwordEncoder() {
@@ -27,7 +33,7 @@
 
     /**
      * 将 Spring Security 的 AuthenticationManager 暴露为 Bean,
-     * 方便在 AuthController 或其它地方手动调用。
+     * 方便在 AuthController 或其它地方手动调用进行认证。
      */
     @Bean
     public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
@@ -35,17 +41,43 @@
     }
 
     /**
-     * 核心安全策略:禁用 CSRF、无状态 Session、开放登录接口、其余接口需认证。
+     * 核心安全策略配置。
+     * 配置内容包括:禁用 CSRF、禁用 Session 管理(使用 JWT 无状态)、
+     * 配置请求的授权规则,并将自定义的 JWT 认证过滤器添加到过滤器链中。
+     *
+     * @param http HttpSecurity 配置对象
+     * @param jwtFilter 自定义的 JWT 认证过滤器,通过 Spring 容器注入
+     * @return 配置好的 SecurityFilterChain
+     * @throws Exception 配置过程中可能抛出的异常
      */
     @Bean
-public SecurityFilterChain filterChain(HttpSecurity http,
-                                       JwtTokenUtil tokenUtil,
-                                       JWTProperties props) throws Exception {
-    JwtAuthenticationFilter jwtFilter = new JwtAuthenticationFilter(tokenUtil, props);
-    http
-      // 省略其他配置…
-      .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
-    return http.build();
-}
-}
+    public SecurityFilterChain filterChain(HttpSecurity http,
+                                           JwtAuthenticationFilter jwtFilter // 直接注入 JwtAuthenticationFilter Bean
+                                           ) throws Exception {
+        http
+            // 禁用 CSRF (跨站请求伪造) 保护,因为 JWT 是无状态的,不需要 CSRF 保护
+            .csrf(AbstractHttpConfigurer::disable)
+            // 配置 Session 管理策略为无状态,不使用 Session
+            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
+            // 配置请求的授权规则
+            .authorizeHttpRequests(auth -> auth
+                // 允许对 /auth/** 路径下的所有请求进行匿名访问 (例如登录、注册接口)
+                .requestMatchers("/auth/**").permitAll()
+                // 其他所有请求都需要进行身份认证
+                .anyRequest().authenticated()
+            )
+            // 禁用默认的表单登录功能,因为我们使用 JWT 进行认证
+            .formLogin(AbstractHttpConfigurer::disable)
+            // 禁用默认的 HTTP Basic 认证
+            .httpBasic(AbstractHttpConfigurer::disable)
+            // 将自定义的 JwtAuthenticationFilter 添加到 UsernamePasswordAuthenticationFilter 之前
+            // 确保在进行基于用户名密码的认证之前,先进行 JWT 认证
+            .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
 
+        // 构建并返回配置好的 SecurityFilterChain
+        return http.build();
+    }
+
+    // 注意:JwtAuthenticationFilter 需要被 Spring 扫描到并作为 Bean 管理
+    // 确保 JwtAuthenticationFilter 类上有 @Component 或其他 Spring Bean 相关的注解
+}
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 08411d7..d7dfe50 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
@@ -4,8 +4,11 @@
 import java.util.List;
 import java.util.Map;
 
-import org.springframework.beans.factory.annotation.Autowired;
 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;
@@ -27,16 +30,18 @@
 public class AuthController {
 
     private final UserService userService;
-    private final PasswordEncoder passwordEncoder;
+    private final PasswordEncoder passwordEncoder; // 虽然在AuthController中不再直接用于比对,但如果UserServiceImpl需要,这里保留注入
     private final JwtTokenUtil jwtTokenUtil;
+    private final AuthenticationManager authenticationManager; // 注入 AuthenticationManager
 
-    @Autowired
     public AuthController(UserService userService,
                           PasswordEncoder passwordEncoder,
-                          JwtTokenUtil jwtTokenUtil) {
+                          JwtTokenUtil jwtTokenUtil,
+                          AuthenticationManager authenticationManager) { // 注入 AuthenticationManager
         this.userService = userService;
         this.passwordEncoder = passwordEncoder;
         this.jwtTokenUtil = jwtTokenUtil;
+        this.authenticationManager = authenticationManager; // 初始化 AuthenticationManager
     }
 
     @PostMapping("/login")
@@ -46,12 +51,40 @@
 
         if (bindingResult.hasErrors()) {
             String errMsg = bindingResult.getFieldErrors().stream()
-                .map(fe -> fe.getField() + ": " + fe.getDefaultMessage())
-                .reduce((a, b) -> a + "; " + b)
-                .orElse("Invalid parameters");
+                    .map(fe -> fe.getField() + ": " + fe.getDefaultMessage())
+                    .reduce((a, b) -> a + "; " + b)
+                    .orElse("Invalid parameters");
             return ResponseEntity.badRequest().body(Map.of("error", errMsg));
         }
 
+        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);
+
+        } catch (Exception e) {
+            // 认证失败(用户名不存在或密码错误),抛出自定义认证异常
+            // GlobalExceptionHandler 会捕获 AuthException 并返回 401
+            throw new AuthException("用户名或密码错误", e);
+        }
+
+        // 移除原有的手动查找用户和密码比对逻辑
+        /*
         User user = userService.lambdaQuery()
                 .eq(User::getUsername, loginRequest.getUsername())
                 .one();
@@ -70,5 +103,6 @@
         response.setRoles(List.of(user.getRole()));
 
         return ResponseEntity.ok(response);
+        */
     }
-}
+}
\ 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
new file mode 100644
index 0000000..0bd501a
--- /dev/null
+++ b/backend/demo/src/main/java/com/example/demo/controller/TorrentController.java
@@ -0,0 +1,130 @@
+// 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 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.http.HttpStatus;
+import org.springframework.http.MediaType; // 导入 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 // 组合了 @Controller 和 @ResponseBody,表示这是一个 RESTful 控制器
+@RequestMapping("/api") // 定义所有处理方法的根路径
+public class TorrentController {
+
+    private final TorrentService torrentService;
+
+    @Value("${file.upload-dir}") // 从 application.properties 中注入文件上传目录
+    private String uploadDir;
+
+    // 通过构造函数注入 TorrentService
+    public TorrentController(TorrentService torrentService) {
+        this.torrentService = torrentService;
+    }
+
+    /**
+     * 处理 .torrent 文件的上传请求。
+     *
+     * @param file 上传的 MultipartFile 对象,通过 @RequestParam("file") 绑定。
+     * @return 包含 TorrentInfoDTO 的 ResponseEntity,表示操作结果。
+     */
+    @PostMapping("/torrents") // 映射到 /api/torrents 的 POST 请求
+    public ResponseEntity<TorrentInfoDTO> uploadTorrent(@RequestParam("file") MultipartFile file) {
+        // 1. 接收 HTTP 请求 (通过 @PostMapping 和 @RequestParam)
+
+        // 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 文件。");
+            return new ResponseEntity<>(errorDto, HttpStatus.UNSUPPORTED_MEDIA_TYPE);
+        }
+
+        try {
+            // 3. 调用 Service 层处理业务逻辑
+            TorrentInfoDTO result = torrentService.handleUpload(file);
+
+            // 4. 返回 TorrentInfoDTO 给前端
+            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(); // 打印堆栈跟踪以便调试
+            return new ResponseEntity<>(errorDto, HttpStatus.INTERNAL_SERVER_ERROR);
+        }
+    }
+
+    /**
+     * 实现 .torrent 文件的下载功能。
+     * 根据 infoHash 找到对应的 .torrent 文件并提供下载。
+     *
+     * @param infoHash 洪流的 info hash,用于查找服务器上的文件。
+     * @param fileName 原始文件名 (用于设置下载时的文件名,实际查找文件仍依赖 infoHash)。
+     * @return 包含 .torrent 文件内容的 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);
+        }
+    } catch (IOException e) { // 只保留 IOException
+        return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
+    }
+    // MalformedURLException 相关的 catch 块已移除
+}
+}
\ No newline at end of file
diff --git a/backend/demo/src/main/java/com/example/demo/dto/TorrentInfoDTO.java b/backend/demo/src/main/java/com/example/demo/dto/TorrentInfoDTO.java
new file mode 100644
index 0000000..45cd326
--- /dev/null
+++ b/backend/demo/src/main/java/com/example/demo/dto/TorrentInfoDTO.java
@@ -0,0 +1,87 @@
+// 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 + '\'' +
+               '}';
+    }
+}
\ No newline at end of file
diff --git a/backend/demo/src/main/java/com/example/demo/entity/TorrentInfo.java b/backend/demo/src/main/java/com/example/demo/entity/TorrentInfo.java
new file mode 100644
index 0000000..da39654
--- /dev/null
+++ b/backend/demo/src/main/java/com/example/demo/entity/TorrentInfo.java
@@ -0,0 +1,118 @@
+// 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 +
+               '}';
+    }
+}
\ No newline at end of file
diff --git a/backend/demo/src/main/java/com/example/demo/entity/User.java b/backend/demo/src/main/java/com/example/demo/entity/User.java
index bcbe6de..ba308af 100644
--- a/backend/demo/src/main/java/com/example/demo/entity/User.java
+++ b/backend/demo/src/main/java/com/example/demo/entity/User.java
@@ -1,7 +1,13 @@
 package com.example.demo.entity;
 
-import java.io.Serializable;
-import java.time.LocalDateTime;
+import java.time.LocalDateTime; // Keep import if Serializable is used elsewhere, otherwise remove
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List; // 用于返回空的权限集合,如果用户没有角色/权限
+
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority; // 用于构建权限列表
+import org.springframework.security.core.userdetails.UserDetails;
 
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
@@ -9,7 +15,8 @@
 
 
 @TableName("user")
-public class User implements Serializable {
+// 实现UserDetails接口 (UserDetails extends Serializable, so Serializable is redundant here)
+public class User implements UserDetails { 
     private static final long serialVersionUID = 1L;
 
     @TableId(type = IdType.AUTO)
@@ -21,16 +28,18 @@
 
     private String email;
 
-    private Integer status; 
+    private Integer status; // 假设status字段表示用户状态,例如 0:禁用, 1:启用
 
-    private Integer score; 
+    private Integer score;
 
-    private String role; 
+    private String role; // 假设role字段存储用户的角色,例如 "ADMIN", "USER"
 
     private LocalDateTime createTime;
 
     private LocalDateTime updateTime;
 
+    // --- Getters and Setters ---
+
     public Long getId() {
         return id;
     }
@@ -39,6 +48,7 @@
         this.id = id;
     }
 
+    @Override // 实现UserDetails接口的getUsername方法
     public String getUsername() {
         return username;
     }
@@ -47,6 +57,7 @@
         this.username = username;
     }
 
+    @Override // 实现UserDetails接口的getPassword方法
     public String getPassword() {
         return password;
     }
@@ -102,4 +113,49 @@
     public void setUpdateTime(LocalDateTime updateTime) {
         this.updateTime = updateTime;
     }
+
+    // --- 实现 UserDetails 接口的方法 ---
+
+    @Override
+    public Collection<? extends GrantedAuthority> getAuthorities() {
+        // 根据用户的角色构建权限列表
+        List<GrantedAuthority> authorities = new ArrayList<>();
+        if (this.role != null && !this.role.isEmpty()) {
+            // Spring Security 推荐角色以 "ROLE_" 开头
+            authorities.add(new SimpleGrantedAuthority("ROLE_" + this.role));
+        }
+        // 如果用户没有角色,返回一个空的集合
+        return authorities;
+    }
+
+    @Override
+    public boolean isAccountNonExpired() {
+        // 根据您的业务逻辑判断账户是否过期
+        // 例如,如果您的User实体有过期时间字段,可以在这里检查
+        // 这里简单返回true,表示账户永不过期
+        return true;
+    }
+
+    @Override
+    public boolean isAccountNonLocked() {
+        // 根据您的业务逻辑判断账户是否被锁定
+        // 例如,如果您的User实体有锁定状态字段,可以在这里检查
+        // 这里简单返回true,表示账户永不锁定
+        return true;
+    }
+
+    @Override
+    public boolean isCredentialsNonExpired() {
+        // 根据您的业务逻辑判断凭证(密码)是否过期
+        // 例如,如果您的User实体有密码最后修改时间字段,可以在这里检查
+        // 这里简单返回true,表示凭证永不过期
+        return true;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        // 根据您的业务逻辑判断账户是否启用
+        // 假设status字段为1表示启用
+        return this.status != null && this.status == 1;
+    }
 }
diff --git a/backend/demo/src/main/java/com/example/demo/mapper/TorrentInfoMapper.java b/backend/demo/src/main/java/com/example/demo/mapper/TorrentInfoMapper.java
new file mode 100644
index 0000000..3f4f245
--- /dev/null
+++ b/backend/demo/src/main/java/com/example/demo/mapper/TorrentInfoMapper.java
@@ -0,0 +1,13 @@
+// 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() 等。
+    // 无需编写任何实现代码。
+}
\ No newline at end of file
diff --git a/backend/demo/src/main/java/com/example/demo/mapper/UserMapper.java b/backend/demo/src/main/java/com/example/demo/mapper/UserMapper.java
index 94ba7a8..0198c39 100644
--- a/backend/demo/src/main/java/com/example/demo/mapper/UserMapper.java
+++ b/backend/demo/src/main/java/com/example/demo/mapper/UserMapper.java
@@ -1,8 +1,9 @@
 package com.example.demo.mapper;
 
+import org.apache.ibatis.annotations.Mapper;
+
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.example.demo.entity.User;
-import org.apache.ibatis.annotations.Mapper;
 
 /**
  * UserMapper 接口
@@ -11,6 +12,10 @@
  */
 @Mapper
 public interface UserMapper extends BaseMapper<User> {
+
+    User findById(Long id);
+
+    User findByUsername(String username);
     
     // List<User> selectByStatus(@Param("status") Integer status);
 }
diff --git a/backend/demo/src/main/java/com/example/demo/result.txt b/backend/demo/src/main/java/com/example/demo/result.txt
new file mode 100644
index 0000000..bd1dd30
--- /dev/null
+++ b/backend/demo/src/main/java/com/example/demo/result.txt
@@ -0,0 +1,1031 @@
+--- Start File Content ---
+
+--- Content of: ./config/JWTProperties.java ---
+package com.example.demo.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * JWT 配置属性,从 application.properties 中 jwt 开头的配置加载
+ */
+@Component
+@ConfigurationProperties(prefix = "jwt")
+public class JWTProperties {
+    /**
+     * 用于签名的密钥
+     */
+    private String secret;
+
+    /**
+     * Token 过期时长,单位毫秒
+     */
+    private long expirationMs;
+
+    /**
+     * HTTP Header 中放置 JWT 的字段名
+     */
+    private String header = "Authorization";
+
+    /**
+     * Header 中 Token 的前缀
+     */
+    private String tokenPrefix = "Bearer ";
+
+    /**
+     * 签发者信息(可选)
+     */
+    private String issuer;
+
+    /**
+     * 接收者信息(可选)
+     */
+    private String audience;
+
+    // --- getters & setters ---
+
+    public String getSecret() {
+        return secret;
+    }
+
+    public void setSecret(String secret) {
+        this.secret = secret;
+    }
+
+    public long getExpirationMs() {
+        return expirationMs;
+    }
+
+    public void setExpirationMs(long expirationMs) {
+        this.expirationMs = expirationMs;
+    }
+
+    public String getHeader() {
+        return header;
+    }
+
+    public void setHeader(String header) {
+        this.header = header;
+    }
+
+    public String getTokenPrefix() {
+        return tokenPrefix;
+    }
+
+    public void setTokenPrefix(String tokenPrefix) {
+        this.tokenPrefix = tokenPrefix;
+    }
+
+    public String getIssuer() {
+        return issuer;
+    }
+
+    public void setIssuer(String issuer) {
+        this.issuer = issuer;
+    }
+
+    public String getAudience() {
+        return audience;
+    }
+
+    public void setAudience(String audience) {
+        this.audience = audience;
+    }
+}
+
+--- End of: ./config/JWTProperties.java ---
+
+--- Content of: ./config/SecurityConfig.java ---
+package com.example.demo.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+import com.example.demo.security.JwtAuthenticationFilter;
+
+/**
+ * Spring Security 配置类,用于定义安全策略和相关的 Bean。
+ */
+@Configuration
+@EnableWebSecurity // 启用 Spring Security 的 Web 安全功能
+public class SecurityConfig {
+
+    /**
+     * 密码加密器,用于注册用户时对密码加密、登录时校验。
+     * 使用 BCryptPasswordEncoder,一种安全的密码哈希算法。
+     */
+    @Bean
+    public PasswordEncoder passwordEncoder() {
+        return new BCryptPasswordEncoder();
+    }
+
+    /**
+     * 将 Spring Security 的 AuthenticationManager 暴露为 Bean,
+     * 方便在 AuthController 或其它地方手动调用进行认证。
+     */
+    @Bean
+    public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
+        return config.getAuthenticationManager();
+    }
+
+    /**
+     * 核心安全策略配置。
+     * 配置内容包括:禁用 CSRF、禁用 Session 管理(使用 JWT 无状态)、
+     * 配置请求的授权规则,并将自定义的 JWT 认证过滤器添加到过滤器链中。
+     *
+     * @param http HttpSecurity 配置对象
+     * @param jwtFilter 自定义的 JWT 认证过滤器,通过 Spring 容器注入
+     * @return 配置好的 SecurityFilterChain
+     * @throws Exception 配置过程中可能抛出的异常
+     */
+    @Bean
+    public SecurityFilterChain filterChain(HttpSecurity http,
+                                           JwtAuthenticationFilter jwtFilter // 直接注入 JwtAuthenticationFilter Bean
+                                           ) throws Exception {
+        http
+            // 禁用 CSRF (跨站请求伪造) 保护,因为 JWT 是无状态的,不需要 CSRF 保护
+            .csrf(AbstractHttpConfigurer::disable)
+            // 配置 Session 管理策略为无状态,不使用 Session
+            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
+            // 配置请求的授权规则
+            .authorizeHttpRequests(auth -> auth
+                // 允许对 /auth/** 路径下的所有请求进行匿名访问 (例如登录、注册接口)
+                .requestMatchers("/auth/**").permitAll()
+                // 其他所有请求都需要进行身份认证
+                .anyRequest().authenticated()
+            )
+            // 禁用默认的表单登录功能,因为我们使用 JWT 进行认证
+            .formLogin(AbstractHttpConfigurer::disable)
+            // 禁用默认的 HTTP Basic 认证
+            .httpBasic(AbstractHttpConfigurer::disable)
+            // 将自定义的 JwtAuthenticationFilter 添加到 UsernamePasswordAuthenticationFilter 之前
+            // 确保在进行基于用户名密码的认证之前,先进行 JWT 认证
+            .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
+
+        // 构建并返回配置好的 SecurityFilterChain
+        return http.build();
+    }
+
+    // 注意:JwtAuthenticationFilter 需要被 Spring 扫描到并作为 Bean 管理
+    // 确保 JwtAuthenticationFilter 类上有 @Component 或其他 Spring Bean 相关的注解
+}
+
+--- End of: ./config/SecurityConfig.java ---
+
+--- 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.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.example.demo.dto.LoginRequestDTO;
+import com.example.demo.dto.LoginResponseDTO;
+import com.example.demo.entity.User;
+import com.example.demo.exception.AuthException;
+import com.example.demo.security.JwtTokenUtil;
+import com.example.demo.service.UserService;
+
+import jakarta.validation.Valid;
+
+@RestController
+@RequestMapping("/api/auth")
+public class AuthController {
+
+    private final UserService userService;
+    private final PasswordEncoder passwordEncoder; // 虽然在AuthController中不再直接用于比对,但如果UserServiceImpl需要,这里保留注入
+    private final JwtTokenUtil jwtTokenUtil;
+    private final AuthenticationManager authenticationManager; // 注入 AuthenticationManager
+
+    public AuthController(UserService userService,
+                          PasswordEncoder passwordEncoder,
+                          JwtTokenUtil jwtTokenUtil,
+                          AuthenticationManager authenticationManager) { // 注入 AuthenticationManager
+        this.userService = userService;
+        this.passwordEncoder = passwordEncoder;
+        this.jwtTokenUtil = jwtTokenUtil;
+        this.authenticationManager = authenticationManager; // 初始化 AuthenticationManager
+    }
+
+    @PostMapping("/login")
+    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));
+        }
+
+        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);
+
+        } 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()));
+
+        return ResponseEntity.ok(response);
+        */
+    }
+}
+--- End of: ./controller/AuthController.java ---
+
+--- Content of: ./DemoApplication.java ---
+package com.example.demo;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+@SpringBootApplication
+@MapperScan("com.example.demo.mapper")
+public class DemoApplication {
+
+	public static void main(String[] args) {
+		SpringApplication.run(DemoApplication.class, args);
+	}
+
+}
+
+--- End of: ./DemoApplication.java ---
+
+--- Content of: ./dto/LoginRequestDTO.java ---
+package com.example.demo.dto;
+
+import java.io.Serializable;
+
+import jakarta.validation.constraints.NotBlank;
+
+
+public class LoginRequestDTO implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @NotBlank(message = "用户名不能为空")
+    private String username;
+
+    @NotBlank(message = "密码不能为空")
+    private String password;
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+}
+--- End of: ./dto/LoginRequestDTO.java ---
+
+--- Content of: ./dto/LoginResponseDTO.java ---
+package com.example.demo.dto;
+
+import java.io.Serializable;
+import java.time.Instant;
+import java.util.List;
+
+public class LoginResponseDTO implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /** 登录成功后返回的 JWT 或会话标识 */
+    private String token;
+
+    /** Token 过期时间戳 */
+    private Instant expiresAt;
+
+    /** 用户 ID,可选 */
+    private Long userId;
+
+    /** 用户名,可选 */
+    private String username;
+
+    /** 用户角色列表,可选 */
+    private List<String> roles;
+
+    public String getToken() {
+        return token;
+    }
+
+    public void setToken(String token) {
+        this.token = token;
+    }
+
+    public Instant getExpiresAt() {
+        return expiresAt;
+    }
+
+    public void setExpiresAt(Instant expiresAt) {
+        this.expiresAt = expiresAt;
+    }
+
+    public Long getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Long userId) {
+        this.userId = userId;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public List<String> getRoles() {
+        return roles;
+    }
+
+    public void setRoles(List<String> roles) {
+        this.roles = roles;
+    }
+}
+
+--- End of: ./dto/LoginResponseDTO.java ---
+
+--- Content of: ./entity/User.java ---
+package com.example.demo.entity;
+
+import java.time.LocalDateTime; // Keep import if Serializable is used elsewhere, otherwise remove
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List; // 用于返回空的权限集合,如果用户没有角色/权限
+
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority; // 用于构建权限列表
+import org.springframework.security.core.userdetails.UserDetails;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+
+
+@TableName("user")
+// 实现UserDetails接口 (UserDetails extends Serializable, so Serializable is redundant here)
+public class User implements UserDetails { 
+    private static final long serialVersionUID = 1L;
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    private String username;
+
+    private String password;
+
+    private String email;
+
+    private Integer status; // 假设status字段表示用户状态,例如 0:禁用, 1:启用
+
+    private Integer score;
+
+    private String role; // 假设role字段存储用户的角色,例如 "ADMIN", "USER"
+
+    private LocalDateTime createTime;
+
+    private LocalDateTime updateTime;
+
+    // --- Getters and Setters ---
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    @Override // 实现UserDetails接口的getUsername方法
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    @Override // 实现UserDetails接口的getPassword方法
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public String getEmail() {
+        return email;
+    }
+
+    public void setEmail(String email) {
+        this.email = email;
+    }
+
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+
+    public Integer getScore() {
+        return score;
+    }
+
+    public void setScore(Integer score) {
+        this.score = score;
+    }
+
+    public String getRole() {
+        return role;
+    }
+
+    public void setRole(String role) {
+        this.role = role;
+    }
+
+    public LocalDateTime getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(LocalDateTime createTime) {
+        this.createTime = createTime;
+    }
+
+    public LocalDateTime getUpdateTime() {
+        return updateTime;
+    }
+
+    public void setUpdateTime(LocalDateTime updateTime) {
+        this.updateTime = updateTime;
+    }
+
+    // --- 实现 UserDetails 接口的方法 ---
+
+    @Override
+    public Collection<? extends GrantedAuthority> getAuthorities() {
+        // 根据用户的角色构建权限列表
+        List<GrantedAuthority> authorities = new ArrayList<>();
+        if (this.role != null && !this.role.isEmpty()) {
+            // Spring Security 推荐角色以 "ROLE_" 开头
+            authorities.add(new SimpleGrantedAuthority("ROLE_" + this.role));
+        }
+        // 如果用户没有角色,返回一个空的集合
+        return authorities;
+    }
+
+    @Override
+    public boolean isAccountNonExpired() {
+        // 根据您的业务逻辑判断账户是否过期
+        // 例如,如果您的User实体有过期时间字段,可以在这里检查
+        // 这里简单返回true,表示账户永不过期
+        return true;
+    }
+
+    @Override
+    public boolean isAccountNonLocked() {
+        // 根据您的业务逻辑判断账户是否被锁定
+        // 例如,如果您的User实体有锁定状态字段,可以在这里检查
+        // 这里简单返回true,表示账户永不锁定
+        return true;
+    }
+
+    @Override
+    public boolean isCredentialsNonExpired() {
+        // 根据您的业务逻辑判断凭证(密码)是否过期
+        // 例如,如果您的User实体有密码最后修改时间字段,可以在这里检查
+        // 这里简单返回true,表示凭证永不过期
+        return true;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        // 根据您的业务逻辑判断账户是否启用
+        // 假设status字段为1表示启用
+        return this.status != null && this.status == 1;
+    }
+}
+
+--- End of: ./entity/User.java ---
+
+--- Content of: ./exception/AuthException.java ---
+package com.example.demo.exception;
+
+/**
+ * 自定义认证/授权异常
+ */
+public class AuthException extends RuntimeException {
+    public AuthException(String message) {
+        super(message);
+    }
+
+    public AuthException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
+
+--- End of: ./exception/AuthException.java ---
+
+--- Content of: ./exception/GlobalExceptionHandler.java ---
+package com.example.demo.exception;
+
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.FieldError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@ControllerAdvice
+public class GlobalExceptionHandler {
+
+    /** 处理自定义认证异常 */
+    @ExceptionHandler(AuthException.class)
+    public ResponseEntity<Map<String, String>> handleAuthException(AuthException ex) {
+        Map<String, String> body = Map.of("error", ex.getMessage());
+        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(body);
+    }
+
+    /** 处理请求参数校验失败 */
+    @ExceptionHandler(MethodArgumentNotValidException.class)
+    public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) {
+        Map<String, String> errors = new HashMap<>();
+        for (FieldError fe : ex.getBindingResult().getFieldErrors()) {
+            errors.put(fe.getField(), fe.getDefaultMessage());
+        }
+        return ResponseEntity.badRequest().body(errors);
+    }
+
+    /** 处理其它未捕获的异常 */
+    @ExceptionHandler(Exception.class)
+    public ResponseEntity<Map<String, String>> handleAllExceptions(Exception ex) {
+        Map<String, String> body = Map.of("error", "Internal server error");
+        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(body);
+    }
+}
+
+--- End of: ./exception/GlobalExceptionHandler.java ---
+
+--- Content of: ./mapper/UserMapper.java ---
+package com.example.demo.mapper;
+
+import org.apache.ibatis.annotations.Mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.example.demo.entity.User;
+
+/**
+ * UserMapper 接口
+ * 
+ * 继承 MyBatis-Plus 提供的 BaseMapper,实现基本的 CRUD 操作
+ */
+@Mapper
+public interface UserMapper extends BaseMapper<User> {
+
+    User findById(Long id);
+
+    User findByUsername(String username);
+    
+    // List<User> selectByStatus(@Param("status") Integer status);
+}
+
+
+--- End of: ./mapper/UserMapper.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.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+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 
+public class JwtAuthenticationFilter extends OncePerRequestFilter {
+
+    private final JwtTokenUtil tokenUtil;
+    private final JWTProperties props;
+
+    public JwtAuthenticationFilter(JwtTokenUtil tokenUtil, JWTProperties props) {
+        this.tokenUtil = tokenUtil;
+        this.props = props;
+    }
+
+    @Override
+    protected void doFilterInternal(HttpServletRequest request,
+                                    HttpServletResponse response,
+                                    FilterChain chain)
+            throws ServletException, IOException {
+
+        String headerValue = request.getHeader(props.getHeader());
+        if (!StringUtils.hasText(headerValue) || !headerValue.startsWith(props.getTokenPrefix())) {
+            chain.doFilter(request, response);
+            return;
+        }
+
+        String token = headerValue.substring(props.getTokenPrefix().length());
+        if (!tokenUtil.validateToken(token)) {
+            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);
+
+        UsernamePasswordAuthenticationToken auth =
+            new UsernamePasswordAuthenticationToken(
+                userId, 
+                null, 
+                List.of(new SimpleGrantedAuthority(role))
+            );
+        SecurityContextHolder.getContext().setAuthentication(auth);
+
+        chain.doFilter(request, response);
+    }
+}
+
+--- End of: ./security/JwtAuthenticationFilter.java ---
+
+--- Content of: ./security/JwtTokenUtil.java ---
+package com.example.demo.security;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+import java.util.Map;
+
+import org.springframework.stereotype.Component;
+
+import com.example.demo.config.JWTProperties;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.JwtException;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import io.jsonwebtoken.security.Keys;
+@Component
+public class JwtTokenUtil {
+
+    private final JWTProperties props;
+    private final byte[] secretBytes;
+
+    public JwtTokenUtil(JWTProperties props) {
+        this.props = props;
+        this.secretBytes = props.getSecret().getBytes(StandardCharsets.UTF_8);
+    }
+
+    /** 生成 JWT */
+    public String generateToken(Long userId, String username, String role) {
+        Date now = new Date();
+        Date expiry = new Date(now.getTime() + props.getExpirationMs());
+
+        return Jwts.builder()
+                .setSubject(username)
+                .setIssuer(props.getIssuer())
+                .setAudience(props.getAudience())
+                .setIssuedAt(now)
+                .setExpiration(expiry)
+                .addClaims(Map.of("userId", userId, "role", role))
+                .signWith(Keys.hmacShaKeyFor(secretBytes), SignatureAlgorithm.HS256)
+                .compact();
+    }
+
+    /** 验证并解析 Token,抛出异常则验证失败 */
+    public Claims parseClaims(String token) {
+        return Jwts.parserBuilder()
+                .setSigningKey(secretBytes)
+                .build()
+                .parseClaimsJws(token)
+                .getBody();
+    }
+
+    /** 校验 Token 是否有效 */
+    public boolean validateToken(String token) {
+        try {
+            parseClaims(token);
+            return true;
+        } catch (JwtException e) {
+            return false;
+        }
+    }
+
+    /** 从 Token 获取用户名(Subject) */
+    public String getUsername(String token) {
+        return parseClaims(token).getSubject();
+    }
+
+    /** 从 Token 获取用户 ID */
+    public Long getUserId(String token) {
+        Object id = parseClaims(token).get("userId");
+        return id == null ? null : Long.valueOf(id.toString());
+    }
+
+    /** 从 Token 获取角色 */
+    public String getRole(String token) {
+        return parseClaims(token).get("role", String.class);
+    }
+
+    /**
+     * 获取 JWT 的过期时间(毫秒)
+     * 修复:返回 JWTProperties 中配置的过期时间
+     */
+    public long getExpiration() {
+         return props.getExpirationMs();
+    }
+}
+
+--- End of: ./security/JwtTokenUtil.java ---
+
+--- Content of: ./service/UserService.java ---
+package com.example.demo.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.example.demo.dto.LoginResponseDTO;
+import com.example.demo.entity.User; // 导入 LoginResponseDTO
+
+/**
+ * User 业务层接口
+ * 继承了 MyBatis-Plus 的 IService,并定义了自定义的用户业务方法。
+ */
+public interface UserService extends IService<User> {
+    // 自定义方法:根据ID查找用户
+    User findById(Long id);
+
+    // 自定义方法:根据用户名查找用户
+    User findByUsername(String username);
+
+    // 自定义方法:创建新用户
+    User createUser(User user);
+
+    // 新增方法:生成用户登录响应(包括 JWT Token)
+    // 这个方法在 AuthController 中被调用,因此必须在接口中声明
+    LoginResponseDTO generateLoginResponse(User userDetails);
+
+    // 如果需要其他自定义方法,可以在这里再添加
+}
+
+--- End of: ./service/UserService.java ---
+
+--- Content of: ./service/impl/UserServiceImpl.java ---
+package com.example.demo.service.impl;
+
+// 导入UserService接口
+import java.time.Instant;
+import java.util.List;
+
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Service;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.example.demo.dto.LoginResponseDTO;
+import com.example.demo.entity.User;
+import com.example.demo.mapper.UserMapper;
+import com.example.demo.security.JwtTokenUtil;
+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;
+
+
+    /**
+     * 构造函数注入非BaseMapper的依赖
+     * ServiceImpl会自动注入BaseMapper
+     * @param passwordEncoder 密码编码器
+     * // 移除 authenticationManager 参数
+     * @param jwtTokenUtil JWT工具类
+     */
+    // @Autowired // 当只有一个构造函数时,@Autowired 可选
+    public UserServiceImpl(PasswordEncoder passwordEncoder,
+                           // AuthenticationManager authenticationManager, // 移除此参数
+                           JwtTokenUtil jwtTokenUtil) {
+        // 当继承ServiceImpl时,不需要在构造函数中注入并初始化BaseMapper
+        // this.userMapper = userMapper; // 移除这行
+
+        this.passwordEncoder = passwordEncoder;
+        // this.authenticationManager = authenticationManager; // 移除此初始化
+        this.jwtTokenUtil = jwtTokenUtil;
+    }
+
+    /**
+     * 根据用户ID查找用户
+     * 实现UserService接口中的findById方法。
+     * 可以直接使用ServiceImpl提供的getById方法。
+     * @param id 用户ID
+     * @return 找到的用户实体,如果不存在则返回null
+     */
+    @Override
+    public User findById(Long id) {
+        // 直接使用ServiceImpl提供的通用getById方法
+        return this.getById(id);
+        // 如果UserMapper有自定义的findById方法且需要使用,可以通过getBaseMapper()调用:
+        // return this.getBaseMapper().findById(id);
+    }
+
+    /**
+     * 根据用户名查找用户
+     * 实现UserService接口中的findByUsername方法。
+     * 可以使用ServiceImpl提供的getOne方法结合Wrapper进行条件查询。
+     * @param username 用户名
+     * @return 找到的用户实体,如果不存在则返回null
+     */
+    @Override
+    public User findByUsername(String username) {
+        // 使用ServiceImpl提供的通用getOne方法结合QueryWrapper进行条件查询
+        return this.getOne(new QueryWrapper<User>().eq("username", username));
+        // 如果UserMapper有自定义的findByUsername方法且需要使用,可以通过getBaseMapper()调用:
+        // return this.getBaseMapper().findByUsername(username);
+    }
+
+    /**
+     * 创建新用户
+     * 实现UserService接口中的createUser方法。
+     * 可以使用ServiceImpl提供的save方法。
+     * @param user 待创建的用户实体
+     * @return 创建成功的用户实体
+     */
+    @Override
+    public User createUser(User user) {
+        // 在保存前对用户密码进行加密
+        user.setPassword(passwordEncoder.encode(user.getPassword()));
+
+        // 使用ServiceImpl提供的通用save方法将用户插入数据库
+        this.save(user);
+        return user;
+    }
+
+    /**
+     * 实现Spring Security的UserDetailsService接口方法
+     * 根据用户名加载用户详情。
+     * 这个方法会被Spring Security的认证流程调用。
+     * @param username 用户名
+     * @return 实现UserDetails接口的用户详情对象
+     * @throws UsernameNotFoundException 如果用户不存在
+     */
+    @Override
+    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+        // 调用findByUsername方法从数据库查找用户
+        User user = findByUsername(username);
+        if (user == null) {
+            // 如果用户不存在,抛出UsernameNotFoundException
+            throw new UsernameNotFoundException("User not found with username: " + username);
+        }
+
+        // 返回Spring Security需要的UserDetails对象。
+        // 假设您的User实体类实现了UserDetails接口,并且其getAuthorities()方法正确返回了权限列表。
+        return user;
+    }
+
+    /**
+     * 用户登录认证成功后,生成JWT Token和构建响应DTO。
+     * 这个方法实现了UserService接口中声明的 generateLoginResponse 方法。
+     * @param userDetails 认证成功的用户详情(User实体)
+     * @return 包含JWT Token和相关信息的登录响应DTO
+     */
+    @Override // 添加 @Override 注解,明确表示实现接口方法
+    public LoginResponseDTO generateLoginResponse(User userDetails) {
+        // 使用JwtTokenUtil生成JWT Token
+        String token = jwtTokenUtil.generateToken(userDetails.getId(), userDetails.getUsername(), userDetails.getRole());
+
+        // 构建登录响应DTO
+        LoginResponseDTO response = new LoginResponseDTO();
+        response.setToken(token);
+        // 调用 JwtTokenUtil 的 getExpiration() 方法获取过期时间毫秒数
+        response.setExpiresAt(Instant.now().plusMillis(jwtTokenUtil.getExpiration()));
+        response.setUserId(userDetails.getId());
+        response.setUsername(userDetails.getUsername());
+        response.setRoles(List.of(userDetails.getRole())); // 假设角色是单个字符串,如果多个角色需要调整
+
+        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 ---
+
+--- End File Content ---
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 108e966..520908c 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
@@ -47,7 +47,7 @@
         }
 
         Claims claims = tokenUtil.parseClaims(token);
-        String username = claims.getSubject();
+        String username =tokenUtil.getUsername(token);
         Long userId = tokenUtil.getUserId(token);
         String role = tokenUtil.getRole(token);
 
diff --git a/backend/demo/src/main/java/com/example/demo/security/JwtTokenUtil.java b/backend/demo/src/main/java/com/example/demo/security/JwtTokenUtil.java
index 3f44d8a..7a9be5a 100644
--- a/backend/demo/src/main/java/com/example/demo/security/JwtTokenUtil.java
+++ b/backend/demo/src/main/java/com/example/demo/security/JwtTokenUtil.java
@@ -75,7 +75,11 @@
         return parseClaims(token).get("role", String.class);
     }
 
+    /**
+     * 获取 JWT 的过期时间(毫秒)
+     * 修复:返回 JWTProperties 中配置的过期时间
+     */
     public long getExpiration() {
-        throw new UnsupportedOperationException("Not supported yet.");
+         return props.getExpirationMs();
     }
 }
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
new file mode 100644
index 0000000..b863798
--- /dev/null
+++ b/backend/demo/src/main/java/com/example/demo/service/TorrentService.java
@@ -0,0 +1,26 @@
+// src/main/java/com/example/demo/service/TorrentService.java
+package com.example.demo.service;
+
+import java.io.IOException;
+
+import org.springframework.web.multipart.MultipartFile;
+
+import com.example.demo.dto.TorrentInfoDTO;
+import com.turn.ttorrent.bcodec.InvalidBEncodingException; // 导入 InvalidBEncodingException
+
+/**
+ * 处理 .torrent 文件上传及相关业务逻辑的服务接口。
+ */
+public interface TorrentService {
+
+    /**
+     * 处理上传的 .torrent 文件,解析其内容,保存到数据库,并生成相关链接。
+     *
+     * @param file 上传的 MultipartFile 对象
+     * @return 包含解析信息、磁力链接和下载 URL 的 TorrentInfoDTO
+     * @throws IOException 如果文件读取或保存失败
+     * @throws InvalidBEncodingException 如果 .torrent 文件内容不符合 B 编码规范
+     * @throws IllegalArgumentException 如果文件为空或解析后的元数据不完整
+     */
+    TorrentInfoDTO handleUpload(MultipartFile file) throws IOException, InvalidBEncodingException, IllegalArgumentException;
+}
\ No newline at end of file
diff --git a/backend/demo/src/main/java/com/example/demo/service/UserService.java b/backend/demo/src/main/java/com/example/demo/service/UserService.java
index 60e8aef..e4c5d7b 100644
--- a/backend/demo/src/main/java/com/example/demo/service/UserService.java
+++ b/backend/demo/src/main/java/com/example/demo/service/UserService.java
@@ -1,13 +1,26 @@
 package com.example.demo.service;
 
-
 import com.baomidou.mybatisplus.extension.service.IService;
-import com.example.demo.entity.User;
+import com.example.demo.dto.LoginResponseDTO;
+import com.example.demo.entity.User; // 导入 LoginResponseDTO
 
 /**
  * User 业务层接口
+ * 继承了 MyBatis-Plus 的 IService,并定义了自定义的用户业务方法。
  */
 public interface UserService extends IService<User> {
-    // 如果需要自定义方法,可以在这里再添加,例如:
-    // User findByUsername(String username);
+    // 自定义方法:根据ID查找用户
+    User findById(Long id);
+
+    // 自定义方法:根据用户名查找用户
+    User findByUsername(String username);
+
+    // 自定义方法:创建新用户
+    User createUser(User user);
+
+    // 新增方法:生成用户登录响应(包括 JWT Token)
+    // 这个方法在 AuthController 中被调用,因此必须在接口中声明
+    LoginResponseDTO generateLoginResponse(User userDetails);
+
+    // 如果需要其他自定义方法,可以在这里再添加
 }
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
new file mode 100644
index 0000000..eb00160
--- /dev/null
+++ b/backend/demo/src/main/java/com/example/demo/service/impl/TorrentServiceImpl.java
@@ -0,0 +1,126 @@
+// src/main/java/com/example/demo/service/impl/TorrentServiceImpl.java
+package com.example.demo.service.impl;
+
+import java.io.IOException;
+import java.nio.file.Files; // 导入 DTO
+import java.nio.file.Path; // 导入实体类
+import java.nio.file.Paths; // 导入 Mapper
+
+import org.springframework.beans.factory.annotation.Value; // 导入服务接口
+import org.springframework.stereotype.Service; // 导入工具类
+import org.springframework.transaction.annotation.Transactional; // 导入 ttorrent 的异常
+import org.springframework.web.multipart.MultipartFile; // 导入 @Value 用于读取配置
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.example.demo.dto.TorrentInfoDTO; // 导入事务注解
+import com.example.demo.entity.TorrentInfo;
+import com.example.demo.mapper.TorrentInfoMapper;
+import com.example.demo.service.TorrentService; // 用于文件操作
+import com.example.demo.util.TorrentUtils;  // 用于文件路径
+import com.turn.ttorrent.bcodec.InvalidBEncodingException; // 用于文件路径
+
+/**
+ * TorrentService 接口的实现类,负责处理 .torrent 文件的上传、解析、持久化及链接生成。
+ */
+@Service // 标记这是一个 Spring Service 组件
+public class TorrentServiceImpl implements TorrentService {
+
+    private final TorrentInfoMapper torrentInfoMapper;
+
+    @Value("${file.upload-dir}") // 从 application.properties 中注入文件上传目录
+    private String uploadDir;
+
+    // 通过构造函数注入 Mapper
+    public TorrentServiceImpl(TorrentInfoMapper torrentInfoMapper) {
+        this.torrentInfoMapper = torrentInfoMapper;
+    }
+
+    @Override
+    @Transactional // 确保文件保存和数据库操作的原子性
+    public TorrentInfoDTO handleUpload(MultipartFile file) throws IOException, InvalidBEncodingException, IllegalArgumentException {
+        if (file.isEmpty()) {
+            throw new IllegalArgumentException("上传文件不能为空。");
+        }
+
+        // 1. 调用 TorrentUtils 解析 .torrent 文件
+        TorrentUtils.TorrentParsedInfo parsedInfo;
+        try {
+            parsedInfo = TorrentUtils.parseTorrentFile(file);
+        } catch (InvalidBEncodingException e) {
+            throw new IllegalArgumentException("无法解析 .torrent 文件,文件格式不正确: " + e.getMessage(), e);
+        } catch (IOException e) {
+            throw new IOException("读取上传文件失败: " + e.getMessage(), e);
+        }
+
+        String infoHash = parsedInfo.getInfoHash();
+        String fileName = parsedInfo.getFileName();
+        long fileSize = parsedInfo.getFileSize();
+
+        // 2. 检查 infoHash 是否已存在,避免重复上传相同的洪流
+        QueryWrapper<TorrentInfo> queryWrapper = new QueryWrapper<>();
+        queryWrapper.eq("info_hash", infoHash); // 根据 info_hash 查询
+        TorrentInfo existingTorrent = torrentInfoMapper.selectOne(queryWrapper);
+
+        if (existingTorrent != null) {
+            System.out.println("Torrent with infoHash " + infoHash + " already exists. Returning existing info.");
+            // 如果已存在,直接返回现有信息对应的 DTO
+            return new TorrentInfoDTO(
+                    existingTorrent.getFileName(),
+                    existingTorrent.getFileSize(),
+                    existingTorrent.getInfoHash(),
+                    existingTorrent.getMagnetUri(),
+                    existingTorrent.getDownloadUrl(),
+                    "该洪流已存在,返回现有信息。"
+            );
+        }
+
+        // 3. 保存 .torrent 文件到服务器文件系统
+        // 使用 infoHash 作为文件名,确保唯一性,并保留原始扩展名 .torrent
+        String storedFileName = infoHash + ".torrent";
+        Path uploadPath = Paths.get(uploadDir).toAbsolutePath().normalize();
+        Path filePath = uploadPath.resolve(storedFileName);
+
+        try {
+            Files.createDirectories(uploadPath); // 如果目录不存在则创建
+            Files.copy(file.getInputStream(), filePath); // 将文件流复制到目标路径
+            System.out.println("Torrent file saved to: " + filePath.toString());
+        } catch (IOException e) {
+            // 如果文件保存失败,需要回滚之前的数据库操作 (如果前面有的话)
+            // @Transactional 会自动处理
+            throw new IOException("无法保存 .torrent 文件到磁盘: " + e.getMessage(), e);
+        }
+
+        // 4. 生成 magnet 链接 & downloadUrl
+        String magnetUri = String.format("magnet:?xt=urn:btih:%s&dn=%s", infoHash, fileName);
+        // downloadUrl 指向后端实际的下载接口
+        String downloadUrl = String.format("/api/downloads/%s/%s", infoHash, fileName);
+
+        // 5. 组装实体 TorrentInfo
+        TorrentInfo torrentInfo = new TorrentInfo(
+                infoHash,
+                fileName,
+                fileSize,
+                magnetUri,
+                downloadUrl
+        );
+
+        // 6. 调用 Mapper.insert() 持久化一条记录到数据库
+        int insertedRows = torrentInfoMapper.insert(torrentInfo);
+        if (insertedRows != 1) {
+            throw new RuntimeException("保存洪流信息到数据库失败。");
+        }
+        System.out.println("Saved TorrentInfo to database: " + torrentInfo);
+
+        // 7. 构造 DTO 返回给前端
+        TorrentInfoDTO dto = new TorrentInfoDTO(
+                torrentInfo.getFileName(),
+                torrentInfo.getFileSize(),
+                torrentInfo.getInfoHash(),
+                torrentInfo.getMagnetUri(),
+                torrentInfo.getDownloadUrl(),
+                "Torrent uploaded and processed successfully!"
+        );
+
+        return dto;
+    }
+}
\ 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 5bd1693..1fc434c 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
@@ -1,23 +1,171 @@
 package com.example.demo.service.impl;
 
-// UserServiceImpl.java
+// 导入UserService接口
+import java.time.Instant;
+import java.util.List;
 
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Service;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.example.demo.dto.LoginResponseDTO;
 import com.example.demo.entity.User;
 import com.example.demo.mapper.UserMapper;
+import com.example.demo.security.JwtTokenUtil;
 import com.example.demo.service.UserService;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
+
 
 /**
- * User 业务层默认实现
+ * 用户业务逻辑服务实现类
+ * 继承了MyBatis-Plus的ServiceImpl,并实现了UserService接口和Spring Security的UserDetailsService接口。
+ * 利用ServiceImpl提供的通用CRUD方法,并实现自定义的用户业务操作和加载用户详情。
+ * 用户登录认证逻辑已移至 Controller 层,以解决循环依赖问题。
  */
 @Service
-@Transactional(rollbackFor = Exception.class)
-public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
-    // 如果你在接口中声明了自定义方法,这里可以重写并实现:
-    // @Override
-    // public User findByUsername(String username) {
-    //     return lambdaQuery().eq(User::getUsername, username).one();
-    // }
+// 继承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;
+
+
+    /**
+     * 构造函数注入非BaseMapper的依赖
+     * ServiceImpl会自动注入BaseMapper
+     * @param passwordEncoder 密码编码器
+     * // 移除 authenticationManager 参数
+     * @param jwtTokenUtil JWT工具类
+     */
+    // @Autowired // 当只有一个构造函数时,@Autowired 可选
+    public UserServiceImpl(PasswordEncoder passwordEncoder,
+                           // AuthenticationManager authenticationManager, // 移除此参数
+                           JwtTokenUtil jwtTokenUtil) {
+        // 当继承ServiceImpl时,不需要在构造函数中注入并初始化BaseMapper
+        // this.userMapper = userMapper; // 移除这行
+
+        this.passwordEncoder = passwordEncoder;
+        // this.authenticationManager = authenticationManager; // 移除此初始化
+        this.jwtTokenUtil = jwtTokenUtil;
+    }
+
+    /**
+     * 根据用户ID查找用户
+     * 实现UserService接口中的findById方法。
+     * 可以直接使用ServiceImpl提供的getById方法。
+     * @param id 用户ID
+     * @return 找到的用户实体,如果不存在则返回null
+     */
+    @Override
+    public User findById(Long id) {
+        // 直接使用ServiceImpl提供的通用getById方法
+        return this.getById(id);
+        // 如果UserMapper有自定义的findById方法且需要使用,可以通过getBaseMapper()调用:
+        // return this.getBaseMapper().findById(id);
+    }
+
+    /**
+     * 根据用户名查找用户
+     * 实现UserService接口中的findByUsername方法。
+     * 可以使用ServiceImpl提供的getOne方法结合Wrapper进行条件查询。
+     * @param username 用户名
+     * @return 找到的用户实体,如果不存在则返回null
+     */
+    @Override
+    public User findByUsername(String username) {
+        // 使用ServiceImpl提供的通用getOne方法结合QueryWrapper进行条件查询
+        return this.getOne(new QueryWrapper<User>().eq("username", username));
+        // 如果UserMapper有自定义的findByUsername方法且需要使用,可以通过getBaseMapper()调用:
+        // return this.getBaseMapper().findByUsername(username);
+    }
+
+    /**
+     * 创建新用户
+     * 实现UserService接口中的createUser方法。
+     * 可以使用ServiceImpl提供的save方法。
+     * @param user 待创建的用户实体
+     * @return 创建成功的用户实体
+     */
+    @Override
+    public User createUser(User user) {
+        // 在保存前对用户密码进行加密
+        user.setPassword(passwordEncoder.encode(user.getPassword()));
+
+        // 使用ServiceImpl提供的通用save方法将用户插入数据库
+        this.save(user);
+        return user;
+    }
+
+    /**
+     * 实现Spring Security的UserDetailsService接口方法
+     * 根据用户名加载用户详情。
+     * 这个方法会被Spring Security的认证流程调用。
+     * @param username 用户名
+     * @return 实现UserDetails接口的用户详情对象
+     * @throws UsernameNotFoundException 如果用户不存在
+     */
+    @Override
+    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+        // 调用findByUsername方法从数据库查找用户
+        User user = findByUsername(username);
+        if (user == null) {
+            // 如果用户不存在,抛出UsernameNotFoundException
+            throw new UsernameNotFoundException("User not found with username: " + username);
+        }
+
+        // 返回Spring Security需要的UserDetails对象。
+        // 假设您的User实体类实现了UserDetails接口,并且其getAuthorities()方法正确返回了权限列表。
+        return user;
+    }
+
+    /**
+     * 用户登录认证成功后,生成JWT Token和构建响应DTO。
+     * 这个方法实现了UserService接口中声明的 generateLoginResponse 方法。
+     * @param userDetails 认证成功的用户详情(User实体)
+     * @return 包含JWT Token和相关信息的登录响应DTO
+     */
+    @Override // 添加 @Override 注解,明确表示实现接口方法
+    public LoginResponseDTO generateLoginResponse(User userDetails) {
+        // 使用JwtTokenUtil生成JWT Token
+        String token = jwtTokenUtil.generateToken(userDetails.getId(), userDetails.getUsername(), userDetails.getRole());
+
+        // 构建登录响应DTO
+        LoginResponseDTO response = new LoginResponseDTO();
+        response.setToken(token);
+        // 调用 JwtTokenUtil 的 getExpiration() 方法获取过期时间毫秒数
+        response.setExpiresAt(Instant.now().plusMillis(jwtTokenUtil.getExpiration()));
+        response.setUserId(userDetails.getId());
+        response.setUsername(userDetails.getUsername());
+        response.setRoles(List.of(userDetails.getRole())); // 假设角色是单个字符串,如果多个角色需要调整
+
+        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
new file mode 100644
index 0000000..3be143b
--- /dev/null
+++ b/backend/demo/src/main/java/com/example/demo/sh.py
@@ -0,0 +1,76 @@
+import os
+
+def read_files_and_write_to_file(file_paths, output_file):
+    """
+    Reads the content of specified files and writes it to an output file.
+
+    Args:
+        file_paths (list): A list of strings, where each string is the path to a file.
+        output_file (str): The path to the file where the output will be written.
+    """
+    try:
+        # Open the output file in write mode ('w').
+        # Use 'utf-8' encoding for wide compatibility.
+        with open(output_file, 'w', encoding='utf-8') as outfile:
+            outfile.write("--- Start File Content ---\n")
+            print(f"Reading files and writing to {output_file}...") # Print to console for feedback
+
+            for file_path in file_paths:
+                if not os.path.exists(file_path):
+                    error_msg = f"Error: File not found at path: {file_path}\n"
+                    outfile.write(error_msg)
+                    print(error_msg.strip()) # Also print error to console
+                    continue
+                if not os.path.isfile(file_path):
+                    error_msg = f"Error: Path is not a file: {file_path}\n"
+                    outfile.write(error_msg)
+                    print(error_msg.strip()) # Also print error to console
+                    continue
+
+                try:
+                    with open(file_path, 'r', encoding='utf-8') as infile:
+                        content = infile.read()
+                        outfile.write(f"\n--- Content of: {file_path} ---\n")
+                        outfile.write(content)
+                        outfile.write(f"\n--- End of: {file_path} ---\n")
+                        print(f"Successfully read: {file_path}") # Print success to console
+                except Exception as e:
+                    error_msg = f"Error reading file {file_path}: {e}\n"
+                    outfile.write(error_msg)
+                    print(error_msg.strip()) # Also print error to console
+
+            outfile.write("\n--- End File Content ---\n")
+            print("Finished writing file content.")
+
+    except Exception as e:
+        print(f"Error opening or writing to output file {output_file}: {e}")
+
+
+if __name__ == "__main__":
+    # 请在这里列出您想要读取的文件路径
+    # 确保路径是相对于您运行脚本的位置,或者使用绝对路径
+    your_file_paths = [
+        './config/JWTProperties.java',
+        './config/SecurityConfig.java',
+        './controller/AuthController.java',
+        './DemoApplication.java',
+        './dto/LoginRequestDTO.java',
+        './dto/LoginResponseDTO.java',
+        './entity/User.java',
+        './exception/AuthException.java',
+        './exception/GlobalExceptionHandler.java',
+        './mapper/UserMapper.java',
+        './security/JwtAuthenticationFilter.java',
+        './security/JwtTokenUtil.java',
+        './service/UserService.java',
+        './service/impl/UserServiceImpl.java', # 假设您已经创建了这个文件
+         
+    ]
+
+    # 指定输出文件的名称
+    output_filename = "result.txt"
+
+    # 调用函数,将文件内容写入到 output_filename
+    read_files_and_write_to_file(your_file_paths, output_filename)
+
+
diff --git a/backend/demo/src/main/java/com/example/demo/util/TorrentUtils.java b/backend/demo/src/main/java/com/example/demo/util/TorrentUtils.java
new file mode 100644
index 0000000..465a167
--- /dev/null
+++ b/backend/demo/src/main/java/com/example/demo/util/TorrentUtils.java
@@ -0,0 +1,102 @@
+// src/main/java/com/example/demo/util/TorrentUtils.java
+package com.example.demo.util;
+
+import java.io.IOException; // 导入 TorrentMetadata 接口
+
+import org.springframework.web.multipart.MultipartFile; // 导入 TorrentParser 类
+
+import com.turn.ttorrent.bcodec.InvalidBEncodingException;
+import com.turn.ttorrent.common.TorrentMetadata;
+import com.turn.ttorrent.common.TorrentParser;
+
+public class TorrentUtils {
+
+    /**
+     * 解析 MultipartFile 中的 .torrent 文件,提取关键信息。
+     *
+     * 该方法利用 ttorrent-common 库中的 TorrentParser 来解析 .torrent 文件的字节内容,
+     * 并从解析得到的 TorrentMetadata 对象中提取 info hash、文件名和文件大小。
+     *
+     * @param file 上传的 MultipartFile 对象
+     * @return 包含解析信息的 TorrentParsedInfo 对象
+     * @throws IOException 如果文件读取失败
+     * @throws BDecodingException 如果 .torrent 文件内容不符合 B 编码规范
+     * @throws IllegalArgumentException 如果文件为空或解析后的元数据不完整
+     */
+    public static TorrentParsedInfo parseTorrentFile(MultipartFile file)
+            throws IOException, IllegalArgumentException {
+
+        if (file.isEmpty()) {
+            throw new IllegalArgumentException("Uploaded file is empty.");
+        }
+
+        byte[] torrentBytes = file.getBytes();
+
+        // 使用 ttorrent-common 库的 TorrentParser 来解析字节数组
+        TorrentParser parser = new TorrentParser();
+        TorrentMetadata metadata;
+        try {
+            metadata = parser.parse(torrentBytes);
+        } catch (com.turn.ttorrent.bcodec.InvalidBEncodingException e) {
+            // 将 InvalidBEncodingException 转换为 BDecodingException
+            throw new InvalidBEncodingException("Invalid B-encoding in .torrent file: " + e.getMessage());
+        } catch (RuntimeException e) {
+            // TorrentParser.parse 可能会包装 IOException 为 RuntimeException
+            if (e.getCause() instanceof IOException) {
+                throw (IOException) e.getCause();
+            }
+            throw e; // 重新抛出其他 RuntimeException
+        }
+
+        // 从 TorrentMetadata 对象中提取所需信息
+        String infoHash = metadata.getHexInfoHash(); // 获取十六进制表示的 info hash
+        String fileName = metadata.getDirectoryName(); // 获取洪流的名称(通常是目录名或单文件名)
+        long totalSize = 0;
+
+        // 计算总文件大小
+        // TorrentMetadata.getFiles() 返回 List<TorrentFile>
+        // TorrentFile 包含 size 字段
+        if (metadata.getFiles() != null && !metadata.getFiles().isEmpty()) {
+            for (com.turn.ttorrent.common.TorrentFile torrentFile : metadata.getFiles()) {
+                totalSize += torrentFile.size;
+            }
+        } else {
+            // 如果没有文件列表,可能是单文件模式,但 TorrentMetadata 接口没有直接提供总大小
+            // 在实际应用中,如果 TorrentMetadata 无法直接提供总大小,
+            // 并且 getFiles() 为空,可能需要回溯到原始的 BEValue 来获取 'length' 字段
+            // 但根据 TorrentMetadata 接口,getFiles() 应该能覆盖所有情况。
+            System.err.println("Warning: Could not determine total file size from TorrentMetadata.getFiles().");
+        }
+
+
+        // 构建并返回解析信息
+        return new TorrentParsedInfo(infoHash, fileName, totalSize);
+    }
+
+    /**
+     * 内部类,用于封装解析后的洪流信息。
+     */
+    public static class TorrentParsedInfo {
+        private final String infoHash;
+        private final String fileName;
+        private final long fileSize;
+
+        public TorrentParsedInfo(String infoHash, String fileName, long fileSize) {
+            this.infoHash = infoHash;
+            this.fileName = fileName;
+            this.fileSize = fileSize;
+        }
+
+        public String getInfoHash() {
+            return infoHash;
+        }
+
+        public String getFileName() {
+            return fileName;
+        }
+
+        public long getFileSize() {
+            return fileSize;
+        }
+    }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..cf06b61
--- /dev/null
+++ b/backend/demo/src/main/resources/META-INF/additional-spring-configuration-metadata.json
@@ -0,0 +1,5 @@
+{"properties": [{
+  "name": "file.upload-dir",
+  "type": "java.lang.String",
+  "description": "Torrent dir for uploading'"
+}]}
\ 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 4472028..9bbb45b 100644
--- a/backend/demo/src/main/resources/application.properties
+++ b/backend/demo/src/main/resources/application.properties
@@ -1,5 +1,5 @@
 # ========== 数据源 ==========
-spring.datasource.url=jdbc:mysql://mysql:3306/mydatabase?serverTimezone=Asia/Shanghai
+spring.datasource.url=jdbc:mysql://mysql:3306/mydatabase?serverTimezone=Asia/Shanghai&createDatabaseIfNotExist=TRUE&useSSL=FALSE&allowPublicKeyRetrieval=TRUE
 spring.datasource.username=myuser
 spring.datasource.password=secret
 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
@@ -10,3 +10,6 @@
 
 # ========== 日志输出(可选) ==========
 logging.level.org.springframework.security=DEBUG
+
+file.upload-dir=./torrents
+