Merge "fix testRegister_success" into main
diff --git a/pom.xml b/pom.xml
index 884f193..fa73ca7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -33,7 +33,7 @@
         <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-webflux</artifactId>
-            <version>6.1.10</version>
+            <version>6.1.14</version>
         </dependency>
         <dependency>
             <groupId>com.auth0</groupId>
@@ -46,11 +46,6 @@
             <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>
@@ -82,13 +77,18 @@
         <dependency>
             <groupId>commons-io</groupId>
             <artifactId>commons-io</artifactId>
-            <version>2.11.0</version> <!-- 确保使用最新版本 -->
+            <version>2.14.0</version>
         </dependency>
         <dependency>
             <groupId>com.github.jeffreyning</groupId>
             <artifactId>mybatisplus-plus</artifactId>
             <version>1.7.5-RELEASE</version>
         </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/src/main/java/com/g9/g9backend/controller/ResourceController.java b/src/main/java/com/g9/g9backend/controller/ResourceController.java
index 5398d0a..b01898d 100644
--- a/src/main/java/com/g9/g9backend/controller/ResourceController.java
+++ b/src/main/java/com/g9/g9backend/controller/ResourceController.java
@@ -2,8 +2,8 @@
 
 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.web.bind.annotation.*;
+
 
 /**
  * ResourceController 资源控制器类,处理与资源相关的请求
diff --git a/src/main/java/com/g9/g9backend/controller/UserController.java b/src/main/java/com/g9/g9backend/controller/UserController.java
index ca6de5c..1be4222 100644
--- a/src/main/java/com/g9/g9backend/controller/UserController.java
+++ b/src/main/java/com/g9/g9backend/controller/UserController.java
@@ -1,9 +1,14 @@
 package com.g9.g9backend.controller;
 
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.g9.g9backend.pojo.*;
+import com.g9.g9backend.pojo.DTO.*;
+import com.g9.g9backend.service.*;
 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.*;
 
 /**
  * UserController 用户控制器类,处理与用户相关的请求
@@ -14,7 +19,128 @@
 @RequestMapping("/user")
 public class UserController {
 
+    private final UserService userService;
+
+    private final InvitationService invitationService;
+
+    private final SubscriptionService subscriptionService;
+
+    public UserController(UserService userService, InvitationService invitationService, SubscriptionService subscriptionService) {
+        this.userService = userService;
+        this.invitationService = invitationService;
+        this.subscriptionService = subscriptionService;
+    }
+
     private final Logger logger = LoggerFactory.getLogger(UserController.class);
-}
 
+    /**
+     * 用户注册
+     *
+     * @param registerDTO 用户注册
+     * @return 注册结果
+     */
+    @PostMapping("/register")
+    public ResponseEntity<String> register(@RequestBody RegisterDTO registerDTO) {
+        String username = registerDTO.getUsername();
+        String password = registerDTO.getPassword();
+        String invitationCode = registerDTO.getInvitationCode();
+        logger.info("Register request received for account: {}", username);
 
+        // 根据用户名查询该用户名是否已存在
+        QueryWrapper<User> userQuery = new QueryWrapper<>();
+        userQuery.eq("username", username);
+        User userCheck = userService.getOne(userQuery);
+
+        if (userCheck != null) {
+            // 用户名重复
+            logger.warn("Registration attempt failed. Account already exists: {}", username);
+            return ResponseEntity.status(407).body("");
+        }
+
+        // 查询邀请码是否存在
+        QueryWrapper<Invitation> invitationQuery = new QueryWrapper<>();
+        invitationQuery.eq("invitation_code", invitationCode);
+        Invitation invitation = invitationService.getOne(invitationQuery);
+
+        if (invitation == null) {
+            // 邀请码不存在
+            logger.info("The invitation code does not exist: {}", invitationCode);
+            return ResponseEntity.status(409).body("");
+        } else if (invitation.getInviteeId() != 0) {
+            // 邀请码已被使用
+            logger.info("The invitation code has been used: {}", invitationCode);
+            return ResponseEntity.status(410).body("");
+        }
+        // 注册
+        // 添加新用户
+        User user = new User();
+        user.setUsername(username);
+        user.setPassword(password);
+        userService.save(user);
+
+        // 设置该邀请码已被使用
+        User userGetId = userService.getOne(userQuery);
+        int newUserId = userGetId.getUserId();
+
+        UpdateWrapper<Invitation> updateWrapper = new UpdateWrapper<>();
+        updateWrapper.eq("invitation_code", invitationCode).set("invitee_id", newUserId);
+        invitationService.update(updateWrapper);
+
+        // 生成五个邀请码并分配给新用户
+        String[] invitationCodes = invitationService.generateInvitationCode();
+
+        for (String code : invitationCodes) {
+            Invitation newInvitation = new Invitation();
+            newInvitation.setInvitationCode(code);
+            newInvitation.setUserId(newUserId);
+            invitationService.save(newInvitation);
+        }
+
+        logger.info("User registered successfully: {}", username);
+        return ResponseEntity.ok("");
+    }
+
+    /**
+     * 用户登录
+     *
+     * @param user 登录信息
+     * @return 登录结果
+     */
+    @PostMapping("/login")
+    public ResponseEntity<String> login(@RequestBody User user) {
+        String username = user.getUsername();
+        String password = user.getPassword();
+        logger.info("Login attempt for account: {}", username);
+
+        // 根据用户名查询该用户名是否已存在
+        QueryWrapper<User> userQuery = new QueryWrapper<>();
+        userQuery.eq("username", username);
+        User userCheck = userService.getOne(userQuery);
+
+        if (userCheck == null) {
+            // 用户名不存在
+            logger.warn("Login failed. User not found: {}", username);
+            return ResponseEntity.status(406).body("");
+        } else {
+            if (userCheck.getPassword().equals(password)) {
+                return ResponseEntity.ok("");
+            } else {
+                // 密码错误
+                logger.warn("Login failed. Incorrect password for account: {}", username);
+                return ResponseEntity.status(408).body("");
+            }
+        }
+    }
+
+    /**
+     * 关注
+     *
+     * @param subscription 关注信息
+     * @return 关注结果
+     */
+    @PostMapping("/subscription")
+    public ResponseEntity<String> subscription(@RequestBody Subscription subscription) {
+        subscriptionService.save(subscription);
+        return ResponseEntity.ok("");
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/g9/g9backend/pojo/Comment.java b/src/main/java/com/g9/g9backend/pojo/Comment.java
index 08ebed6..eddc42b 100644
--- a/src/main/java/com/g9/g9backend/pojo/Comment.java
+++ b/src/main/java/com/g9/g9backend/pojo/Comment.java
@@ -1,5 +1,6 @@
 package com.g9.g9backend.pojo;
 
+import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
 import lombok.*;
 
@@ -15,7 +16,7 @@
 @NoArgsConstructor
 public class Comment {
 
-    @TableId
+    @TableId(type = IdType.AUTO)
     private int commentId;
 
     private int userId;
diff --git a/src/main/java/com/g9/g9backend/pojo/Community.java b/src/main/java/com/g9/g9backend/pojo/Community.java
index 35f63de..6e1f370 100644
--- a/src/main/java/com/g9/g9backend/pojo/Community.java
+++ b/src/main/java/com/g9/g9backend/pojo/Community.java
@@ -1,5 +1,6 @@
 package com.g9.g9backend.pojo;
 
+import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
 import lombok.*;
 
@@ -13,7 +14,7 @@
 @NoArgsConstructor
 public class Community {
 
-    @TableId
+    @TableId(type = IdType.AUTO)
     private int communityId;
 
     private String communityName;
diff --git a/src/main/java/com/g9/g9backend/pojo/DTO/GetResourceInfoDTO.java b/src/main/java/com/g9/g9backend/pojo/DTO/GetResourceInfoDTO.java
new file mode 100644
index 0000000..770ceb8
--- /dev/null
+++ b/src/main/java/com/g9/g9backend/pojo/DTO/GetResourceInfoDTO.java
@@ -0,0 +1,14 @@
+package com.g9.g9backend.pojo.DTO;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class GetResourceInfoDTO {
+
+    // todo:待完善
+    private int id;
+}
diff --git a/src/main/java/com/g9/g9backend/pojo/DTO/GetResourceSearchDTO.java b/src/main/java/com/g9/g9backend/pojo/DTO/GetResourceSearchDTO.java
new file mode 100644
index 0000000..2348a8f
--- /dev/null
+++ b/src/main/java/com/g9/g9backend/pojo/DTO/GetResourceSearchDTO.java
@@ -0,0 +1,26 @@
+package com.g9.g9backend.pojo.DTO;
+
+import com.g9.g9backend.pojo.Resource;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class GetResourceSearchDTO {
+
+    // Todo: 待完善
+    private List<Resource> records;
+
+    private long total;
+
+    private long pages;
+
+    private long current;
+
+    private long size;
+
+}
diff --git a/src/main/java/com/g9/g9backend/pojo/DTO/GetUserDTO.java b/src/main/java/com/g9/g9backend/pojo/DTO/GetUserDTO.java
new file mode 100644
index 0000000..3f2ab83
--- /dev/null
+++ b/src/main/java/com/g9/g9backend/pojo/DTO/GetUserDTO.java
@@ -0,0 +1,29 @@
+package com.g9.g9backend.pojo.DTO;
+
+import com.g9.g9backend.pojo.User;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+public class GetUserDTO {
+
+    private List<Subscriber> userList = new ArrayList<>();
+
+    public GetUserDTO(List<User> users) {
+        for (User user : users) {
+            Subscriber subscriber = new Subscriber(user.getUserId(), user.getUsername());
+            userList.add(subscriber);
+        }
+    }
+
+    @Data
+    @AllArgsConstructor
+    public static class Subscriber {
+        private int userId;
+
+        private String username;
+    }
+}
diff --git a/src/main/java/com/g9/g9backend/pojo/DTO/GetUserDataDTO.java b/src/main/java/com/g9/g9backend/pojo/DTO/GetUserDataDTO.java
new file mode 100644
index 0000000..609cccf
--- /dev/null
+++ b/src/main/java/com/g9/g9backend/pojo/DTO/GetUserDataDTO.java
@@ -0,0 +1,18 @@
+package com.g9.g9backend.pojo.DTO;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+
+@Data
+@AllArgsConstructor
+public class GetUserDataDTO {
+
+    private int subscriberCount;
+
+    private int uploadAmount;
+
+    private int beDownloadedAmount;
+
+    private float[] seedPercentageList;
+}
diff --git a/src/main/java/com/g9/g9backend/pojo/DTO/GetUserResourceListDTO.java b/src/main/java/com/g9/g9backend/pojo/DTO/GetUserResourceListDTO.java
new file mode 100644
index 0000000..fc398d6
--- /dev/null
+++ b/src/main/java/com/g9/g9backend/pojo/DTO/GetUserResourceListDTO.java
@@ -0,0 +1,28 @@
+package com.g9.g9backend.pojo.DTO;
+
+import com.g9.g9backend.pojo.Resource;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * GetResourceListDTO DTO类,用户返回用户收藏列表信息
+ *
+ * @author hcy
+ */
+@Data
+@AllArgsConstructor
+public class GetUserResourceListDTO {
+
+    private List<Resource> records;
+
+    private long total;
+
+    private long pages;
+
+    private long current;
+
+    private long size;
+
+}
diff --git a/src/main/java/com/g9/g9backend/pojo/DTO/GetUserSearchHistoryDTO.java b/src/main/java/com/g9/g9backend/pojo/DTO/GetUserSearchHistoryDTO.java
new file mode 100644
index 0000000..b74fc6e
--- /dev/null
+++ b/src/main/java/com/g9/g9backend/pojo/DTO/GetUserSearchHistoryDTO.java
@@ -0,0 +1,14 @@
+package com.g9.g9backend.pojo.DTO;
+
+import com.g9.g9backend.pojo.SearchHistory;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+public class GetUserSearchHistoryDTO {
+
+    private List<SearchHistory> historyList;
+}
diff --git a/src/main/java/com/g9/g9backend/pojo/DTO/ModifyPasswordDTO.java b/src/main/java/com/g9/g9backend/pojo/DTO/ModifyPasswordDTO.java
new file mode 100644
index 0000000..6af4d14
--- /dev/null
+++ b/src/main/java/com/g9/g9backend/pojo/DTO/ModifyPasswordDTO.java
@@ -0,0 +1,17 @@
+package com.g9.g9backend.pojo.DTO;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class ModifyPasswordDTO {
+
+    private int userId;
+
+    private String password;
+
+    private String newPassword;
+}
diff --git a/src/main/java/com/g9/g9backend/pojo/DTO/PostResourceDTO.java b/src/main/java/com/g9/g9backend/pojo/DTO/PostResourceDTO.java
new file mode 100644
index 0000000..632ff3c
--- /dev/null
+++ b/src/main/java/com/g9/g9backend/pojo/DTO/PostResourceDTO.java
@@ -0,0 +1,21 @@
+package com.g9.g9backend.pojo.DTO;
+
+import com.g9.g9backend.pojo.Resource;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class PostResourceDTO {
+
+    private Resource resource;
+
+    private String[] gameplayList;
+
+    private int completeRewardId;
+
+    private int userId;
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/g9/g9backend/pojo/DTO/UserResourceDTO.java b/src/main/java/com/g9/g9backend/pojo/DTO/UserResourceDTO.java
new file mode 100644
index 0000000..fe61dec
--- /dev/null
+++ b/src/main/java/com/g9/g9backend/pojo/DTO/UserResourceDTO.java
@@ -0,0 +1,15 @@
+package com.g9.g9backend.pojo.DTO;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class UserResourceDTO {
+
+    private int userId;
+
+    private int resourceId;
+}
diff --git a/src/main/java/com/g9/g9backend/pojo/Notification.java b/src/main/java/com/g9/g9backend/pojo/Notification.java
index 18f8e37..7ebf7c1 100644
--- a/src/main/java/com/g9/g9backend/pojo/Notification.java
+++ b/src/main/java/com/g9/g9backend/pojo/Notification.java
@@ -1,5 +1,6 @@
 package com.g9.g9backend.pojo;
 
+import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
 import lombok.*;
 
@@ -15,7 +16,7 @@
 @NoArgsConstructor
 public class Notification {
 
-    @TableId
+    @TableId(type = IdType.AUTO)
     private int notificationId;
 
     private int userId;
diff --git a/src/main/java/com/g9/g9backend/pojo/Resource.java b/src/main/java/com/g9/g9backend/pojo/Resource.java
index f4346b2..a3b17f3 100644
--- a/src/main/java/com/g9/g9backend/pojo/Resource.java
+++ b/src/main/java/com/g9/g9backend/pojo/Resource.java
@@ -1,5 +1,6 @@
 package com.g9.g9backend.pojo;
 
+import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
 import lombok.*;
 
@@ -15,7 +16,7 @@
 @NoArgsConstructor
 public class Resource {
 
-    @TableId
+    @TableId(type = IdType.AUTO)
     private int resourceId;
 
     private String resourceName;
diff --git a/src/main/java/com/g9/g9backend/pojo/ResourceVersion.java b/src/main/java/com/g9/g9backend/pojo/ResourceVersion.java
index 1e4acf9..4395a5a 100644
--- a/src/main/java/com/g9/g9backend/pojo/ResourceVersion.java
+++ b/src/main/java/com/g9/g9backend/pojo/ResourceVersion.java
@@ -1,5 +1,6 @@
 package com.g9.g9backend.pojo;
 
+import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
 import lombok.*;
 
@@ -13,7 +14,7 @@
 @NoArgsConstructor
 public class ResourceVersion {
 
-    @TableId
+    @TableId(type = IdType.AUTO)
     private int resourceVersionId;
 
     private String resourceVersionName;
diff --git a/src/main/java/com/g9/g9backend/pojo/Reward.java b/src/main/java/com/g9/g9backend/pojo/Reward.java
index 91e376d..5812284 100644
--- a/src/main/java/com/g9/g9backend/pojo/Reward.java
+++ b/src/main/java/com/g9/g9backend/pojo/Reward.java
@@ -1,5 +1,6 @@
 package com.g9.g9backend.pojo;
 
+import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
 import lombok.*;
 
@@ -15,7 +16,7 @@
 @NoArgsConstructor
 public class Reward {
 
-    @TableId
+    @TableId(type = IdType.AUTO)
     private int rewardId;
 
     private String rewardName;
diff --git a/src/main/java/com/g9/g9backend/pojo/SearchHistory.java b/src/main/java/com/g9/g9backend/pojo/SearchHistory.java
index ba8894c..a400cef 100644
--- a/src/main/java/com/g9/g9backend/pojo/SearchHistory.java
+++ b/src/main/java/com/g9/g9backend/pojo/SearchHistory.java
@@ -1,5 +1,6 @@
 package com.g9.g9backend.pojo;
 
+import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
 import lombok.*;
 
@@ -13,7 +14,7 @@
 @NoArgsConstructor
 public class SearchHistory {
 
-    @TableId
+    @TableId(type = IdType.AUTO)
     private int searchHistoryId;
 
     private String searchContent;
diff --git a/src/main/java/com/g9/g9backend/pojo/Thread.java b/src/main/java/com/g9/g9backend/pojo/Thread.java
index f14929c..ddcea67 100644
--- a/src/main/java/com/g9/g9backend/pojo/Thread.java
+++ b/src/main/java/com/g9/g9backend/pojo/Thread.java
@@ -1,5 +1,6 @@
 package com.g9.g9backend.pojo;
 
+import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
 import lombok.*;
 
@@ -15,7 +16,7 @@
 @NoArgsConstructor
 public class Thread {
 
-    @TableId
+    @TableId(type = IdType.AUTO)
     private int threadId;
 
     private int userId;
diff --git a/src/main/java/com/g9/g9backend/pojo/TorrentRecord.java b/src/main/java/com/g9/g9backend/pojo/TorrentRecord.java
index 89ed56b..6e26727 100644
--- a/src/main/java/com/g9/g9backend/pojo/TorrentRecord.java
+++ b/src/main/java/com/g9/g9backend/pojo/TorrentRecord.java
@@ -1,5 +1,6 @@
 package com.g9.g9backend.pojo;
 
+import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
 import lombok.*;
 
@@ -15,7 +16,7 @@
 @NoArgsConstructor
 public class TorrentRecord {
 
-    @TableId
+    @TableId(type = IdType.AUTO)
     private int torrentRecordId;
 
     private String torrentUrl;
diff --git a/src/main/java/com/g9/g9backend/pojo/User.java b/src/main/java/com/g9/g9backend/pojo/User.java
index 2915460..370631b 100644
--- a/src/main/java/com/g9/g9backend/pojo/User.java
+++ b/src/main/java/com/g9/g9backend/pojo/User.java
@@ -1,5 +1,6 @@
 package com.g9.g9backend.pojo;
 
+import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
 import lombok.*;
 
@@ -13,7 +14,7 @@
 @NoArgsConstructor
 public class User {
 
-    @TableId
+    @TableId(type = IdType.AUTO)
     private int userId;
 
     private String username;
diff --git a/src/main/java/com/g9/g9backend/pojo/UserCollection.java b/src/main/java/com/g9/g9backend/pojo/UserCollection.java
index 7ffb746..df5e542 100644
--- a/src/main/java/com/g9/g9backend/pojo/UserCollection.java
+++ b/src/main/java/com/g9/g9backend/pojo/UserCollection.java
@@ -1,6 +1,5 @@
 package com.g9.g9backend.pojo;
 
-import com.baomidou.mybatisplus.annotation.TableId;
 import com.github.jeffreyning.mybatisplus.anno.MppMultiId;
 import lombok.*;
 
diff --git a/src/main/java/com/g9/g9backend/pojo/UserLike.java b/src/main/java/com/g9/g9backend/pojo/UserLike.java
index 585661b..7aa0783 100644
--- a/src/main/java/com/g9/g9backend/pojo/UserLike.java
+++ b/src/main/java/com/g9/g9backend/pojo/UserLike.java
@@ -1,6 +1,5 @@
 package com.g9.g9backend.pojo;
 
-import com.baomidou.mybatisplus.annotation.TableId;
 import com.github.jeffreyning.mybatisplus.anno.MppMultiId;
 import lombok.*;
 
diff --git a/src/main/java/com/g9/g9backend/pojo/UserPurchase.java b/src/main/java/com/g9/g9backend/pojo/UserPurchase.java
index 80ac247..e170978 100644
--- a/src/main/java/com/g9/g9backend/pojo/UserPurchase.java
+++ b/src/main/java/com/g9/g9backend/pojo/UserPurchase.java
@@ -1,6 +1,5 @@
 package com.g9.g9backend.pojo;
 
-import com.baomidou.mybatisplus.annotation.TableId;
 import com.github.jeffreyning.mybatisplus.anno.MppMultiId;
 import lombok.*;
 
diff --git a/src/main/java/com/g9/g9backend/pojo/UserUpload.java b/src/main/java/com/g9/g9backend/pojo/UserUpload.java
index 8f9fc42..cb7b039 100644
--- a/src/main/java/com/g9/g9backend/pojo/UserUpload.java
+++ b/src/main/java/com/g9/g9backend/pojo/UserUpload.java
@@ -1,6 +1,5 @@
 package com.g9.g9backend.pojo;
 
-import com.baomidou.mybatisplus.annotation.TableId;
 import com.github.jeffreyning.mybatisplus.anno.MppMultiId;
 import lombok.*;
 
diff --git a/src/main/java/com/g9/g9backend/pojo/Version.java b/src/main/java/com/g9/g9backend/pojo/Version.java
index 1f4fe9f..180d332 100644
--- a/src/main/java/com/g9/g9backend/pojo/Version.java
+++ b/src/main/java/com/g9/g9backend/pojo/Version.java
@@ -1,6 +1,5 @@
 package com.g9.g9backend.pojo;
 
-import com.baomidou.mybatisplus.annotation.TableId;
 import com.github.jeffreyning.mybatisplus.anno.MppMultiId;
 import lombok.*;
 
diff --git a/src/main/java/com/g9/g9backend/service/InvitationService.java b/src/main/java/com/g9/g9backend/service/InvitationService.java
index 3708d53..bc17e9a 100644
--- a/src/main/java/com/g9/g9backend/service/InvitationService.java
+++ b/src/main/java/com/g9/g9backend/service/InvitationService.java
@@ -4,4 +4,7 @@
 import com.g9.g9backend.pojo.Invitation;
 
 public interface InvitationService extends IService<Invitation> {
+
+    // 定义生成邀请码的方法
+    String[] generateInvitationCode();
 }
diff --git a/src/main/java/com/g9/g9backend/service/impl/InvitationServiceImpl.java b/src/main/java/com/g9/g9backend/service/impl/InvitationServiceImpl.java
index 9530312..0a04c00 100644
--- a/src/main/java/com/g9/g9backend/service/impl/InvitationServiceImpl.java
+++ b/src/main/java/com/g9/g9backend/service/impl/InvitationServiceImpl.java
@@ -1,11 +1,54 @@
 package com.g9.g9backend.service.impl;
 
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.g9.g9backend.mapper.InvitationMapper;
 import com.g9.g9backend.pojo.Invitation;
 import com.g9.g9backend.service.InvitationService;
 import org.springframework.stereotype.Service;
 
+import java.util.HashSet;
+import java.util.Random;
+import java.util.Set;
+
 @Service
 public class InvitationServiceImpl extends ServiceImpl<InvitationMapper, Invitation> implements InvitationService {
+
+    private static final String CHAR_POOL = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; // 字符池
+    private static final int CODE_LENGTH = 8; // 邀请码长度
+    private static final int CODE_COUNT = 5; // 邀请码数量
+    private final Random random = new Random();
+
+    @Override
+    public String[] generateInvitationCode() {
+        // 使用不允许存储重复的元素的数据结构:Set
+        Set<String> uniqueCodes = new HashSet<>();
+
+        // 生成 5 个互不重复的邀请码
+        while (uniqueCodes.size() < CODE_COUNT) {
+            String code = generateRandomCode();
+
+            // 检查数据库中是否已存在该邀请码
+            QueryWrapper<Invitation> invitationQuery = new QueryWrapper<>();
+            invitationQuery.eq("invitation_code", code);
+            boolean exists = this.count(invitationQuery) > 0;
+
+            // 如果数据库中不存在,加入集合
+            if (!exists) {
+                uniqueCodes.add(code);
+            }
+        }
+
+        // 返回字符串数组
+        return uniqueCodes.toArray(new String[0]);
+    }
+
+    // 生成随机邀请码的方法
+    private String generateRandomCode() {
+        StringBuilder code = new StringBuilder(CODE_LENGTH);
+        for (int i = 0; i < CODE_LENGTH; i++) {
+            code.append(CHAR_POOL.charAt(random.nextInt(CHAR_POOL.length())));
+        }
+        return code.toString();
+    }
 }
diff --git a/src/test/java/com/g9/g9backend/controller/ResourceControllerTest.java b/src/test/java/com/g9/g9backend/controller/ResourceControllerTest.java
new file mode 100644
index 0000000..c02be49
--- /dev/null
+++ b/src/test/java/com/g9/g9backend/controller/ResourceControllerTest.java
@@ -0,0 +1,5 @@
+package com.g9.g9backend.controller;
+
+public class ResourceControllerTest {
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/g9/g9backend/controller/UserControllerTest.java b/src/test/java/com/g9/g9backend/controller/UserControllerTest.java
new file mode 100644
index 0000000..208645c
--- /dev/null
+++ b/src/test/java/com/g9/g9backend/controller/UserControllerTest.java
@@ -0,0 +1,172 @@
+package com.g9.g9backend.controller;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.g9.g9backend.pojo.*;
+import com.g9.g9backend.pojo.DTO.*;
+import com.g9.g9backend.service.*;
+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 static org.mockito.ArgumentMatchers.*;
+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 UserControllerTest {
+
+    private MockMvc mockMvc;
+
+    @InjectMocks
+    private UserController userController;
+
+    @Mock
+    private UserService userService;
+
+    @Mock
+    private InvitationService invitationService;
+
+    @Mock
+    private SubscriptionService subscriptionService;
+
+    private final ObjectMapper objectMapper = new ObjectMapper();
+
+    @BeforeEach
+    public void setup() {
+        MockitoAnnotations.openMocks(this);
+        mockMvc = MockMvcBuilders.standaloneSetup(userController).build();
+    }
+
+    // 注册
+    @Test
+    public void testRegister_success() throws Exception {
+        /*
+        注意单元测试中(使用Mockito ),当多次为同一个方法设置不同的返回值时,后面的设置会覆盖前面的设置
+        可见单元测试在执行时,本质上在跑实际接口实现,当遇到被mock模拟的方法时,会来测试代码里找对应的when...thenReturn,直接看最后一个设置,所以当测试方法时,测试代码里写的和实际接口的顺序上不是一一对应
+        而是测试代码里写B C A,实际接口代码里写A B C,当实际代码跑到A时去测试代码里从后往前找到A,而不是看测试代码里第一个是不是A若是再用的逻辑
+        所以如果写成如下形式:
+        when(userService.getOne(any())).thenReturn(null);
+        ....其他代码
+        when(userService.getOne(any())).thenReturn(new User(2, "hcy", "123", null, 0, 0, null, 0, 0, 0));
+        则下面的设置会覆盖上面的设置,导致无论什么时候调用该方法,返回的都是非空对象
+        所以应该写成如下形式:
+        when(userService.getOne(any()))
+            .thenReturn(null)          // 第一次调用返回null
+            .thenReturn(new User(2, "hcy", "123", null, 0, 0, null, 0, 0, 0)); // 第二次调用返回非空对象
+        */
+
+        when(userService.getOne(any()))
+                .thenReturn(null)          // 第一次调用返回null,表示用户名不是已存在的
+                .thenReturn(new User(2, "hcy", "123", null, 0, 0, null, 0, 0, 0)); // 第二次调用返回非空对象,表示获取添加后的用户信息
+        // 邀请码存在且未被使用
+        when(invitationService.getOne(any())).thenReturn(new Invitation("6RCA7Y8J", 1, 0));
+        // 添加新用户
+        when(userService.save(any())).thenReturn(true);
+        // 设置该邀请码被新添加的新用户使用
+        when(invitationService.update(any())).thenReturn(true);
+        // 获取五个新邀请码
+        when(invitationService.generateInvitationCode()).thenReturn(new String[5]);
+        // 分配给新用户邀请码
+        when(invitationService.save(any())).thenReturn(true);
+        mockMvc.perform(post("/user/register")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(new RegisterDTO("hcy", "123", "6RCA7Y8J"))))
+                .andExpect(status().isOk());
+    }
+
+    @Test
+    public void testRegister_userExists() throws Exception {
+        // 用户名不是已存在的
+        when(userService.getOne(any())).thenReturn(new User(2, "hcy", "123", null, 0, 0, null, 0, 0, 0));
+        mockMvc.perform(post("/user/register")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(new RegisterDTO("hcy", "123", "6RCA7Y8J"))))
+                .andExpect(status().is(407));
+    }
+
+    @Test
+    public void testRegister_invitationNotExist() throws Exception {
+        // 用户名不是已存在的
+        when(userService.getOne(any())).thenReturn(null);
+        // 邀请码不存在
+        when(invitationService.getOne(any())).thenReturn(null);
+        mockMvc.perform(post("/user/register")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(new RegisterDTO("hcy", "123", "6RCA7Y8J"))))
+                .andExpect(status().is(409));
+    }
+
+    @Test
+    public void testRegister_invitationUsed() throws Exception {
+        // 用户名不是已存在的
+        when(userService.getOne(any())).thenReturn(null);
+        // 邀请码存在但已被使用
+        when(invitationService.getOne(any())).thenReturn(new Invitation("6RCA7Y8J", 1, 2));
+        mockMvc.perform(post("/user/register")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(new RegisterDTO("hcy", "123", "6RCA7Y8J"))))
+                .andExpect(status().is(410));
+    }
+
+    // 登录
+    @Test
+    public void testLogin_success() throws Exception {
+        // 设置请求参数
+        User user = new User();
+        user.setUsername("hcy");
+        user.setPassword("123");
+
+        when(userService.getOne(any())).thenReturn(new User(1, "hcy", "123", null, 0, 0, null, 0, 0, 0));
+
+        mockMvc.perform(post("/user/login")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(user)))
+                .andExpect(status().isOk());
+    }
+
+    @Test
+    public void testLogin_userNotFound() throws Exception {
+        //设置请求参数
+        User user = new User();
+        user.setUsername("hcy");
+        user.setPassword("123");
+
+        when(userService.getOne(any())).thenReturn(null);
+
+        mockMvc.perform(post("/user/login")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(user)))
+                .andExpect(status().is(406));
+    }
+
+    @Test
+    public void testLogin_wrongPassword() throws Exception {
+        // 设置请求参数
+        User user = new User();
+        user.setUsername("hcy");
+        user.setPassword("wrongPassword");
+
+        when(userService.getOne(any())).thenReturn(new User(1, "hcy", "123", null, 0, 0, null, 0, 0, 0));
+
+        mockMvc.perform(post("/user/login")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(user)))
+                .andExpect(status().is(408));
+    }
+
+    // 关注
+    @Test
+    public void testSubscription() throws Exception {
+        when(subscriptionService.save(any())).thenReturn(true);
+
+        mockMvc.perform(post("/user/subscription")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(new Subscription(1, 2))))
+                .andExpect(status().isOk());
+    }
+}
\ No newline at end of file