重新合并
Change-Id: Ied67fad091c03cf964124921ae8a69e720bd36de
diff --git a/src/main/java/com/ptp/ptplatform/controller/AnnouncementController.java b/src/main/java/com/ptp/ptplatform/controller/AnnouncementController.java
new file mode 100644
index 0000000..b751f0c
--- /dev/null
+++ b/src/main/java/com/ptp/ptplatform/controller/AnnouncementController.java
@@ -0,0 +1,67 @@
+package com.ptp.ptplatform.controller;
+
+import com.ptp.ptplatform.entity.ANNOUNCEMENT;
+import com.ptp.ptplatform.mapper.AnnouncementMapper;
+import com.ptp.ptplatform.utils.Result;
+import org.springframework.web.bind.annotation.*;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@RestController
+@RequestMapping("/announcement")
+public class AnnouncementController {
+
+ private final AnnouncementMapper announcementMapper;
+
+ public AnnouncementController(AnnouncementMapper announcementMapper) {
+ this.announcementMapper = announcementMapper;
+ }
+
+ // 获取所有公告
+ @GetMapping("/list")
+ public Result listAll() {
+ List<ANNOUNCEMENT> announcements = announcementMapper.selectAll();
+ return Result.ok().data("announcements", announcements);
+ }
+
+ // 获取最新5条公告(用于首页展示)
+ @GetMapping("/latest")
+ public Result listLatest() {
+ List<ANNOUNCEMENT> announcements = announcementMapper.selectLatest(5);
+ return Result.ok().data("announcements", announcements);
+ }
+
+ // 获取公告详情
+ @GetMapping("/{id}")
+ public Result getDetail(@PathVariable Integer id) {
+ ANNOUNCEMENT announcement = announcementMapper.selectById(id);
+ if (announcement == null) {
+ return Result.error(404).message("公告不存在");
+ }
+ return Result.ok().data("announcement", announcement);
+ }
+
+ // 新增公告
+ @PostMapping("/create")
+ public Result createAnnouncement(@RequestParam String title, @RequestParam String content) {
+ try {
+ ANNOUNCEMENT announcement = new ANNOUNCEMENT();
+ announcement.setTitle(title);
+ announcement.setContent(content);
+ announcement.setCreateTime(LocalDateTime.now());
+
+ int result = announcementMapper.insert(announcement);
+ if (result > 0) {
+ return Result.ok().message("公告发布成功");
+ } else {
+ // 添加错误码参数(如500表示服务器错误)
+ return Result.error(500).message("公告发布失败");
+ }
+ } catch (Exception e) {
+ // 添加错误码参数(如500表示服务器错误)
+ return Result.error(500).message("服务器错误: " + e.getMessage());
+ }
+ }
+}
+
diff --git a/src/main/java/com/ptp/ptplatform/controller/RequestCommentController.java b/src/main/java/com/ptp/ptplatform/controller/RequestCommentController.java
new file mode 100644
index 0000000..cfc7acd
--- /dev/null
+++ b/src/main/java/com/ptp/ptplatform/controller/RequestCommentController.java
@@ -0,0 +1,147 @@
+package com.ptp.ptplatform.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.ptp.ptplatform.entity.RequestComment;
+import com.ptp.ptplatform.service.RequestCommentService;
+import com.ptp.ptplatform.service.RequestPostService;
+import com.ptp.ptplatform.utils.Result;
+import lombok.AllArgsConstructor;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.io.IOException;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.UUID;
+
+@RestController
+@RequestMapping("/request/comments")
+@AllArgsConstructor
+public class RequestCommentController {
+ private final RequestCommentService commentService;
+ private final RequestPostService postService;
+
+ @PostMapping("/{commentId}/like")
+ public Result like(@PathVariable int commentId) {
+ commentService.incrementLike(commentId);
+ return Result.ok();
+ }
+
+ // 获取评论的回复
+ @GetMapping("/{commentId}/replies")
+ public Result getReplies(@PathVariable int commentId) {
+ List<RequestComment> replies = commentService.getReplies(commentId);
+ return Result.ok().data("replies", replies);
+ }
+
+ @PostMapping(value = "/{commentId}/replies", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+ public Result addReply(@PathVariable int commentId,
+ @RequestParam("authorId") String authorId,
+ @RequestParam("content") String content,
+ @RequestParam(value = "image", required = false) MultipartFile image) {
+ // 校验父评论是否存在
+ RequestComment parentComment = commentService.getById(commentId);
+ if (parentComment == null) {
+ return Result.error(404).setMessage("被回复的评论不存在");
+ }
+
+ // 构造回复评论对象
+ RequestComment reply = new RequestComment();
+ reply.setPostId(parentComment.getPostId());
+ reply.setParentId(commentId);
+ reply.setAuthorId(authorId); // String 类型
+ reply.setReplyTo(parentComment.getAuthorId());
+ reply.setContent(content);
+ reply.setCreateTime(LocalDateTime.now());
+ reply.setLikeCount(0);
+
+ // 可选:处理图片上传
+ if (image != null && !image.isEmpty()) {
+ try {
+ String fileExt = image.getOriginalFilename()
+ .substring(image.getOriginalFilename().lastIndexOf('.') + 1);
+ String fileName = UUID.randomUUID() + "." + fileExt;
+
+ String uploadDir = System.getProperty("user.dir") + File.separator + "uploads";
+ File dir = new File(uploadDir);
+ if (!dir.exists()) dir.mkdirs();
+
+ File dest = new File(dir, fileName);
+ image.transferTo(dest);
+
+ // 保存相对访问路径
+ reply.setImageUrl("/uploads/" + fileName);
+ } catch (IOException e) {
+ return Result.error(404).setMessage("图片上传失败:" + e.getMessage());
+ }
+ }
+
+ // 保存到数据库
+ commentService.save(reply);
+
+ // 更新帖子评论数
+ postService.incrementReplyCount(parentComment.getPostId());
+
+ // 返回新回复及其父评论 ID
+ return Result.ok()
+ .data("reply", reply)
+ .data("parentCommentId", commentId);
+ }
+
+
+ // 删除评论
+ @DeleteMapping("/{commentId}")
+ public Result deleteComment(
+ @PathVariable int commentId,
+ @RequestParam String authorId) {
+
+ // 1. 获取要删除的评论
+ RequestComment comment = commentService.getById(commentId);
+ if (comment == null) {
+ return Result.error(404).setMessage("评论不存在");
+ }
+
+ // 2. 验证当前用户是否有权限删除(比对authorId)
+ if (!comment.getAuthorId().equals(authorId)) {
+ return Result.error(403).setMessage("无权删除此评论");
+ }
+
+ // 3. 递归删除所有子回复
+ deleteRepliesRecursively(commentId);
+
+ // 4. 删除主评论
+ boolean removed = commentService.removeById(commentId);
+
+// // 5. 更新帖子回复数(如果需要)
+// if (removed && comment.getParentId() == 0) {
+// postService.decrementReplyCount(comment.getPostId());
+// }
+
+ return removed ? Result.ok() : Result.error(500).setMessage("删除评论失败");
+ }
+ // 递归删除所有回复
+ private void deleteRepliesRecursively(int parentCommentId) {
+ // 添加保护措施防止无限递归
+ if (parentCommentId <= 0) return;
+
+ // 查找所有直接回复
+ List<RequestComment> replies = commentService.list(
+ new QueryWrapper<RequestComment>().eq("parent_id", parentCommentId)
+ );
+
+ // 递归删除每个回复及其子回复
+ for (RequestComment reply : replies) {
+ deleteRepliesRecursively(reply.getId());
+ commentService.removeById(reply.getId());
+
+// // 如果是主评论(parent_id=0),更新帖子回复数
+// if (reply.getParentId() == 0) {
+// postService.decrementReplyCount(reply.getPostId());
+// }
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/ptp/ptplatform/controller/RequestPostController.java b/src/main/java/com/ptp/ptplatform/controller/RequestPostController.java
new file mode 100644
index 0000000..28a18ea
--- /dev/null
+++ b/src/main/java/com/ptp/ptplatform/controller/RequestPostController.java
@@ -0,0 +1,270 @@
+package com.ptp.ptplatform.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.ptp.ptplatform.entity.RequestComment;
+import com.ptp.ptplatform.entity.RequestPost;
+import com.ptp.ptplatform.service.RequestCommentService;
+import com.ptp.ptplatform.service.RequestPostService;
+import com.ptp.ptplatform.service.NotificationService;
+import com.ptp.ptplatform.utils.Result;
+import lombok.AllArgsConstructor;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.*;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.io.IOException;
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@RestController
+@RequestMapping("/request/posts")
+@AllArgsConstructor
+public class RequestPostController {
+ private final RequestPostService postService;
+ private final RequestCommentService commentService;
+ private final NotificationService notificationService;
+
+ // 修改创建帖子的方法,支持图片上传
+ @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
+ public Result createPost(
+
+ @RequestParam("title") String title,
+ @RequestParam("content") String content,
+ @RequestParam("authorId") String authorId,
+ @RequestParam(value = "image", required = false) MultipartFile image) {
+
+ RequestPost post = new RequestPost();
+ post.setTitle(title);
+ post.setContent(content);
+ post.setAuthorId(authorId);
+ post.setCreateTime(LocalDateTime.now());
+ post.setLikeCount(0);
+ post.setReplyCount(0);
+ post.setImageUrl(null);
+// post.setIsSolved(false);
+
+ // 处理图片上传
+ if (image != null && !image.isEmpty()) {
+ try {
+ String originalFilename = image.getOriginalFilename();
+ if(originalFilename != null) {
+ String fileExt = originalFilename.substring(image.getOriginalFilename().lastIndexOf('.') + 1);
+ String fileName = UUID.randomUUID() + "." + fileExt;
+
+ String uploadDir = System.getProperty("user.dir") + File.separator + "uploads";
+ File dir = new File(uploadDir);
+ if (!dir.exists()) dir.mkdirs();
+
+ File dest = new File(dir, fileName);
+ image.transferTo(dest);
+
+ // 保存相对访问路径
+ post.setImageUrl("/uploads/" + fileName);
+ }
+ } catch (IOException e) {
+ return Result.error(404).setMessage("图片上传失败:" + e.getMessage());
+ }
+ } else {
+ post.setImageUrl(null);
+ }
+ postService.save(post);
+ return Result.ok().data("post", post);
+ }
+
+ // 列表分页
+ @GetMapping
+ public Result listPosts(@RequestParam(defaultValue = "1") int page,
+ @RequestParam(defaultValue = "5") int size) {
+ IPage<RequestPost> ipage = postService.page(
+ new Page<>(page, size),
+ new QueryWrapper<RequestPost>().orderByDesc("create_time")
+ );
+ return Result.ok()
+ .data("records", ipage.getRecords())
+ .data("total", ipage.getTotal());
+ }
+
+ @GetMapping("/search")
+ public Result searchPosts(
+ @RequestParam String keyword,
+ @RequestParam(defaultValue = "1") int page,
+ @RequestParam(defaultValue = "5") int size) {
+
+ // 1. 把搜索词拆分成单词(中英文混合拆分)
+ List<String> keywords = splitKeywords(keyword); // 例如 "你好hello" → ["你", "好", "hello"]
+
+ // 2. 构建动态查询条件(每个关键词用 OR 连接)
+ QueryWrapper<RequestPost> queryWrapper = new QueryWrapper<>();
+ for (String word : keywords) {
+ queryWrapper.and(wrapper ->
+ // 使用LOWER函数使搜索不区分大小写
+ wrapper.apply("LOWER(title) LIKE LOWER({0})", "%" + word + "%")
+ .or()
+ .apply("LOWER(content) LIKE LOWER({0})", "%" + word + "%")
+ );
+ }
+ queryWrapper.orderByDesc("create_time");
+
+ // 3. 执行分页查询
+ IPage<RequestPost> ipage = postService.page(new Page<>(page, size), queryWrapper);
+ return Result.ok()
+ .data("records", ipage.getRecords())
+ .data("total", ipage.getTotal());
+ }
+
+ /**
+ * 拆分关键词(支持中英文混合)
+ * 例如:"你好hello 世界" → ["你", "好", "hello", "世", "界"]
+ */
+ private List<String> splitKeywords(String keyword) {
+ List<String> keywords = new ArrayList<>();
+
+ // 用正则匹配中文字符和英文单词
+ Pattern pattern = Pattern.compile("([\\u4e00-\\u9fa5])|([a-zA-Z0-9]+)");
+ Matcher matcher = pattern.matcher(keyword);
+
+ while (matcher.find()) {
+ String matched = matcher.group();
+ keywords.add(matched);
+ }
+
+ return keywords;
+ }
+
+ @GetMapping("/{Id}")
+ public Result getPost(@PathVariable int Id) {
+ RequestPost post = postService.getById(Id);
+ if (post == null) {
+ return Result.error(404).setMessage("帖子不存在");
+ }
+
+ // 获取所有评论(按创建时间排序)
+ List<RequestComment> allComments = commentService.list(
+ new QueryWrapper<RequestComment>()
+ .eq("post_id", Id)
+ .orderByAsc("create_time")
+ );
+
+ // 获取实时评论总数(包括所有层级)
+ int totalComments = allComments.size(); // 直接使用列表大小
+ post.setReplyCount(totalComments); // 更新计数
+
+ // 构建评论树形结构
+ List<RequestComment> rootComments = new ArrayList<>();
+ Map<Integer, RequestComment> commentMap = new HashMap<>();
+
+ // 第一遍:初始化所有评论到map中
+ for (RequestComment comment : allComments) {
+ comment.setReplies(new ArrayList<>()); // 初始化replies列表
+ commentMap.put(comment.getId(), comment);
+ }
+
+ // 第二遍:构建父子关系
+ for (RequestComment comment : allComments) {
+ if (comment.getParentId() == 0) {
+ rootComments.add(comment);
+ } else {
+ RequestComment parent = commentMap.get(comment.getParentId());
+ if (parent != null) {
+ parent.getReplies().add(comment);
+ }
+ }
+ }
+
+ return Result.ok()
+ .data("post", post)
+ .data("comments", rootComments);
+ }
+
+ // 点赞帖子
+ @PostMapping("/{Id}/like")
+ 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 commentOnPost(
+ @PathVariable("Id") int Id,
+ @RequestParam("authorId") String authorId,
+ @RequestParam("content") String content,
+ @RequestParam(value = "image", required = false) MultipartFile image
+ ) {
+ // 1. 构造 HelpComment 对象
+ RequestComment comment = new RequestComment();
+ comment.setPostId(Id);
+ comment.setAuthorId(authorId);
+ comment.setContent(content);
+ comment.setCreateTime(LocalDateTime.now());
+ comment.setLikeCount(0);
+ 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().toString() + "." + fileExt;
+
+ // 上传到项目根目录的 uploads 文件夹
+ String uploadDir = System.getProperty("user.dir")
+ + File.separator + "uploads";
+ File dir = new File(uploadDir);
+ if (!dir.exists()) dir.mkdirs();
+
+ File dest = new File(dir, fileName);
+ image.transferTo(dest);
+
+ comment.setImageUrl("/uploads/" + fileName);
+ } catch (IOException e) {
+ return Result.error(500).message("图片上传失败:" + e.getMessage());
+ }
+ }
+
+ // 3. 通过 ServiceImpl 完成“保存评论 + 更新 reply_count + 发通知给帖子作者”
+ commentService.commentOnRequestPost(comment);
+
+ // 4. 构造返回值:把刚刚保存的评论(从数据库中查出来的完整对象)和帖子最新的 replyCount 都返回
+ // 注意:comment.insert(...) 后,comment.getId() 将有 DB 自动生成的主键
+ RequestComment newComment = commentService.getById(comment.getId());
+ int updatedReplyCount = postService.getById(Id).getReplyCount();
+
+ return Result.ok()
+ .data("comment", newComment)
+ .data("newReplyCount", updatedReplyCount);
+ }
+
+ // 删除帖子
+ @DeleteMapping("/{postId}")
+ public Result deletePost(@PathVariable int postId,
+ @RequestParam String authorId) {
+ try {
+ // 1. 验证当前用户是否有权限删除(比对authorId)
+ RequestPost post = postService.getById(postId);
+ if (post == null) {
+ return Result.error(404).setMessage("帖子不存在");
+ }
+ if (!post.getAuthorId().equals(authorId)) {
+ return Result.error(403).setMessage("无权删除此帖子");
+ }
+
+ // 2. 删除帖子及关联评论
+ commentService.remove(new QueryWrapper<RequestComment>().eq("post_id", postId));
+ boolean removed = postService.removeById(postId);
+ return removed ? Result.ok() : Result.error(404).setMessage("删除评论失败");
+ } catch (Exception e) {
+ return Result.error(500).setMessage("服务器错误: " + e.getMessage());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ptp/ptplatform/controller/TorrentController.java b/src/main/java/com/ptp/ptplatform/controller/TorrentController.java
index e69c70e..fa0f92e 100644
--- a/src/main/java/com/ptp/ptplatform/controller/TorrentController.java
+++ b/src/main/java/com/ptp/ptplatform/controller/TorrentController.java
@@ -23,6 +23,8 @@
import org.springframework.web.bind.annotation.*;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.web.multipart.MultipartFile;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.File;
@@ -67,6 +69,7 @@
private TrackerService trackerservice = new TrackerService(new ObjectMapper());
private ClientService clientservice = new ClientService();
+ private static final Logger logger = LoggerFactory.getLogger(TorrentController.class);
@GetMapping
public Result listPosts(
@@ -316,6 +319,7 @@
torrent.setFilePath(uploadedFile.getAbsolutePath());
torrent.setCreateTime(LocalDateTime.now());
torrentMapper.insertTorrent(torrent);
+ System.out.println("Generated ID: " + torrent.getId());
TrackedPeer tp = createTrackedPeer(request, tt, torrent.getId());
tp.update(TrackedPeer.PeerState.COMPLETED, fileSize, 0, 0);
@@ -367,27 +371,41 @@
// 能成功获取到数据
// 能够根据此值,完成对相关数据的修改
@GetMapping("/downloadTorrent")
- public Result getTorrent(HttpServletRequest request, int id, String downloadPath) throws Exception {
+ public Result getTorrent(HttpServletRequest request,
+ @RequestParam int id,
+ @RequestParam String downloadPath) throws Exception {
+ // 1. 获取种子和用户信息
TORRENT torrent = torrentMapper.selectByID(id);
+ if (torrent == null) {
+ logger.error("尝试下载不存在的种子,ID: {}", id);
+ return Result.error(404).message("种子不存在");
+ }
USER userDownload = userController.getUserInRequest(request);
USER userUpload = userMapper.selectByUsername(torrent.getUsername());
- //检查分享率是否允许下载
+ // 2. 记录下载日志
+ logger.info("用户 {} 尝试下载种子 {} (上传者: {})",
+ userDownload.getUsername(), id, userUpload.getUsername());
- if(!userDownload.allowDownload(torrent.getSize())){
+ // 3. 检查分享率(如果是自己的种子则跳过检查)
+ if (!userDownload.getUsername().equals(userUpload.getUsername()) &&
+ !userDownload.allowDownload(torrent.getSize())) {
+ logger.warn("用户 {} 分享率不足,无法下载种子 {}",
+ userDownload.getUsername(), id);
return Result.error(409).message("分享率不足下载此资源");
}
- // 下载完成
- // 传入变量:下载种子的id 下载到文件的路径
- clientservice.downloadTorrent(torrent.getFilePath(), downloadPath, userDownload.getUsername());
+ // 4. 执行下载
+ try {
+ // 4.1 调用客户端服务下载
- TrackedTorrent tt = trackerservice.getTrackedTorrent(torrent.getHash());
- TrackedPeer tp = createTrackedPeer(request, tt, torrent.getId());
- tp.update(TrackedPeer.PeerState.COMPLETED, 0, 0, torrent.getSize());
- tt.addPeer(tp);
-
- trackerservice.serTracker();
+ clientservice.downloadTorrent(torrent.getFilePath(), downloadPath, userDownload.getUsername());
+ // 4.2 更新Tracker信息
+ TrackedTorrent tt = trackerservice.getTrackedTorrent(torrent.getHash());
+ TrackedPeer tp = createTrackedPeer(request, tt, torrent.getId());
+ tp.update(TrackedPeer.PeerState.COMPLETED, 0, 0, torrent.getSize());
+ tt.addPeer(tp);
+ trackerservice.serTracker();
// 通知下载完成
Notification downloadCompleteNotice = new Notification();
@@ -403,16 +421,24 @@
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()));
- userMapper.updateUser(userUpload);
- userMapper.updateUser(userDownload);
- DOWNLOAD_TORRENT dt = new DOWNLOAD_TORRENT(userDownload.getUsername(), torrent.getId(), torrent.getSize());
- downloadTorrentMapper.insertDownloadTorrent(dt);
+ // 4.3 更新用户数据(只有下载他人种子时才更新)
+ if (!userDownload.getUsername().equals(userUpload.getUsername())) {
+ userDownload.updateDownload(SizeCalculation.getDownload(torrent.getSize(), discountMapper, torrent.getCreateTime()));
+ userUpload.updateUpload(SizeCalculation.getUpload(torrent.getSize(), discountMapper, torrent.getCreateTime()));
+ userMapper.updateUser(userUpload);
+ userMapper.updateUser(userDownload);
- return Result.ok().data("fileName", torrent.getTorrentName())
- .data("taskId", torrent.getHash());
+ DOWNLOAD_TORRENT dt = new DOWNLOAD_TORRENT(userDownload.getUsername(), torrent.getId(), torrent.getSize());
+ downloadTorrentMapper.insertDownloadTorrent(dt);
+ }
+
+ logger.info("用户 {} 成功下载种子 {}", userDownload.getUsername(), id);
+ return Result.ok().data("fileName", torrent.getTorrentName())
+ .data("taskId", torrent.getHash());
+ }catch (Exception e) {
+ logger.error("下载种子失败", e);
+ return Result.error(500).message("下载失败: " + e.getMessage());
+ }
}
@GetMapping("/getProgress")
@@ -424,14 +450,15 @@
for (String[] task : tasks) {
if (task[0].equals(username)) {
- Double progress = 0.0; // 默认进度为0.0
try {
- progress = clientservice.getDownloadProgress(task[1]); // 获取下载进度
- } catch (IllegalStateException e) {
- // 如果发生异常,则进度保持为0.0,并继续处理下一个任务
- progress = 0.0;
+ double progress = clientservice.getDownloadProgress(task[1]);
+ // 确保进度在0-1之间
+ progress = Math.max(0, Math.min(1, progress));
+ progresses.put(task[1], progress);
+ } catch (Exception e) {
+ // 如果出现异常,假设下载已完成
+ progresses.put(task[1], 1.0);
}
- progresses.put(task[1], progress);
}
}
return Result.ok().data("progresses", progresses);
@@ -440,24 +467,38 @@
//根据id删除
@DeleteMapping("/deleteTorrent/{id}")
public Result deleteTorrent(HttpServletRequest request, @PathVariable("id") int id) throws Exception {
+ // 1. 验证用户权限
+ USER currentUser = userController.getUserInRequest(request);
TORRENT torrent = torrentMapper.selectByID(id);
- String filePath = torrent.getFilePath();
- try {
- // 检查文件是否存在
- if (Files.exists(Paths.get(filePath))) {
- Files.delete(Paths.get(filePath)); // 删除文件
- torrentMapper.deleteTorrent(id);
+ if (torrent == null) {
+ return Result.error(404).setMessage("种子不存在");
+ }
- return Result.ok().message("种子文件删除成功");
- } else {
- throw new IOException("File not found: " + filePath);
+ // 3. 删除关联记录(分步骤)
+ try {
+ // 3.1 先删除下载记录
+ downloadTorrentMapper.deleteByTorrentId(id);
+
+ // 3.2 删除种子文件
+ if (Files.exists(Paths.get(torrent.getFilePath()))) {
+ Files.delete(Paths.get(torrent.getFilePath()));
}
+
+ // 3.3 最后删除种子记录
+ int deleteCount = torrentMapper.deleteTorrent(id);
+ if (deleteCount == 0) {
+ return Result.error(404).setMessage("种子删除失败,可能已被删除");
+ }
+
+ return Result.ok().message("种子删除成功");
+
} catch (IOException e) {
- // 异常处理
- e.printStackTrace();
- // 返回失败结果或其他处理逻辑
- return Result.error(404).setMessage("种子文件删除失败");
+ logger.error("文件删除失败", e);
+ return Result.error(500).setMessage("文件删除失败: " + e.getMessage());
+ } catch (Exception e) {
+ logger.error("种子删除失败", e);
+ return Result.error(500).setMessage("种子删除失败: " + e.getMessage());
}
}
diff --git a/src/main/java/com/ptp/ptplatform/entity/ANNOUNCEMENT.java b/src/main/java/com/ptp/ptplatform/entity/ANNOUNCEMENT.java
new file mode 100644
index 0000000..d522335
--- /dev/null
+++ b/src/main/java/com/ptp/ptplatform/entity/ANNOUNCEMENT.java
@@ -0,0 +1,12 @@
+package com.ptp.ptplatform.entity;
+
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+public class ANNOUNCEMENT {
+ private Integer id;
+ private String title;
+ private String content;
+ private LocalDateTime createTime;
+}
\ No newline at end of file
diff --git a/src/main/java/com/ptp/ptplatform/entity/RequestComment.java b/src/main/java/com/ptp/ptplatform/entity/RequestComment.java
new file mode 100644
index 0000000..043263a
--- /dev/null
+++ b/src/main/java/com/ptp/ptplatform/entity/RequestComment.java
@@ -0,0 +1,37 @@
+package com.ptp.ptplatform.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+@TableName("request_comments")
+public class RequestComment {
+ @TableId(value = "id", type = IdType.AUTO)
+ private Integer id; // 使用包装类型以支持 null
+ private Integer postId;
+ private String authorId;
+ private String content;
+ private Integer likeCount;
+ private LocalDateTime createTime;
+ private Integer parentId; // 新增字段,用于回复功能,0表示主评论,非0表示回复某条评论
+ private String replyTo;
+ private String ImageUrl;
+
+ @TableField(exist = false)
+ private List<RequestComment> replies = new ArrayList<>(); // 必须初始化
+
+ // 确保有getter和setter
+ public List<RequestComment> getReplies() {
+ return replies;
+ }
+
+ public void setReplies(List<RequestComment> replies) {
+ this.replies = replies;
+ }
+}
diff --git a/src/main/java/com/ptp/ptplatform/entity/RequestPost.java b/src/main/java/com/ptp/ptplatform/entity/RequestPost.java
new file mode 100644
index 0000000..be7fb47
--- /dev/null
+++ b/src/main/java/com/ptp/ptplatform/entity/RequestPost.java
@@ -0,0 +1,21 @@
+package com.ptp.ptplatform.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName(value = "request_posts")
+public class RequestPost {
+ @TableId(value = "id", type = IdType.AUTO)
+ private Integer id; // 使用包装类型以支持 null
+ private String authorId;
+ private String title;
+ private String content;
+ private String imageUrl;
+ private Integer likeCount;
+ private Integer replyCount;
+ private LocalDateTime createTime;
+}
\ No newline at end of file
diff --git a/src/main/java/com/ptp/ptplatform/entity/TORRENT.java b/src/main/java/com/ptp/ptplatform/entity/TORRENT.java
index ce06e22..b80986e 100644
--- a/src/main/java/com/ptp/ptplatform/entity/TORRENT.java
+++ b/src/main/java/com/ptp/ptplatform/entity/TORRENT.java
@@ -11,6 +11,8 @@
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Data;
+import lombok.Setter;
+
import java.time.LocalDateTime;
import java.util.Date;
@@ -18,6 +20,7 @@
@TableName("torrent") // 使用MyBatis-Plus的注解指定表名
public class TORRENT {
+ @Setter
private Integer id;
private String torrentName; // 文件名
@@ -69,7 +72,7 @@
public long getSize() {
return this.size;
}
- public int getId(){
+ public Integer getId(){
return this.id;
}
}
\ 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 3798696..3c93c76 100644
--- a/src/main/java/com/ptp/ptplatform/entity/USER.java
+++ b/src/main/java/com/ptp/ptplatform/entity/USER.java
@@ -183,7 +183,18 @@
if (this.upload / (fileSize + this.download) < 0.5) {
return false;
}
- return true;
+
+ // 避免除以零
+ if (download == 0) return true;
+
+ // 计算当前分享率
+ double currentRatio = (double)upload / download;
+
+ // 计算下载后的分享率
+ double newRatio = (double)upload / (download + fileSize);
+
+ // 设置阈值,例如0.5
+ return newRatio >= 0.5;
}
diff --git a/src/main/java/com/ptp/ptplatform/mapper/AnnouncementMapper.java b/src/main/java/com/ptp/ptplatform/mapper/AnnouncementMapper.java
new file mode 100644
index 0000000..6fc3f80
--- /dev/null
+++ b/src/main/java/com/ptp/ptplatform/mapper/AnnouncementMapper.java
@@ -0,0 +1,43 @@
+package com.ptp.ptplatform.mapper;
+
+import com.ptp.ptplatform.entity.ANNOUNCEMENT;
+import org.apache.ibatis.annotations.*;
+
+import java.util.List;
+
+@Mapper
+public interface AnnouncementMapper {
+ // 使用注解方式实现
+ // 插入新公告(返回自增ID)
+ @Insert("INSERT INTO announcement (title, content, create_time) " +
+ "VALUES (#{title}, #{content}, #{createTime})")
+ @Options(useGeneratedKeys = true, keyProperty = "id") // 获取自增ID
+ int insert(ANNOUNCEMENT announcement);
+
+ @Select("SELECT id, title, content, create_time FROM announcement WHERE id = #{id}")
+ @Results({
+ @Result(property = "id", column = "id"),
+ @Result(property = "title", column = "title"),
+ @Result(property = "content", column = "content"),
+ @Result(property = "createTime", column = "create_time")
+ })
+ ANNOUNCEMENT selectById(Integer id);
+
+ @Select("SELECT id, title, content, create_time FROM announcement ORDER BY create_time DESC LIMIT #{limit}")
+ @Results({
+ @Result(property = "id", column = "id"),
+ @Result(property = "title", column = "title"),
+ @Result(property = "content", column = "content"),
+ @Result(property = "createTime", column = "create_time")
+ })
+ List<ANNOUNCEMENT> selectLatest(int limit);
+
+ @Select("SELECT id, title, content, create_time FROM announcement ORDER BY create_time DESC")
+ @Results({
+ @Result(property = "id", column = "id"),
+ @Result(property = "title", column = "title"),
+ @Result(property = "content", column = "content"),
+ @Result(property = "createTime", column = "create_time")
+ })
+ List<ANNOUNCEMENT> selectAll();
+}
\ No newline at end of file
diff --git a/src/main/java/com/ptp/ptplatform/mapper/DownloadTorrentMapper.java b/src/main/java/com/ptp/ptplatform/mapper/DownloadTorrentMapper.java
index 22ff203..b488b7f 100644
--- a/src/main/java/com/ptp/ptplatform/mapper/DownloadTorrentMapper.java
+++ b/src/main/java/com/ptp/ptplatform/mapper/DownloadTorrentMapper.java
@@ -5,6 +5,7 @@
import org.apache.ibatis.annotations.*;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.*;
import java.util.List;
@@ -34,4 +35,8 @@
@Select("SELECT DISTINCT username FROM download_torrent WHERE torrentid = #{torrentId}")
List<String> findUsersByTorrentId(@Param("torrentId") Integer torrentId);
+ // 添加删除方法
+ @Delete("DELETE FROM download_torrent WHERE torrentid = #{torrentId}")
+ int deleteByTorrentId(@Param("torrentId") int torrentId);
+
}
diff --git a/src/main/java/com/ptp/ptplatform/mapper/RequestCommentMapper.java b/src/main/java/com/ptp/ptplatform/mapper/RequestCommentMapper.java
new file mode 100644
index 0000000..b4566cc
--- /dev/null
+++ b/src/main/java/com/ptp/ptplatform/mapper/RequestCommentMapper.java
@@ -0,0 +1,8 @@
+package com.ptp.ptplatform.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ptp.ptplatform.entity.RequestComment;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface RequestCommentMapper extends BaseMapper<RequestComment> {}
\ No newline at end of file
diff --git a/src/main/java/com/ptp/ptplatform/mapper/RequestPostMapper.java b/src/main/java/com/ptp/ptplatform/mapper/RequestPostMapper.java
new file mode 100644
index 0000000..e746175
--- /dev/null
+++ b/src/main/java/com/ptp/ptplatform/mapper/RequestPostMapper.java
@@ -0,0 +1,8 @@
+package com.ptp.ptplatform.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ptp.ptplatform.entity.RequestPost;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface RequestPostMapper extends BaseMapper<RequestPost> {}
\ No newline at end of file
diff --git a/src/main/java/com/ptp/ptplatform/mapper/TorrentMapper.java b/src/main/java/com/ptp/ptplatform/mapper/TorrentMapper.java
index deb43fc..bdb5d6f 100644
--- a/src/main/java/com/ptp/ptplatform/mapper/TorrentMapper.java
+++ b/src/main/java/com/ptp/ptplatform/mapper/TorrentMapper.java
@@ -61,6 +61,7 @@
int countByUsername(@Param("username") String username);
// 插入Torrent
+ @Options(useGeneratedKeys = true, keyProperty = "id")
@Insert("INSERT INTO torrent (torrent_name, description, category, region, resolution, subtitle, size, hash, username, create_time, file_path) " +
"VALUES (#{torrentName}, #{description}, #{category}, #{region}, #{resolution}, #{subtitle}, #{size}, #{hash}, #{username}, #{createTime}, #{filePath})")
int insertTorrent(TORRENT torrent);
diff --git a/src/main/java/com/ptp/ptplatform/service/ClientService.java b/src/main/java/com/ptp/ptplatform/service/ClientService.java
index daa4544..848b132 100644
--- a/src/main/java/com/ptp/ptplatform/service/ClientService.java
+++ b/src/main/java/com/ptp/ptplatform/service/ClientService.java
@@ -36,12 +36,19 @@
// 修改后的获取下载进度方法
public double getDownloadProgress(String torrentFilePath) throws Exception {
- TorrentStatistic stats = client.getStatistics(torrentFilePath);
- if (stats == null) {
- return 0.0;
+ try {
+ TorrentStatistic stats = client.getStatistics(torrentFilePath);
+ if (stats == null) {
+ // 如果统计信息为空,可能下载已完成
+ return 1.0; // 返回100%表示已完成
+ }
+ double progress = stats.getPercentageDownloaded();
+ // 确保进度在0-1之间
+ return Math.max(0, Math.min(1, progress));
+ } catch (Exception e) {
+ // 如果出现异常,可能下载已完成
+ return 1.0;
}
- // 直接使用类中已有的百分比计算方法
- return stats.getPercentageDownloaded();
}
public List<String[]> getTasks() {
diff --git a/src/main/java/com/ptp/ptplatform/service/RequestCommentService.java b/src/main/java/com/ptp/ptplatform/service/RequestCommentService.java
new file mode 100644
index 0000000..d661294
--- /dev/null
+++ b/src/main/java/com/ptp/ptplatform/service/RequestCommentService.java
@@ -0,0 +1,15 @@
+package com.ptp.ptplatform.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.ptp.ptplatform.entity.RequestComment;
+
+import java.util.List;
+
+public interface RequestCommentService extends IService<RequestComment> {
+ void incrementLike(int commentId);
+ List<RequestComment> getReplies(int parentId);
+ // 新增:获取帖子直接评论数
+ long countByPostId(Integer postId);// 新增方法
+ void commentOnRequestPost(RequestComment comment);
+ void replyComment(RequestComment reply);
+}
\ No newline at end of file
diff --git a/src/main/java/com/ptp/ptplatform/service/RequestPostService.java b/src/main/java/com/ptp/ptplatform/service/RequestPostService.java
new file mode 100644
index 0000000..b3703f5
--- /dev/null
+++ b/src/main/java/com/ptp/ptplatform/service/RequestPostService.java
@@ -0,0 +1,12 @@
+package com.ptp.ptplatform.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.ptp.ptplatform.entity.RequestComment;
+import com.ptp.ptplatform.entity.RequestPost;
+
+public interface RequestPostService extends IService<RequestPost> {
+ void incrementLike(Integer postId);
+ void incrementReplyCount(Integer postId);
+ void likePost(Integer postId, String likerId);//点赞,并通知该帖子的作者。
+ void commentOnRequestPost(RequestComment comment); //在帖子下发表一条新评论,并通知该帖子的作者。
+}
\ No newline at end of file
diff --git a/src/main/java/com/ptp/ptplatform/service/impl/RequestCommentServiceImpl.java b/src/main/java/com/ptp/ptplatform/service/impl/RequestCommentServiceImpl.java
new file mode 100644
index 0000000..fd92b9d
--- /dev/null
+++ b/src/main/java/com/ptp/ptplatform/service/impl/RequestCommentServiceImpl.java
@@ -0,0 +1,105 @@
+package com.ptp.ptplatform.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ptp.ptplatform.entity.RequestComment;
+import com.ptp.ptplatform.entity.RequestPost;
+import com.ptp.ptplatform.entity.Notification;
+import com.ptp.ptplatform.mapper.RequestCommentMapper;
+import com.ptp.ptplatform.mapper.RequestPostMapper;
+import com.ptp.ptplatform.service.RequestCommentService;
+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;
+
+@Service
+public class RequestCommentServiceImpl
+ extends ServiceImpl<RequestCommentMapper, RequestComment>
+ implements RequestCommentService {
+ @Autowired
+ private RequestCommentMapper requestCommentMapper;
+
+ @Autowired
+ private RequestPostMapper requestPostMapper;
+
+ @Autowired
+ private NotificationService notificationService;
+
+ @Override
+ @Transactional
+ public void incrementLike(int commentId) {
+ this.update(null,
+ new com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper<RequestComment>()
+ .eq("id", commentId)
+ .setSql("like_count = like_count + 1")
+ );
+ }
+
+ @Override
+ @Transactional(readOnly = true)
+ public List<RequestComment> getReplies(int parentId) {
+ return this.baseMapper.selectList(
+ new QueryWrapper<RequestComment>()
+ .eq("parent_id", parentId)
+ .orderByAsc("create_time")
+ );
+ }
+ @Override
+ public long countByPostId(Integer postId) {
+ return count(new QueryWrapper<RequestComment>()
+ .eq("post_id", postId)); // 只按post_id统计
+ }
+
+ @Override
+ public void commentOnRequestPost(RequestComment comment) {
+ // 1. 保存评论
+ requestCommentMapper.insert(comment);
+
+ // 2. 通知帖子作者
+ RequestPost post = requestPostMapper.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(RequestComment reply) {
+ // 1. 保存子评论
+ requestCommentMapper.insert(reply);
+
+ // 2. 通知父评论作者
+ RequestComment parent = requestCommentMapper.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("RequestComment");
+ notificationService.saveNotification(n);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ptp/ptplatform/service/impl/RequestPostServiceImpl.java b/src/main/java/com/ptp/ptplatform/service/impl/RequestPostServiceImpl.java
new file mode 100644
index 0000000..08b5767
--- /dev/null
+++ b/src/main/java/com/ptp/ptplatform/service/impl/RequestPostServiceImpl.java
@@ -0,0 +1,122 @@
+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.RequestComment;
+import com.ptp.ptplatform.entity.RequestPost;
+import com.ptp.ptplatform.entity.Notification;
+import com.ptp.ptplatform.mapper.RequestCommentMapper;
+import com.ptp.ptplatform.mapper.RequestPostMapper;
+import com.ptp.ptplatform.service.RequestPostService;
+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 RequestPostServiceImpl extends ServiceImpl<RequestPostMapper, RequestPost> implements RequestPostService {
+ @Autowired
+ private RequestPostMapper requestPostMapper;
+ @Autowired
+ private RequestCommentMapper requestCommentMapper;
+ @Autowired
+ private NotificationService notificationService;
+ @Override
+ @Transactional
+ public void likePost(Integer postId, String likerId) {
+ // 1. 查询帖子本身,获取 authorId 和 title
+ RequestPost post = requestPostMapper.selectById(postId);
+ if (post == null) {
+ throw new RuntimeException("帖子不存在,ID=" + postId);
+ }
+
+ // 2. 更新 like_count 字段
+ requestPostMapper.update(
+ null,
+ new UpdateWrapper<RequestPost>()
+ .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<RequestPost>()
+ .eq("id", postId)
+ .setSql("like_count = like_count + 1")
+ );
+ }
+
+ @Override
+ @Transactional
+ public void incrementReplyCount(Integer postId) {
+ this.update(
+ null,
+ new UpdateWrapper<RequestPost>()
+ .eq("id", postId)
+ .setSql("reply_count = reply_count + 1")
+ );
+ }
+
+ @Override
+ @Transactional
+ public void commentOnRequestPost(RequestComment comment) {
+ // 1. 将新的 HelpComment 插入数据库
+ // 如果 parentId != 0,则说明这是一条对子评论的回复;如果 parentId = 0,则这就是对帖子的一级评论。
+ requestCommentMapper.insert(comment);
+
+ // 2. 帖子回复数 +1(reply_count 字段)
+ Integer postId = comment.getPostId();
+ requestPostMapper.update(
+ null,
+ new UpdateWrapper<RequestPost>()
+ .eq("id", postId)
+ .setSql("reply_count = reply_count + 1")
+ );
+
+ // 3. 给帖子作者发一条通知(如果评论人不是作者自己)
+ RequestPost post = requestPostMapper.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/TorrentServiceImpl.java b/src/main/java/com/ptp/ptplatform/service/impl/TorrentServiceImpl.java
index 3281d83..96829e7 100644
--- a/src/main/java/com/ptp/ptplatform/service/impl/TorrentServiceImpl.java
+++ b/src/main/java/com/ptp/ptplatform/service/impl/TorrentServiceImpl.java
@@ -11,6 +11,7 @@
public class TorrentServiceImpl extends ServiceImpl<TorrentMapper, TORRENT> implements TorrentService {
@Override
+
public TORRENT getTorrentById(Integer id) {
return this.getById(id); // 直接调用 MyBatis-Plus 提供的 getById 方法
}
diff --git a/src/main/java/com/ptp/ptplatform/utils/JwtUtils.java b/src/main/java/com/ptp/ptplatform/utils/JwtUtils.java
index 2ed9068..5706b5e 100644
--- a/src/main/java/com/ptp/ptplatform/utils/JwtUtils.java
+++ b/src/main/java/com/ptp/ptplatform/utils/JwtUtils.java
@@ -6,7 +6,9 @@
import java.util.Date;
+
public class JwtUtils {
+
private static long expire = 36000;
// 使用足够长的密钥(至少64字符)
private static String secret = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl";
diff --git a/src/test/java/com/ptp/ptplatform/controller/AnnouncementControllerTest.java b/src/test/java/com/ptp/ptplatform/controller/AnnouncementControllerTest.java
new file mode 100644
index 0000000..ad5d5a3
--- /dev/null
+++ b/src/test/java/com/ptp/ptplatform/controller/AnnouncementControllerTest.java
@@ -0,0 +1,158 @@
+package com.ptp.ptplatform.controller;
+
+import com.ptp.ptplatform.entity.ANNOUNCEMENT;
+import com.ptp.ptplatform.mapper.AnnouncementMapper;
+import com.ptp.ptplatform.utils.Result;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.time.LocalDateTime;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+class AnnouncementControllerTest {
+
+ @Mock
+ private AnnouncementMapper announcementMapper;
+
+ @InjectMocks
+ private AnnouncementController announcementController;
+
+ private ANNOUNCEMENT mockAnnouncement;
+ private final LocalDateTime now = LocalDateTime.now();
+
+ @BeforeEach
+ void setUp() {
+ MockitoAnnotations.openMocks(this);
+
+ // 初始化测试数据
+ mockAnnouncement = new ANNOUNCEMENT();
+ mockAnnouncement.setId(1);
+ mockAnnouncement.setTitle("测试公告");
+ mockAnnouncement.setContent("这是测试公告内容");
+ mockAnnouncement.setCreateTime(now);
+ }
+
+ @Test
+ void listAll_ShouldReturnAllAnnouncements() {
+ // 准备测试数据
+ ANNOUNCEMENT a2 = new ANNOUNCEMENT();
+ a2.setId(2);
+ a2.setTitle("另一个公告");
+
+ List<ANNOUNCEMENT> announcements = Arrays.asList(mockAnnouncement, a2);
+
+ // 模拟Mapper行为
+ when(announcementMapper.selectAll()).thenReturn(announcements);
+
+ // 调用控制器方法
+ Result result = announcementController.listAll();
+
+ // 验证结果
+ assertEquals(200, result.getCode());
+ assertEquals(announcements, result.getData().get("announcements"));
+
+ // 验证Mapper调用
+ verify(announcementMapper, times(1)).selectAll();
+ }
+
+ @Test
+ void listLatest_ShouldReturnLatest5Announcements() {
+ // 准备测试数据
+ List<ANNOUNCEMENT> latestAnnouncements = Arrays.asList(mockAnnouncement);
+
+ // 模拟Mapper行为
+ when(announcementMapper.selectLatest(5)).thenReturn(latestAnnouncements);
+
+ // 调用控制器方法
+ Result result = announcementController.listLatest();
+
+ // 验证结果
+ assertEquals(200, result.getCode());
+ assertEquals(latestAnnouncements, result.getData().get("announcements"));
+
+ // 验证Mapper调用
+ verify(announcementMapper, times(1)).selectLatest(5);
+ }
+
+ @Test
+ void getDetail_ShouldReturnAnnouncement_WhenExists() {
+ // 模拟Mapper行为
+ when(announcementMapper.selectById(1)).thenReturn(mockAnnouncement);
+
+ // 调用控制器方法
+ Result result = announcementController.getDetail(1);
+
+ // 验证结果
+ assertEquals(200, result.getCode());
+ assertEquals(mockAnnouncement, result.getData().get("announcement"));
+
+ // 验证Mapper调用
+ verify(announcementMapper, times(1)).selectById(1);
+ }
+
+
+
+ @Test
+ void createAnnouncement_ShouldReturnSuccess_WhenValidInput() {
+ // 模拟Mapper行为
+ when(announcementMapper.insert(any(ANNOUNCEMENT.class))).thenReturn(1);
+
+ // 调用控制器方法
+ Result result = announcementController.createAnnouncement("测试标题", "测试内容");
+
+ // 验证结果
+ assertEquals(200, result.getCode());
+ assertEquals("公告发布成功", result.getMessage());
+
+ // 验证Mapper调用
+ ArgumentCaptor<ANNOUNCEMENT> captor = ArgumentCaptor.forClass(ANNOUNCEMENT.class);
+ verify(announcementMapper, times(1)).insert(captor.capture());
+
+ ANNOUNCEMENT saved = captor.getValue();
+ assertEquals("测试标题", saved.getTitle());
+ assertEquals("测试内容", saved.getContent());
+ assertNotNull(saved.getCreateTime());
+ }
+
+ @Test
+ void createAnnouncement_ShouldReturnError_WhenInsertFails() {
+ // 模拟Mapper行为
+ when(announcementMapper.insert(any(ANNOUNCEMENT.class))).thenReturn(0);
+
+ // 调用控制器方法
+ Result result = announcementController.createAnnouncement("测试标题", "测试内容");
+
+ // 验证结果
+ assertEquals(500, result.getCode());
+ assertEquals("公告发布失败", result.getMessage());
+
+ // 验证Mapper调用
+ verify(announcementMapper, times(1)).insert(any(ANNOUNCEMENT.class));
+ }
+
+ @Test
+ void createAnnouncement_ShouldReturnError_WhenExceptionOccurs() {
+ // 模拟Mapper抛出异常
+ when(announcementMapper.insert(any(ANNOUNCEMENT.class)))
+ .thenThrow(new RuntimeException("数据库错误"));
+
+ // 调用控制器方法
+ Result result = announcementController.createAnnouncement("测试标题", "测试内容");
+
+ // 验证结果
+ assertEquals(500, result.getCode());
+ assertTrue(result.getMessage().contains("服务器错误"));
+
+ // 验证Mapper调用
+ verify(announcementMapper, times(1)).insert(any(ANNOUNCEMENT.class));
+ }
+}
diff --git a/src/test/java/com/ptp/ptplatform/controller/RequestCommentControllerTest.java b/src/test/java/com/ptp/ptplatform/controller/RequestCommentControllerTest.java
new file mode 100644
index 0000000..a51a300
--- /dev/null
+++ b/src/test/java/com/ptp/ptplatform/controller/RequestCommentControllerTest.java
@@ -0,0 +1,173 @@
+package com.ptp.ptplatform.controller;
+
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.ptp.ptplatform.entity.RequestComment;
+import com.ptp.ptplatform.service.RequestCommentService;
+import com.ptp.ptplatform.service.RequestPostService;
+import com.ptp.ptplatform.utils.Result;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.springframework.mock.web.MockMultipartFile;
+
+import java.time.LocalDateTime;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+class RequestCommentControllerTest {
+
+ @Mock
+ private RequestCommentService commentService;
+
+ @Mock
+ private RequestPostService postService;
+
+ @InjectMocks
+ private RequestCommentController commentController;
+
+ private final String authorId = "user123";
+ private final String otherAuthorId = "user456";
+
+ @BeforeEach
+ void setUp() {
+ MockitoAnnotations.openMocks(this);
+ }
+
+ @Test
+ void likeComment_ShouldReturnSuccess() {
+ int commentId = 1;
+ doNothing().when(commentService).incrementLike(commentId);
+
+ Result result = commentController.like(commentId);
+
+ assertEquals(200, result.getCode());
+ assertEquals("成功", result.getMessage());
+ verify(commentService, times(1)).incrementLike(commentId);
+ }
+
+ @Test
+ void getReplies_ShouldReturnReplies() {
+ int commentId = 1;
+ RequestComment r1 = new RequestComment();
+ r1.setId(2); r1.setPostId(1); r1.setParentId(commentId);
+ r1.setAuthorId("user2"); r1.setContent("Reply 1");
+ r1.setCreateTime(LocalDateTime.now()); r1.setLikeCount(0);
+ r1.setReplyTo("user1");
+
+ RequestComment r2 = new RequestComment();
+ r2.setId(3); r2.setPostId(1); r2.setParentId(commentId);
+ r2.setAuthorId("user3"); r2.setContent("Reply 2");
+ r2.setCreateTime(LocalDateTime.now()); r2.setLikeCount(0);
+ r2.setReplyTo("user1");
+
+ List<RequestComment> mockReplies = Arrays.asList(r1, r2);
+ when(commentService.getReplies(commentId)).thenReturn(mockReplies);
+
+ Result result = commentController.getReplies(commentId);
+
+ assertEquals(200, result.getCode());
+ @SuppressWarnings("unchecked")
+ List<RequestComment> ret = (List<RequestComment>) result.getData().get("replies");
+ assertEquals(mockReplies, ret);
+ verify(commentService, times(1)).getReplies(commentId);
+ }
+
+ @Test
+ void addReplyWithoutImage_ShouldReturnSuccess() {
+ int commentId = 5;
+ String authorId = "userX";
+ String content = "no image reply";
+
+ RequestComment parent = new RequestComment();
+ parent.setId(commentId);
+ parent.setAuthorId("userParent");
+ parent.setPostId(10);
+ when(commentService.getById(commentId)).thenReturn(parent);
+
+ when(commentService.save(any(RequestComment.class))).thenReturn(true);
+
+ Result result = commentController.addReply(commentId, authorId, content, null);
+
+ assertEquals(200, result.getCode());
+ RequestComment reply = (RequestComment) result.getData().get("reply");
+ assertNotNull(reply);
+ assertEquals(authorId, reply.getAuthorId());
+ assertEquals(content, reply.getContent());
+ assertNull(reply.getImageUrl());
+ assertEquals(commentId, result.getData().get("parentCommentId"));
+ verify(commentService, times(1)).save(any(RequestComment.class));
+ }
+
+ @Test
+ void addReplyWithImage_ShouldReturnSuccess() {
+ int commentId = 8;
+ String authorId = "userY";
+ String content = "with image reply";
+
+ RequestComment parent = new RequestComment();
+ parent.setId(commentId);
+ parent.setAuthorId("userParent");
+ parent.setPostId(20);
+ when(commentService.getById(commentId)).thenReturn(parent);
+
+ byte[] data = {0x1, 0x2};
+ MockMultipartFile image = new MockMultipartFile(
+ "image", "test.png", "image/png", data
+ );
+
+ when(commentService.save(any(RequestComment.class))).thenReturn(true);
+
+ Result result = commentController.addReply(commentId, authorId, content, image);
+
+ assertEquals(200, result.getCode());
+ RequestComment reply = (RequestComment) result.getData().get("reply");
+ assertNotNull(reply);
+ assertEquals(authorId, reply.getAuthorId());
+ assertEquals(content, reply.getContent());
+ assertNotNull(reply.getImageUrl());
+ assertTrue(reply.getImageUrl().startsWith("/uploads/"));
+ assertEquals(commentId, result.getData().get("parentCommentId"));
+ verify(commentService, times(1)).save(any(RequestComment.class));
+ }
+
+ @Test
+ void addReply_ShouldReturnError_WhenParentNotExist() {
+ int commentId = 99;
+ when(commentService.getById(commentId)).thenReturn(null);
+
+ Result result = commentController.addReply(commentId, "any", "content", null);
+
+ assertEquals(500, result.getCode());
+ assertEquals("被回复的评论不存在", result.getMessage());
+ verify(commentService, never()).save(any());
+ }
+
+ @Test
+ void deleteComment_ShouldReturnSuccess_WhenAuthorMatches() {
+ // 准备测试数据
+ int commentId = 1;
+ RequestComment comment = new RequestComment();
+ comment.setId(commentId);
+ comment.setAuthorId(authorId);
+ comment.setPostId(10);
+
+ when(commentService.getById(commentId)).thenReturn(comment);
+ when(commentService.removeById(commentId)).thenReturn(true);
+
+ // 调用方法
+ Result result = commentController.deleteComment(commentId, authorId);
+
+ // 验证结果
+ assertEquals(200, result.getCode());
+
+ // 验证服务调用
+ verify(commentService).removeById(commentId);
+ }
+
+}
diff --git a/src/test/java/com/ptp/ptplatform/controller/RequestPostControllerTest.java b/src/test/java/com/ptp/ptplatform/controller/RequestPostControllerTest.java
new file mode 100644
index 0000000..f7b8975
--- /dev/null
+++ b/src/test/java/com/ptp/ptplatform/controller/RequestPostControllerTest.java
@@ -0,0 +1,275 @@
+package com.ptp.ptplatform.controller;
+
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ptp.ptplatform.entity.RequestComment;
+import com.ptp.ptplatform.entity.RequestPost;
+import com.ptp.ptplatform.entity.Notification;
+import com.ptp.ptplatform.mapper.RequestPostMapper;
+import com.ptp.ptplatform.service.RequestCommentService;
+import com.ptp.ptplatform.service.RequestPostService;
+import com.ptp.ptplatform.service.NotificationService;
+import com.ptp.ptplatform.utils.Result;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.springframework.mock.web.MockMultipartFile;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.time.LocalDateTime;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.*;
+
+class RequestPostControllerTest {
+
+ @Mock
+ private RequestPostService postService;
+
+ @Mock
+ private RequestCommentService commentService;
+
+ @Mock
+ private NotificationService notificationService;
+
+ @InjectMocks
+ private RequestPostController postController;
+
+ private final String authorId = "user123";
+ private final String otherAuthorId = "user456";
+
+ @BeforeEach
+ void setUp() {
+ MockitoAnnotations.openMocks(this);
+ }
+
+ @Test
+ void createPost_ShouldReturnSuccess() throws Exception {
+ // 准备测试数据
+ String title = "Test Title";
+ String content = "Test Content";
+ MockMultipartFile image = new MockMultipartFile(
+ "image", "test.jpg", "image/jpeg", "test image".getBytes());
+
+ // 调用方法
+ Result result = postController.createPost(title, content, authorId, image);
+
+ // 验证结果
+ assertEquals(200, result.getCode());
+ RequestPost createdPost = (RequestPost) result.getData().get("post");
+ assertNotNull(createdPost.getImageUrl());
+ assertTrue(createdPost.getImageUrl().startsWith("/uploads/"));
+
+ // 验证服务调用
+ verify(postService).save(any(RequestPost.class));
+ }
+
+ @Test
+ void getPost_ShouldReturnPostWithComments_WhenPostExists() {
+ int postId = 1;
+ RequestPost post = new RequestPost();
+ post.setId(postId);
+ post.setTitle("Test Post");
+ post.setContent("Test Content");
+ post.setAuthorId("user1");
+ post.setCreateTime(LocalDateTime.now());
+
+ RequestComment c1 = new RequestComment();
+ c1.setId(1); c1.setPostId(postId); c1.setParentId(0);
+ c1.setAuthorId("user2"); c1.setContent("Comment 1");
+ c1.setCreateTime(LocalDateTime.now());
+
+ RequestComment c2 = new RequestComment();
+ c2.setId(2); c2.setPostId(postId); c2.setParentId(0);
+ c2.setAuthorId("user3"); c2.setContent("Comment 2");
+ c2.setCreateTime(LocalDateTime.now());
+
+ List<RequestComment> comments = Arrays.asList(c1, c2);
+
+ when(postService.getById(postId)).thenReturn(post);
+ // 避免重载二义性,明确 stub list(Wrapper)
+ doReturn(comments)
+ .when(commentService)
+ .list(any(Wrapper.class));
+
+ Result result = postController.getPost(postId);
+
+ assertEquals(200, result.getCode());
+ assertEquals(post, result.getData().get("post"));
+ @SuppressWarnings("unchecked")
+ List<RequestComment> ret = (List<RequestComment>) result.getData().get("comments");
+ assertEquals(2, ret.size());
+
+ verify(postService, times(1)).getById(postId);
+ verify(commentService, times(1)).list(any(Wrapper.class));
+ }
+
+ @Test
+ void listPosts_ShouldReturnPaginatedPosts() {
+ int page = 1, size = 5;
+ RequestPost p1 = new RequestPost(); p1.setId(1); p1.setAuthorId("u1");
+ RequestPost p2 = new RequestPost(); p2.setId(2); p2.setAuthorId("u2");
+ List<RequestPost> posts = Arrays.asList(p1, p2);
+
+ Page<RequestPost> pageResult = new Page<>(page, size);
+ pageResult.setRecords(posts).setTotal(posts.size());
+
+ when(postService.page(any(Page.class), any())).thenReturn(pageResult);
+
+ Result result = postController.listPosts(page, size);
+
+ assertEquals(200, result.getCode());
+ assertEquals(posts, result.getData().get("records"));
+ assertEquals((long) posts.size(), result.getData().get("total"));
+
+ verify(postService, times(1)).page(any(Page.class), any());
+ }
+
+ @Test
+ void getPost_ShouldReturnError_WhenPostNotExists() {
+ int postId = 1;
+ when(postService.getById(postId)).thenReturn(null);
+
+ Result result = postController.getPost(postId);
+
+ assertEquals(500, result.getCode());
+ assertEquals("帖子不存在", result.getMessage());
+
+ verify(postService, times(1)).getById(postId);
+ }
+
+ @Test
+ void likePost_ShouldReturnSuccess() {
+ int postId = 1;
+ String likerId = "alice123";
+ // 对应 ServiceImpl 中的 likePost(postId, likerId)
+ doNothing().when(postService).likePost(postId, likerId);
+
+ Result result = postController.likePost(postId, likerId);
+
+ assertEquals(200, result.getCode());
+ assertEquals("点赞成功", result.getMessage());
+ verify(postService, times(1)).likePost(postId, likerId);
+ }
+
+ @Test
+ void deletePost_ShouldReturnSuccess_WhenAuthorMatches() {
+ int postId = 42;
+ RequestPost mockPost = new RequestPost();
+ mockPost.setAuthorId(authorId);
+ // remove 返回 boolean,而非 void
+ when(postService.getById(postId)).thenReturn(mockPost);
+ when(commentService.remove(any(Wrapper.class))).thenReturn(true);
+ when(postService.removeById(postId)).thenReturn(true);
+
+ Result result = postController.deletePost(postId, authorId);
+
+ assertEquals(200, result.getCode());
+ verify(commentService, times(1)).remove(any(Wrapper.class));
+ verify(postService, times(1)).removeById(postId);
+ }
+
+ @Test
+ void addCommentWithoutImage_ShouldReturnSuccess() {
+ int postId = 7;
+ String author = "user2";
+ String content= "Hello";
+
+ // 构造“保存后”返回给前端的 RequestComment 对象
+ RequestComment saved = new RequestComment();
+ saved.setId(99);
+ saved.setPostId(postId);
+ saved.setAuthorId(author);
+ saved.setContent(content);
+ saved.setImageUrl(null);
+
+ // 1) 当 commentService.commentOnRequestPost(...) 被调用时,
+ // 我们在 doAnswer 中给它传入的 HelpComment 对象设置 ID = 99
+ doAnswer(invocation -> {
+ RequestComment arg = invocation.getArgument(0);
+ arg.setId(saved.getId());
+ return null; // commentOnRequestPost 返回 void
+ }).when(commentService).commentOnRequestPost(any(RequestComment.class));
+
+ // 2) 后面 commentService.getById(99) 返回我们上面构造的 saved
+ when(commentService.getById(saved.getId())).thenReturn(saved);
+
+ // 模拟:commentOnHelpPost() 内部会调用 postService.incrementReplyCount(postId)
+ // 但控制器本身并不直接调用 incrementReplyCount,所以测试里不再 verify 这一行。
+ // 只要保证 postService.getById(postId) 能返回一个带有 replyCount 的 RequestPost 即可:
+ RequestPost stubPost = new RequestPost();
+ stubPost.setReplyCount(5);
+ when(postService.getById(postId)).thenReturn(stubPost);
+
+ // 调用控制器的 commentOnPost(...) 方法,第四个参数传 null(不带图片)
+ Result result = postController.commentOnPost(postId, author, content, null);
+
+ assertEquals(200, result.getCode());
+ assertEquals(saved, result.getData().get("comment"));
+ assertEquals(5, result.getData().get("newReplyCount"));
+
+ // 确保 commentService.commentOnHelpPost(...) 被调用一次
+ verify(commentService, times(1)).commentOnRequestPost(any(RequestComment.class));
+ // postService.incrementReplyCount(...) 由 commentOnRequestPost 内部调用,因此测试里无需 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}
+ );
+
+ // 构造“保存后”返回给前端的 RequestComment 对象
+ RequestComment saved = new RequestComment();
+ saved.setId(100);
+ saved.setPostId(postId);
+ saved.setAuthorId(author);
+ saved.setContent(content);
+ saved.setImageUrl("/uploads/whatever.jpg");
+
+ // 当 commentService.commentOnRequestPost(...) 被调用时,给传入的对象设置 ID 与 imageUrl
+ doAnswer(invocation -> {
+ RequestComment arg = invocation.getArgument(0);
+ arg.setId(saved.getId());
+ arg.setImageUrl(saved.getImageUrl());
+ return null; // commentOnRequestPost 返回 void
+ }).when(commentService).commentOnRequestPost(any(RequestComment.class));
+
+ // 后面 commentService.getById(100) 返回我们构造的 saved
+ when(commentService.getById(saved.getId())).thenReturn(saved);
+
+ // 同样,postService.getById(postId) 返回一个带 replyCount=10 的 HelpPost
+ RequestPost stubPost = new RequestPost();
+ stubPost.setReplyCount(10);
+ when(postService.getById(postId)).thenReturn(stubPost);
+
+ // 调用 commentOnPost,第四个参数传 mockFile(模拟有图片上传)
+ Result result = postController.commentOnPost(postId, author, content, mockFile);
+
+ assertEquals(200, result.getCode());
+ assertEquals(saved, result.getData().get("comment"));
+ assertEquals(10, result.getData().get("newReplyCount"));
+
+ // 验证真正传给 commentService.commentOnRequestPost(...) 的 HelpComment 对象中,
+ // imageUrl 已经被设置成 "/uploads/whatever.jpg"
+ ArgumentCaptor<RequestComment> captor = ArgumentCaptor.forClass(RequestComment.class);
+ verify(commentService).commentOnRequestPost(captor.capture());
+ RequestComment toSave = captor.getValue();
+ assertEquals(saved.getImageUrl(), toSave.getImageUrl());
+
+ // 同样无需 verify postService.incrementReplyCount(...),因为该调用在 commentOnHelpPost 内部完成
+ }
+}
diff --git a/torrents/1748539198669_32e995db2ab833ae6a12a3b1d521e5b.jpg.torrent b/torrents/1748539198669_32e995db2ab833ae6a12a3b1d521e5b.jpg.torrent
new file mode 100644
index 0000000..7e57dcc
--- /dev/null
+++ b/torrents/1748539198669_32e995db2ab833ae6a12a3b1d521e5b.jpg.torrent
@@ -0,0 +1 @@
+d8:announce30:http://localhost:6969/announce10:created by21:qBittorrent v4.5.3.1013:creation datei1748520246e4:infod6:lengthi53458e4:name35:32e995db2ab833ae6a12a3b1d521e5b.jpg12:piece lengthi16384e6:pieces80:6<MÙ>¿?ïÿ!v`÷F¤zj&âþS¹²jùk50Õ·ÁIó¶I»®þ¦ÄbÏ$:âm«%»®#ô~å'5\~4¨U.*ee
\ No newline at end of file
diff --git a/torrents/1748539331027_32e995db2ab833ae6a12a3b1d521e5b.jpg.torrent b/torrents/1748539331027_32e995db2ab833ae6a12a3b1d521e5b.jpg.torrent
new file mode 100644
index 0000000..7e57dcc
--- /dev/null
+++ b/torrents/1748539331027_32e995db2ab833ae6a12a3b1d521e5b.jpg.torrent
@@ -0,0 +1 @@
+d8:announce30:http://localhost:6969/announce10:created by21:qBittorrent v4.5.3.1013:creation datei1748520246e4:infod6:lengthi53458e4:name35:32e995db2ab833ae6a12a3b1d521e5b.jpg12:piece lengthi16384e6:pieces80:6<MÙ>¿?ïÿ!v`÷F¤zj&âþS¹²jùk50Õ·ÁIó¶I»®þ¦ÄbÏ$:âm«%»®#ô~å'5\~4¨U.*ee
\ No newline at end of file
diff --git a/torrents/1748581612614_32e995db2ab833ae6a12a3b1d521e5b.jpg.torrent b/torrents/1748581612614_32e995db2ab833ae6a12a3b1d521e5b.jpg.torrent
new file mode 100644
index 0000000..7e57dcc
--- /dev/null
+++ b/torrents/1748581612614_32e995db2ab833ae6a12a3b1d521e5b.jpg.torrent
@@ -0,0 +1 @@
+d8:announce30:http://localhost:6969/announce10:created by21:qBittorrent v4.5.3.1013:creation datei1748520246e4:infod6:lengthi53458e4:name35:32e995db2ab833ae6a12a3b1d521e5b.jpg12:piece lengthi16384e6:pieces80:6<MÙ>¿?ïÿ!v`÷F¤zj&âþS¹²jùk50Õ·ÁIó¶I»®þ¦ÄbÏ$:âm«%»®#ô~å'5\~4¨U.*ee
\ No newline at end of file
diff --git a/torrents/32e995db2ab833ae6a12a3b1d521e5b.jpg.torrent b/torrents/32e995db2ab833ae6a12a3b1d521e5b.jpg.torrent
new file mode 100644
index 0000000..7e57dcc
--- /dev/null
+++ b/torrents/32e995db2ab833ae6a12a3b1d521e5b.jpg.torrent
@@ -0,0 +1 @@
+d8:announce30:http://localhost:6969/announce10:created by21:qBittorrent v4.5.3.1013:creation datei1748520246e4:infod6:lengthi53458e4:name35:32e995db2ab833ae6a12a3b1d521e5b.jpg12:piece lengthi16384e6:pieces80:6<MÙ>¿?ïÿ!v`÷F¤zj&âþS¹²jùk50Õ·ÁIó¶I»®þ¦ÄbÏ$:âm«%»®#ô~å'5\~4¨U.*ee
\ No newline at end of file
diff --git a/tracker.json b/tracker.json
index 5475d24..b632be7 100644
--- a/tracker.json
+++ b/tracker.json
@@ -1 +1 @@
-{"announceUrl":"http://10.60.12.94:6969/announce","announceURI":"http://10.60.12.94:6969/announce","trackedTorrents":[{"announceInterval":10,"peers":{"PeerUID{address=/10.60.12.94:6881, torrent hash='E5FD501A5E6F07FA02777809C83EB2B815A2C3EA'}":{"address":"10.60.12.94:6881","peerId":"Mzg3ZGMxYTgtMzU4Yy00ZDMyLWFhOTctNzQ2MzdhMmMyZDg1","hexPeerId":"33383764633161382D333538632D346433322D616139372D373436333761326332643835","hexInfoHash":null,"uploaded":0,"downloaded":0,"left":3063963,"completed":true,"port":6881,"shortHexPeerId":"..643835","stringPeerId":"387dc1a8-358c-4d32-aa97-74637a2c2d85","hostIdentifier":"/10.60.12.94:6881","ip":"10.60.12.94","peerIdArray":"Mzg3ZGMxYTgtMzU4Yy00ZDMyLWFhOTctNzQ2MzdhMmMyZDg1","rawIp":"CjwMXg=="},"PeerUID{address=/10.60.12.94:6883, torrent hash='E5FD501A5E6F07FA02777809C83EB2B815A2C3EA'}":{"address":"10.60.12.94:6883","peerId":"LVRPMDA0Mi0wYjI5MDQwZDE3MWQ=","hexPeerId":"2D544F303034322D306232393034306431373164","hexInfoHash":null,"uploaded":0,"downloaded":0,"left":3063963,"completed":false,"port":6883,"shortHexPeerId":"..373164","stringPeerId":"-TO0042-0b29040d171d","hostIdentifier":"/10.60.12.94:6883","ip":"10.60.12.94","peerIdArray":"LVRPMDA0Mi0wYjI5MDQwZDE3MWQ=","rawIp":"CjwMXg=="},"PeerUID{address=/10.60.12.94:6882, torrent hash='E5FD501A5E6F07FA02777809C83EB2B815A2C3EA'}":{"address":"10.60.12.94:6882","peerId":"LVRPMDA0Mi0xNWI3N2EwZWViZWY=","hexPeerId":"2D544F303034322D313562373761306565626566","hexInfoHash":null,"uploaded":0,"downloaded":0,"left":3063963,"completed":false,"port":6882,"shortHexPeerId":"..626566","stringPeerId":"-TO0042-15b77a0eebef","hostIdentifier":"/10.60.12.94:6882","ip":"10.60.12.94","peerIdArray":"LVRPMDA0Mi0xNWI3N2EwZWViZWY=","rawIp":"CjwMXg=="}},"hexInfoHash":"E5FD501A5E6F07FA02777809C83EB2B815A2C3EA","infoHash":"5f1QGl5vB/oCd3gJyD6yuBWiw+o="},{"announceInterval":10,"peers":{"PeerUID{address=/10.60.12.94:6882, torrent hash='34D94F184C0DC45372F52DD0FBAEB79AF4BD758E'}":{"address":"10.60.12.94:6882","peerId":"LVRPMDA0Mi0xNWI3N2EwZWViZWY=","hexPeerId":"2D544F303034322D313562373761306565626566","hexInfoHash":null,"uploaded":0,"downloaded":0,"left":1609568975,"completed":false,"port":6882,"shortHexPeerId":"..626566","stringPeerId":"-TO0042-15b77a0eebef","hostIdentifier":"/10.60.12.94:6882","ip":"10.60.12.94","peerIdArray":"LVRPMDA0Mi0xNWI3N2EwZWViZWY=","rawIp":"CjwMXg=="},"PeerUID{address=/10.60.12.94:6881, torrent hash='34D94F184C0DC45372F52DD0FBAEB79AF4BD758E'}":{"address":"10.60.12.94:6881","peerId":"YmI0YzJkYTgtMWRiMy00ODRiLTg0ZDgtNjhkMjAwMzViNzhk","hexPeerId":"62623463326461382D316462332D343834622D383464382D363864323030333562373864","hexInfoHash":null,"uploaded":0,"downloaded":0,"left":1609568975,"completed":false,"port":6881,"shortHexPeerId":"..373864","stringPeerId":"bb4c2da8-1db3-484b-84d8-68d20035b78d","hostIdentifier":"/10.60.12.94:6881","ip":"10.60.12.94","peerIdArray":"YmI0YzJkYTgtMWRiMy00ODRiLTg0ZDgtNjhkMjAwMzViNzhk","rawIp":"CjwMXg=="}},"hexInfoHash":"34D94F184C0DC45372F52DD0FBAEB79AF4BD758E","infoHash":"NNlPGEwNxFNy9S3Q+663mvS9dY4="}]}
\ No newline at end of file
+{"trackedTorrents":[{"announceInterval":10,"peers":{"PeerUID{address=/10.61.51.238:6881, torrent hash='53475A61645AA5B1141807367D7AA258016E1215'}":{"address":"10.61.51.238:6881","peerId":"NjE5ZDk5NTEtN2ExOS00MDRlLWIzOTktYzQ5YWU4YTJhNTJj","hexPeerId":"36313964393935312D376131392D343034652D623339392D633439616538613261353263","hexInfoHash":null,"uploaded":0,"downloaded":0,"left":53458,"completed":true,"port":6881,"ip":"10.61.51.238","rawIp":"Cj0z7g==","peerIdArray":"NjE5ZDk5NTEtN2ExOS00MDRlLWIzOTktYzQ5YWU4YTJhNTJj","hostIdentifier":"/10.61.51.238:6881","stringPeerId":"619d9951-7a19-404e-b399-c49ae8a2a52c","shortHexPeerId":"..353263"},"PeerUID{address=/127.0.0.1:62128, torrent hash='53475A61645AA5B1141807367D7AA258016E1215'}":{"address":"127.0.0.1:62128","peerId":"LXFCNDUzQS1WTEdLODI4S1lLRUc=","hexPeerId":"2D7142343533412D564C474B3832384B594B4547","hexInfoHash":null,"uploaded":320748,"downloaded":0,"left":0,"completed":true,"port":62128,"ip":"127.0.0.1","rawIp":"fwAAAQ==","peerIdArray":"LXFCNDUzQS1WTEdLODI4S1lLRUc=","hostIdentifier":"/127.0.0.1:62128","stringPeerId":"-qB453A-VLGK828KYKEG","shortHexPeerId":"..4B4547"},"PeerUID{address=/[0:0:0:0:0:0:0:1]:62128, torrent hash='53475A61645AA5B1141807367D7AA258016E1215'}":{"address":"[0:0:0:0:0:0:0:1]:62128","peerId":"LXFCNDUzQS1WTEdLODI4S1lLRUc=","hexPeerId":"2D7142343533412D564C474B3832384B594B4547","hexInfoHash":null,"uploaded":320748,"downloaded":0,"left":0,"completed":true,"port":62128,"ip":"0:0:0:0:0:0:0:1","rawIp":"AAAAAAAAAAAAAAAAAAAAAQ==","peerIdArray":"LXFCNDUzQS1WTEdLODI4S1lLRUc=","hostIdentifier":"/0:0:0:0:0:0:0:1:62128","stringPeerId":"-qB453A-VLGK828KYKEG","shortHexPeerId":"..4B4547"}},"hexInfoHash":"53475A61645AA5B1141807367D7AA258016E1215","infoHash":"U0daYWRapbEUGAc2fXqiWAFuEhU="}],"announceUrl":"http://LAPTOP-C3Q7MHHD:6969/announce","announceURI":"http://LAPTOP-C3Q7MHHD:6969/announce"}
\ No newline at end of file
diff --git a/uploads/1110a6dc-b6b6-4408-b33f-97afacded945.jpg b/uploads/1110a6dc-b6b6-4408-b33f-97afacded945.jpg
deleted file mode 100644
index aed2973..0000000
--- a/uploads/1110a6dc-b6b6-4408-b33f-97afacded945.jpg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/uploads/15b11ff9-2344-4d3f-b4bc-49734a1e7156.png b/uploads/15b11ff9-2344-4d3f-b4bc-49734a1e7156.png
deleted file mode 100644
index 71bd63e..0000000
--- a/uploads/15b11ff9-2344-4d3f-b4bc-49734a1e7156.png
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/uploads/1ed6e86b-955c-4426-b170-73bc7558eca5.png b/uploads/1ed6e86b-955c-4426-b170-73bc7558eca5.png
deleted file mode 100644
index 71bd63e..0000000
--- a/uploads/1ed6e86b-955c-4426-b170-73bc7558eca5.png
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/uploads/38ee81c5-ee84-4371-94bd-55ff19784e97.png b/uploads/38ee81c5-ee84-4371-94bd-55ff19784e97.png
deleted file mode 100644
index ef08183..0000000
--- a/uploads/38ee81c5-ee84-4371-94bd-55ff19784e97.png
+++ /dev/null
Binary files differ
diff --git a/uploads/3faa8c92-715f-49b1-9fb4-3f8fea12cbd8.png b/uploads/3faa8c92-715f-49b1-9fb4-3f8fea12cbd8.png
deleted file mode 100644
index cee3e35..0000000
--- a/uploads/3faa8c92-715f-49b1-9fb4-3f8fea12cbd8.png
+++ /dev/null
Binary files differ
diff --git a/uploads/47983664-980d-4831-979d-22eee853c22b.jpg b/uploads/47983664-980d-4831-979d-22eee853c22b.jpg
deleted file mode 100644
index a6d7f38..0000000
--- a/uploads/47983664-980d-4831-979d-22eee853c22b.jpg
+++ /dev/null
@@ -1 +0,0 @@
-test image
\ No newline at end of file
diff --git a/uploads/4876d616-3494-4aed-896e-01edf4e0f6a6.jpg b/uploads/4876d616-3494-4aed-896e-01edf4e0f6a6.jpg
deleted file mode 100644
index aed2973..0000000
--- a/uploads/4876d616-3494-4aed-896e-01edf4e0f6a6.jpg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/uploads/49d1a2db-5482-4d21-9a22-0071bd4cf5e5.jpg b/uploads/49d1a2db-5482-4d21-9a22-0071bd4cf5e5.jpg
deleted file mode 100644
index a6d7f38..0000000
--- a/uploads/49d1a2db-5482-4d21-9a22-0071bd4cf5e5.jpg
+++ /dev/null
@@ -1 +0,0 @@
-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
deleted file mode 100644
index aed2973..0000000
--- a/uploads/4ed56b76-c367-45db-98e1-d0f36508d8f1.jpg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/uploads/5ea70d6e-50d5-44ec-b35e-e9e7101ee24f.png b/uploads/5ea70d6e-50d5-44ec-b35e-e9e7101ee24f.png
deleted file mode 100644
index e5d4cab..0000000
--- a/uploads/5ea70d6e-50d5-44ec-b35e-e9e7101ee24f.png
+++ /dev/null
Binary files differ
diff --git a/uploads/5fb50a1b-c4d7-42cf-82c5-92438d94b1c1.png b/uploads/5fb50a1b-c4d7-42cf-82c5-92438d94b1c1.png
deleted file mode 100644
index 71bd63e..0000000
--- a/uploads/5fb50a1b-c4d7-42cf-82c5-92438d94b1c1.png
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/uploads/665a02db-5b60-40ae-92dd-c46b31637372.jpg b/uploads/665a02db-5b60-40ae-92dd-c46b31637372.jpg
deleted file mode 100644
index a6d7f38..0000000
--- a/uploads/665a02db-5b60-40ae-92dd-c46b31637372.jpg
+++ /dev/null
@@ -1 +0,0 @@
-test image
\ No newline at end of file
diff --git a/uploads/69925fae-3c62-460c-a5c1-ee9695bc8ae2.jpg b/uploads/69925fae-3c62-460c-a5c1-ee9695bc8ae2.jpg
deleted file mode 100644
index aed2973..0000000
--- a/uploads/69925fae-3c62-460c-a5c1-ee9695bc8ae2.jpg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/uploads/69c706c7-6ae4-4e2c-9c22-6d0e8874b228.jpg b/uploads/69c706c7-6ae4-4e2c-9c22-6d0e8874b228.jpg
deleted file mode 100644
index aed2973..0000000
--- a/uploads/69c706c7-6ae4-4e2c-9c22-6d0e8874b228.jpg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/uploads/72db8eb6-40f8-445c-a833-43ec6fecebdd.png b/uploads/72db8eb6-40f8-445c-a833-43ec6fecebdd.png
deleted file mode 100644
index 71bd63e..0000000
--- a/uploads/72db8eb6-40f8-445c-a833-43ec6fecebdd.png
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/uploads/77032da9-b19b-4bad-a5f4-97bb656ad462.jpg b/uploads/77032da9-b19b-4bad-a5f4-97bb656ad462.jpg
deleted file mode 100644
index aed2973..0000000
--- a/uploads/77032da9-b19b-4bad-a5f4-97bb656ad462.jpg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/uploads/813ea0ee-bddf-42fb-93ea-3e96be10a63a.jpg b/uploads/813ea0ee-bddf-42fb-93ea-3e96be10a63a.jpg
deleted file mode 100644
index aed2973..0000000
--- a/uploads/813ea0ee-bddf-42fb-93ea-3e96be10a63a.jpg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/uploads/82d39fb4-7a1e-491f-8a62-e14f796629fe.jpg b/uploads/82d39fb4-7a1e-491f-8a62-e14f796629fe.jpg
deleted file mode 100644
index 4a7a6e2..0000000
--- a/uploads/82d39fb4-7a1e-491f-8a62-e14f796629fe.jpg
+++ /dev/null
Binary files differ
diff --git a/uploads/8dcddc1c-ec93-4ffb-881d-1531a3b3f3e0.png b/uploads/8dcddc1c-ec93-4ffb-881d-1531a3b3f3e0.png
deleted file mode 100644
index 71bd63e..0000000
--- a/uploads/8dcddc1c-ec93-4ffb-881d-1531a3b3f3e0.png
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/uploads/9f99ac77-48d0-4535-9f2f-5fe7c804a559.png b/uploads/9f99ac77-48d0-4535-9f2f-5fe7c804a559.png
deleted file mode 100644
index 5a29a05..0000000
--- a/uploads/9f99ac77-48d0-4535-9f2f-5fe7c804a559.png
+++ /dev/null
Binary files differ
diff --git a/uploads/ac46e0ec-891b-4cfb-bece-799f30cbb1f9.jpg b/uploads/ac46e0ec-891b-4cfb-bece-799f30cbb1f9.jpg
deleted file mode 100644
index aed2973..0000000
--- a/uploads/ac46e0ec-891b-4cfb-bece-799f30cbb1f9.jpg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/uploads/bad196f2-d2a7-4f24-8cf9-6d450f82daf1.jpg b/uploads/bad196f2-d2a7-4f24-8cf9-6d450f82daf1.jpg
deleted file mode 100644
index 4a7a6e2..0000000
--- a/uploads/bad196f2-d2a7-4f24-8cf9-6d450f82daf1.jpg
+++ /dev/null
Binary files differ
diff --git a/uploads/baef0e69-225a-4c70-92e2-f8b1301c9dbc.jpg b/uploads/baef0e69-225a-4c70-92e2-f8b1301c9dbc.jpg
deleted file mode 100644
index aed2973..0000000
--- a/uploads/baef0e69-225a-4c70-92e2-f8b1301c9dbc.jpg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/uploads/c1deedcd-b66c-4d91-b0ef-baed3edbdd17.png b/uploads/c1deedcd-b66c-4d91-b0ef-baed3edbdd17.png
deleted file mode 100644
index 71bd63e..0000000
--- a/uploads/c1deedcd-b66c-4d91-b0ef-baed3edbdd17.png
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/uploads/d967fdec-79e7-4633-9015-73c8aee165e7.jpg b/uploads/d967fdec-79e7-4633-9015-73c8aee165e7.jpg
deleted file mode 100644
index aed2973..0000000
--- a/uploads/d967fdec-79e7-4633-9015-73c8aee165e7.jpg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/uploads/df715d55-ccff-46ff-8009-9a0972344742.png b/uploads/df715d55-ccff-46ff-8009-9a0972344742.png
deleted file mode 100644
index e129f0d..0000000
--- a/uploads/df715d55-ccff-46ff-8009-9a0972344742.png
+++ /dev/null
Binary files differ
diff --git a/uploads/e2255186-e4c7-49c3-b673-229c5a16a6d6.jpg b/uploads/e2255186-e4c7-49c3-b673-229c5a16a6d6.jpg
deleted file mode 100644
index a6d7f38..0000000
--- a/uploads/e2255186-e4c7-49c3-b673-229c5a16a6d6.jpg
+++ /dev/null
@@ -1 +0,0 @@
-test image
\ No newline at end of file
diff --git a/uploads/ecbe499c-3342-4e8e-bf23-e2f77a0ad40b.png b/uploads/ecbe499c-3342-4e8e-bf23-e2f77a0ad40b.png
deleted file mode 100644
index e129f0d..0000000
--- a/uploads/ecbe499c-3342-4e8e-bf23-e2f77a0ad40b.png
+++ /dev/null
Binary files differ
diff --git a/uploads/ef17d022-9c80-436b-87cd-227d783baadf.jpg b/uploads/ef17d022-9c80-436b-87cd-227d783baadf.jpg
deleted file mode 100644
index aed2973..0000000
--- a/uploads/ef17d022-9c80-436b-87cd-227d783baadf.jpg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/uploads/f2e35f6d-2d7b-456a-8bf4-27526e20c5b5.jpg b/uploads/f2e35f6d-2d7b-456a-8bf4-27526e20c5b5.jpg
deleted file mode 100644
index aed2973..0000000
--- a/uploads/f2e35f6d-2d7b-456a-8bf4-27526e20c5b5.jpg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/uploads/fdaf20d3-faa6-4409-96cb-215869948ebf.jpg b/uploads/fdaf20d3-faa6-4409-96cb-215869948ebf.jpg
deleted file mode 100644
index a6d7f38..0000000
--- a/uploads/fdaf20d3-faa6-4409-96cb-215869948ebf.jpg
+++ /dev/null
@@ -1 +0,0 @@
-test image
\ No newline at end of file