Merge "增加了悬赏,标签查看,评论页面,标签上传后端有问题,评论还没跟后端连,优化了一些小界面"
diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml
index 4fe1dc0..0f7fa42 100644
--- a/ruoyi-admin/pom.xml
+++ b/ruoyi-admin/pom.xml
@@ -46,6 +46,7 @@
         <dependency>
             <groupId>com.mysql</groupId>
             <artifactId>mysql-connector-j</artifactId>
+            <version>8.2.0</version>
         </dependency>
 
         <!-- 核心模块-->
@@ -66,6 +67,23 @@
             <artifactId>ruoyi-generator</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>org.hibernate.validator</groupId>
+            <artifactId>hibernate-validator</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>annotationProcessor</scope>
+        </dependency>
+
+
     </dependencies>
 
     <build>
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/torrent/util/TorrentFileUtil.java b/ruoyi-admin/src/main/java/com/ruoyi/torrent/util/TorrentFileUtil.java
new file mode 100644
index 0000000..a1cd422
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/torrent/util/TorrentFileUtil.java
@@ -0,0 +1,234 @@
+package com.ruoyi.torrent.util;
+
+import org.springframework.web.multipart.MultipartFile;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.*;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class TorrentFileUtil {
+
+    /**
+     * 文件上传工具方法
+     *
+     * @param file     上传的文件对象
+     * @param savePath 文件保存路径
+     * @return 返回包含文件信息的Map
+     * @throws IOException 文件操作异常
+     */
+    public static Map<String, Object> uploadFile(MultipartFile file, String savePath) throws IOException {
+        if (file.isEmpty()) {
+            throw new IllegalArgumentException("上传文件不能为空");
+        }
+
+        // 确保目录存在
+        Path saveDir = Paths.get(savePath);
+        if (!Files.exists(saveDir)) {
+            Files.createDirectories(saveDir);
+        }
+
+        // 获取文件信息
+        String originalFilename = Objects.requireNonNull(file.getOriginalFilename());
+        String fileExtension = originalFilename.substring(originalFilename.lastIndexOf("."));
+        String storedFilename = System.currentTimeMillis() + "_" + UUID.randomUUID() + fileExtension;
+        long fileSize = file.getSize();
+
+        // 保存文件
+        Path targetPath = saveDir.resolve(storedFilename);
+        Files.copy(file.getInputStream(), targetPath, StandardCopyOption.REPLACE_EXISTING);
+
+        // 返回文件信息
+        Map<String, Object> fileInfo = new HashMap<>();
+        fileInfo.put("originalName", originalFilename);
+        fileInfo.put("storedName", storedFilename);
+        fileInfo.put("filePath", targetPath.toString());
+        fileInfo.put("fileSize", fileSize);
+        fileInfo.put("fileType", fileExtension.substring(1));
+        fileInfo.put("uploadTime", new Date());
+
+        return fileInfo;
+    }
+
+    /**
+     * 文件下载工具方法
+     *
+     * @param response   HttpServletResponse对象
+     * @param filePath   要下载的文件路径
+     * @param fileName   下载时显示的文件名
+     * @param deleteAfterDownload 下载后是否删除原文件
+     * @throws IOException 文件操作异常
+     */
+    public static void downloadFile(HttpServletResponse response, String filePath,
+                                    String fileName, boolean deleteAfterDownload) throws IOException {
+        Path file = Paths.get(filePath);
+        if (!Files.exists(file)) {
+            throw new FileNotFoundException("文件不存在: " + filePath);
+        }
+
+        // 设置响应头
+        response.setContentType("application/octet-stream");
+        response.setHeader("Content-Disposition",
+                "attachment;filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8));
+        response.setContentLength((int) Files.size(file));
+
+        // 使用NIO提高性能
+        try (InputStream is = Files.newInputStream(file);
+             OutputStream os = response.getOutputStream()) {
+            byte[] buffer = new byte[4096];
+            int bytesRead;
+            while ((bytesRead = is.read(buffer)) != -1) {
+                os.write(buffer, 0, bytesRead);
+            }
+            os.flush();
+        }
+
+        // 下载后删除原文件
+        if (deleteAfterDownload) {
+            Files.delete(file);
+        }
+    }
+
+    /**
+     * 文件下载工具方法(简化版,不删除原文件)
+     */
+    public static void downloadFile(HttpServletResponse response, String filePath, String fileName) throws IOException {
+        downloadFile(response, filePath, fileName, false);
+    }
+
+    /**
+     * 删除文件
+     *
+     * @param filePath 文件路径
+     * @return 是否删除成功
+     */
+    public static boolean deleteFile(String filePath) {
+        try {
+            return Files.deleteIfExists(Paths.get(filePath));
+        } catch (IOException e) {
+            return false;
+        }
+    }
+
+    /**
+     * 重命名文件
+     *
+     * @param oldPath 原文件路径
+     * @param newName 新文件名(不含路径)
+     * @return 新文件路径
+     * @throws IOException 文件操作异常
+     */
+    public static String renameFile(String oldPath, String newName) throws IOException {
+        Path source = Paths.get(oldPath);
+        Path target = source.resolveSibling(newName);
+        return Files.move(source, target, StandardCopyOption.REPLACE_EXISTING).toString();
+    }
+
+    /**
+     * 获取文件信息
+     *
+     * @param filePath 文件路径
+     * @return 包含文件信息的Map
+     * @throws IOException 文件操作异常
+     */
+    public static Map<String, Object> getFileInfo(String filePath) throws IOException {
+        Path path = Paths.get(filePath);
+        if (!Files.exists(path)) {
+            return null;
+        }
+
+        Map<String, Object> fileInfo = new HashMap<>();
+        fileInfo.put("fileName", path.getFileName().toString());
+        fileInfo.put("filePath", path.toString());
+        fileInfo.put("fileSize", Files.size(path));
+        fileInfo.put("lastModified", new Date(Files.getLastModifiedTime(path).toMillis()));
+        fileInfo.put("isDirectory", Files.isDirectory(path));
+
+        return fileInfo;
+    }
+
+    /**
+     * 获取目录下的文件列表
+     *
+     * @param dirPath 目录路径
+     * @return 文件信息列表
+     * @throws IOException 文件操作异常
+     */
+    public static List<Map<String, Object>> listFiles(String dirPath) throws IOException {
+        return Files.list(Paths.get(dirPath))
+                .map(path -> {
+                    try {
+                        Map<String, Object> fileInfo = new HashMap<>();
+                        fileInfo.put("name", path.getFileName().toString());
+                        fileInfo.put("path", path.toString());
+                        fileInfo.put("size", Files.size(path));
+                        fileInfo.put("lastModified", new Date(Files.getLastModifiedTime(path).toMillis()));
+                        fileInfo.put("isDirectory", Files.isDirectory(path));
+                        return fileInfo;
+                    } catch (IOException e) {
+                        return null;
+                    }
+                })
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * 复制文件
+     *
+     * @param sourcePath 源文件路径
+     * @param targetPath 目标文件路径
+     * @return 是否复制成功
+     */
+    public static boolean copyFile(String sourcePath, String targetPath) {
+        try {
+            Files.copy(Paths.get(sourcePath), Paths.get(targetPath), StandardCopyOption.REPLACE_EXISTING);
+            return true;
+        } catch (IOException e) {
+            return false;
+        }
+    }
+
+    /**
+     * 获取文件大小(格式化字符串)
+     *
+     * @param size 文件大小(字节)
+     * @return 格式化后的字符串
+     */
+    public static String formatFileSize(long size) {
+        if (size <= 0) return "0B";
+        String[] units = new String[]{"B", "KB", "MB", "GB", "TB"};
+        int digitGroups = (int) (Math.log10(size) / Math.log10(1024));
+        return String.format("%.2f %s", size / Math.pow(1024, digitGroups), units[digitGroups]);
+    }
+
+    /**
+     * 检查文件是否存在
+     *
+     * @param filePath 文件路径
+     * @return 是否存在
+     */
+    public static boolean fileExists(String filePath) {
+        return Files.exists(Paths.get(filePath));
+    }
+
+    /**
+     * 创建目录
+     *
+     * @param dirPath 目录路径
+     * @return 是否创建成功
+     */
+    public static boolean createDirectory(String dirPath) {
+        try {
+            Files.createDirectories(Paths.get(dirPath));
+            return true;
+        } catch (IOException e) {
+            return false;
+        }
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysTorrentCommentController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysTorrentCommentController.java
new file mode 100644
index 0000000..44524a5
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysTorrentCommentController.java
@@ -0,0 +1,35 @@
+// 种子评论控制器
+package com.ruoyi.web.controller.system;
+
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.system.domain.SysTorrentComment;
+import com.ruoyi.system.service.ISysTorrentCommentService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/system/torrent/comment")
+public class SysTorrentCommentController extends BaseController {
+    @Autowired
+    private ISysTorrentCommentService commentService;
+
+    @PreAuthorize("@ss.hasPermi('system:torrent:comment:add')")
+    @PostMapping
+    public AjaxResult add(@RequestBody SysTorrentComment comment) {
+        comment.setUserId(getUserId());
+        return toAjax(commentService.addComment(comment));
+    }
+
+    @GetMapping("/{torrentId}")
+    public AjaxResult list(@PathVariable Long torrentId) {
+        return AjaxResult.success(commentService.getCommentList(torrentId));
+    }
+
+    @PreAuthorize("@ss.hasPermi('system:torrent:comment:remove')")
+    @DeleteMapping("/{commentId}")
+    public AjaxResult remove(@PathVariable Long commentId) {
+        return toAjax(commentService.deleteComment(commentId));
+    }
+}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserFollowController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserFollowController.java
new file mode 100644
index 0000000..17d7ca1
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserFollowController.java
@@ -0,0 +1,44 @@
+// 作者关注控制器
+package com.ruoyi.web.controller.system;
+
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.system.domain.SysUserFollow;
+import com.ruoyi.system.service.ISysUserFollowService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/system/user/follow")
+public class SysUserFollowController extends BaseController {
+    @Autowired
+    private ISysUserFollowService followService;
+
+    @PreAuthorize("@ss.hasPermi('system:user:follow:add')")
+    @PostMapping
+    public AjaxResult follow(@RequestBody SysUserFollow follow) {
+        follow.setUserId(getUserId());
+        return toAjax(followService.followAuthor(follow));
+    }
+
+    @PreAuthorize("@ss.hasPermi('system:user:follow:remove')")
+    @DeleteMapping
+    public AjaxResult unfollow(@RequestBody SysUserFollow follow) {
+        follow.setUserId(getUserId());
+        return toAjax(followService.unfollowAuthor(follow));
+    }
+
+    @GetMapping("/list")
+    public AjaxResult list() {
+        return AjaxResult.success(followService.getFollowList(getUserId()));
+    }
+
+    @GetMapping("/isFollowing/{authorId}")
+    public AjaxResult isFollowing(@PathVariable Long authorId) {
+        SysUserFollow follow = new SysUserFollow();
+        follow.setUserId(getUserId());
+        follow.setAuthorId(authorId);
+        return AjaxResult.success(followService.isFollowing(follow));
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserMessageController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserMessageController.java
new file mode 100644
index 0000000..d6b030c
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserMessageController.java
@@ -0,0 +1,28 @@
+package com.ruoyi.web.controller.system;
+
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.system.domain.SysUserMessage;
+import com.ruoyi.system.service.ISysUserMessageService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/system/user/message")
+public class SysUserMessageController extends BaseController {
+    @Autowired
+    private ISysUserMessageService messageService;
+
+    @PreAuthorize("@ss.hasPermi('system:user:message:add')")
+    @PostMapping
+    public AjaxResult sendMessage(@RequestBody SysUserMessage message) {
+        message.setSenderId(getUserId());
+        return toAjax(messageService.sendMessage(message));
+    }
+
+    @GetMapping("/list")
+    public AjaxResult list(@RequestParam Long userId1, @RequestParam Long userId2) {
+        return AjaxResult.success(messageService.getMessageList(userId1, userId2));
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-system/pom.xml b/ruoyi-system/pom.xml
index 51f3f3c..c688993 100644
--- a/ruoyi-system/pom.xml
+++ b/ruoyi-system/pom.xml
@@ -16,12 +16,31 @@
     </description>
 
     <dependencies>
-
         <!-- 通用工具-->
+
         <dependency>
             <groupId>com.ruoyi</groupId>
             <artifactId>ruoyi-common</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>annotationProcessor</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
 
     </dependencies>
 
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTorrentComment.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTorrentComment.java
new file mode 100644
index 0000000..940a358
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTorrentComment.java
@@ -0,0 +1,31 @@
+//种子评论
+package com.ruoyi.system.domain;
+
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.core.domain.BaseEntity;
+import lombok.Data;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+
+@Data
+public class SysTorrentComment extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    @Excel(name = "评论ID")
+    private Long commentId;
+
+    @Excel(name = "种子ID")
+    @NotNull(message = "种子ID不能为空")
+    private Long torrentId;
+
+    @Excel(name = "用户ID")
+    @NotNull(message = "用户ID不能为空")
+    private Long userId;
+
+    @Excel(name = "评论内容")
+    @NotBlank(message = "评论内容不能为空")
+    private String content;
+
+    @Excel(name = "父评论ID")
+    private Long parentId;
+}
\ No newline at end of file
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserFollow.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserFollow.java
new file mode 100644
index 0000000..a5878b1
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserFollow.java
@@ -0,0 +1,23 @@
+// 作者关注
+package com.ruoyi.system.domain;
+
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.core.domain.BaseEntity;
+import lombok.Data;
+import jakarta.validation.constraints.NotNull;
+
+@Data
+public class SysUserFollow extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    @Excel(name = "关注ID")
+    private Long followId;
+
+    @Excel(name = "用户ID")
+    @NotNull(message = "用户ID不能为空")
+    private Long userId;
+
+    @Excel(name = "作者ID")
+    @NotNull(message = "作者ID不能为空")
+    private Long authorId;
+}
\ No newline at end of file
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserMessage.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserMessage.java
new file mode 100644
index 0000000..0796dd5
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUserMessage.java
@@ -0,0 +1,27 @@
+package com.ruoyi.system.domain;
+
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.core.domain.BaseEntity;
+import lombok.Data;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+
+@Data
+public class SysUserMessage extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    @Excel(name = "消息ID")
+    private Long messageId;
+
+    @Excel(name = "发送者ID")
+    @NotNull(message = "发送者ID不能为空")
+    private Long senderId;
+
+    @Excel(name = "接收者ID")
+    @NotNull(message = "接收者ID不能为空")
+    private Long receiverId;
+
+    @Excel(name = "消息内容")
+    @NotBlank(message = "消息内容不能为空")
+    private String content;
+}
\ No newline at end of file
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTorrentCommentMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTorrentCommentMapper.java
new file mode 100644
index 0000000..2f519d1
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTorrentCommentMapper.java
@@ -0,0 +1,11 @@
+// 种子评论 Mapper
+package com.ruoyi.system.mapper;
+
+import com.ruoyi.system.domain.SysTorrentComment;
+import java.util.List;
+
+public interface SysTorrentCommentMapper {
+    int insertComment(SysTorrentComment comment);
+    List<SysTorrentComment> selectCommentListByTorrentId(Long torrentId);
+    int deleteCommentById(Long commentId);
+}
\ No newline at end of file
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserFollowMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserFollowMapper.java
new file mode 100644
index 0000000..77bca7e
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserFollowMapper.java
@@ -0,0 +1,12 @@
+// 作者关注 Mapper
+package com.ruoyi.system.mapper;
+
+import com.ruoyi.system.domain.SysUserFollow;
+import java.util.List;
+
+public interface SysUserFollowMapper {
+    int insertFollow(SysUserFollow follow);
+    int deleteFollow(SysUserFollow follow);
+    List<SysUserFollow> selectFollowListByUserId(Long userId);
+    SysUserFollow selectFollow(SysUserFollow follow);
+}
\ No newline at end of file
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMessageMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMessageMapper.java
new file mode 100644
index 0000000..6e2fc1d
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMessageMapper.java
@@ -0,0 +1,9 @@
+package com.ruoyi.system.mapper;
+
+import com.ruoyi.system.domain.SysUserMessage;
+import java.util.List;
+
+public interface SysUserMessageMapper {
+    int insertMessage(SysUserMessage message);
+    List<SysUserMessage> selectMessageListByUserIds(Long userId1, Long userId2);
+}
\ No newline at end of file
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTorrentCommentService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTorrentCommentService.java
new file mode 100644
index 0000000..49106cb
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTorrentCommentService.java
@@ -0,0 +1,11 @@
+// 种子评论服务接口
+package com.ruoyi.system.service;
+
+import com.ruoyi.system.domain.SysTorrentComment;
+import java.util.List;
+
+public interface ISysTorrentCommentService {
+    int addComment(SysTorrentComment comment);
+    List<SysTorrentComment> getCommentList(Long torrentId);
+    int deleteComment(Long commentId);
+}
\ No newline at end of file
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserFollowService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserFollowService.java
new file mode 100644
index 0000000..e79824e
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserFollowService.java
@@ -0,0 +1,12 @@
+// 作者关注服务接口
+package com.ruoyi.system.service;
+
+import com.ruoyi.system.domain.SysUserFollow;
+import java.util.List;
+
+public interface ISysUserFollowService {
+    int followAuthor(SysUserFollow follow);
+    int unfollowAuthor(SysUserFollow follow);
+    List<SysUserFollow> getFollowList(Long userId);
+    boolean isFollowing(SysUserFollow follow);
+}
\ No newline at end of file
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserMessageService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserMessageService.java
new file mode 100644
index 0000000..1bcc9f5
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserMessageService.java
@@ -0,0 +1,9 @@
+package com.ruoyi.system.service;
+
+import com.ruoyi.system.domain.SysUserMessage;
+import java.util.List;
+
+public interface ISysUserMessageService {
+    int sendMessage(SysUserMessage message);
+    List<SysUserMessage> getMessageList(Long userId1, Long userId2);
+}
\ No newline at end of file
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTorrentCommentServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTorrentCommentServiceImpl.java
new file mode 100644
index 0000000..28603d4
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTorrentCommentServiceImpl.java
@@ -0,0 +1,30 @@
+// 种子评论服务实现 (SysTorrentCommentServiceImpl.java)
+package com.ruoyi.system.service.impl;
+
+import com.ruoyi.system.domain.SysTorrentComment;
+import com.ruoyi.system.mapper.SysTorrentCommentMapper;
+import com.ruoyi.system.service.ISysTorrentCommentService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import java.util.List;
+
+@Service
+public class SysTorrentCommentServiceImpl implements ISysTorrentCommentService {
+    @Autowired
+    private SysTorrentCommentMapper commentMapper;
+
+    @Override
+    public int addComment(SysTorrentComment comment) {
+        return commentMapper.insertComment(comment);
+    }
+
+    @Override
+    public List<SysTorrentComment> getCommentList(Long torrentId) {
+        return commentMapper.selectCommentListByTorrentId(torrentId);
+    }
+
+    @Override
+    public int deleteComment(Long commentId) {
+        return commentMapper.deleteCommentById(commentId);
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserFollowServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserFollowServiceImpl.java
new file mode 100644
index 0000000..9cec124
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserFollowServiceImpl.java
@@ -0,0 +1,38 @@
+// 作者关注服务实现
+package com.ruoyi.system.service.impl;
+
+import com.ruoyi.system.domain.SysUserFollow;
+import com.ruoyi.system.mapper.SysUserFollowMapper;
+import com.ruoyi.system.service.ISysUserFollowService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import java.util.List;
+
+@Service
+public class SysUserFollowServiceImpl implements ISysUserFollowService {
+    @Autowired
+    private SysUserFollowMapper followMapper;
+
+    @Override
+    public int followAuthor(SysUserFollow follow) {
+        if (followMapper.selectFollow(follow) != null) {
+            return 0; // 已经关注
+        }
+        return followMapper.insertFollow(follow);
+    }
+
+    @Override
+    public int unfollowAuthor(SysUserFollow follow) {
+        return followMapper.deleteFollow(follow);
+    }
+
+    @Override
+    public List<SysUserFollow> getFollowList(Long userId) {
+        return followMapper.selectFollowListByUserId(userId);
+    }
+
+    @Override
+    public boolean isFollowing(SysUserFollow follow) {
+        return followMapper.selectFollow(follow) != null;
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserMessageServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserMessageServiceImpl.java
new file mode 100644
index 0000000..94c9e6e
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserMessageServiceImpl.java
@@ -0,0 +1,24 @@
+package com.ruoyi.system.service.impl;
+
+import com.ruoyi.system.domain.SysUserMessage;
+import com.ruoyi.system.mapper.SysUserMessageMapper;
+import com.ruoyi.system.service.ISysUserMessageService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import java.util.List;
+
+@Service
+public class SysUserMessageServiceImpl implements ISysUserMessageService {
+    @Autowired
+    private SysUserMessageMapper messageMapper;
+
+    @Override
+    public int sendMessage(SysUserMessage message) {
+        return messageMapper.insertMessage(message);
+    }
+
+    @Override
+    public List<SysUserMessage> getMessageList(Long userId1, Long userId2) {
+        return messageMapper.selectMessageListByUserIds(userId1, userId2);
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysTorrentCommentMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysTorrentCommentMapper.xml
new file mode 100644
index 0000000..139e0e1
--- /dev/null
+++ b/ruoyi-system/src/main/resources/mapper/system/SysTorrentCommentMapper.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.SysTorrentCommentMapper">
+    <insert id="insertComment" parameterType="com.ruoyi.system.domain.SysTorrentComment">
+        insert into sys_torrent_comment (torrent_id, user_id, content, parent_id, create_time)
+        values (#{torrentId}, #{userId}, #{content}, #{parentId}, sysdate())
+    </insert>
+    <select id="selectCommentListByTorrentId" resultType="com.ruoyi.system.domain.SysTorrentComment">
+        select comment_id, torrent_id, user_id, content, parent_id, create_time
+        from sys_torrent_comment
+        where torrent_id = #{torrentId}
+        order by create_time desc
+    </select>
+    <delete id="deleteCommentById">
+        delete from sys_torrent_comment where comment_id = #{commentId}
+    </delete>
+</mapper>
\ No newline at end of file
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysUserFollowMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysUserFollowMapper.xml
new file mode 100644
index 0000000..6fb9169
--- /dev/null
+++ b/ruoyi-system/src/main/resources/mapper/system/SysUserFollowMapper.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.SysUserFollowMapper">
+    <resultMap id="SysUserFollowMap" type="com.ruoyi.system.domain.SysUserFollow">
+        <id property="followId" column="follow_id"/>
+        <result property="userId" column="user_id"/>
+        <result property="authorId" column="author_id"/>
+        <result property="createTime" column="create_time"/>
+    </resultMap>
+
+    <insert id="insertFollow" parameterType="com.ruoyi.system.domain.SysUserFollow">
+        insert into sys_user_follow (user_id, author_id, create_time)
+        values (#{userId}, #{authorId}, sysdate())
+    </insert>
+
+    <delete id="deleteFollow">
+        delete from sys_user_follow where user_id = #{userId} and author_id = #{authorId}
+    </delete>
+
+    <select id="selectFollowListByUserId" resultMap="SysUserFollowMap">
+        select follow_id, user_id, author_id, create_time
+        from sys_user_follow
+        where user_id = #{userId}
+    </select>
+
+    <select id="selectFollow" resultMap="SysUserFollowMap">
+        select follow_id, user_id, author_id, create_time
+        from sys_user_follow
+        where user_id = #{userId} and author_id = #{authorId}
+    </select>
+</mapper>
\ No newline at end of file
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysUserMessageMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysUserMessageMapper.xml
new file mode 100644
index 0000000..79fe8b7
--- /dev/null
+++ b/ruoyi-system/src/main/resources/mapper/system/SysUserMessageMapper.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.SysUserMessageMapper">
+    <resultMap id="SysUserMessageMap" type="com.ruoyi.system.domain.SysUserMessage">
+        <id property="messageId" column="message_id"/>
+        <result property="senderId" column="sender_id"/>
+        <result property="receiverId" column="receiver_id"/>
+        <result property="content" column="content"/>
+        <result property="createTime" column="create_time"/>
+    </resultMap>
+
+    <insert id="insertMessage" parameterType="com.ruoyi.system.domain.SysUserMessage">
+        insert into sys_user_message (sender_id, receiver_id, content, create_time)
+        values (#{senderId}, #{receiverId}, #{content}, sysdate())
+    </insert>
+
+    <select id="selectMessageListByUserIds" resultMap="SysUserMessageMap">
+        select message_id, sender_id, receiver_id, content, create_time
+        from sys_user_message
+        where (sender_id = #{userId1} and receiver_id = #{userId2})
+           or (sender_id = #{userId2} and receiver_id = #{userId1})
+            and del_flag = '0'
+        order by create_time asc
+    </select>
+</mapper>
\ No newline at end of file