消息通

Change-Id: Ibf0f82b7d2843d0a67935ceb986047934f0642dd
diff --git a/src/main/java/com/ptp/ptplatform/controller/HelpPostController.java b/src/main/java/com/ptp/ptplatform/controller/HelpPostController.java
index 9d18320..457823a 100644
--- a/src/main/java/com/ptp/ptplatform/controller/HelpPostController.java
+++ b/src/main/java/com/ptp/ptplatform/controller/HelpPostController.java
@@ -5,6 +5,7 @@
 import com.ptp.ptplatform.entity.HelpPost;
 import com.ptp.ptplatform.service.HelpCommentService;
 import com.ptp.ptplatform.service.HelpPostService;
+import com.ptp.ptplatform.service.NotificationService;
 import com.ptp.ptplatform.utils.Result;
 import lombok.AllArgsConstructor;
 import org.springframework.http.MediaType;
@@ -26,6 +27,7 @@
 public class HelpPostController {
     private final HelpPostService postService;
     private final HelpCommentService commentService;
+    private final NotificationService notificationService;
 
     // 修改创建帖子的方法,支持图片上传
     @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@@ -149,7 +151,7 @@
         );
 
         // 获取实时评论总数(包括所有层级)
-        long totalComments = allComments.size(); // 直接使用列表大小
+        int totalComments = allComments.size(); // 直接使用列表大小
         post.setReplyCount(totalComments); // 更新计数
 
         // 构建评论树形结构
@@ -181,33 +183,43 @@
 
     // 点赞帖子
     @PostMapping("/{Id}/like")
-    public Result likePost(@PathVariable int Id) {
-        postService.incrementLike(Id);
-        return Result.ok();
+    public Result likePost(
+            @PathVariable("Id") int Id,
+            @RequestParam("likerId") String likerId
+    ) {
+        // 调用 ServiceImpl.likePost(...),自动更新 like_count 并插入通知
+        postService.likePost(Id, likerId);
+        return Result.ok().message("点赞成功");
     }
 
+    // 评论帖子
     @PostMapping(value = "/{Id}/comments", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
-    public Result comment(@PathVariable int Id,
-                          @RequestParam("authorId") String authorId,
-                          @RequestParam("content") String content,
-                          @RequestParam(value = "image", required = false) MultipartFile image) {
-
+    public Result commentOnPost(
+            @PathVariable("Id") int Id,
+            @RequestParam("authorId") String authorId,
+            @RequestParam("content") String content,
+            @RequestParam(value = "image", required = false) MultipartFile image
+    ) {
+        // 1. 构造 HelpComment 对象
         HelpComment comment = new HelpComment();
         comment.setPostId(Id);
-        comment.setAuthorId(authorId); // 类型为 String
+        comment.setAuthorId(authorId);
         comment.setContent(content);
         comment.setCreateTime(LocalDateTime.now());
         comment.setLikeCount(0);
-        comment.setParentId(0);
-        comment.setReplyTo(null);
+        comment.setParentId(0);       // 这里是对帖子的一级评论
+        comment.setImageUrl(null);    // 默认无图片
 
+        // 2. 处理可选的图片上传逻辑
         if (image != null && !image.isEmpty()) {
             try {
                 String fileExt = image.getOriginalFilename()
-                        .substring(image.getOriginalFilename().lastIndexOf(".") + 1);
-                String fileName = UUID.randomUUID() + "." + fileExt;
+                        .substring(image.getOriginalFilename().lastIndexOf('.') + 1);
+                String fileName = UUID.randomUUID().toString() + "." + fileExt;
 
-                String uploadDir = System.getProperty("user.dir") + File.separator + "uploads";
+                // 上传到项目根目录的 uploads 文件夹
+                String uploadDir = System.getProperty("user.dir")
+                        + File.separator + "uploads";
                 File dir = new File(uploadDir);
                 if (!dir.exists()) dir.mkdirs();
 
@@ -216,20 +228,21 @@
 
                 comment.setImageUrl("/uploads/" + fileName);
             } catch (IOException e) {
-                return Result.error(404).setMessage("图片上传失败:" + e.getMessage());
+                return Result.error(500).message("图片上传失败:" + e.getMessage());
             }
         }
 
-        // 保存评论
-        boolean saved = commentService.save(comment);
-        if (!saved) return Result.error(500).setMessage("评论保存失败");
+        // 3. 通过 ServiceImpl 完成“保存评论 + 更新 reply_count + 发通知给帖子作者”
+        commentService.commentOnHelpPost(comment);
 
-        postService.incrementReplyCount(Id);
+        // 4. 构造返回值:把刚刚保存的评论(从数据库中查出来的完整对象)和帖子最新的 replyCount 都返回
+        //    注意:comment.insert(...) 后,comment.getId() 将有 DB 自动生成的主键
         HelpComment newComment = commentService.getById(comment.getId());
+        int updatedReplyCount = postService.getById(Id).getReplyCount();
 
         return Result.ok()
                 .data("comment", newComment)
-                .data("newReplyCount", postService.getById(Id).getReplyCount());
+                .data("newReplyCount", updatedReplyCount);
     }
 
     // 删除帖子
diff --git a/src/main/java/com/ptp/ptplatform/controller/NotificationController.java b/src/main/java/com/ptp/ptplatform/controller/NotificationController.java
new file mode 100644
index 0000000..61a0f12
--- /dev/null
+++ b/src/main/java/com/ptp/ptplatform/controller/NotificationController.java
@@ -0,0 +1,29 @@
+package com.ptp.ptplatform.controller;
+
+import com.ptp.ptplatform.entity.Notification;
+import com.ptp.ptplatform.service.NotificationService;
+import com.ptp.ptplatform.utils.Result;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/api/notifications")
+public class NotificationController {
+
+    @Autowired
+    private NotificationService notificationService;
+
+    @GetMapping("")
+    public Result listNotifications(@RequestParam("userId") String userId) {
+        List<Notification> list = notificationService.listByUser(userId);
+        return Result.ok().data("notifications", list);
+    }
+
+    @PostMapping("/{id}/read")
+    public Result markAsRead(@PathVariable("id") Integer id) {
+        notificationService.markAsRead(id);
+        return Result.ok().message("通知已标记为已读");
+    }
+}
diff --git a/src/main/java/com/ptp/ptplatform/controller/SystemNotificationController.java b/src/main/java/com/ptp/ptplatform/controller/SystemNotificationController.java
new file mode 100644
index 0000000..81b111a
--- /dev/null
+++ b/src/main/java/com/ptp/ptplatform/controller/SystemNotificationController.java
@@ -0,0 +1,92 @@
+package com.ptp.ptplatform.controller;
+
+import com.ptp.ptplatform.entity.Notification;
+import com.ptp.ptplatform.entity.USER;
+import com.ptp.ptplatform.mapper.UserMapper;
+import com.ptp.ptplatform.service.NotificationService;
+import com.ptp.ptplatform.utils.Result;
+import lombok.AllArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 调用示例:
+ *   POST /api/system/announce
+ *   Content-Type: application/json
+ *   {
+ *     "title":   "系统维护提醒",
+ *     "content": "本周六(6月5日)凌晨2:00-4:00将进行全站维护,届时功能暂不可用。"
+ *   }
+ */
+@RestController
+@RequestMapping("/system")
+@AllArgsConstructor
+public class SystemNotificationController {
+
+    private final UserMapper userMapper;                  // 用于查询所有用户
+    private final NotificationService notificationService; // 用于保存通知
+
+
+    //发布一条系统公告,系统会给当前所有注册用户发一条通知。
+
+    @PostMapping("/announce")
+    public Result announce(@RequestBody AnnounceRequest body) {
+        // 1. 参数校验
+        if (body.getTitle() == null || body.getTitle().isBlank()
+                || body.getContent() == null || body.getContent().isBlank()) {
+            return Result.error(400).message("请求参数 title/content 不能为空");
+        }
+
+        // 2. 查询所有用户
+        List<USER> allUsers = userMapper.selectAllUsers();
+        if (allUsers == null || allUsers.isEmpty()) {
+            return Result.error(500).message("当前系统中没有可发送通知的用户");
+        }
+
+        // 3. 遍历所有用户,为每个用户创建并保存一条 Notification
+        int sentCount = 0;
+        LocalDateTime now = LocalDateTime.now();
+        for (USER u : allUsers) {
+            String uid = u.getUsername();  // 获取用户名(用户主键)
+
+            Notification n = new Notification();
+            n.setUserId(uid);                 // 通知接收者 = 当前用户名
+            n.setType("SYSTEM");              // 通知类型标记为系统消息
+            n.setTitle(body.getTitle());      // 通知标题
+            n.setContent(body.getContent());  // 通知内容
+            n.setTargetId(null);              // 系统公告通常无需跳转,设为 null
+            n.setTargetType(null);
+            n.setIsRead(false);
+            n.setCreateTime(now);
+
+            notificationService.saveNotification(n);
+            sentCount++;
+        }
+
+        // 4. 返回发送成功及发送数量
+        return Result.ok()
+                .message("公告已发送,通知条数:" + sentCount)
+                .data("sentCount", sentCount);
+    }
+
+    public static class AnnounceRequest {
+        private String title;
+        private String content;
+
+        // Getter & Setter
+        public String getTitle() {
+            return title;
+        }
+        public void setTitle(String title) {
+            this.title = title;
+        }
+        public String getContent() {
+            return content;
+        }
+        public void setContent(String content) {
+            this.content = content;
+        }
+    }
+}
diff --git a/src/main/java/com/ptp/ptplatform/controller/TorrentController.java b/src/main/java/com/ptp/ptplatform/controller/TorrentController.java
index 2c6031c..e69c70e 100644
--- a/src/main/java/com/ptp/ptplatform/controller/TorrentController.java
+++ b/src/main/java/com/ptp/ptplatform/controller/TorrentController.java
@@ -7,11 +7,8 @@
 import com.ptp.ptplatform.mapper.TorrentMapper;
 import com.ptp.ptplatform.mapper.UserMapper;
 import com.ptp.ptplatform.mapper.DownloadTorrentMapper;
-import com.ptp.ptplatform.service.ClientService;
-import com.ptp.ptplatform.service.TrackerService;
+import com.ptp.ptplatform.service.*;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
-import com.ptp.ptplatform.service.TorrentCommentService;
-import com.ptp.ptplatform.service.TorrentService;
 import com.ptp.ptplatform.utils.Result;
 import com.ptp.ptplatform.utils.SizeCalculation;
 import com.turn.ttorrent.bcodec.BDecoder;
@@ -54,6 +51,7 @@
 public class TorrentController {
     private final TorrentService postService;
     private final TorrentCommentService commentService;
+    private final NotificationService notificationService;
 
     @Resource
     private TorrentMapper torrentMapper;
@@ -188,36 +186,55 @@
 
     // 点赞帖子
     @PostMapping("/{Id}/like")
-    public Result likePost(@PathVariable int Id) {
+    public Result likePost(
+            @PathVariable int Id,
+            @RequestParam("likerId") String likerId
+    ) {
         postService.incrementLike(Id);
+        // 通知发帖人
+        TORRENT post = postService.getById(Id);
+        if (post != null) {
+            String authorId = post.getUsername(); // TORRENT 实体中发帖人字段是 username
+            if (!authorId.equals(likerId)) {
+                Notification n = new Notification();
+                n.setUserId(authorId);  // 通知接收者 = 帖子作者
+                n.setType("TORRENT_LIKE");
+                n.setTitle("您的 Torrent 帖子被点赞");
+                n.setContent("用户 "
+                        + likerId
+                        + " 点赞了您的 Torrent 帖子: \""
+                        + post.getTorrentName()
+                        + "\"");
+                n.setTargetId(Id);
+                n.setTargetType("TORRENT");
+                n.setIsRead(false);
+                n.setCreateTime(LocalDateTime.now());
+                notificationService.saveNotification(n);
+            }
+        }
+
         return Result.ok();
     }
 
     @PostMapping("/{Id}/comments")
-    public Result comment(@PathVariable int Id,
-                          @RequestBody TorrentComment comment) {
-        // 设置评论信息
+    public Result comment(
+            @PathVariable int Id,
+            @RequestBody TorrentComment comment
+    ) {
         comment.setPostId(Id);
         comment.setCreateTime(LocalDateTime.now());
-        comment.setLikeCount(0); // 初始化点赞数
-        comment.setParentId(0);  // 默认父评论ID
-        comment.setReplyTo(null); // 主评论 replyTo=null
-
-        // 保存评论
-        boolean saved = commentService.save(comment);
-        if (!saved) {
-            return Result.error(404).setMessage("评论保存失败");
+        comment.setLikeCount(0);
+        if (comment.getParentId() == null) {
+            comment.setParentId(0); // 默认一级评论
         }
 
-        // 更新回复数
-        postService.incrementReplyCount(Id);
-
-        // 获取更新后的完整评论(包含数据库生成的ID和时间)
+        commentService.commentOnTorrentPost(comment); //通知作者
         TorrentComment newComment = commentService.getById(comment.getId());
+        int updatedReplyCount = postService.getById(Id).getReply_count();
 
         return Result.ok()
-                .data("comment", newComment)  // 返回完整评论数据
-                .data("newReplyCount", postService.getById(Id).getReply_count());
+                .data("comment", newComment)
+                .data("newReplyCount", updatedReplyCount);
     }
 
     @PostConstruct //启动项目时候自动启动tracker服务器
@@ -372,6 +389,20 @@
 
         trackerservice.serTracker();
 
+        // 通知下载完成
+        Notification downloadCompleteNotice = new Notification();
+        downloadCompleteNotice.setUserId(userDownload.getUsername());
+        downloadCompleteNotice.setType("DOWNLOAD_COMPLETE");
+        downloadCompleteNotice.setTitle("下载完成提醒");
+        downloadCompleteNotice.setContent(
+                "您下载的种子 \"" + torrent.getTorrentName() + "\" 已经成功完成。"
+        );
+        downloadCompleteNotice.setTargetId(id);
+        downloadCompleteNotice.setTargetType("TORRENT");
+        downloadCompleteNotice.setIsRead(false);
+        downloadCompleteNotice.setCreateTime(LocalDateTime.now());
+        notificationService.saveNotification(downloadCompleteNotice);
+
         //更新上传量和下载量
         userDownload.updateDownload(SizeCalculation.getDownload(torrent.getSize(), discountMapper, torrent.getCreateTime()));
         userUpload.updateUpload(SizeCalculation.getUpload(torrent.getSize(), discountMapper, torrent.getCreateTime()));
diff --git a/src/main/java/com/ptp/ptplatform/entity/HelpPost.java b/src/main/java/com/ptp/ptplatform/entity/HelpPost.java
index 4a2cfe6..a7cfd11 100644
--- a/src/main/java/com/ptp/ptplatform/entity/HelpPost.java
+++ b/src/main/java/com/ptp/ptplatform/entity/HelpPost.java
@@ -16,6 +16,6 @@
     private String content;
     private String imageUrl;
     private Integer likeCount;
-    private long replyCount;
+    private Integer replyCount;
     private LocalDateTime createTime;
 }
\ No newline at end of file
diff --git a/src/main/java/com/ptp/ptplatform/entity/Notification.java b/src/main/java/com/ptp/ptplatform/entity/Notification.java
new file mode 100644
index 0000000..af1677b
--- /dev/null
+++ b/src/main/java/com/ptp/ptplatform/entity/Notification.java
@@ -0,0 +1,23 @@
+package com.ptp.ptplatform.entity;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.IdType;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+@TableName("notification")
+public class Notification {
+    @TableId(type = IdType.AUTO)
+    private Integer id;
+    private String userId;
+    private String type;
+    private String title;
+    private String content;
+    private Integer targetId;
+    private String targetType;
+    private Boolean isRead;
+    private LocalDateTime createTime;
+}
\ No newline at end of file
diff --git a/src/main/java/com/ptp/ptplatform/entity/USER.java b/src/main/java/com/ptp/ptplatform/entity/USER.java
index 9762d88..3798696 100644
--- a/src/main/java/com/ptp/ptplatform/entity/USER.java
+++ b/src/main/java/com/ptp/ptplatform/entity/USER.java
@@ -3,6 +3,8 @@
 
 package com.ptp.ptplatform.entity;
 
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
 import com.ptp.ptplatform.utils.SizeCalculation;
 import jakarta.persistence.*;
 import lombok.Data;
@@ -11,6 +13,7 @@
 
 @Data
 @Table(name = "user")
+@TableName("user")
 public class USER {
 
     @jakarta.persistence.Id
@@ -22,15 +25,19 @@
 
     private int level; // 用户等级0-6
 
+    @TableField("registTime")
     @Temporal(TemporalType.DATE)
     private Date registTime = new Date();
 
+    @TableField("lastLogin")
     @Temporal(TemporalType.DATE)
     private Date lastLogin;
 
     private long upload;
     private long download;
+    @TableField("shareRate")
     private double shareRate;//分享率 前端展示数据应该为 90.23%这种
+    @TableField("magicPoints")
     private long magicPoints;// 魔力值
 
     public enum Authority {
diff --git a/src/main/java/com/ptp/ptplatform/mapper/NotificationMapper.java b/src/main/java/com/ptp/ptplatform/mapper/NotificationMapper.java
new file mode 100644
index 0000000..76a5d83
--- /dev/null
+++ b/src/main/java/com/ptp/ptplatform/mapper/NotificationMapper.java
@@ -0,0 +1,8 @@
+package com.ptp.ptplatform.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ptp.ptplatform.entity.Notification;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface NotificationMapper extends BaseMapper<Notification> {}
\ No newline at end of file
diff --git a/src/main/java/com/ptp/ptplatform/service/HelpCommentService.java b/src/main/java/com/ptp/ptplatform/service/HelpCommentService.java
index 8236fa7..4cead77 100644
--- a/src/main/java/com/ptp/ptplatform/service/HelpCommentService.java
+++ b/src/main/java/com/ptp/ptplatform/service/HelpCommentService.java
@@ -10,4 +10,6 @@
     List<HelpComment> getReplies(int parentId);
     // 新增:获取帖子直接评论数
     long countByPostId(Integer postId);// 新增方法
-}
+    void commentOnHelpPost(HelpComment comment);
+    void replyComment(HelpComment reply);
+}
\ No newline at end of file
diff --git a/src/main/java/com/ptp/ptplatform/service/HelpPostService.java b/src/main/java/com/ptp/ptplatform/service/HelpPostService.java
index 541f927..f005189 100644
--- a/src/main/java/com/ptp/ptplatform/service/HelpPostService.java
+++ b/src/main/java/com/ptp/ptplatform/service/HelpPostService.java
@@ -1,9 +1,12 @@
 package com.ptp.ptplatform.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.ptp.ptplatform.entity.HelpComment;
 import com.ptp.ptplatform.entity.HelpPost;
 
 public interface HelpPostService extends IService<HelpPost> {
-    void incrementLike(int postId);
-    void incrementReplyCount(int postId);
+    void incrementLike(Integer postId);
+    void incrementReplyCount(Integer postId);
+    void likePost(Integer postId, String likerId);//点赞,并通知该帖子的作者。
+    void commentOnHelpPost(HelpComment comment); //在帖子下发表一条新评论,并通知该帖子的作者。
 }
\ No newline at end of file
diff --git a/src/main/java/com/ptp/ptplatform/service/NotificationService.java b/src/main/java/com/ptp/ptplatform/service/NotificationService.java
new file mode 100644
index 0000000..d213484
--- /dev/null
+++ b/src/main/java/com/ptp/ptplatform/service/NotificationService.java
@@ -0,0 +1,10 @@
+package com.ptp.ptplatform.service;
+
+import com.ptp.ptplatform.entity.Notification;
+import java.util.List;
+
+public interface NotificationService {
+    void saveNotification(Notification notification);
+    List<Notification> listByUser(String userId);
+    void markAsRead(Integer id);
+}
\ No newline at end of file
diff --git a/src/main/java/com/ptp/ptplatform/service/TorrentCommentService.java b/src/main/java/com/ptp/ptplatform/service/TorrentCommentService.java
index 3b60257..648405f 100644
--- a/src/main/java/com/ptp/ptplatform/service/TorrentCommentService.java
+++ b/src/main/java/com/ptp/ptplatform/service/TorrentCommentService.java
@@ -8,4 +8,5 @@
 public interface TorrentCommentService extends IService<TorrentComment> {
     void incrementLike(int commentId);
     List<TorrentComment> getReplies(int parentId);
+    void commentOnTorrentPost(TorrentComment comment);
 }
\ No newline at end of file
diff --git a/src/main/java/com/ptp/ptplatform/service/UserLevelService.java b/src/main/java/com/ptp/ptplatform/service/UserLevelService.java
index a95dec4..1e77b3f 100644
--- a/src/main/java/com/ptp/ptplatform/service/UserLevelService.java
+++ b/src/main/java/com/ptp/ptplatform/service/UserLevelService.java
@@ -6,6 +6,7 @@
 import java.util.Date;
 import java.util.List;
 
+import com.ptp.ptplatform.entity.Notification;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
@@ -22,6 +23,8 @@
 
     @Autowired
     private UserMapper userMapper;
+    @Autowired
+    private NotificationService notificationService; // 新注入,用于发送通知
     private static final double BYTES_PER_GB = 1024d * 1024 * 1024;
 
     // 每周一凌晨 2 点触发
@@ -31,24 +34,56 @@
         log.info("===== 开始刷新所有用户等级 =====");
         LocalDate today = LocalDate.now();
 
+        // 1. 查询数据库里所有用户
         List<USER> users = userMapper.selectList(null);
         log.info(">> 从数据库查到 {} 个用户", users.size());
 
         for (USER u : users) {
-            double uploadGb    = u.getUpload()  / BYTES_PER_GB;
-            long   magicPoints = u.getMagicPoints();
-            log.info("[LevelCheck] 用户={} shareRate={} uploadGB={} magicPoints={}",
-                    u.getUsername(),
-                    u.getShareRate(),
-                    uploadGb,
-                    magicPoints);
-            int newLevel = calcLevel(u, today);
-            if (newLevel != u.getLevel()) {
-                log.info("[LevelUpdate] 用户={} level: {} -> {}", u.getUsername(), u.getLevel(), newLevel);
+            double shareRate    = u.getShareRate();                // 当前分享率
+            double uploadGb     = u.getUpload() / BYTES_PER_GB;    // 上传大小 (GB)
+            long   magicPoints  = u.getMagicPoints();              // 魔力值
+            Date   regTime      = u.getRegistTime();               // 注册时间
+            LocalDate regDate   = regTime.toInstant()
+                    .atZone(ZoneId.systemDefault())
+                    .toLocalDate();
+            long   monthsSince  = ChronoUnit.MONTHS.between(regDate, today);
+
+            log.info("[LevelCheck] 用户={} shareRate={} uploadGB={} magicPoints={} months={}",
+                    u.getUsername(), shareRate, uploadGb, magicPoints, monthsSince);
+
+            // 2. 先记录旧等级
+            int oldLevel = u.getLevel();
+
+            // 3. 根据业务规则计算新等级
+            int newLevel = calcLevel(u, uploadGb, shareRate, magicPoints, monthsSince);
+
+            if (newLevel != oldLevel) {
+                log.info("[LevelUpdate] 用户={} 等级: {} -> {}", u.getUsername(), oldLevel, newLevel);
+
+                // 4. 更新用户实体的等级并写库
                 u.setLevel(newLevel);
-                userMapper.updateById(u);
+//                userMapper.updateById(u);
+                userMapper.updateUser(u);
+
+                // 5. 构造并保存“等级提升”通知
+                Notification n = new Notification();
+                n.setUserId(u.getUsername());                    // 通知接收者 = 用户自己
+                n.setType("LEVEL_UP");                            // 通知类型:LEVEL_UP
+                n.setTitle("恭喜您,用户等级已提升");              // 通知标题
+                n.setContent("您的用户等级已从 "
+                        + oldLevel
+                        + " 级提升至 "
+                        + newLevel
+                        + " 级!");                         // 通知内容
+                n.setTargetId(null);                              // 等级通知无需跳转
+                n.setTargetType(null);
+                n.setIsRead(false);
+                n.setCreateTime(java.time.LocalDateTime.now());
+
+                notificationService.saveNotification(n);
             }
         }
+
         log.info("===== 刷新完毕 =====");
     }
 
@@ -56,46 +91,40 @@
     /**
      * 根据各项指标和注册时长计算用户等级
      */
-    private int calcLevel(USER u, LocalDate today) {
-        double shareRate   = u.getShareRate();            // 0.9023 表示 90.23%
-        double uploadGb    = u.getUpload()  / BYTES_PER_GB;
-        long   magicPoints = u.getMagicPoints();
-
-        // 注册日期转 LocalDate
-        Date   regTime = u.getRegistTime();
-        LocalDate regDate = regTime.toInstant()
-                .atZone(ZoneId.systemDefault())
-                .toLocalDate();
-
-        long months = ChronoUnit.MONTHS.between(regDate, today);
-
-        if (shareRate   >= 1.2  &&
+    private int calcLevel(
+            USER u,
+            double uploadGb,
+            double shareRate,
+            long magicPoints,
+            long monthsSince
+    ) {
+        if (shareRate   >= 1.2    &&
                 magicPoints >= 100_000 &&
                 uploadGb    >= 10_240  &&
-                months      >= 12) {
+                monthsSince >= 12) {
             return 5;
         }
-        if (shareRate   >= 1.0  &&
+        if (shareRate   >= 1.0    &&
                 magicPoints >=  50_000 &&
                 uploadGb    >=  5_120  &&
-                months      >= 6) {
+                monthsSince >= 6) {
             return 4;
         }
-        if (shareRate   >= 0.8  &&
+        if (shareRate   >= 0.8    &&
                 magicPoints >=  20_000 &&
                 uploadGb    >=  1_024  &&
-                months      >= 3) {
+                monthsSince >= 3) {
             return 3;
         }
-        if (shareRate   >= 0.6  &&
+        if (shareRate   >= 0.6    &&
                 magicPoints >=   5_000 &&
-                uploadGb    >=     200 &&
-                months      >= 1) {
+                uploadGb    >=    200  &&
+                monthsSince >= 1) {
             return 2;
         }
-        if (shareRate   >= 0.4  &&
+        if (shareRate   >= 0.4    &&
                 magicPoints >=   1_000 &&
-                uploadGb    >=      50) {
+                uploadGb    >=     50) {
             return 1;
         }
         return 0;
diff --git a/src/main/java/com/ptp/ptplatform/service/impl/HelpCommentServiceImpl.java b/src/main/java/com/ptp/ptplatform/service/impl/HelpCommentServiceImpl.java
index f15f618..7ba564b 100644
--- a/src/main/java/com/ptp/ptplatform/service/impl/HelpCommentServiceImpl.java
+++ b/src/main/java/com/ptp/ptplatform/service/impl/HelpCommentServiceImpl.java
@@ -3,8 +3,13 @@
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.ptp.ptplatform.entity.HelpComment;
+import com.ptp.ptplatform.entity.HelpPost;
+import com.ptp.ptplatform.entity.Notification;
 import com.ptp.ptplatform.mapper.HelpCommentMapper;
+import com.ptp.ptplatform.mapper.HelpPostMapper;
 import com.ptp.ptplatform.service.HelpCommentService;
+import com.ptp.ptplatform.service.NotificationService;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import java.util.List;
@@ -13,6 +18,14 @@
 public class HelpCommentServiceImpl
         extends ServiceImpl<HelpCommentMapper, HelpComment>
         implements HelpCommentService {
+    @Autowired
+    private HelpCommentMapper helpCommentMapper;
+
+    @Autowired
+    private HelpPostMapper helpPostMapper;
+
+    @Autowired
+    private NotificationService notificationService;
 
     @Override
     @Transactional
@@ -38,4 +51,55 @@
         return count(new QueryWrapper<HelpComment>()
                 .eq("post_id", postId)); // 只按post_id统计
     }
+
+    @Override
+    public void commentOnHelpPost(HelpComment comment) {
+        // 1. 保存评论
+        helpCommentMapper.insert(comment);
+
+        // 2. 通知帖子作者
+        HelpPost post = helpPostMapper.selectById(comment.getPostId());
+        if (post != null
+                && !post.getAuthorId().equals(comment.getAuthorId())) {
+            Notification n = new Notification();
+            n.setUserId(post.getAuthorId());
+            n.setType("POST_REPLY");
+            n.setTitle("您的帖子有了新回复");
+            n.setContent("用户 "
+                    + comment.getAuthorId()
+                    + " 评论了您的帖子: \""
+                    + comment.getContent()
+                    + "\"");
+            n.setTargetId(post.getId());
+            n.setTargetType("HelpPost");
+            notificationService.saveNotification(n);
+        }
+    }
+
+    /**
+     * 用户回复已有评论
+     */
+    @Override
+    public void replyComment(HelpComment reply) {
+        // 1. 保存子评论
+        helpCommentMapper.insert(reply);
+
+        // 2. 通知父评论作者
+        HelpComment parent = helpCommentMapper.selectById(reply.getParentId());
+        if (parent != null
+                && !parent.getAuthorId().equals(reply.getAuthorId())) {
+            Notification n = new Notification();
+            n.setUserId(parent.getAuthorId());
+            n.setType("COMMENT_REPLY");
+            n.setTitle("您的评论有了新回复");
+            n.setContent("用户 "
+                    + reply.getAuthorId()
+                    + " 回复了您的评论: \""
+                    + reply.getContent()
+                    + "\"");
+            n.setTargetId(parent.getId());
+            n.setTargetType("HelpComment");
+            notificationService.saveNotification(n);
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/main/java/com/ptp/ptplatform/service/impl/HelpPostServiceImpl.java b/src/main/java/com/ptp/ptplatform/service/impl/HelpPostServiceImpl.java
index 5e8d5d0..748eec5 100644
--- a/src/main/java/com/ptp/ptplatform/service/impl/HelpPostServiceImpl.java
+++ b/src/main/java/com/ptp/ptplatform/service/impl/HelpPostServiceImpl.java
@@ -1,19 +1,68 @@
 package com.ptp.ptplatform.service.impl;
 
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ptp.ptplatform.entity.HelpComment;
 import com.ptp.ptplatform.entity.HelpPost;
+import com.ptp.ptplatform.entity.Notification;
+import com.ptp.ptplatform.mapper.HelpCommentMapper;
 import com.ptp.ptplatform.mapper.HelpPostMapper;
 import com.ptp.ptplatform.service.HelpPostService;
+import com.ptp.ptplatform.service.NotificationService;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.time.LocalDateTime;
+
 @Service
 public class HelpPostServiceImpl extends ServiceImpl<HelpPostMapper, HelpPost> implements HelpPostService {
+    @Autowired
+    private HelpPostMapper helpPostMapper;
+    @Autowired
+    private HelpCommentMapper helpCommentMapper;
+    @Autowired
+    private NotificationService notificationService;
     @Override
     @Transactional
-    public void incrementLike(int postId) {
-        this.update(null,
-                new com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper<HelpPost>()
+    public void likePost(Integer postId, String likerId) {
+        // 1. 查询帖子本身,获取 authorId 和 title
+        HelpPost post = helpPostMapper.selectById(postId);
+        if (post == null) {
+            throw new RuntimeException("帖子不存在,ID=" + postId);
+        }
+
+        // 2. 更新 like_count 字段
+        helpPostMapper.update(
+                null,
+                new UpdateWrapper<HelpPost>()
+                        .eq("id", postId)
+                        .setSql("like_count = like_count + 1")
+        );
+
+        // 3. 给帖子作者发通知(排除自己给自己点赞的情况)
+        String authorId = post.getAuthorId();
+        if (!authorId.equals(likerId)) {
+            Notification n = new Notification();
+            n.setUserId(authorId);               // 通知接收方 = 帖子作者
+            n.setType("POST_LIKE");              // 通知类型,可前端约定
+            n.setTitle("您的帖子被点赞");        // 通知标题
+            n.setContent("用户 "
+                    + likerId
+                    + " 点赞了您的帖子: \""
+                    + post.getTitle()
+                    + "\"");
+            n.setTargetId(postId);               // 通知跳转时可指向该帖子
+            n.setTargetType("HelpPost");         // 前端可根据此值决定跳转逻辑
+            notificationService.saveNotification(n);
+        }
+    }
+    @Override
+    @Transactional
+    public void incrementLike(Integer postId) {
+        this.update(
+                null,
+                new UpdateWrapper<HelpPost>()
                         .eq("id", postId)
                         .setSql("like_count = like_count + 1")
         );
@@ -21,11 +70,53 @@
 
     @Override
     @Transactional
-    public void incrementReplyCount(int postId) {
-        this.update(null,
-                new com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper<HelpPost>()
+    public void incrementReplyCount(Integer postId) {
+        this.update(
+                null,
+                new UpdateWrapper<HelpPost>()
                         .eq("id", postId)
                         .setSql("reply_count = reply_count + 1")
         );
     }
+
+    @Override
+    @Transactional
+    public void commentOnHelpPost(HelpComment comment) {
+        // 1. 将新的 HelpComment 插入数据库
+        //    如果 parentId != 0,则说明这是一条对子评论的回复;如果 parentId = 0,则这就是对帖子的一级评论。
+        helpCommentMapper.insert(comment);
+
+        // 2. 帖子回复数 +1(reply_count 字段)
+        Integer postId = comment.getPostId();
+        helpPostMapper.update(
+                null,
+                new UpdateWrapper<HelpPost>()
+                        .eq("id", postId)
+                        .setSql("reply_count = reply_count + 1")
+        );
+
+        // 3. 给帖子作者发一条通知(如果评论人不是作者自己)
+        HelpPost post = helpPostMapper.selectById(postId);
+        if (post != null) {
+            String authorId = post.getAuthorId();            // 帖子作者的 ID
+            String commenterId = comment.getAuthorId();      // 当前发表评论的用户 ID
+            if (!authorId.equals(commenterId)) {
+                Notification n = new Notification();
+                n.setUserId(authorId);                      // 通知接收人 = 帖子作者
+                n.setType("POST_REPLY");                    // 通知类型,可与前端约定
+                n.setTitle("您的帖子有了新回复");             // 通知标题
+                // 通知内容示例: 用户 <commenterId> 评论了您的帖子: "评论内容"
+                n.setContent("用户 "
+                        + commenterId
+                        + " 评论了您的帖子: \""
+                        + comment.getContent()
+                        + "\"");
+                n.setTargetId(postId);                      // targetId 可指向该帖子
+                n.setTargetType("HelpPost");                // targetType = "HelpPost"
+                n.setIsRead(false);
+                n.setCreateTime(LocalDateTime.now());
+                notificationService.saveNotification(n);
+            }
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/main/java/com/ptp/ptplatform/service/impl/NotificationServiceImpl.java b/src/main/java/com/ptp/ptplatform/service/impl/NotificationServiceImpl.java
new file mode 100644
index 0000000..faeae12
--- /dev/null
+++ b/src/main/java/com/ptp/ptplatform/service/impl/NotificationServiceImpl.java
@@ -0,0 +1,41 @@
+package com.ptp.ptplatform.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.ptp.ptplatform.entity.Notification;
+import com.ptp.ptplatform.mapper.NotificationMapper;
+import com.ptp.ptplatform.service.NotificationService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Service
+public class NotificationServiceImpl implements NotificationService {
+    @Autowired
+    private NotificationMapper notificationMapper;
+
+    @Override
+    public void saveNotification(Notification n) {
+        n.setCreateTime(LocalDateTime.now());
+        n.setIsRead(false);
+        notificationMapper.insert(n);
+    }
+
+    @Override
+    public List<Notification> listByUser(String userId) {
+        return notificationMapper.selectList(
+                new QueryWrapper<Notification>()
+                        .eq("user_id", userId)
+                        .orderByDesc("create_time")
+        );
+    }
+
+    @Override
+    public void markAsRead(Integer id) {
+        Notification n = new Notification();
+        n.setId(id);
+        n.setIsRead(true);
+        notificationMapper.updateById(n);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ptp/ptplatform/service/impl/TorrentCommentServiceImpl.java b/src/main/java/com/ptp/ptplatform/service/impl/TorrentCommentServiceImpl.java
index 540df71..95b2a7d 100644
--- a/src/main/java/com/ptp/ptplatform/service/impl/TorrentCommentServiceImpl.java
+++ b/src/main/java/com/ptp/ptplatform/service/impl/TorrentCommentServiceImpl.java
@@ -1,17 +1,33 @@
 package com.ptp.ptplatform.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ptp.ptplatform.entity.Notification;
+import com.ptp.ptplatform.entity.TORRENT;
 import com.ptp.ptplatform.entity.TorrentComment;
 import com.ptp.ptplatform.mapper.TorrentCommentMapper;
+import com.ptp.ptplatform.mapper.TorrentMapper;
+import com.ptp.ptplatform.service.NotificationService;
 import com.ptp.ptplatform.service.TorrentCommentService;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.time.LocalDateTime;
 import java.util.List;
 
 @Service
 public class TorrentCommentServiceImpl extends ServiceImpl<TorrentCommentMapper, TorrentComment> implements TorrentCommentService {
+    @Autowired
+    private TorrentCommentMapper torrentCommentMapper;
+
+    @Autowired
+    private TorrentMapper torrentMapper;
+
+    @Autowired
+    private NotificationService notificationService;
+
     @Override
     @Transactional
     public void incrementLike(int commentId) {
@@ -31,4 +47,76 @@
                         .orderByAsc("create_time")
         );
     }
+
+    @Override
+    @Transactional
+    public void commentOnTorrentPost(TorrentComment comment) {
+        // ————————————————
+        // 1. 插入新评论到 torrent_comments 表
+        //    插入后,MyBatis-Plus 会自动将自增主键 id 回写到 comment.getId()
+        // ————————————————
+        comment.setCreateTime(LocalDateTime.now());
+        comment.setLikeCount(0);
+        torrentCommentMapper.insert(comment);
+
+        // ————————————————
+        // 2. 更新对应 TORRENT 帖子的 reply_count 字段 +1
+        // ————————————————
+        Integer postId = comment.getPostId();
+        torrentMapper.update(
+                null,
+                new UpdateWrapper<TORRENT>()
+                        .eq("id", postId)
+                        .setSql("reply_count = reply_count + 1")
+        );
+
+        // ————————————————
+        // 3. 给该帖子的作者发通知(如果评论人 != 作者)
+        // ————————————————
+        TORRENT post = torrentMapper.selectById(postId);
+        if (post != null) {
+            String postAuthorId = post.getUsername();
+            String commenterId  = comment.getAuthorId();
+
+            if (!postAuthorId.equals(commenterId)) {
+                Notification n = new Notification();
+                n.setUserId(postAuthorId);                    // 通知接收人 = 帖子作者
+                n.setType("TORRENT_POST_REPLY");               // 通知类型,可自定义
+                n.setTitle("您的 Torrent 帖子有新回复");       // 通知标题
+                n.setContent(
+                        "用户 " + commenterId + " 评论了您的 Torrent 帖子: \"" +
+                                comment.getContent() + "\""
+                );
+                n.setTargetId(postId);                         // 目标 ID = 帖子 ID
+                n.setTargetType("TORRENT");                    // 目标类型 = TORRENT
+                n.setIsRead(false);                            // 默认未读
+                n.setCreateTime(LocalDateTime.now());
+                notificationService.saveNotification(n);
+            }
+
+            if (comment.getParentId() != null && comment.getParentId() > 0) {
+                Integer parentCommentId = comment.getParentId();
+                TorrentComment parentComment = torrentCommentMapper.selectById(parentCommentId);
+                if (parentComment != null) {
+                    String parentAuthorId = parentComment.getAuthorId();
+                    // 排除顶层评论 + 排除作者自己给自己回复
+                    if (!parentAuthorId.equals(commenterId)) {
+                        Notification n2 = new Notification();
+                        n2.setUserId(parentAuthorId);
+                        n2.setType("TORRENT_COMMENT_REPLY");
+                        n2.setTitle("您的评论有新回复");
+                        n2.setContent(
+                                "用户 " + commenterId + " 回复了您的评论: \"" +
+                                        comment.getContent() + "\""
+                        );
+                        n2.setTargetId(parentCommentId);
+                        n2.setTargetType("TorrentComment");
+                        n2.setIsRead(false);
+                        n2.setCreateTime(LocalDateTime.now());
+                        notificationService.saveNotification(n2);
+                    }
+                }
+            }
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/test/java/com/ptp/ptplatform/controller/HelpPostControllerTest.java b/src/test/java/com/ptp/ptplatform/controller/HelpPostControllerTest.java
index 2176e5b..07d89d3 100644
--- a/src/test/java/com/ptp/ptplatform/controller/HelpPostControllerTest.java
+++ b/src/test/java/com/ptp/ptplatform/controller/HelpPostControllerTest.java
@@ -4,8 +4,11 @@
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;

 import com.ptp.ptplatform.entity.HelpComment;

 import com.ptp.ptplatform.entity.HelpPost;

+import com.ptp.ptplatform.entity.Notification;

+import com.ptp.ptplatform.mapper.HelpPostMapper;

 import com.ptp.ptplatform.service.HelpCommentService;

 import com.ptp.ptplatform.service.HelpPostService;

+import com.ptp.ptplatform.service.NotificationService;

 import com.ptp.ptplatform.utils.Result;

 import org.junit.jupiter.api.BeforeEach;

 import org.junit.jupiter.api.Test;

@@ -33,6 +36,9 @@
     @Mock

     private HelpCommentService commentService;

 

+    @Mock

+    private NotificationService notificationService;

+

     @InjectMocks

     private HelpPostController postController;

 

@@ -142,12 +148,15 @@
     @Test

     void likePost_ShouldReturnSuccess() {

         int postId = 1;

-        doNothing().when(postService).incrementLike(postId);

+        String likerId = "alice123";

+        // 对应 ServiceImpl 中的 likePost(postId, likerId)

+        doNothing().when(postService).likePost(postId, likerId);

 

-        Result result = postController.likePost(postId);

+        Result result = postController.likePost(postId, likerId);

 

         assertEquals(200, result.getCode());

-        verify(postService, times(1)).incrementLike(postId);

+        assertEquals("点赞成功", result.getMessage());

+        verify(postService, times(1)).likePost(postId, likerId);

     }

 

     @Test

@@ -173,7 +182,7 @@
         String author = "user2";

         String content= "Hello";

 

-        // 这是我们希望最后返回的那个对象

+        // 构造“保存后”返回给前端的 HelpComment 对象

         HelpComment saved = new HelpComment();

         saved.setId(99);

         saved.setPostId(postId);

@@ -181,46 +190,49 @@
         saved.setContent(content);

         saved.setImageUrl(null);

 

-        // 1) stub save(...):拦截到 comment,将它的 id 设为 99

+        // 1) 当 commentService.commentOnHelpPost(...) 被调用时,

+        //    我们在 doAnswer 中给它传入的 HelpComment 对象设置 ID = 99

         doAnswer(invocation -> {

             HelpComment arg = invocation.getArgument(0);

             arg.setId(saved.getId());

-            return true;

-        }).when(commentService).save(any(HelpComment.class));

+            return null; // commentOnHelpPost 返回 void

+        }).when(commentService).commentOnHelpPost(any(HelpComment.class));

 

-        // 2) stub getById(99) -> saved

+        // 2) 后面 commentService.getById(99) 返回我们上面构造的 saved

         when(commentService.getById(saved.getId())).thenReturn(saved);

 

-        // 回复数的模拟

-        doNothing().when(postService).incrementReplyCount(postId);

+        // 模拟:commentOnHelpPost() 内部会调用 postService.incrementReplyCount(postId)

+        // 但控制器本身并不直接调用 incrementReplyCount,所以测试里不再 verify 这一行。

+        // 只要保证 postService.getById(postId) 能返回一个带有 replyCount 的 HelpPost 即可:

         HelpPost stubPost = new HelpPost();

         stubPost.setReplyCount(5);

         when(postService.getById(postId)).thenReturn(stubPost);

 

-        // 调用接口

-        Result result = postController.comment(postId, author, content, null);

+        // 调用控制器的 commentOnPost(...) 方法,第四个参数传 null(不带图片)

+        Result result = postController.commentOnPost(postId, author, content, null);

 

-        // 断言

         assertEquals(200, result.getCode());

         assertEquals(saved, result.getData().get("comment"));

-        assertEquals(5L,     result.getData().get("newReplyCount"));

+        assertEquals(5,     result.getData().get("newReplyCount"));

 

-        verify(commentService, times(1)).save(any(HelpComment.class));

-        verify(postService,   times(1)).incrementReplyCount(postId);

+        // 确保 commentService.commentOnHelpPost(...) 被调用一次

+        verify(commentService, times(1)).commentOnHelpPost(any(HelpComment.class));

+        // postService.incrementReplyCount(...) 由 commentOnHelpPost 内部调用,因此测试里无需 verify

     }

 

+

     @Test

     void addCommentWithImage_ShouldReturnSuccess() throws Exception {

         int postId    = 8;

         String author = "user3";

         String content= "With Image";

 

-        // 准备一个小文件

+        // 准备一个 “模拟上传文件”

         MockMultipartFile mockFile = new MockMultipartFile(

                 "image", "test.jpg", "image/jpeg", new byte[]{1,2,3}

         );

 

-        // 我们要“拿回”给前端的对象

+        // 构造“保存后”返回给前端的 HelpComment 对象

         HelpComment saved = new HelpComment();

         saved.setId(100);

         saved.setPostId(postId);

@@ -228,39 +240,36 @@
         saved.setContent(content);

         saved.setImageUrl("/uploads/whatever.jpg");

 

-        // 1) save(...) 时给 comment 赋 id=100

+        // 当 commentService.commentOnHelpPost(...) 被调用时,给传入的对象设置 ID 与 imageUrl

         doAnswer(invocation -> {

             HelpComment arg = invocation.getArgument(0);

             arg.setId(saved.getId());

-            // 同时把 imageUrl 塞一下,模拟上传后持久化时已经写入

             arg.setImageUrl(saved.getImageUrl());

-            return true;

-        }).when(commentService).save(any(HelpComment.class));

+            return null; // commentOnHelpPost 返回 void

+        }).when(commentService).commentOnHelpPost(any(HelpComment.class));

 

-        // 2) getById(100) -> saved

+        // 后面 commentService.getById(100) 返回我们构造的 saved

         when(commentService.getById(saved.getId())).thenReturn(saved);

 

-        // 回复数

-        doNothing().when(postService).incrementReplyCount(postId);

+        // 同样,postService.getById(postId) 返回一个带 replyCount=10 的 HelpPost

         HelpPost stubPost = new HelpPost();

         stubPost.setReplyCount(10);

         when(postService.getById(postId)).thenReturn(stubPost);

 

-        // 调用

-        Result result = postController.comment(postId, author, content, mockFile);

+        // 调用 commentOnPost,第四个参数传 mockFile(模拟有图片上传)

+        Result result = postController.commentOnPost(postId, author, content, mockFile);

 

         assertEquals(200, result.getCode());

         assertEquals(saved, result.getData().get("comment"));

-        assertEquals(10L,    result.getData().get("newReplyCount"));

+        assertEquals(10,    result.getData().get("newReplyCount"));

 

-        // 并且真正保存的对象也带上了 imageUrl

+        // 验证真正传给 commentService.commentOnHelpPost(...) 的 HelpComment 对象中,

+        // imageUrl 已经被设置成 "/uploads/whatever.jpg"

         ArgumentCaptor<HelpComment> captor = ArgumentCaptor.forClass(HelpComment.class);

-        verify(commentService).save(captor.capture());

+        verify(commentService).commentOnHelpPost(captor.capture());

         HelpComment toSave = captor.getValue();

         assertEquals(saved.getImageUrl(), toSave.getImageUrl());

 

-        verify(postService, times(1)).incrementReplyCount(postId);

+        // 同样无需 verify postService.incrementReplyCount(...),因为该调用在 commentOnHelpPost 内部完成

     }

-

-

 }

diff --git a/uploads/15b11ff9-2344-4d3f-b4bc-49734a1e7156.png b/uploads/15b11ff9-2344-4d3f-b4bc-49734a1e7156.png
new file mode 100644
index 0000000..71bd63e
--- /dev/null
+++ b/uploads/15b11ff9-2344-4d3f-b4bc-49734a1e7156.png
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/uploads/47983664-980d-4831-979d-22eee853c22b.jpg b/uploads/47983664-980d-4831-979d-22eee853c22b.jpg
new file mode 100644
index 0000000..a6d7f38
--- /dev/null
+++ b/uploads/47983664-980d-4831-979d-22eee853c22b.jpg
@@ -0,0 +1 @@
+test image
\ No newline at end of file
diff --git a/uploads/49d1a2db-5482-4d21-9a22-0071bd4cf5e5.jpg b/uploads/49d1a2db-5482-4d21-9a22-0071bd4cf5e5.jpg
new file mode 100644
index 0000000..a6d7f38
--- /dev/null
+++ b/uploads/49d1a2db-5482-4d21-9a22-0071bd4cf5e5.jpg
@@ -0,0 +1 @@
+test image
\ No newline at end of file
diff --git a/uploads/4ed56b76-c367-45db-98e1-d0f36508d8f1.jpg b/uploads/4ed56b76-c367-45db-98e1-d0f36508d8f1.jpg
new file mode 100644
index 0000000..aed2973
--- /dev/null
+++ b/uploads/4ed56b76-c367-45db-98e1-d0f36508d8f1.jpg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/uploads/f2e35f6d-2d7b-456a-8bf4-27526e20c5b5.jpg b/uploads/f2e35f6d-2d7b-456a-8bf4-27526e20c5b5.jpg
new file mode 100644
index 0000000..aed2973
--- /dev/null
+++ b/uploads/f2e35f6d-2d7b-456a-8bf4-27526e20c5b5.jpg
@@ -0,0 +1 @@
+
\ No newline at end of file