Merge "user module API" 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..6b47fed
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,28 @@
+# 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 8082
+EXPOSE 8080
+
+CMD java -jar app.jar & nginx -g "daemon off;"
diff --git a/nginx.conf b/nginx.conf
new file mode 100644
index 0000000..dd6cedc
--- /dev/null
+++ b/nginx.conf
@@ -0,0 +1,20 @@
+worker_processes 1;
+events { worker_connections 1024; }
+
+http {
+ include mime.types;
+ default_type application/octet-stream;
+
+ sendfile on;
+ keepalive_timeout 65;
+
+ server {
+ listen 8082;
+
+ # 1. 代理静态资源:如 http://localhost/static/index.html
+ location /upload/ {
+ alias /app/upload/;
+ autoindex on;
+ }
+ }
+}
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/NotificationController.java b/src/main/java/com/g9/g9backend/controller/NotificationController.java
index c774041..55a30f3 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> pageNotification = new Page<>(pageNumber, rows);
+ LambdaQueryWrapper<Notification> query = new LambdaQueryWrapper<Notification>()
+ .eq(Notification::getUserId, userId)
+ .orderByDesc(Notification::getCreateAt);
+
+ IPage<Notification> result = notificationService.page(pageNotification, query);
+ logger.info("通知已返回");
+
+ return ResponseEntity.ok(result);
+ }
+
}
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/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/Thread.java b/src/main/java/com/g9/g9backend/pojo/Thread.java
index ddcea67..a69fb82 100644
--- a/src/main/java/com/g9/g9backend/pojo/Thread.java
+++ b/src/main/java/com/g9/g9backend/pojo/Thread.java
@@ -29,8 +29,6 @@
private int likes;
- private boolean isLike;
-
private Date createAt;
private int commentNumber;
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/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/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));
+ }
+}