Merge "fix uploadResource" into main
diff --git a/.gitignore b/.gitignore
index a0134b4..a135372 100644
--- a/.gitignore
+++ b/.gitignore
@@ -165,3 +165,4 @@
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
+/upload
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..3dbfba9
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,27 @@
+# Step 1: 构建 jar
+FROM maven:3.9.9-eclipse-temurin-21 AS builder
+
+WORKDIR /app
+COPY . .
+RUN mvn clean package -DskipTests
+
+FROM nginx:alpine
+
+# 创建运行目录
+WORKDIR /app
+
+# 拷贝 jar 到运行目录
+COPY --from=builder /app/target/*.jar app.jar
+
+RUN mkdir /app/upload/
+
+# 拷贝 Nginx 配置文件
+COPY nginx.conf /etc/nginx/nginx.conf
+
+# 安装 OpenJDK 运行环境
+RUN apk add --no-cache openjdk21-jdk curl
+
+# 后台启动 Spring Boot + 前台运行 Nginx
+EXPOSE 5009
+
+CMD java -jar app.jar & nginx -g "daemon off;"
diff --git a/nginx.conf b/nginx.conf
new file mode 100644
index 0000000..d06c697
--- /dev/null
+++ b/nginx.conf
@@ -0,0 +1,56 @@
+worker_processes 1;
+
+events { worker_connections 1024; }
+
+http {
+ include mime.types;
+ default_type application/octet-stream;
+
+ sendfile on;
+ keepalive_timeout 65;
+
+ server {
+ listen 5009;
+
+ # 支持 CORS 的全局选项处理(可选)
+ if ($request_method = OPTIONS) {
+ add_header Access-Control-Allow-Origin *;
+ add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS, PUT, DELETE';
+ add_header Access-Control-Allow-Headers 'Origin, Content-Type, Accept, Authorization';
+ add_header Access-Control-Max-Age 3600;
+ return 204;
+ }
+
+ # 1. 代理静态资源:如 http://localhost/static/index.html
+ location /upload/ {
+ alias /app/upload/;
+ autoindex on;
+ }
+
+ # 反向代理 /api 到 localhost:8080
+ location /api/ {
+ proxy_pass http://localhost:8080/;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+
+ # CORS headers
+ add_header Access-Control-Allow-Origin *;
+ add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS, PUT, DELETE';
+ add_header Access-Control-Allow-Headers 'Origin, Content-Type, Accept, Authorization';
+ }
+
+ # 反向代理 /tracker 到 localhost:6969/announce
+ location /tracker {
+ proxy_pass http://localhost:6969/announce;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+
+ # CORS headers
+ add_header Access-Control-Allow-Origin *;
+ add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS, PUT, DELETE';
+ add_header Access-Control-Allow-Headers 'Origin, Content-Type, Accept, Authorization';
+ }
+ }
+}
diff --git a/pom.xml b/pom.xml
index fa73ca7..e677d09 100644
--- a/pom.xml
+++ b/pom.xml
@@ -46,6 +46,11 @@
<version>3.5.8</version>
</dependency>
<dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
<version>3.1.0</version>
@@ -60,21 +65,11 @@
<scope>runtime</scope>
</dependency>
<dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <optional>true</optional>
- </dependency>
- <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
- <groupId>com.h2database</groupId>
- <artifactId>h2</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.14.0</version>
@@ -89,6 +84,30 @@
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>com.turn</groupId>
+ <artifactId>ttorrent-client</artifactId>
+ <version>1.3.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>com.turn</groupId>
+ <artifactId>ttorrent-common</artifactId>
+ <version>1.3.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>com.turn</groupId>
+ <artifactId>ttorrent-tracker</artifactId>
+ <version>1.3.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.data</groupId>
+ <artifactId>spring-data-jpa</artifactId>
+ <version>3.3.4</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-data-jpa</artifactId>
+ </dependency>
</dependencies>
<build>
diff --git a/src/main/java/com/g9/g9backend/controller/CommentController.java b/src/main/java/com/g9/g9backend/controller/CommentController.java
new file mode 100644
index 0000000..fec8e68
--- /dev/null
+++ b/src/main/java/com/g9/g9backend/controller/CommentController.java
@@ -0,0 +1,94 @@
+package com.g9.g9backend.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.g9.g9backend.pojo.Comment;
+import com.g9.g9backend.pojo.DTO.GetCommentDTO;
+import com.g9.g9backend.service.CommentService;
+import org.jetbrains.annotations.NotNull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+
+@RestController
+@RequestMapping("/comment")
+public class CommentController {
+
+ private final CommentService commentService;
+
+ private final Logger logger = LoggerFactory.getLogger(CommentController.class);
+
+ public CommentController(CommentService commentService) {
+ this.commentService = commentService;
+ }
+
+ @PostMapping
+ public ResponseEntity<String> postComment(@RequestBody Comment comment) {
+ System.out.println(comment.toString());
+ commentService.save(comment);
+
+ logger.info("评论已发布");
+
+ return ResponseEntity.ok("");
+ }
+
+ @DeleteMapping
+ public ResponseEntity<String> deleteComment(@RequestParam Integer commentId) {
+ commentService.removeById(commentId);
+
+ return ResponseEntity.noContent().build();
+ }
+
+ @GetMapping
+ public ResponseEntity<GetCommentDTO> getComment(@RequestParam Integer id,
+ @RequestParam Integer pageNumber,
+ @RequestParam Integer rows,
+ @RequestParam String type) {
+ Page<Comment> commentPage = new Page<>(pageNumber, rows);
+ LambdaQueryWrapper<Comment> rewardQuery = new LambdaQueryWrapper<Comment>()
+ .orderByDesc(Comment::getCreateAt);
+
+
+ if (Objects.equals(type, "资源")) {
+
+ rewardQuery.eq(Comment::getResourceId, id);
+ }
+
+ if (Objects.equals(type, "帖子")) {
+
+ rewardQuery.eq(Comment::getThreadId, id);
+ }
+
+ if (Objects.equals(type, "悬赏")) {
+
+ rewardQuery.eq(Comment::getRewardId, id);
+ }
+
+ Page<Comment> result = commentService.page(commentPage, rewardQuery);
+
+ GetCommentDTO getCommentDTO = wrapCommentPage(result, item -> {
+ GetCommentDTO.Comment comment = new GetCommentDTO.Comment();
+ comment.setCommentId(item.getCommentId());
+ comment.setUserId(item.getUserId());
+ comment.setReplyId(item.getReplyId() != null ? item.getReplyId() : 0);
+ comment.setContent(item.getContent());
+ comment.setCreateAt(item.getCreateAt());
+ return comment;
+ });
+
+ return ResponseEntity.ok(getCommentDTO);
+ }
+
+ @NotNull
+ private <T> GetCommentDTO wrapCommentPage(Page<T> page, Function<T, GetCommentDTO.Comment> mapper) {
+ List<GetCommentDTO.Comment> records = page.getRecords().stream().map(mapper).toList();
+
+ return new GetCommentDTO(records, (int) page.getTotal(), (int) page.getPages(), (int) page.getCurrent(), (int) page.getSize());
+ }
+
+}
diff --git a/src/main/java/com/g9/g9backend/controller/CommunityController.java b/src/main/java/com/g9/g9backend/controller/CommunityController.java
index 229fe96..ea79dbb 100644
--- a/src/main/java/com/g9/g9backend/controller/CommunityController.java
+++ b/src/main/java/com/g9/g9backend/controller/CommunityController.java
@@ -1,8 +1,25 @@
package com.g9.g9backend.controller;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.g9.g9backend.pojo.Community;
+import com.g9.g9backend.pojo.DTO.*;
+import com.g9.g9backend.pojo.Subscription;
+import com.g9.g9backend.pojo.Thread;
+import com.g9.g9backend.pojo.ThreadLike;
+import com.g9.g9backend.service.*;
+import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
/**
* CommunityController 社区控制器类,处理与社区相关的请求
@@ -12,5 +29,250 @@
@RestController
public class CommunityController {
+ private final CommunityService communityService;
+
+ private final ThreadService threadService;
+
+ private final SubscriptionService subscriptionService;
+
+ private final ThreadLikeService threadLikeService;
+
private final Logger logger = LoggerFactory.getLogger(CommunityController.class);
+
+ public CommunityController(CommunityService communityService, ThreadService threadService, SubscriptionService subscriptionService, ThreadLikeService threadLikeService) {
+ this.communityService = communityService;
+ this.threadService = threadService;
+ this.threadLikeService = threadLikeService;
+ this.subscriptionService = subscriptionService;
+ }
+
+ @PostMapping(value = "/thread")
+ public ResponseEntity<String> postThread(@RequestBody Thread thread) {
+ thread.setLikes(0);
+ thread.setCommentNumber(0);
+ threadService.save(thread);
+
+ Community community = communityService.getById(thread.getCommunityId());
+ community.setThreadNumber(community.getThreadNumber() + 1);
+ communityService.updateById(community);
+
+ logger.info("帖子已发布");
+
+ return ResponseEntity.ok("");
+ }
+
+ @PostMapping(value = "/thread/like")
+ public ResponseEntity<String> postThreadLike(@RequestBody Map<String, Integer> request) {
+ Integer userId = request.get("userId");
+ Integer threadId = request.get("threadId");
+
+ ThreadLike threadLike = new ThreadLike(userId, threadId);
+ threadLikeService.save(threadLike);
+
+ Thread thread = threadService.getById(threadId);
+ thread.setLikes(thread.getLikes() + 1);
+ threadService.updateById(thread);
+
+ logger.info("点赞成功");
+
+ return ResponseEntity.ok("");
+ }
+
+ @DeleteMapping(value = "/thread")
+ public ResponseEntity<String> deleteThread(@RequestParam Integer threadId) {
+ Thread thread = threadService.getById(threadId);
+ threadService.removeById(threadId);
+
+ Community community = communityService.getById(thread.getCommunityId());
+ community.setThreadNumber(community.getThreadNumber() - 1);
+ communityService.updateById(community);
+
+ logger.info("帖子已删除");
+
+ return ResponseEntity.noContent().build();
+ }
+
+ @DeleteMapping(value = "/thread/like")
+ public ResponseEntity<String> deleteLike(@RequestParam Integer userId, @RequestParam Integer threadId) {
+ LambdaQueryWrapper<ThreadLike> threadLikeQuery = new LambdaQueryWrapper<ThreadLike>()
+ .eq(ThreadLike::getUserId, userId)
+ .eq(ThreadLike::getThreadId, threadId);
+
+ threadLikeService.remove(threadLikeQuery);
+
+ Thread thread = threadService.getById(threadId);
+ thread.setLikes(thread.getLikes() - 1);
+ threadService.updateById(thread);
+
+ logger.info("取消点赞成功");
+
+ return ResponseEntity.noContent().build();
+ }
+
+ @GetMapping(value = "/community")
+ public ResponseEntity<GetCommunityDTO> getCommunity(@RequestParam String searchValue,
+ @RequestParam String type,
+ @RequestParam Integer pageNumber,
+ @RequestParam Integer rows) {
+ Page<Community> communityPage = new Page<>(pageNumber, rows);
+ LambdaQueryWrapper<Community> communityQuery = new LambdaQueryWrapper<Community>()
+ .eq(Community::getType, type)
+ .like(StringUtils.isNotBlank(searchValue), Community::getCommunityName, searchValue)
+ .orderByDesc(Community::getHot);
+
+ Page<Community> result = communityService.page(communityPage, communityQuery);
+
+ GetCommunityDTO getCommunityDTO = wrapCommunityPage(result, item -> {
+ GetCommunityDTO.Community community = new GetCommunityDTO.Community();
+ community.setCommunityId(item.getCommunityId());
+ community.setCommunityName(item.getCommunityName());
+ community.setCommunityPicture(item.getCommunityPicture());
+ community.setDescription(item.getDescription());
+ community.setHot(item.getHot());
+ community.setThreadNumber(item.getThreadNumber());
+ community.setResourceId(item.getResourceId());
+ return community;
+ });
+
+ return ResponseEntity.ok(getCommunityDTO);
+ }
+
+ @NotNull
+ private <T> GetCommunityDTO wrapCommunityPage(Page<T> page, Function<T, GetCommunityDTO.Community> mapper) {
+ List<GetCommunityDTO.Community> records = page.getRecords().stream().map(mapper).toList();
+
+ return new GetCommunityDTO(records, (int) page.getTotal(), (int) page.getPages(), (int) page.getCurrent(), (int) page.getSize());
+ }
+
+ @GetMapping(value = "/community/info")
+ public ResponseEntity<Community> getCommunityInfo(@RequestParam Integer communityId) {
+
+ return ResponseEntity.ok(communityService.getById(communityId));
+ }
+
+ @GetMapping(value = "/thread")
+ public ResponseEntity<ThreadDTO> getThread(@RequestParam Integer threadId, @RequestParam Integer userId) {
+ LambdaQueryWrapper<ThreadLike> threadLikeQuery = new LambdaQueryWrapper<ThreadLike>()
+ .eq(ThreadLike::getUserId, userId)
+ .eq(ThreadLike::getThreadId, threadId);
+ Thread thread = threadService.getById(threadId);
+ ThreadDTO threadDTO = new ThreadDTO(
+ threadId,
+ thread.getUserId(),
+ thread.getThreadPicture(),
+ thread.getTitle(),
+ thread.getContent(),
+ thread.getLikes(),
+ threadLikeService.getOne(threadLikeQuery) != null,
+ thread.getCreateAt(),
+ thread.getCommentNumber(),
+ thread.getCommunityId()
+ );
+
+ return ResponseEntity.ok(threadDTO);
+ }
+
+ @GetMapping(value = "/community/threads")
+ public ResponseEntity<GetCommunityThreadsDTO> getCommunityThreads(@RequestParam Integer communityId,
+ @RequestParam Integer pageNumber,
+ @RequestParam Integer rows,
+ @RequestParam String option,
+ @RequestParam String searchValue,
+ @RequestParam Integer userId) {
+ Page<Thread> threadPage = new Page<>(pageNumber, rows);
+ LambdaQueryWrapper<Thread> threadQuery = new LambdaQueryWrapper<Thread>()
+ .eq(Thread::getCommunityId, communityId)
+ .like(StringUtils.isNotBlank(searchValue), Thread::getTitle, searchValue);
+
+ if (Objects.equals(option, "最高热度")) {
+
+ threadQuery.orderByDesc(Thread::getLikes);
+ } else {
+
+ List<Integer> userIds = subscriptionService.list(new LambdaQueryWrapper<Subscription>()
+ .eq(Subscription::getUserId, userId))
+ .stream()
+ .map(Subscription::getFollowerId)
+ .toList();
+
+ threadQuery.in(!userIds.isEmpty(), Thread::getUserId, userIds)
+ .orderByDesc(Thread::getLikes);
+ }
+
+ Page<Thread> result = threadService.page(threadPage, threadQuery);
+
+ GetCommunityThreadsDTO getCommunityThreadsDTO = wrapThreadPage(result, item -> {
+ GetCommunityThreadsDTO.Thread thread = new GetCommunityThreadsDTO.Thread();
+ thread.setThreadId(item.getThreadId());
+ thread.setUserId(item.getUserId());
+ thread.setThreadPicture(item.getThreadPicture());
+ thread.setTitle(item.getTitle());
+ thread.setLikes(item.getLikes());
+ thread.setCreateAt(item.getCreateAt());
+ return thread;
+ });
+
+ return ResponseEntity.ok(getCommunityThreadsDTO);
+ }
+
+ @NotNull
+ private <T> GetCommunityThreadsDTO wrapThreadPage(Page<T> page, Function<T, GetCommunityThreadsDTO.Thread> mapper) {
+ List<GetCommunityThreadsDTO.Thread> records = page.getRecords().stream().map(mapper).toList();
+
+ return new GetCommunityThreadsDTO(records, (int) page.getTotal(), (int) page.getPages(), (int) page.getCurrent(), (int) page.getSize());
+ }
+
+ @GetMapping(value = "/community/hot")
+ public ResponseEntity<GetCommunityHotDTO> getCommunityHot() {
+ Page<Community> communityPage = new Page<>(1, 3);
+ LambdaQueryWrapper<Community> communityQuery = new LambdaQueryWrapper<Community>()
+ .orderByDesc(Community::getHot);
+
+ Page<Community> result = communityService.page(communityPage, communityQuery);
+
+ List<GetCommunityHotDTO.Community> communityList = getCommunityList(result);
+
+ GetCommunityHotDTO getCommunityHotDTO = new GetCommunityHotDTO(communityList);
+
+ return ResponseEntity.ok(getCommunityHotDTO);
+ }
+
+ @NotNull
+ private static List<GetCommunityHotDTO.Community> getCommunityList(Page<Community> communityPage) {
+ List<Community> communityListT = communityPage.getRecords();
+ List<GetCommunityHotDTO.Community> communityList = new ArrayList<>();
+ int status = 1;
+ for (Community community : communityListT) {
+ GetCommunityHotDTO.Community communityHot = new GetCommunityHotDTO.Community(
+ community.getCommunityId(),
+ community.getCommunityName(),
+ community.getCommunityPicture(),
+ community.getCommunityPicture(),
+ community.getHot(),
+ community.getType(),
+ community.getThreadNumber(),
+ community.getResourceId(),
+ status);
+ communityList.add(communityHot);
+ status++;
+ }
+
+ return communityList;
+ }
+
+ @GetMapping(value = "/community/common")
+ public ResponseEntity<GetCommunityCommonDTO> getCommunityCommon() {
+ Page<Community> communityPage = new Page<>(2, 3);
+ LambdaQueryWrapper<Community> communityQuery = new LambdaQueryWrapper<Community>()
+ .orderByDesc(Community::getHot);
+
+ Page<Community> result = communityService.page(communityPage, communityQuery);
+
+ List<Community> communityList = result.getRecords();
+
+ GetCommunityCommonDTO getCommunityCommonDTO = new GetCommunityCommonDTO(communityList);
+
+ return ResponseEntity.ok(getCommunityCommonDTO);
+ }
+
}
diff --git a/src/main/java/com/g9/g9backend/controller/FileController.java b/src/main/java/com/g9/g9backend/controller/FileController.java
index 1f9ccd2..7b4e339 100644
--- a/src/main/java/com/g9/g9backend/controller/FileController.java
+++ b/src/main/java/com/g9/g9backend/controller/FileController.java
@@ -1,18 +1,86 @@
package com.g9.g9backend.controller;
+import com.g9.g9backend.pojo.TorrentRecord;
+import com.g9.g9backend.service.TorrentRecordService;
+import lombok.Getter;
+import lombok.Setter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.io.IOException;
/**
* FileController 文件控制器类,处理与文件相关的请求
*
* @author Seamher
*/
+@Getter
+@Setter
@RestController
@RequestMapping("/file")
public class FileController {
+ private TorrentRecordService torrentRecordService;
+
private final Logger logger = LoggerFactory.getLogger(FileController.class);
+
+ public FileController(TorrentRecordService torrentRecordService) {
+ this.torrentRecordService = torrentRecordService;
+ }
+
+ @PostMapping
+ public ResponseEntity<String> uploadFile(@RequestBody MultipartFile file) {
+ // 相对路径(可在资源根路径创建 upload 文件夹)
+ String UPLOAD_DIR = "upload";
+
+ // 获取原始文件名
+ String originalFilename = file.getOriginalFilename();
+ if (originalFilename.isEmpty()) {
+ return ResponseEntity.badRequest().body("文件名为空");
+ }
+
+ // 为文件名加时间戳,防止重名冲突
+ String filename = System.currentTimeMillis() + "_" + originalFilename;
+
+ // 构建文件保存目录(确保 upload 目录存在)
+ File uploadDir = new File(UPLOAD_DIR);
+ if (!uploadDir.exists()) {
+ boolean created = uploadDir.mkdirs(); // 创建目录
+ if (!created) {
+ logger.error("无法创建上传目录: " + uploadDir.getAbsolutePath());
+ return ResponseEntity.internalServerError().body("无法创建上传目录");
+ }
+ }
+
+ // 构建完整的文件路径
+ File dest = new File(uploadDir, filename);
+
+ try {
+ // 保存文件
+ file.transferTo(dest);
+ } catch (IOException e) {
+ logger.error("上传失败: " + e.getMessage(), e);
+ return ResponseEntity.internalServerError().body("上传失败");
+ }
+
+ // 返回访问 URL(你可能需要根据 Nginx 的静态资源映射来配置)
+ String url = "http://localhost:65/" + filename;
+
+ return ResponseEntity.ok(url);
+ }
+
+ @PostMapping(value = "bt")
+ public ResponseEntity<String> uploadBTFile(@RequestBody TorrentRecord torrentRecord) {
+ torrentRecordService.save(torrentRecord);
+
+ return ResponseEntity.ok("");
+ }
+
}
diff --git a/src/main/java/com/g9/g9backend/controller/NotificationController.java b/src/main/java/com/g9/g9backend/controller/NotificationController.java
index c774041..4f451df 100644
--- a/src/main/java/com/g9/g9backend/controller/NotificationController.java
+++ b/src/main/java/com/g9/g9backend/controller/NotificationController.java
@@ -1,9 +1,15 @@
package com.g9.g9backend.controller;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.g9.g9backend.pojo.Notification;
+import com.g9.g9backend.service.NotificationService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
/**
* NotificationController 通知控制器类,处理与通知相关的请求
@@ -14,5 +20,46 @@
@RequestMapping("/notification")
public class NotificationController {
+ private final NotificationService notificationService;
+
+ public NotificationController(NotificationService notificationService) {
+ this.notificationService = notificationService;
+ }
+
private final Logger logger = LoggerFactory.getLogger(NotificationController.class);
+
+ @PostMapping(value = "/read")
+ public ResponseEntity<String> getNotificationRead(@RequestBody Notification notification) {
+ Integer notificationId = notification.getNotificationId();
+ System.out.println(notificationId);
+ Notification notificationUpdate = notificationService.getById(notificationId);
+ notificationUpdate.setRead(true);
+ notificationService.updateById(notificationUpdate);
+
+ return ResponseEntity.ok("");
+ }
+
+ @DeleteMapping
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ public ResponseEntity<String> deleteNotification(@RequestParam Integer notificationId) {
+ notificationService.removeById(notificationId);
+
+ return ResponseEntity.noContent().build();
+ }
+
+ @GetMapping
+ public ResponseEntity<IPage<Notification>> getNotification(@RequestParam Integer userId,
+ @RequestParam Integer pageNumber,
+ @RequestParam Integer rows) {
+ Page<Notification> notificationPage = new Page<>(pageNumber, rows);
+ LambdaQueryWrapper<Notification> notificationQuery = new LambdaQueryWrapper<Notification>()
+ .eq(Notification::getUserId, userId)
+ .orderByDesc(Notification::getCreateAt);
+
+ IPage<Notification> result = notificationService.page(notificationPage, notificationQuery);
+ logger.info("通知已返回");
+
+ return ResponseEntity.ok(result);
+ }
+
}
diff --git a/src/main/java/com/g9/g9backend/controller/RewardController.java b/src/main/java/com/g9/g9backend/controller/RewardController.java
index c5b316b..3563f41 100644
--- a/src/main/java/com/g9/g9backend/controller/RewardController.java
+++ b/src/main/java/com/g9/g9backend/controller/RewardController.java
@@ -1,9 +1,23 @@
package com.g9.g9backend.controller;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.g9.g9backend.pojo.DTO.GetRewardDTO;
+import com.g9.g9backend.pojo.Reward;
+import com.g9.g9backend.service.RewardService;
+import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
/**
* RewardController 悬赏控制器类,处理与悬赏相关的请求
@@ -14,5 +28,103 @@
@RequestMapping("/reward")
public class RewardController {
+ private final RewardService rewardService;
+
private final Logger logger = LoggerFactory.getLogger(RewardController.class);
+
+ public RewardController(RewardService rewardService) {
+ this.rewardService = rewardService;
+ }
+
+ @PostMapping
+ public ResponseEntity<String> postReward(@RequestBody Reward reward) {
+ reward.setCompletedBy(null);
+ reward.setResourceId(null);
+ rewardService.save(reward);
+
+ logger.info("悬赏已发布");
+
+ return ResponseEntity.ok("");
+ }
+
+ @DeleteMapping
+ public ResponseEntity<String> deleteReward(@RequestParam Integer rewardId) {
+ rewardService.removeById(rewardId);
+
+ logger.info("悬赏已删除");
+
+ return ResponseEntity.noContent().build();
+ }
+
+ @GetMapping
+ public ResponseEntity<GetRewardDTO> getReward(@RequestParam Integer pageNumber,
+ @RequestParam Integer rows,
+ @RequestParam String searchValue,
+ @RequestParam String option) {
+ Page<Reward> rewardPage = new Page<>(pageNumber, rows);
+ LambdaQueryWrapper<Reward> rewardQuery = new LambdaQueryWrapper<Reward>()
+ .like(StringUtils.isNotBlank(searchValue), Reward::getRewardName, searchValue);
+
+ if (Objects.equals(option, "赏金最高")) {
+
+ rewardQuery.orderByDesc(Reward::getPrice);
+ } else {
+
+ rewardQuery.orderByDesc(Reward::getCreateAt);
+ }
+
+ Page<Reward> result = rewardService.page(rewardPage, rewardQuery);
+
+ GetRewardDTO getRewardDTO = wrapRewardPage(result, item -> {
+ GetRewardDTO.Reward reward = new GetRewardDTO.Reward();
+ reward.setRewardId(item.getRewardId());
+ reward.setRewardName(item.getRewardName());
+ reward.setRewardPicture(item.getRewardPicture());
+ reward.setUserId(item.getUserId());
+ reward.setPrice(item.getPrice());
+ reward.setCreateAt(item.getCreateAt());
+ return reward;
+ });
+
+ return ResponseEntity.ok(getRewardDTO);
+ }
+
+ @NotNull
+ private <T> GetRewardDTO wrapRewardPage(Page<T> page, Function<T, GetRewardDTO.Reward> mapper) {
+ List<GetRewardDTO.Reward> records = page.getRecords().stream().map(mapper).toList();
+
+ return new GetRewardDTO(records, (int) page.getTotal(), (int) page.getPages(), (int) page.getCurrent(), (int) page.getSize());
+ }
+
+ @GetMapping(value = "/info")
+ public ResponseEntity<Reward> getRewardInfo(@RequestParam Integer rewardId) {
+
+ return ResponseEntity.ok(rewardService.getById(rewardId));
+ }
+
+ @PutMapping("/info")
+ public ResponseEntity<String> putReward(@RequestBody Reward reward) {
+ Reward rewardUpdate = rewardService.getById(reward.getRewardId());
+ rewardUpdate.setRewardName(reward.getRewardName());
+ rewardUpdate.setRewardPicture(reward.getRewardPicture());
+ rewardUpdate.setPrice(reward.getPrice());
+ rewardUpdate.setRewardDescription(reward.getRewardDescription());
+ rewardUpdate.setLastUpdateAt(Date.from(LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant()));
+ rewardService.updateById(rewardUpdate);
+
+ return ResponseEntity.ok("");
+ }
+
+ @PostMapping(value = "/completion")
+ public ResponseEntity<String> putRewardCompletion(@RequestBody Reward reward) {
+ Reward rewardUpdate = rewardService.getById(reward.getRewardId());
+ rewardUpdate.setCompletedBy(reward.getCompletedBy());
+ rewardUpdate.setCompletedAt(reward.getCompletedAt());
+ rewardUpdate.setResourceId(reward.getResourceId());
+ rewardService.updateById(rewardUpdate);
+
+ return ResponseEntity.ok("");
+ }
+
+
}
diff --git a/src/main/java/com/g9/g9backend/controller/TotalController.java b/src/main/java/com/g9/g9backend/controller/TotalController.java
new file mode 100644
index 0000000..2359586
--- /dev/null
+++ b/src/main/java/com/g9/g9backend/controller/TotalController.java
@@ -0,0 +1,67 @@
+package com.g9.g9backend.controller;
+
+import com.g9.g9backend.service.ResourceService;
+import com.g9.g9backend.service.ThreadService;
+import com.g9.g9backend.service.UserPurchaseService;
+import com.g9.g9backend.service.UserUploadService;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/total")
+public class TotalController {
+
+ private final ThreadService threadService;
+
+ private final UserPurchaseService userPurchaseService;
+
+ private final UserUploadService userUploadService;
+
+ private final ResourceService resourceService;
+
+ public TotalController(ThreadService threadService, UserPurchaseService userPurchaseService, UserUploadService userUploadService, ResourceService resourceService) {
+ this.threadService = threadService;
+ this.userPurchaseService = userPurchaseService;
+ this.userUploadService = userUploadService;
+ this.resourceService = resourceService;
+ }
+
+ private final Logger logger = LoggerFactory.getLogger(TotalController.class);
+
+
+ @GetMapping(value = "/info")
+ public ResponseEntity<Info> getTotalInfo() {
+
+ long threadCount = threadService.count();
+ long downloadCount = userPurchaseService.count();
+ long authorCount = userUploadService.count();
+ long resourceCount = resourceService.count();
+
+ Info info = new Info(threadCount, downloadCount, authorCount, resourceCount);
+ logger.info("统计数据返回:{}", info);
+
+ return ResponseEntity.ok(info);
+ }
+
+ @Data
+ @AllArgsConstructor
+ @NoArgsConstructor
+ public static class Info {
+
+ private long threadCount;
+
+ private long downloadCount;
+
+ private long authorCount;
+
+ private long resourceCount;
+
+ }
+}
diff --git a/src/main/java/com/g9/g9backend/mapper/ThreadLikeMapper.java b/src/main/java/com/g9/g9backend/mapper/ThreadLikeMapper.java
new file mode 100644
index 0000000..6903868
--- /dev/null
+++ b/src/main/java/com/g9/g9backend/mapper/ThreadLikeMapper.java
@@ -0,0 +1,9 @@
+package com.g9.g9backend.mapper;
+
+import com.g9.g9backend.pojo.ThreadLike;
+import com.github.jeffreyning.mybatisplus.base.MppBaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface ThreadLikeMapper extends MppBaseMapper<ThreadLike> {
+}
diff --git a/src/main/java/com/g9/g9backend/pojo/Comment.java b/src/main/java/com/g9/g9backend/pojo/Comment.java
index eddc42b..1f6e74f 100644
--- a/src/main/java/com/g9/g9backend/pojo/Comment.java
+++ b/src/main/java/com/g9/g9backend/pojo/Comment.java
@@ -21,13 +21,13 @@
private int userId;
- private int threadId;
+ private Integer threadId;
- private int resourceId;
+ private Integer resourceId;
- private int rewardId;
+ private Integer rewardId;
- private int replyId;
+ private Integer replyId;
private String content;
diff --git a/src/main/java/com/g9/g9backend/pojo/DTO/GetCommentDTO.java b/src/main/java/com/g9/g9backend/pojo/DTO/GetCommentDTO.java
new file mode 100644
index 0000000..0fb7c44
--- /dev/null
+++ b/src/main/java/com/g9/g9backend/pojo/DTO/GetCommentDTO.java
@@ -0,0 +1,42 @@
+package com.g9.g9backend.pojo.DTO;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Date;
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class GetCommentDTO {
+
+ private List<Comment> records;
+
+ private long total;
+
+ private long pages;
+
+ private long current;
+
+ private long size;
+
+ @Data
+ @AllArgsConstructor
+ @NoArgsConstructor
+ public static class Comment {
+
+ private int commentId;
+
+ private int userId;
+
+ private int replyId;
+
+ private String content;
+
+ private Date createAt;
+
+ }
+
+}
diff --git a/src/main/java/com/g9/g9backend/pojo/DTO/GetCommunityCommonDTO.java b/src/main/java/com/g9/g9backend/pojo/DTO/GetCommunityCommonDTO.java
new file mode 100644
index 0000000..8e24f40
--- /dev/null
+++ b/src/main/java/com/g9/g9backend/pojo/DTO/GetCommunityCommonDTO.java
@@ -0,0 +1,17 @@
+package com.g9.g9backend.pojo.DTO;
+
+import com.g9.g9backend.pojo.Community;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class GetCommunityCommonDTO {
+
+ List<Community> communityList;
+
+}
diff --git a/src/main/java/com/g9/g9backend/pojo/DTO/GetCommunityDTO.java b/src/main/java/com/g9/g9backend/pojo/DTO/GetCommunityDTO.java
new file mode 100644
index 0000000..791bb10
--- /dev/null
+++ b/src/main/java/com/g9/g9backend/pojo/DTO/GetCommunityDTO.java
@@ -0,0 +1,45 @@
+package com.g9.g9backend.pojo.DTO;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class GetCommunityDTO {
+
+ private List<Community> records;
+
+ private Integer total;
+
+ private Integer pages;
+
+ private Integer current;
+
+ private Integer size;
+
+ @Data
+ @AllArgsConstructor
+ @NoArgsConstructor
+ public static class Community {
+
+ private int communityId;
+
+ private String communityName;
+
+ private String communityPicture;
+
+ private String description;
+
+ private float hot;
+
+ private int threadNumber;
+
+ private int resourceId;
+
+ }
+
+}
diff --git a/src/main/java/com/g9/g9backend/pojo/DTO/GetCommunityHotDTO.java b/src/main/java/com/g9/g9backend/pojo/DTO/GetCommunityHotDTO.java
new file mode 100644
index 0000000..222eb07
--- /dev/null
+++ b/src/main/java/com/g9/g9backend/pojo/DTO/GetCommunityHotDTO.java
@@ -0,0 +1,42 @@
+package com.g9.g9backend.pojo.DTO;
+
+import com.g9.g9backend.pojo.Community;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class GetCommunityHotDTO {
+
+ List<Community> communityList;
+
+ @Data
+ @AllArgsConstructor
+ @NoArgsConstructor
+ public static class Community{
+
+ private int communityId;
+
+ private String communityName;
+
+ private String communityPicture;
+
+ private String description;
+
+ private float hot;
+
+ private String type;
+
+ private int threadNumber;
+
+ private int resourceId;
+
+ private int status;
+
+ }
+
+}
diff --git a/src/main/java/com/g9/g9backend/pojo/DTO/GetCommunityThreadsDTO.java b/src/main/java/com/g9/g9backend/pojo/DTO/GetCommunityThreadsDTO.java
new file mode 100644
index 0000000..d734433
--- /dev/null
+++ b/src/main/java/com/g9/g9backend/pojo/DTO/GetCommunityThreadsDTO.java
@@ -0,0 +1,44 @@
+package com.g9.g9backend.pojo.DTO;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Date;
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class GetCommunityThreadsDTO {
+
+ private List<Thread> records;
+
+ private Integer total;
+
+ private Integer pages;
+
+ private Integer current;
+
+ private Integer size;
+
+ @Data
+ @AllArgsConstructor
+ @NoArgsConstructor
+ public static class Thread{
+
+ private int threadId;
+
+ private int userId;
+
+ private String threadPicture;
+
+ private String title;
+
+ private int likes;
+
+ private Date createAt;
+
+ }
+
+}
diff --git a/src/main/java/com/g9/g9backend/pojo/DTO/GetRewardDTO.java b/src/main/java/com/g9/g9backend/pojo/DTO/GetRewardDTO.java
new file mode 100644
index 0000000..f56b878
--- /dev/null
+++ b/src/main/java/com/g9/g9backend/pojo/DTO/GetRewardDTO.java
@@ -0,0 +1,44 @@
+package com.g9.g9backend.pojo.DTO;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Date;
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class GetRewardDTO {
+
+ private List<Reward> records;
+
+ private long total;
+
+ private long pages;
+
+ private long current;
+
+ private long size;
+
+ @Data
+ @AllArgsConstructor
+ @NoArgsConstructor
+ public static class Reward {
+
+ private int rewardId;
+
+ private String rewardName;
+
+ private String rewardPicture;
+
+ private int userId;
+
+ private float price;
+
+ private Date createAt;
+
+ }
+
+}
diff --git a/src/main/java/com/g9/g9backend/pojo/DTO/ThreadDTO.java b/src/main/java/com/g9/g9backend/pojo/DTO/ThreadDTO.java
new file mode 100644
index 0000000..5e0aec7
--- /dev/null
+++ b/src/main/java/com/g9/g9backend/pojo/DTO/ThreadDTO.java
@@ -0,0 +1,36 @@
+package com.g9.g9backend.pojo.DTO;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Date;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class ThreadDTO {
+
+ private int threadId;
+
+ private int userId;
+
+ private String threadPicture;
+
+ private String title;
+
+ private String content;
+
+ private int likes;
+
+ @JsonProperty("isLike")
+ private boolean isLike;
+
+ private Date createAt;
+
+ private int commentNumber;
+
+ private int communityId;
+
+}
diff --git a/src/main/java/com/g9/g9backend/pojo/Notification.java b/src/main/java/com/g9/g9backend/pojo/Notification.java
index 7ebf7c1..249eb7e 100644
--- a/src/main/java/com/g9/g9backend/pojo/Notification.java
+++ b/src/main/java/com/g9/g9backend/pojo/Notification.java
@@ -2,6 +2,7 @@
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
+import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;
import java.util.Date;
@@ -21,14 +22,13 @@
private int userId;
- private String type;
-
private String title;
private String content;
private Date createAt;
+ @JsonProperty("isRead")
private boolean isRead;
private int triggeredBy;
diff --git a/src/main/java/com/g9/g9backend/pojo/Reward.java b/src/main/java/com/g9/g9backend/pojo/Reward.java
index aea9c84..fc2d1ed 100644
--- a/src/main/java/com/g9/g9backend/pojo/Reward.java
+++ b/src/main/java/com/g9/g9backend/pojo/Reward.java
@@ -33,9 +33,9 @@
private Date lastUpdateAt;
- private int completedBy;
+ private Integer completedBy;
private Date completedAt;
- private int resourceId;
+ private Integer resourceId;
}
\ No newline at end of file
diff --git a/src/main/java/com/g9/g9backend/pojo/ThreadLike.java b/src/main/java/com/g9/g9backend/pojo/ThreadLike.java
new file mode 100644
index 0000000..6818ed4
--- /dev/null
+++ b/src/main/java/com/g9/g9backend/pojo/ThreadLike.java
@@ -0,0 +1,18 @@
+package com.g9.g9backend.pojo;
+
+import com.github.jeffreyning.mybatisplus.anno.MppMultiId;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class ThreadLike {
+
+ @MppMultiId
+ private int userId;
+
+ @MppMultiId
+ private int threadId;
+}
diff --git a/src/main/java/com/g9/g9backend/service/ThreadLikeService.java b/src/main/java/com/g9/g9backend/service/ThreadLikeService.java
new file mode 100644
index 0000000..6b782df
--- /dev/null
+++ b/src/main/java/com/g9/g9backend/service/ThreadLikeService.java
@@ -0,0 +1,7 @@
+package com.g9.g9backend.service;
+
+import com.g9.g9backend.pojo.ThreadLike;
+import com.github.jeffreyning.mybatisplus.service.IMppService;
+
+public interface ThreadLikeService extends IMppService<ThreadLike> {
+}
diff --git a/src/main/java/com/g9/g9backend/service/TrackerService.java b/src/main/java/com/g9/g9backend/service/TrackerService.java
new file mode 100644
index 0000000..3d30d6c
--- /dev/null
+++ b/src/main/java/com/g9/g9backend/service/TrackerService.java
@@ -0,0 +1,26 @@
+package com.g9.g9backend.service;
+
+import com.turn.ttorrent.tracker.Tracker;
+import jakarta.annotation.PostConstruct;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+@Service
+public class TrackerService {
+
+ private final Logger logger = LoggerFactory.getLogger(TrackerService.class);
+
+ @PostConstruct
+ public void initTracker() {
+ try {
+ Tracker tracker = new Tracker(6969, "http://localhost:6969/announce");
+ tracker.setAcceptForeignTorrents(true);
+ tracker.start(false);
+ logger.info("Tracker服务器已启动,监听端口 6969");
+ } catch (Exception e) {
+ logger.error("Tracker启动失败:", e);
+ }
+ }
+
+}
diff --git a/src/main/java/com/g9/g9backend/service/impl/ThreadLikeServiceImpl.java b/src/main/java/com/g9/g9backend/service/impl/ThreadLikeServiceImpl.java
new file mode 100644
index 0000000..7dbd3bd
--- /dev/null
+++ b/src/main/java/com/g9/g9backend/service/impl/ThreadLikeServiceImpl.java
@@ -0,0 +1,11 @@
+package com.g9.g9backend.service.impl;
+
+import com.g9.g9backend.mapper.ThreadLikeMapper;
+import com.g9.g9backend.pojo.ThreadLike;
+import com.g9.g9backend.service.ThreadLikeService;
+import com.github.jeffreyning.mybatisplus.service.MppServiceImpl;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ThreadLikeServiceImpl extends MppServiceImpl<ThreadLikeMapper, ThreadLike> implements ThreadLikeService {
+}
diff --git a/src/test/java/com/g9/g9backend/controller/CommentControllerTest.java b/src/test/java/com/g9/g9backend/controller/CommentControllerTest.java
new file mode 100644
index 0000000..d9f9f87
--- /dev/null
+++ b/src/test/java/com/g9/g9backend/controller/CommentControllerTest.java
@@ -0,0 +1,68 @@
+package com.g9.g9backend.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.g9.g9backend.pojo.Comment;
+import com.g9.g9backend.pojo.DTO.GetCommentDTO;
+import com.g9.g9backend.service.CommentService;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentMatchers;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+
+import org.springframework.http.ResponseEntity;
+
+import java.util.Collections;
+import java.util.Date;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+import static org.mockito.BDDMockito.given;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+public class CommentControllerTest {
+
+ @Mock
+ private CommentService commentService;
+
+ @InjectMocks
+ private CommentController commentController;
+
+ @Test
+ void testPostComment() {
+ Comment comment = new Comment();
+ ResponseEntity<String> response = commentController.postComment(comment);
+ verify(commentService).save(comment);
+ assertEquals(200, response.getStatusCode().value());
+ }
+
+ @Test
+ void testDeleteComment() {
+ Integer id = 1;
+ ResponseEntity<String> response = commentController.deleteComment(id);
+ verify(commentService).removeById(id);
+ assertEquals(204, response.getStatusCode().value());
+ }
+
+ @Test
+ void testGetCommentWithResourceType() {
+ Comment comment = new Comment();
+ comment.setCommentId(1);
+ comment.setUserId(2);
+ comment.setReplyId(null);
+ comment.setContent("Test");
+ comment.setCreateAt(new Date());
+ comment.setResourceId(1);
+
+ Page<Comment> page = new Page<>();
+ page.setRecords(Collections.singletonList(comment));
+
+ given(commentService.page(ArgumentMatchers.<Page<Comment>>any(), ArgumentMatchers.<LambdaQueryWrapper<Comment>>any())).willReturn(page);
+
+ ResponseEntity<GetCommentDTO> response = commentController.getComment(1, 1, 10, "资源");
+ assertEquals(200, response.getStatusCode().value());
+ assertEquals(1, response.getBody().getRecords().size());
+ }
+}
diff --git a/src/test/java/com/g9/g9backend/controller/CommunityControllerTest.java b/src/test/java/com/g9/g9backend/controller/CommunityControllerTest.java
new file mode 100644
index 0000000..da9bf57
--- /dev/null
+++ b/src/test/java/com/g9/g9backend/controller/CommunityControllerTest.java
@@ -0,0 +1,268 @@
+package com.g9.g9backend.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.g9.g9backend.pojo.Community;
+import com.g9.g9backend.pojo.Subscription;
+import com.g9.g9backend.pojo.Thread;
+import com.g9.g9backend.service.CommunityService;
+import com.g9.g9backend.service.SubscriptionService;
+import com.g9.g9backend.service.ThreadLikeService;
+import com.g9.g9backend.service.ThreadService;
+import org.jetbrains.annotations.NotNull;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+@ExtendWith(MockitoExtension.class)
+public class CommunityControllerTest {
+
+ private MockMvc mockMvc;
+
+ @InjectMocks
+ private CommunityController communityController;
+
+ @Mock
+ private CommunityService communityService;
+
+ @Mock
+ private ThreadService threadService;
+
+ @Mock
+ private SubscriptionService subscriptionService;
+
+ @Mock
+ private ThreadLikeService threadLikeService;
+
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+ @BeforeEach
+ public void setup() {
+ mockMvc = MockMvcBuilders.standaloneSetup(communityController).build();
+ }
+
+ @Test
+ public void shouldReturnHotCommunities_whenCallingHotEndpoint() throws Exception {
+ Community c1 = new Community(1, "社区A", "pic", "desc", 9.0f, "type", 5, 1);
+ Page<Community> page = createCommunityPage(List.of(c1));
+ when(communityService.page(any(), any())).thenReturn(page);
+
+ mockMvc.perform(get("/community/hot"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.communityList[0].communityId").value(1));
+ }
+
+ @Test
+ public void shouldReturnCommonCommunities_whenCallingCommonEndpoint() throws Exception {
+ Community c1 = new Community(2, "社区B", "pic", "desc", 7.0f, "type", 3, 1);
+ Page<Community> page = createCommunityPage(List.of(c1));
+ when(communityService.page(any(), any())).thenReturn(page);
+
+ mockMvc.perform(get("/community/common"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.communityList[0].communityId").value(2));
+ }
+
+ @Test
+ public void shouldReturnCommunityInfo_whenIdIsValid() throws Exception {
+ Community community = new Community(3, "社区C", "pic", "desc", 6.0f, "type", 4, 2);
+ when(communityService.getById(3)).thenReturn(community);
+
+ mockMvc.perform(get("/community/info").param("communityId", "3"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.communityId").value(3));
+ }
+
+ @Test
+ public void shouldReturnThreadDetails_whenThreadIdAndUserIdAreValid() throws Exception {
+ Thread thread = mockThread(1, 10);
+ when(threadService.getById(1)).thenReturn(thread);
+ when(threadLikeService.getOne(any())).thenReturn(null);
+
+ mockMvc.perform(get("/thread")
+ .param("threadId", "1")
+ .param("userId", "10"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.threadId").value(1))
+ .andExpect(jsonPath("$.userId").value(10));
+ }
+
+ @Test
+ public void shouldPostThread_whenValidRequest() throws Exception {
+ Thread thread = new Thread();
+ thread.setThreadId(100);
+ thread.setCommunityId(1);
+
+ Community community = new Community();
+ community.setCommunityId(1);
+ community.setThreadNumber(5);
+
+ when(communityService.getById(1)).thenReturn(community);
+
+ mockMvc.perform(post("/thread")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(thread)))
+ .andExpect(status().isOk());
+ }
+
+ @Test
+ public void shouldLikeThread_whenValidInput() throws Exception {
+ Thread thread = new Thread();
+ thread.setThreadId(100);
+ thread.setLikes(5);
+
+ when(threadService.getById(100)).thenReturn(thread);
+
+ mockMvc.perform(post("/thread/like")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content("{\"userId\":1, \"threadId\":100}"))
+ .andExpect(status().isOk());
+ }
+
+ @Test
+ public void shouldDeleteThread_whenThreadExists() throws Exception {
+ Thread thread = new Thread();
+ thread.setThreadId(100);
+ thread.setCommunityId(1);
+
+ Community community = new Community();
+ community.setCommunityId(1);
+ community.setThreadNumber(5);
+
+ when(threadService.getById(100)).thenReturn(thread);
+ when(communityService.getById(1)).thenReturn(community);
+
+ mockMvc.perform(delete("/thread").param("threadId", "100"))
+ .andExpect(status().isNoContent());
+ }
+
+ @Test
+ public void shouldCancelLike_whenValidRequest() throws Exception {
+ Thread thread = new Thread();
+ thread.setThreadId(100);
+ thread.setLikes(5);
+
+ when(threadService.getById(100)).thenReturn(thread);
+
+ mockMvc.perform(delete("/thread/like")
+ .param("userId", "1")
+ .param("threadId", "100"))
+ .andExpect(status().isNoContent());
+ }
+
+ @Test
+ public void shouldReturnCommunities_whenSearchingByType() throws Exception {
+ Community c = new Community(1, "搜索社区", "pic", "desc", 9.5f, "test", 8, 5);
+ Page<Community> page = createCommunityPage(List.of(c));
+
+ when(communityService.page(any(), any())).thenReturn(page);
+
+ mockMvc.perform(get("/community")
+ .param("searchValue", "搜索")
+ .param("type", "test")
+ .param("pageNumber", "1")
+ .param("rows", "10"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.records[0].communityId").value(1));
+ }
+
+ @Test
+ public void shouldReturnHotThreads_whenOptionIsHot() throws Exception {
+ Thread t = new Thread();
+ t.setThreadId(1);
+ t.setUserId(2);
+ t.setTitle("热帖");
+ t.setLikes(10);
+ Page<Thread> page = new Page<>(1, 10);
+ page.setRecords(List.of(t));
+ page.setTotal(1L);
+
+ when(threadService.page(any(), any())).thenReturn(page);
+
+ mockMvc.perform(get("/community/threads")
+ .param("communityId", "1")
+ .param("pageNumber", "1")
+ .param("rows", "10")
+ .param("option", "最高热度")
+ .param("searchValue", "")
+ .param("userId", "2"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.records[0].threadId").value(1));
+ }
+
+ @Test
+ public void shouldReturnFollowedThreads_whenOptionIsFollowed() throws Exception {
+ Subscription s = new Subscription();
+ s.setUserId(1);
+ s.setFollowerId(2);
+
+ List<Subscription> subscriptions = new ArrayList<>();
+ subscriptions.add(s);
+
+ @SuppressWarnings("unchecked")
+ LambdaQueryWrapper<Subscription> wrapper = any(LambdaQueryWrapper.class);
+ when(subscriptionService.list(wrapper)).thenReturn(subscriptions);
+
+ Thread t = new Thread();
+ t.setThreadId(2);
+ t.setUserId(2);
+ t.setTitle("关注者帖子");
+ t.setLikes(8);
+
+ Page<Thread> page = new Page<>(1, 10);
+ page.setRecords(List.of(t));
+ page.setTotal(1L);
+
+ when(threadService.page(any(), any())).thenReturn(page);
+
+ mockMvc.perform(get("/community/threads")
+ .param("communityId", "1")
+ .param("pageNumber", "1")
+ .param("rows", "10")
+ .param("option", "关注者")
+ .param("searchValue", "")
+ .param("userId", "1"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.records[0].threadId").value(2));
+ }
+
+ @NotNull
+ private Page<Community> createCommunityPage(List<Community> communities) {
+ Page<Community> page = new Page<>(1, 3);
+ page.setRecords(communities);
+ page.setTotal(communities.size());
+ return page;
+ }
+
+ @NotNull
+ @SuppressWarnings("SameParameterValue")
+ private Thread mockThread(int threadId, int userId) {
+ Thread thread = new Thread();
+ thread.setThreadId(threadId);
+ thread.setUserId(userId);
+ thread.setThreadPicture("pic");
+ thread.setTitle("title");
+ thread.setContent("content");
+ thread.setLikes(5);
+ thread.setCreateAt(new Date());
+ thread.setCommentNumber(2);
+ thread.setCommunityId(99);
+ return thread;
+ }
+}
diff --git a/src/test/java/com/g9/g9backend/controller/FileControllerTest.java b/src/test/java/com/g9/g9backend/controller/FileControllerTest.java
new file mode 100644
index 0000000..a8b3d01
--- /dev/null
+++ b/src/test/java/com/g9/g9backend/controller/FileControllerTest.java
@@ -0,0 +1,82 @@
+package com.g9.g9backend.controller;
+
+import com.g9.g9backend.pojo.TorrentRecord;
+import com.g9.g9backend.service.TorrentRecordService;
+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 org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+
+import static org.mockito.Mockito.verify;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import static org.hamcrest.Matchers.containsString;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+
+public class FileControllerTest {
+
+ private MockMvc mockMvc;
+
+ @InjectMocks
+ private FileController fileController;
+
+ @Mock
+ private TorrentRecordService torrentRecordService;
+
+ @BeforeEach
+ public void setup() {
+ MockitoAnnotations.openMocks(this);
+ mockMvc = MockMvcBuilders.standaloneSetup(fileController).build();
+ }
+
+ // 测试上传文件接口(模拟 multipart/form-data)
+ @Test
+ public void testUploadFile_success() throws Exception {
+ MockMultipartFile mockFile = new MockMultipartFile(
+ "file", // 参数名,必须为 file
+ "test.txt", // 文件名
+ "text/plain", // MIME 类型
+ "test content".getBytes(StandardCharsets.UTF_8) // 内容
+ );
+
+ mockMvc.perform(multipart("/file").file(mockFile))
+ .andExpect(status().isOk())
+ .andExpect(content().string(containsString("http://localhost:65/")));
+ }
+
+ // 测试上传 BT 文件接口
+ @Test
+ public void testUploadBTFile_success() throws Exception {
+ TorrentRecord record = new TorrentRecord();
+ record.setTorrentRecordId(1);
+ record.setTorrentUrl("test.torrent");
+ record.setInfoHash("abc123");
+ record.setUploaderUserId(1);
+ record.setUploadTime(new Date());
+
+ // 模拟 post 请求,传 json 数据
+ mockMvc.perform(post("/file/bt")
+ .contentType("application/json")
+ .content("""
+ {
+ "torrentRecordId": 1,
+ "torrentUrl": "test.torrent",
+ "infoHash": "abc123",
+ "uploaderUserId": 1,
+ "uploadTime": "2025-06-09T10:00:00"
+ }
+ """))
+ .andExpect(status().isOk());
+
+ // 验证 service 是否调用
+ verify(torrentRecordService).save(org.mockito.ArgumentMatchers.any(TorrentRecord.class));
+ }
+}
diff --git a/src/test/java/com/g9/g9backend/controller/NotificationControllerTest.java b/src/test/java/com/g9/g9backend/controller/NotificationControllerTest.java
new file mode 100644
index 0000000..121258f
--- /dev/null
+++ b/src/test/java/com/g9/g9backend/controller/NotificationControllerTest.java
@@ -0,0 +1,99 @@
+package com.g9.g9backend.controller;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.g9.g9backend.pojo.Notification;
+import com.g9.g9backend.service.NotificationService;
+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.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+import java.util.Date;
+import java.util.List;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+public class NotificationControllerTest {
+
+ private MockMvc mockMvc;
+
+ @InjectMocks
+ private NotificationController notificationController;
+
+ @Mock
+ private NotificationService notificationService;
+
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+ @BeforeEach
+ public void setup() {
+ MockitoAnnotations.openMocks(this);
+ mockMvc = MockMvcBuilders.standaloneSetup(notificationController).build();
+ }
+
+ // ✅ 测试标记已读
+ @Test
+ public void testGetNotificationRead_success() throws Exception {
+ Notification notification = new Notification();
+ notification.setNotificationId(1);
+
+ Notification updatedNotification = new Notification();
+ updatedNotification.setNotificationId(1);
+ updatedNotification.setRead(false);
+
+ when(notificationService.getById(1)).thenReturn(updatedNotification);
+ when(notificationService.updateById(any())).thenReturn(true);
+
+ mockMvc.perform(post("/notification/read")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(notification)))
+ .andExpect(status().isOk());
+ }
+
+ // ✅ 测试删除通知
+ @Test
+ public void testDeleteNotification_success() throws Exception {
+ when(notificationService.removeById(1)).thenReturn(true);
+
+ mockMvc.perform(delete("/notification")
+ .param("notificationId", "1"))
+ .andExpect(status().isNoContent());
+ }
+
+ // ✅ 测试分页获取通知
+ @Test
+ public void testGetNotification_success() throws Exception {
+ Notification notification = new Notification();
+ notification.setNotificationId(1);
+ notification.setUserId(1);
+ notification.setTitle("title");
+ notification.setContent("content");
+ notification.setCreateAt(new Date());
+ notification.setRead(false);
+ notification.setTriggeredBy(2);
+ notification.setRelatedId(3);
+
+ Page<Notification> page = new Page<>(1, 10);
+ page.setRecords(List.of(notification));
+ page.setTotal(1);
+
+ when(notificationService.page(any(), any())).thenReturn(page);
+
+ mockMvc.perform(get("/notification")
+ .param("userId", "1")
+ .param("pageNumber", "1")
+ .param("rows", "10"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.records[0].notificationId").value(1))
+ .andExpect(jsonPath("$.records[0].title").value("title"))
+ .andExpect(jsonPath("$.total").value(1));
+ }
+}
diff --git a/src/test/java/com/g9/g9backend/controller/RewardControllerTest.java b/src/test/java/com/g9/g9backend/controller/RewardControllerTest.java
new file mode 100644
index 0000000..7f560f7
--- /dev/null
+++ b/src/test/java/com/g9/g9backend/controller/RewardControllerTest.java
@@ -0,0 +1,135 @@
+package com.g9.g9backend.controller;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.g9.g9backend.pojo.Reward;
+import com.g9.g9backend.service.RewardService;
+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.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+import java.util.Collections;
+import java.util.Date;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+public class RewardControllerTest {
+
+ private MockMvc mockMvc;
+
+ @InjectMocks
+ private RewardController rewardController;
+
+ @Mock
+ private RewardService rewardService;
+
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+ @BeforeEach
+ public void setUp() {
+ MockitoAnnotations.openMocks(this);
+ mockMvc = MockMvcBuilders.standaloneSetup(rewardController).build();
+ }
+
+ @Test
+ public void testPostReward() throws Exception {
+ Reward reward = new Reward();
+ reward.setRewardName("测试悬赏");
+
+ mockMvc.perform(post("/reward")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(reward)))
+ .andExpect(status().isOk());
+ }
+
+ @Test
+ public void testDeleteReward() throws Exception {
+ mockMvc.perform(delete("/reward")
+ .param("rewardId", "1"))
+ .andExpect(status().isNoContent());
+ }
+
+ @Test
+ public void testGetReward_sortByPrice() throws Exception {
+ Reward reward = new Reward();
+ reward.setRewardId(1);
+ reward.setRewardName("测试");
+ reward.setPrice(100);
+ reward.setCreateAt(new Date());
+
+ Page<Reward> page = new Page<>(1, 10);
+ page.setRecords(Collections.singletonList(reward));
+ page.setTotal(1);
+
+ when(rewardService.page(any(), any())).thenReturn(page);
+
+ mockMvc.perform(get("/reward")
+ .param("pageNumber", "1")
+ .param("rows", "10")
+ .param("searchValue", "")
+ .param("option", "赏金最高"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.records[0].rewardId").value(1));
+ }
+
+ @Test
+ public void testGetRewardInfo() throws Exception {
+ Reward reward = new Reward();
+ reward.setRewardId(1);
+ reward.setRewardName("详情测试");
+
+ when(rewardService.getById(1)).thenReturn(reward);
+
+ mockMvc.perform(get("/reward/info")
+ .param("rewardId", "1"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.rewardId").value(1));
+ }
+
+ @Test
+ public void testPutReward() throws Exception {
+ Reward oldReward = new Reward();
+ oldReward.setRewardId(1);
+
+ when(rewardService.getById(1)).thenReturn(oldReward);
+
+ Reward updateRequest = new Reward();
+ updateRequest.setRewardId(1);
+ updateRequest.setRewardName("更新名称");
+ updateRequest.setRewardDescription("更新描述");
+ updateRequest.setPrice(500);
+ updateRequest.setRewardPicture("pic.png");
+
+ mockMvc.perform(put("/reward/info")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(updateRequest)))
+ .andExpect(status().isOk());
+ }
+
+ @Test
+ public void testPutRewardCompletion() throws Exception {
+ Reward existing = new Reward();
+ existing.setRewardId(1);
+
+ when(rewardService.getById(1)).thenReturn(existing);
+
+ Reward completed = new Reward();
+ completed.setRewardId(1);
+ completed.setCompletedBy(2);
+ completed.setCompletedAt(new Date());
+ completed.setResourceId(3);
+
+ mockMvc.perform(post("/reward/completion")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(completed)))
+ .andExpect(status().isOk());
+ }
+}
diff --git a/src/test/java/com/g9/g9backend/controller/TotalControllerTest.java b/src/test/java/com/g9/g9backend/controller/TotalControllerTest.java
new file mode 100644
index 0000000..35c2484
--- /dev/null
+++ b/src/test/java/com/g9/g9backend/controller/TotalControllerTest.java
@@ -0,0 +1,53 @@
+package com.g9.g9backend.controller;
+
+import com.g9.g9backend.service.ResourceService;
+import com.g9.g9backend.service.ThreadService;
+import com.g9.g9backend.service.UserPurchaseService;
+import com.g9.g9backend.service.UserUploadService;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.http.ResponseEntity;
+
+@ExtendWith(MockitoExtension.class)
+public class TotalControllerTest {
+
+ @Mock
+ private ThreadService threadService;
+
+ @Mock
+ private UserPurchaseService userPurchaseService;
+
+ @Mock
+ private UserUploadService userUploadService;
+
+ @Mock
+ private ResourceService resourceService;
+
+ @InjectMocks
+ private TotalController totalController;
+
+ @Test
+ void testGetTotalInfo() {
+ when(threadService.count()).thenReturn(10L);
+ when(userPurchaseService.count()).thenReturn(20L);
+ when(userUploadService.count()).thenReturn(5L);
+ when(resourceService.count()).thenReturn(50L);
+
+ ResponseEntity<TotalController.Info> response = totalController.getTotalInfo();
+
+ assertEquals(200, response.getStatusCode().value());
+ TotalController.Info info = response.getBody();
+ assertNotNull(info);
+ assertEquals(10L, info.getThreadCount());
+ assertEquals(20L, info.getDownloadCount());
+ assertEquals(5L, info.getAuthorCount());
+ assertEquals(50L, info.getResourceCount());
+ }
+}