post_rating

Change-Id: Ia1a6fb3f87b793a6307046e36951c1fb36b213c8
diff --git a/src/main/java/com/example/g8backend/controller/AuthController.java b/src/main/java/com/example/g8backend/controller/AuthController.java
index e824b63..538d433 100644
--- a/src/main/java/com/example/g8backend/controller/AuthController.java
+++ b/src/main/java/com/example/g8backend/controller/AuthController.java
@@ -1,13 +1,15 @@
 package com.example.g8backend.controller;
 
+import com.example.g8backend.dto.ApiResponse;
 import com.example.g8backend.dto.UserRegisterDTO;
 import com.example.g8backend.entity.User;
+import com.example.g8backend.entity.UserStats;
 import com.example.g8backend.service.IUserService;
+import com.example.g8backend.service.IUserStatsService;
 import com.example.g8backend.util.JwtUtil;
 import com.example.g8backend.util.mailUtil;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.RedisTemplate;
-import org.springframework.http.ResponseEntity;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.web.bind.annotation.*;
 
@@ -21,72 +23,88 @@
 
     @Autowired
     private IUserService userService;
+
+    @Autowired
+    private IUserStatsService userStatsService;
+
     @Autowired
     private mailUtil mailUtil;
+
     @Autowired
     private PasswordEncoder passwordEncoder;
+
     @Autowired
     private JwtUtil jwtUtil;
+
     @Autowired
-    RedisTemplate<String, Object> redisTemplate;
+    private RedisTemplate<String, Object> redisTemplate;
 
     // 发送验证码
     @PostMapping("/send_verification_code")
-    public ResponseEntity<?> sendVerificationCode(@RequestBody UserRegisterDTO registerDTO) {
+    public ApiResponse<String> sendVerificationCode(@RequestBody UserRegisterDTO registerDTO) {
         if (userService.getUserByEmail(registerDTO.getEmail()) != null) {
-            return ResponseEntity.badRequest().body("邮箱已存在");
+            return ApiResponse.error(400, "邮箱已存在");
         }
 
         String verificationCode = UUID.randomUUID().toString().substring(0, 6);
         mailUtil.sendMail(registerDTO.getEmail(), "PT平台注册验证码", "您的验证码为:" + verificationCode + ",验证码十分钟内有效,请勿泄露。");
 
         redisTemplate.opsForValue().set(registerDTO.getEmail(), verificationCode, 10 * 60, java.util.concurrent.TimeUnit.SECONDS);
-        return ResponseEntity.ok("验证码发送成功");
+        return ApiResponse.success("验证码发送成功");
     }
 
     // 用户注册
     @PostMapping("/register")
-    public ResponseEntity<?> register(@RequestBody UserRegisterDTO registerDTO) {
+    public ApiResponse<String> register(@RequestBody UserRegisterDTO registerDTO) {
         if (userService.getUserByName(registerDTO.getUserName()) != null) {
-            return ResponseEntity.badRequest().body("用户名已存在");
+            return ApiResponse.error(400, "用户名已存在");
         }
 
         if (!redisTemplate.hasKey(registerDTO.getInvitationCode())) {
-            return ResponseEntity.badRequest().body("邀请码错误");
+            return ApiResponse.error(400, "邀请码错误");
         }
-        if (!registerDTO.getVerificationCode().equals(redisTemplate.opsForValue().get(registerDTO.getEmail()))) {
-            return ResponseEntity.badRequest().body("验证码错误");
+
+        Object cachedCode = redisTemplate.opsForValue().get(registerDTO.getEmail());
+        if (!registerDTO.getVerificationCode().equals(cachedCode)) {
+            return ApiResponse.error(400, "验证码错误");
         }
+
         redisTemplate.delete(registerDTO.getEmail());
 
         User user = new User();
         user.setUserName(registerDTO.getUserName());
         user.setPassword(passwordEncoder.encode(registerDTO.getPassword()));
         user.setEmail(registerDTO.getEmail());
-
-        // passkey 用于在客户端发送announce请求时获取用户信息
         user.setPasskey(UUID.randomUUID().toString().replace("-", ""));
         userService.save(user);
 
-        return ResponseEntity.ok("注册成功");
+        UserStats userStats = new UserStats();
+        userStats.setUserId(user.getUserId());
+        userStats.setPasskey(user.getPasskey());
+        userStatsService.save(userStats);
+
+        return ApiResponse.message("注册成功");
     }
 
     // 用户登录
     @PostMapping("/login")
-    public ResponseEntity<?> login(@RequestBody User user) {
+    public ApiResponse<Map<String, String>> login(@RequestBody User user) {
         User existingUser = userService.getUserByEmail(user.getEmail());
         if (existingUser == null || !passwordEncoder.matches(user.getPassword(), existingUser.getPassword())) {
-            return ResponseEntity.badRequest().body("用户名或密码错误");
+            return ApiResponse.error(400, "用户名或密码错误");
         }
+
         String token = jwtUtil.generateToken(existingUser.getUserId());
         Map<String, String> response = new HashMap<>();
         response.put("token", token);
-        return ResponseEntity.ok(response);
+
+        return ApiResponse.success(response);
     }
 
+    // 测试 Redis
     @GetMapping("/test_redis")
-    public ResponseEntity<?> testRedis() {
-        redisTemplate.opsForValue().get("test");
-        return ResponseEntity.ok("test redis ok");
+    public ApiResponse<String> testRedis() {
+        Object value = redisTemplate.opsForValue().get("test");
+        return ApiResponse.success("test redis ok");
     }
-}
\ No newline at end of file
+}
diff --git a/src/main/java/com/example/g8backend/controller/ForgotPasswordController.java b/src/main/java/com/example/g8backend/controller/ForgotPasswordController.java
new file mode 100644
index 0000000..5ca8a8e
--- /dev/null
+++ b/src/main/java/com/example/g8backend/controller/ForgotPasswordController.java
@@ -0,0 +1,28 @@
+package com.example.g8backend.controller;
+
+import com.example.g8backend.dto.ApiResponse;
+import com.example.g8backend.dto.ForgotPasswordDTO;
+import com.example.g8backend.dto.ResetPasswordDTO;
+import com.example.g8backend.service.IForgotPasswordService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/forgot-password")
+public class ForgotPasswordController {
+
+    @Autowired
+    private IForgotPasswordService forgotPasswordService;
+
+    @PostMapping("/send-code")
+    public ApiResponse<String> sendCode(@RequestBody ForgotPasswordDTO dto) {
+        forgotPasswordService.sendCodeToEmail(dto.getUsername());
+        return ApiResponse.success("验证码已发送到注册邮箱");
+    }
+
+    @PostMapping("/reset")
+    public ApiResponse<String> resetPassword(@RequestBody ResetPasswordDTO dto) {
+        forgotPasswordService.resetPassword(dto.getUsername(), dto.getCode(), dto.getNewPassword());
+        return ApiResponse.success("密码重置成功");
+    }
+}
diff --git a/src/main/java/com/example/g8backend/controller/PostController.java b/src/main/java/com/example/g8backend/controller/PostController.java
index 9ac733f..e5e4eab 100644
--- a/src/main/java/com/example/g8backend/controller/PostController.java
+++ b/src/main/java/com/example/g8backend/controller/PostController.java
@@ -8,17 +8,20 @@
 import com.example.g8backend.entity.Post;
 import com.example.g8backend.entity.PostView;
 import com.example.g8backend.mapper.PostViewMapper;
+import com.example.g8backend.service.IPostRatingService;
 import com.example.g8backend.service.IPostService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.ResponseEntity;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
 
 @RestController
 @RequestMapping("/post")
+@Validated
 public class PostController {
 
     @Autowired
@@ -149,4 +152,43 @@
         Page<Post> result = postService.getRecommendedByTags(page, size, userId);
         return ResponseEntity.ok(ApiResponse.success(result));
     }
+    @Autowired
+    private IPostRatingService postRatingService;
+
+    @PostMapping("/{postId}/rate")
+    public ResponseEntity<ApiResponse<String>> ratePost(
+            @PathVariable Long postId,
+            @RequestParam Integer rating) {
+        try {
+            long userId = (long) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
+
+            // 调用服务层方法(服务层已内置校验逻辑)
+            postRatingService.ratePost(userId, postId, rating);
+
+            // 成功时返回空数据
+            return ResponseEntity.ok(ApiResponse.success("评分成功"));
+
+        } catch (IllegalArgumentException e) {
+            // 处理参数校验异常(如评分范围错误)
+            return ResponseEntity.badRequest()
+                    .body(ApiResponse.error(400, e.getMessage()));
+
+        } catch (RuntimeException e) {
+            // 处理数据库操作失败等运行时异常
+            return ResponseEntity.internalServerError()
+                    .body(ApiResponse.error(500, e.getMessage()));
+        }
+    }
+
+    @GetMapping("/{postId}/average-rating")
+    public ResponseEntity<ApiResponse<Double>> getAverageRating(@PathVariable Long postId) {
+        Double avg = postRatingService.getAverageRating(postId);
+        return ResponseEntity.ok(ApiResponse.success(avg));
+    }
+
+    @GetMapping("/{postId}/rating-users/count")
+    public ResponseEntity<ApiResponse<Long>> getRatingUserCount(@PathVariable Long postId) {
+        Long count = postRatingService.getRatingUserCount(postId);
+        return ResponseEntity.ok(ApiResponse.success(count));
+    }
 }
diff --git a/src/main/java/com/example/g8backend/controller/TorrentController.java b/src/main/java/com/example/g8backend/controller/TorrentController.java
index 0c016f4..ea7fdf2 100644
--- a/src/main/java/com/example/g8backend/controller/TorrentController.java
+++ b/src/main/java/com/example/g8backend/controller/TorrentController.java
@@ -1,15 +1,15 @@
 package com.example.g8backend.controller;
 
+import com.example.g8backend.dto.ApiResponse;
 import com.example.g8backend.entity.User;
 import com.example.g8backend.entity.Torrent;
 import com.example.g8backend.service.IUserService;
+import com.example.g8backend.service.ITorrentService;
 import jakarta.servlet.http.HttpServletResponse;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.http.ResponseEntity;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.web.bind.annotation.*;
-import com.example.g8backend.service.ITorrentService;
 import org.springframework.web.multipart.MultipartFile;
 
 import java.io.File;
@@ -20,36 +20,51 @@
 @RestController
 @RequestMapping("/torrent")
 public class TorrentController {
+
     @Autowired
     private ITorrentService torrentService;
 
     @Autowired
     private IUserService userService;
 
-    @RequestMapping("/upload")
-    public ResponseEntity<?> handleTorrentUpload(@RequestParam("file") MultipartFile multipartFile) throws IOException {
+    // 处理种子文件上传
+    @PostMapping("/upload")
+    public ApiResponse<String> handleTorrentUpload(@RequestParam("file") MultipartFile multipartFile) throws IOException {
         Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
         long userId = (long) authentication.getPrincipal();
 
         User user = userService.getById(userId);
         String passkey = user.getPasskey();
 
+        String fileName = multipartFile.getOriginalFilename();
+
+        try {
+            assert fileName != null;
+        } catch (AssertionError e) {
+            return ApiResponse.error(400, "文件名不能为空");
+        }
+
+        if (!fileName.endsWith(".torrent")) {
+            return ApiResponse.error(400, "文件格式不正确,请上传.torrent格式的文件");
+        }
+
         File tempFile = File.createTempFile("upload-", ".torrent");
         multipartFile.transferTo(tempFile);
 
         try {
-            torrentService.handleTorrentUpload(tempFile, userId, passkey);
+            torrentService.handleTorrentUpload(tempFile, fileName, userId, passkey);
         } catch (IllegalArgumentException e) {
-            return ResponseEntity.badRequest().body(e.getMessage());
+            return ApiResponse.error(400, e.getMessage());
         }
 
         // 删除临时文件
-        if(!tempFile.delete()){
+        if (!tempFile.delete()) {
             throw new IOException("Failed to delete temporary file: " + tempFile.getAbsolutePath());
         }
-        return ResponseEntity.ok("种子上传成功");
+        return ApiResponse.success("种子上传成功");
     }
 
+    // 下载种子文件
     @GetMapping("/download/{torrentId}")
     public void downloadTorrent(@PathVariable String torrentId, HttpServletResponse response) throws IOException {
         Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
diff --git a/src/main/java/com/example/g8backend/controller/UserSecurityController.java b/src/main/java/com/example/g8backend/controller/UserSecurityController.java
new file mode 100644
index 0000000..0f41549
--- /dev/null
+++ b/src/main/java/com/example/g8backend/controller/UserSecurityController.java
@@ -0,0 +1,29 @@
+package com.example.g8backend.controller;
+
+import com.example.g8backend.dto.PasswordChangeDTO;
+import com.example.g8backend.dto.ApiResponse;
+import com.example.g8backend.service.IUserSecurityService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/user/security")
+public class UserSecurityController {
+
+    @Autowired
+    private IUserSecurityService userSecurityService;
+
+    @PutMapping("/change-password")
+    public ApiResponse<String> changePassword(@RequestBody PasswordChangeDTO dto) {
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        Long userId = (Long) authentication.getPrincipal();
+
+        // 调用服务层进行密码修改
+        userSecurityService.changePassword(userId, dto.getOldPassword(), dto.getNewPassword());
+
+        // 返回统一的成功响应
+        return ApiResponse.success("密码修改成功");
+    }
+}