等级更新

Change-Id: I1ced9fab16019e2175ce165b4bd16c1bbef3e05b
diff --git a/src/main/java/com/ptp/ptplatform/PtPlatformApplication.java b/src/main/java/com/ptp/ptplatform/PtPlatformApplication.java
index deff713..a3bc876 100644
--- a/src/main/java/com/ptp/ptplatform/PtPlatformApplication.java
+++ b/src/main/java/com/ptp/ptplatform/PtPlatformApplication.java
@@ -3,13 +3,13 @@
 import org.mybatis.spring.annotation.MapperScan;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableScheduling;
 
 @SpringBootApplication
+@EnableScheduling
 @MapperScan("com.ptp.ptplatform.mapper")
 public class PtPlatformApplication {
-
     public static void main(String[] args) {
-
         SpringApplication.run(PtPlatformApplication.class, args);
     }
 
diff --git a/src/main/java/com/ptp/ptplatform/controller/InviteCodeController.java b/src/main/java/com/ptp/ptplatform/controller/InviteCodeController.java
index 4a4e625..3950ee8 100644
--- a/src/main/java/com/ptp/ptplatform/controller/InviteCodeController.java
+++ b/src/main/java/com/ptp/ptplatform/controller/InviteCodeController.java
@@ -2,10 +2,9 @@
 
 
 import com.ptp.ptplatform.entity.INVITE_CODE;
-import com.ptp.ptplatform.entity.USER;
+import com.ptp.ptplatform.entity.User;
 import com.ptp.ptplatform.mapper.InviteCodeMapper;
 import com.ptp.ptplatform.mapper.UserMapper;
-import com.ptp.ptplatform.utils.JwtUtils;
 import com.ptp.ptplatform.utils.Result;
 import jakarta.annotation.Resource;
 import jakarta.servlet.http.HttpServletRequest;
@@ -13,7 +12,6 @@
 import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
-import java.util.Map;
 
 @RestController
 @RequestMapping("/invitecode")
@@ -29,7 +27,7 @@
     //使用魔力值兑换邀请码
     @PostMapping("/generate")
     public Result generateInviteCode(HttpServletRequest request) {
-        USER user = userController.getUserInRequest(request);
+        User user = userController.getUserInRequest(request);
         System.out.println(user.getMagicPoints());
         if (user.getMagicPoints() >= 10) {
             user.generateInviteCode();
@@ -49,7 +47,7 @@
     //用户获取持有的邀请码
     @GetMapping("/userInviteCode")
     public Result userInviteCode(HttpServletRequest request) {
-        USER user = userController.getUserInRequest(request);
+        User user = userController.getUserInRequest(request);
 
         List<INVITE_CODE> inviteCode = inviteCodeMapper.selectByUser(user.getUsername());
         return Result.ok().data("inviteCode", inviteCode);
diff --git a/src/main/java/com/ptp/ptplatform/controller/TorrentController.java b/src/main/java/com/ptp/ptplatform/controller/TorrentController.java
index 50cc785..b44bc76 100644
--- a/src/main/java/com/ptp/ptplatform/controller/TorrentController.java
+++ b/src/main/java/com/ptp/ptplatform/controller/TorrentController.java
@@ -2,7 +2,7 @@
 
 import com.ptp.ptplatform.entity.DISCOUNT;
 import com.ptp.ptplatform.entity.TORRENT;
-import com.ptp.ptplatform.entity.USER;
+import com.ptp.ptplatform.entity.User;
 import com.ptp.ptplatform.mapper.DiscountMapper;
 import com.ptp.ptplatform.mapper.TorrentMapper;
 import com.ptp.ptplatform.mapper.UserMapper;
@@ -15,7 +15,6 @@
 import com.ptp.ptplatform.utils.Result;
 import com.turn.ttorrent.bcodec.BDecoder;
 import com.turn.ttorrent.bcodec.BEValue;
-import com.turn.ttorrent.common.TorrentStatistic;
 import com.turn.ttorrent.tracker.TrackedPeer;
 import com.turn.ttorrent.tracker.TrackedTorrent;
 import jakarta.annotation.PostConstruct;
@@ -23,7 +22,6 @@
 import jakarta.servlet.http.HttpServletRequest;
 import lombok.AllArgsConstructor;
 import org.springframework.web.bind.annotation.*;
-import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 
 import java.io.ByteArrayInputStream;
@@ -254,8 +252,8 @@
 //        TorrentStatistic ts = cs.getStatistics(torrent.getFilePath());
 
         //修改用户对应的信息
-        USER downloadUser = userController.getUserInRequest(request);
-        USER uploadUser = userMapper.selectByUsername(torrent.getUsername());
+        User downloadUser = userController.getUserInRequest(request);
+        User uploadUser = userMapper.selectByUsername(torrent.getUsername());
 
         long up = 30000L;
         long down = 30000L;
@@ -380,7 +378,7 @@
             @RequestParam(defaultValue = "1") int page,
             @RequestParam(defaultValue = "5") int size) throws Exception {
 
-        USER user = userController.getUserInRequest(request);
+        User user = userController.getUserInRequest(request);
         int offset = (page - 1) * size;
         List<TORRENT> pagedList = torrentMapper.selectTorrentByUsernameWithPage(user.getUsername(), offset, size);
         int total = torrentMapper.countByUsername(user.getUsername());
diff --git a/src/main/java/com/ptp/ptplatform/controller/UserController.java b/src/main/java/com/ptp/ptplatform/controller/UserController.java
index 70885de..7dc0a7d 100644
--- a/src/main/java/com/ptp/ptplatform/controller/UserController.java
+++ b/src/main/java/com/ptp/ptplatform/controller/UserController.java
@@ -6,14 +6,14 @@
 import com.ptp.ptplatform.entity.*;
 import com.ptp.ptplatform.mapper.UserMapper;
 import com.ptp.ptplatform.mapper.InviteCodeMapper;
+import com.ptp.ptplatform.service.UserLevelService;
 import com.ptp.ptplatform.utils.SizeCalculation;
-import jakarta.annotation.Resource;
 import jakarta.servlet.http.HttpServletRequest;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 import com.ptp.ptplatform.utils.Result;
 import com.ptp.ptplatform.utils.JwtUtils;
-import com.ptp.ptplatform.entity.USER;
+import com.ptp.ptplatform.entity.User;
 
 import java.util.Date;
 import java.util.HashMap;
@@ -29,23 +29,25 @@
     private UserMapper userMapper;
     @Autowired
     private InviteCodeMapper inviteCodeMapper;
+    @Autowired
+    private UserLevelService userLevelService;
 
     //个人中心获取用户登录信息
     @GetMapping("/info") //获取
     public Result info(HttpServletRequest request) {
-        USER user = this.getUserInRequest(request);
+        User user = this.getUserInRequest(request);
         return Result.ok().data("info", user);
     }
 
     @PostMapping("/login") //用户登录
     public Result login(String username, String password) {
-        USER user = userMapper.selectByUsername(username);
+        User user = userMapper.selectByUsername(username);
 
         // 检查用户是否存在
         if (user == null) {
             return Result.error(404).setMessage("用户不存在");
         }
-        if(user.getAuthority() == USER.Authority.BAN) {
+        if(user.getAuthority() == User.Authority.BAN) {
             return Result.error(403).setMessage("用户被封禁,禁止登录");
         }
 
@@ -65,7 +67,7 @@
 
     @PostMapping("/regist")
     public Result regist(String username, String password, String code) {
-        USER userCheck = userMapper.selectByUsername(username);
+        User userCheck = userMapper.selectByUsername(username);
         if (userCheck == null) {
             //获取邀请码
             INVITE_CODE inviteCode = inviteCodeMapper.selectByCode(code);
@@ -74,7 +76,7 @@
 
                 if (!inviteCode.getIsUsed()) {
                     Date time = new Date();
-                    USER user = new USER(username, password, time);
+                    User user = new User(username, password, time);
 
                     userMapper.insertUser(user);
                     inviteCodeMapper.updateCodeUser(code);
@@ -98,7 +100,7 @@
     //获取允许下载额度的相关信息
     @GetMapping("/allowDownload")
     public Result allowDownload(HttpServletRequest request) {
-        USER user = this.getUserInRequest(request);
+        User user = this.getUserInRequest(request);
         // 总额度 已经使用 剩余 单位是GB
         int totalSize = SizeCalculation.byteToGB(user.getUpload());
         int usedSize = SizeCalculation.byteToGB(user.getDownload());
@@ -116,7 +118,7 @@
     //修改用户密码
     @PutMapping("/password")
     public Result updatePassword(HttpServletRequest request, @RequestBody Map<String, String> passwordMap) {
-        USER user = this.getUserInRequest(request);
+        User user = this.getUserInRequest(request);
 
         String oldPassword = passwordMap.get("oldPassword");
         String newPassword = passwordMap.get("newPassword");
@@ -134,12 +136,12 @@
     @GetMapping("/searchUser")
     public Result searchUser(@RequestParam(value = "key", required = false) String key, HttpServletRequest request) {
         // 从请求中获取当前用户
-        USER user = this.getUserInRequest(request);
+        User user = this.getUserInRequest(request);
         String username = user.getUsername();
 
         // 检查是否提供了搜索关键词
         if (key != null && !key.isEmpty()) {
-            List<USER> searchResults = userMapper.searchUsername(username, key);
+            List<User> searchResults = userMapper.searchUsername(username, key);
             return Result.ok().data("data", searchResults);
         } else {
             // 如果没有提供关键词,返回默认结果或者一个错误消息
@@ -150,7 +152,7 @@
     //获取到全部用户
     @GetMapping("/allUser")
     public Result getAllUser(){
-        List<USER> users = userMapper.selectAllUsers();
+        List<User> users = userMapper.selectAllUsers();
 
         return Result.ok().data("data", users);
     }
@@ -162,8 +164,8 @@
         String authority = authorityMap.get("authority");
         String changeUsername = authorityMap.get("changeUsername");
 
-        USER changeUser = userMapper.selectByUsername(changeUsername);
-        changeUser.setAuthority(USER.Authority.valueOf(authority));
+        User changeUser = userMapper.selectByUsername(changeUsername);
+        changeUser.setAuthority(User.Authority.valueOf(authority));
 
         if(userMapper.updateUser(changeUser) > 0){
             return Result.ok().setMessage("修改用户权限成功");
@@ -172,9 +174,16 @@
     }
 
     //从http请求中获取到用户
-    public USER getUserInRequest(HttpServletRequest request) {
+    public User getUserInRequest(HttpServletRequest request) {
         String UserName = JwtUtils.getClaimByToken(request.getHeader("Authorization")).getSubject();
         return userMapper.selectByUsername(UserName);
     }
 
+    // 用户等级更新
+    @PostMapping("/refreshLevel")
+    public Result refresh() {
+        userLevelService.refreshAllUserLevels();
+        return Result.ok();
+    }
+
 }
diff --git a/src/main/java/com/ptp/ptplatform/entity/USER.java b/src/main/java/com/ptp/ptplatform/entity/User.java
similarity index 68%
rename from src/main/java/com/ptp/ptplatform/entity/USER.java
rename to src/main/java/com/ptp/ptplatform/entity/User.java
index f002fc2..ed555c7 100644
--- a/src/main/java/com/ptp/ptplatform/entity/USER.java
+++ b/src/main/java/com/ptp/ptplatform/entity/User.java
@@ -2,46 +2,72 @@
 // @GeneratedValue(strategy = GenerationType.IDENTITY) 对于一些需要自动生成的主键id进行注解
 
 package com.ptp.ptplatform.entity;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
 import jakarta.persistence.*;
 
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
 import java.util.Date;
 
+//@Entity
+//@Table(name = "`user`")
+//public class User {
+//
+//    @jakarta.persistence.Id
+//    private String username;
+//    private String password;
+//
+//    @Enumerated(EnumType.STRING)
+//    private Authority authority;
+//
+//    private int level; // 用户等级0-6
+//
+//    @Temporal(TemporalType.DATE)
+//    private Date registTime = new Date();
+//
+//    @Temporal(TemporalType.DATE)
+//    private Date lastLogin;
+//
+//    private long upload;
+//    private long download;
+//    private double shareRate;//分享率 前端展示数据应该为 90.23%这种
+//    private long magicPoints;// 魔力值
+//
+//    public enum Authority {
+//        USER, ADMIN, LIMIT, BAN
+//    }
+@TableName("`user`")
+public class User {
 
-@Table(name = "user")
-public class USER {
-
-    @jakarta.persistence.Id
-    private String username;
-    private String password;
-
-    @Enumerated(EnumType.STRING)
-    private Authority authority;
-
-    private int level; // 用户等级0-6
-
-    @Temporal(TemporalType.DATE)
-    private Date registTime = new Date();
-
-    @Temporal(TemporalType.DATE)
-    private Date lastLogin;
-
-    private long upload;
-    private long download;
-    private double shareRate;//分享率 前端展示数据应该为 90.23%这种
-    private long magicPoints;// 魔力值
-
-    public enum Authority {
-        USER, ADMIN, LIMIT, BAN
-    }
-
-
-    // 函数
     public String getUsername() {
         return username;
     }
 
+    @TableId
+    private String username;
+    private String password;
+    private int level;
+    @TableField("authority")
+    private Authority authority;
+
+    @TableField("registTime")
+    private Date registTime;
+    @TableField("lastLogin")
+    private Date lastLogin;
+
+    @TableField("upload")
+    private long upload;
+    @TableField("download")
+    private long download;
+
+    @TableField("shareRate")
+    private double shareRate;
+    @TableField("magicPoints")
+    private long magicPoints;
+
+    public enum Authority { USER, ADMIN, LIMIT, BAN }
+
+
     public void setUsername(String username) {
         this.username = username;
     }
@@ -106,7 +132,7 @@
         return shareRate;
     }
 
-    public void setShareRate(int shareRate) {
+    public void setShareRate(double shareRate) {
         this.shareRate = shareRate;
     }
 
@@ -114,20 +140,20 @@
         return this.magicPoints;
     }
 
-    public void setMagicPoints(int magicPoints) {
+    public void setMagicPoints(long magicPoints) {
         this.magicPoints = magicPoints;
     }
 
-    public USER() {
+    public User() {
     }
 
-    public USER(String username, String password, Authority authority) {
+    public User(String username, String password, Authority authority) {
         this.username = username;
         this.password = password;
         this.authority = authority;
     }
 
-    public USER(String username, String password, Date registTime) {
+    public User(String username, String password, Date registTime) {
         this.username = username;
         this.registTime = registTime;
 
diff --git a/src/main/java/com/ptp/ptplatform/mapper/UserMapper.java b/src/main/java/com/ptp/ptplatform/mapper/UserMapper.java
index eee9042..5ee4bce 100644
--- a/src/main/java/com/ptp/ptplatform/mapper/UserMapper.java
+++ b/src/main/java/com/ptp/ptplatform/mapper/UserMapper.java
@@ -1,32 +1,32 @@
 package com.ptp.ptplatform.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.ptp.ptplatform.entity.USER;
+import com.ptp.ptplatform.entity.User;
 import org.apache.ibatis.annotations.*;
 
 import java.util.*;
 
 
 @Mapper
-public interface UserMapper extends BaseMapper<USER> {
+public interface UserMapper extends BaseMapper<User> {
     // 查询
     @Select("SELECT * FROM user WHERE username = #{username}")
-    USER selectByUsername(String username);
+    User selectByUsername(String username);
 
     @Select("SELECT * FROM user WHERE username = #{username} AND password = #{password}")
-    USER selectByUsernameAndPassword(String username, String password);
+    User selectByUsernameAndPassword(String username, String password);
 
     @Select("SELECT * FROM user WHERE username LIKE CONCAT('%', #{key}, '%') AND username != #{username}")
-    List<USER> searchUsername(String username, String key);
+    List<User> searchUsername(String username, String key);
 
     @Select("SELECT username, authority, level, registTime, lastLogin, upload, download, shareRate, magicPoints FROM user")
-    List<USER> selectAllUsers();
+    List<User> selectAllUsers();
 
 
     // 注册用户
     @Insert("INSERT INTO user (username, password, registTime) " +
             "VALUES (#{username}, #{password}, #{registTime})")
-    int insertUser(USER user);
+    int insertUser(User user);
 
     // 更新用户信息
     @Update("UPDATE user SET " +
@@ -39,6 +39,6 @@
             "shareRate = #{shareRate}, " +
             "magicPoints = #{magicPoints} " +
             "WHERE username = #{username}")
-    int updateUser(USER user);
+    int updateUser(User user);
 
 }
diff --git a/src/main/java/com/ptp/ptplatform/service/UserLevelService.java b/src/main/java/com/ptp/ptplatform/service/UserLevelService.java
new file mode 100644
index 0000000..d885691
--- /dev/null
+++ b/src/main/java/com/ptp/ptplatform/service/UserLevelService.java
@@ -0,0 +1,103 @@
+package com.ptp.ptplatform.service;
+
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.time.temporal.ChronoUnit;
+import java.util.Date;
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.ptp.ptplatform.entity.User;
+import com.ptp.ptplatform.mapper.UserMapper;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Service
+@Slf4j
+public class UserLevelService {
+
+    @Autowired
+    private UserMapper userMapper;
+    private static final double BYTES_PER_GB = 1024d * 1024 * 1024;
+
+    // 每周一凌晨 2 点触发
+    @Scheduled(cron = "0 0 2 * * MON")
+    @Transactional
+    public void refreshAllUserLevels() {
+        log.info("===== 开始刷新所有用户等级 =====");
+        LocalDate today = LocalDate.now();
+
+        List<User> users = userMapper.selectList(null);
+        log.info(">> 从数据库查到 {} 个用户", users.size());
+
+        for (User u : users) {
+            double uploadGb    = u.getUpload()  / BYTES_PER_GB;
+            long   magicPoints = u.getMagicPoints();
+            log.info("[LevelCheck] 用户={} shareRate={} uploadGB={} magicPoints={}",
+                    u.getUsername(),
+                    u.getShareRate(),
+                    uploadGb,
+                    magicPoints);
+            int newLevel = calcLevel(u, today);
+            if (newLevel != u.getLevel()) {
+                log.info("[LevelUpdate] 用户={} level: {} -> {}", u.getUsername(), u.getLevel(), newLevel);
+                u.setLevel(newLevel);
+                userMapper.updateById(u);
+            }
+        }
+        log.info("===== 刷新完毕 =====");
+    }
+
+
+    /**
+     * 根据各项指标和注册时长计算用户等级
+     */
+    private int calcLevel(User u, LocalDate today) {
+        double shareRate   = u.getShareRate();            // 0.9023 表示 90.23%
+        double uploadGb    = u.getUpload()  / BYTES_PER_GB;
+        long   magicPoints = u.getMagicPoints();
+
+        // 注册日期转 LocalDate
+        Date   regTime = u.getRegistTime();
+        LocalDate regDate = regTime.toInstant()
+                .atZone(ZoneId.systemDefault())
+                .toLocalDate();
+
+        long months = ChronoUnit.MONTHS.between(regDate, today);
+
+        if (shareRate   >= 1.2  &&
+                magicPoints >= 100_000 &&
+                uploadGb    >= 10_240  &&
+                months      >= 12) {
+            return 5;
+        }
+        if (shareRate   >= 1.0  &&
+                magicPoints >=  50_000 &&
+                uploadGb    >=  5_120  &&
+                months      >= 6) {
+            return 4;
+        }
+        if (shareRate   >= 0.8  &&
+                magicPoints >=  20_000 &&
+                uploadGb    >=  1_024  &&
+                months      >= 3) {
+            return 3;
+        }
+        if (shareRate   >= 0.6  &&
+                magicPoints >=   5_000 &&
+                uploadGb    >=     200 &&
+                months      >= 1) {
+            return 2;
+        }
+        if (shareRate   >= 0.4  &&
+                magicPoints >=   1_000 &&
+                uploadGb    >=      50) {
+            return 1;
+        }
+        return 0;
+    }
+}
diff --git a/src/main/java/com/ptp/ptplatform/utils/JwtUtils.java b/src/main/java/com/ptp/ptplatform/utils/JwtUtils.java
index 0728819..4a5f6d1 100644
--- a/src/main/java/com/ptp/ptplatform/utils/JwtUtils.java
+++ b/src/main/java/com/ptp/ptplatform/utils/JwtUtils.java
@@ -1,16 +1,9 @@
 package com.ptp.ptplatform.utils;
 
-import com.ptp.ptplatform.entity.USER;
-import com.ptp.ptplatform.mapper.UserMapper;
 import io.jsonwebtoken.Claims;
 import io.jsonwebtoken.Jwts;
 import io.jsonwebtoken.SignatureAlgorithm;
-import jakarta.annotation.Resource;
-import jakarta.servlet.http.HttpServletRequest;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.RestController;
 
-import javax.xml.crypto.Data;
 import java.util.Date;
 
 
diff --git a/src/test/java/com/ptp/ptplatform/controller/InviteCodeTest.java b/src/test/java/com/ptp/ptplatform/controller/InviteCodeTest.java
index 9c000f3..0a50c00 100644
--- a/src/test/java/com/ptp/ptplatform/controller/InviteCodeTest.java
+++ b/src/test/java/com/ptp/ptplatform/controller/InviteCodeTest.java
@@ -1,10 +1,9 @@
 package com.ptp.ptplatform.controller;
 
 import com.ptp.ptplatform.entity.INVITE_CODE;
-import com.ptp.ptplatform.entity.USER;
+import com.ptp.ptplatform.entity.User;
 import com.ptp.ptplatform.mapper.InviteCodeMapper;
 import com.ptp.ptplatform.mapper.UserMapper;
-import com.ptp.ptplatform.utils.JwtUtils;
 import com.ptp.ptplatform.utils.Result;
 import jakarta.servlet.http.HttpServletRequest;
 import org.junit.jupiter.api.BeforeEach;
@@ -12,7 +11,6 @@
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import org.springframework.http.ResponseEntity;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -37,14 +35,14 @@
     @InjectMocks
     private InviteCodeController inviteCodeController;
 
-    private USER testUser;
+    private User testUser;
     private INVITE_CODE testInviteCode;
 
     @BeforeEach
     void setUp() {
         MockitoAnnotations.openMocks(this);
 
-        testUser = new USER();
+        testUser = new User();
         testUser.setUsername("testUser");
         testUser.setMagicPoints(15);
 
diff --git a/src/test/java/com/ptp/ptplatform/controller/UserControllerTest.java b/src/test/java/com/ptp/ptplatform/controller/UserControllerTest.java
index bc8250f..d69c4ea 100644
--- a/src/test/java/com/ptp/ptplatform/controller/UserControllerTest.java
+++ b/src/test/java/com/ptp/ptplatform/controller/UserControllerTest.java
@@ -1,12 +1,12 @@
 package com.ptp.ptplatform.controller;
 
 import com.ptp.ptplatform.entity.INVITE_CODE;
-import com.ptp.ptplatform.entity.USER;
+import com.ptp.ptplatform.entity.User;
 import com.ptp.ptplatform.mapper.InviteCodeMapper;
 import com.ptp.ptplatform.mapper.UserMapper;
+import com.ptp.ptplatform.service.UserLevelService;
 import com.ptp.ptplatform.utils.JwtUtils;
 import com.ptp.ptplatform.utils.Result;
-import com.ptp.ptplatform.utils.SizeCalculation;
 import io.jsonwebtoken.Claims;
 import jakarta.servlet.http.HttpServletRequest;
 import org.junit.jupiter.api.BeforeEach;
@@ -16,13 +16,18 @@
 import org.mockito.MockedStatic;
 import org.mockito.MockitoAnnotations;
 import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
 
 import java.util.*;
 
 import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 
 class UserControllerTest {
+    private MockMvc mockMvc;
 
     @Mock
     private UserMapper userMapper;
@@ -33,17 +38,20 @@
     @Mock
     private HttpServletRequest request;
 
+    @Mock
+    private UserLevelService userLevelService;
+
     @InjectMocks
     private UserController userController;
 
-    private USER testUser;
+    private User testUser;
     private INVITE_CODE testInviteCode;
 
     @BeforeEach
     void setUp() {
         MockitoAnnotations.openMocks(this);
 
-        testUser = new USER();
+        testUser = new User();
         testUser.setUsername("testUser");
         testUser.setPassword("testPassword");
         testUser.setLastLogin(new Date());
@@ -53,6 +61,9 @@
         testInviteCode = new INVITE_CODE("testUser");
         testInviteCode.setCode("TESTCODE123");
         testInviteCode.setIsUsed(false);
+        mockMvc = MockMvcBuilders
+                .standaloneSetup(userController)
+                .build();
     }
 
     @Test
@@ -129,7 +140,7 @@
         // Assert
         assertTrue(result.isSuccess());
         assertEquals("新建用户成功", result.getMessage());
-        verify(userMapper, times(1)).insertUser(any(USER.class));
+        verify(userMapper, times(1)).insertUser(any(User.class));
         verify(inviteCodeMapper, times(1)).updateCodeUser("VALIDCODE");
     }
 
@@ -270,7 +281,7 @@
             when(userMapper.selectByUsername(testUser.getUsername())).thenReturn(testUser);
 
             // Act
-            USER result = userController.getUserInRequest(request);
+            User result = userController.getUserInRequest(request);
 
             // Assert
             assertEquals(testUser, result);
@@ -288,13 +299,13 @@
             mockedJwtUtils.when(() -> JwtUtils.getClaimByToken(anyString())).thenReturn(mockClaims);
 
             // 模拟用户查询
-            USER mockUser = new USER("testUser", "password", USER.Authority.USER);
+            User mockUser = new User("testUser", "password", User.Authority.USER);
             when(userMapper.selectByUsername("testUser")).thenReturn(mockUser);
 
             // 模拟搜索结果
-            List<USER> mockResults = Arrays.asList(
-                    new USER("user1", "pass1", USER.Authority.USER),
-                    new USER("user2", "pass2", USER.Authority.USER)
+            List<User> mockResults = Arrays.asList(
+                    new User("user1", "pass1", User.Authority.USER),
+                    new User("user2", "pass2", User.Authority.USER)
             );
             when(userMapper.searchUsername("testUser", "zzz")).thenReturn(mockResults);
 
@@ -318,10 +329,10 @@
     @Test
     public void testGetAllUser() {
         // 模拟返回所有用户
-        List<USER> mockUsers = Arrays.asList(
-                new USER("admin", "adminPass", USER.Authority.ADMIN),
-                new USER("user1", "pass1", USER.Authority.USER),
-                new USER("user2", "pass2", USER.Authority.USER)
+        List<User> mockUsers = Arrays.asList(
+                new User("admin", "adminPass", User.Authority.ADMIN),
+                new User("user1", "pass1", User.Authority.USER),
+                new User("user2", "pass2", User.Authority.USER)
         );
         when(userMapper.selectAllUsers()).thenReturn(mockUsers);
 
@@ -344,14 +355,22 @@
         authorityMap.put("changeUsername", "testUser");
         authorityMap.put("authority", "ADMIN");
 
-        USER mockUser = new USER("testUser", "password", USER.Authority.USER);
+        User mockUser = new User("testUser", "password", User.Authority.USER);
         when(userMapper.selectByUsername("testUser")).thenReturn(mockUser);
-        when(userMapper.updateUser(any(USER.class))).thenReturn(1);
+        when(userMapper.updateUser(any(User.class))).thenReturn(1);
 
         Result successResult = userController.changeAuthority(new MockHttpServletRequest(), authorityMap);
         assertEquals(200, successResult.getCode());
         assertEquals("修改用户权限成功", successResult.getMessage());
 
     }
+    @Test
+    void whenPostRefreshLevel_thenServiceCalled_andReturnOk() throws Exception {
+        mockMvc.perform(post("/user/refreshLevel"))
+                .andExpect(status().isOk());
 
-}
\ No newline at end of file
+        // 验证 service 方法被调用
+        verify(userLevelService).refreshAllUserLevels();
+    }
+}
+
diff --git a/src/test/resources/schema.sql b/src/test/resources/schema.sql
index 677d41c..945e692 100644
--- a/src/test/resources/schema.sql
+++ b/src/test/resources/schema.sql
@@ -38,3 +38,16 @@
                                create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP(),
                                CONSTRAINT fk_seed_comments_post FOREIGN KEY (post_id) REFERENCES seed_posts(id) ON DELETE CASCADE
 );
+
+CREATE TABLE IF NOT EXISTS "user" (
+                                      username     VARCHAR(20)  NOT NULL PRIMARY KEY,
+    password     VARCHAR(200) NOT NULL,
+    authority    VARCHAR(10)  NOT NULL,
+    level        INT          NOT NULL DEFAULT 0,
+    registTime   DATE         NOT NULL,
+    lastLogin    DATE         NULL,
+    upload       BIGINT       NOT NULL DEFAULT 0,
+    download     BIGINT       NOT NULL DEFAULT 0,
+    shareRate    DOUBLE       NULL,
+    magicPoints  BIGINT       NOT NULL DEFAULT 0
+    );