expandAdminFunction

Change-Id: If3b875b3017d1922b15150dd735ca2ce5a3a77f0
diff --git a/src/main/java/com/example/g8backend/controller/AdminController.java b/src/main/java/com/example/g8backend/controller/AdminController.java
index 394e445..a4193cb 100644
--- a/src/main/java/com/example/g8backend/controller/AdminController.java
+++ b/src/main/java/com/example/g8backend/controller/AdminController.java
@@ -1,10 +1,13 @@
 package com.example.g8backend.controller;
 
 import com.example.g8backend.dto.ApiResponse;
+import com.example.g8backend.entity.Post;
 import com.example.g8backend.entity.Report;
 import com.example.g8backend.service.AdminService;
+import com.example.g8backend.service.IPostService;
 import com.example.g8backend.service.IReportService;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.web.bind.annotation.*;
@@ -17,6 +20,8 @@
     @Autowired
     private AdminService adminService;
     private IReportService reportService;
+    @Autowired
+    private IPostService postService;
     @PostMapping("/grant-vip/{userId}")
     @PreAuthorize("hasRole('ADMIN')") // 仅允许管理员访问
     public String grantVip(@PathVariable Long userId) {
@@ -43,4 +48,68 @@
         return ApiResponse.success("举报处理完成");
     }
 
+
+    // 封禁用户
+    @PostMapping("/users/{userId}/ban")
+    @PreAuthorize("hasRole('ADMIN')")
+    public ApiResponse<String> banUser(
+            @PathVariable Long userId,
+            @RequestParam String reason) {
+        Long adminId = getCurrentAdminId();
+        boolean success = adminService.banUser(userId, reason, adminId);
+        return success ?
+                ApiResponse.success("用户封禁成功") :
+                ApiResponse.error(400, "操作失败");
+    }
+
+    // 解封用户
+    @PostMapping("/users/{userId}/unban")
+    @PreAuthorize("hasRole('ADMIN')")
+    public ApiResponse<String> unbanUser(@PathVariable Long userId) {
+        Long adminId = getCurrentAdminId();
+        boolean success = adminService.unbanUser(userId, adminId);
+        return success ?
+                ApiResponse.success("用户解封成功") :
+                ApiResponse.error(400, "操作失败");
+    }
+
+    // 锁定帖子
+    @PostMapping("/posts/{postId}/lock")
+    @PreAuthorize("hasRole('ADMIN')")
+    public ApiResponse<String> lockPost(
+            @PathVariable Long postId,
+            @RequestParam String reason) {
+        Long adminId = getCurrentAdminId();
+        boolean success = adminService.lockPost(postId, reason, adminId);
+        return success ?
+                ApiResponse.success("帖子已锁定") :
+                ApiResponse.error(400, "操作失败");
+    }
+
+    // 解锁帖子
+    @PostMapping("/posts/{postId}/unlock")
+    @PreAuthorize("hasRole('ADMIN')")
+    public ApiResponse<String> unlockPost(@PathVariable Long postId) {
+        Long adminId = getCurrentAdminId();
+        boolean success = adminService.unlockPost(postId, adminId);
+        return success ?
+                ApiResponse.success("帖子已解锁") :
+                ApiResponse.error(400, "操作失败");
+    }
+    @DeleteMapping("/{postId}")
+    public ResponseEntity<ApiResponse<String>> deletePost(@PathVariable Long postId) {
+        long userId = (long) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
+        Post post = postService.getById(postId);
+        if (post == null) {
+            return ResponseEntity.status(404).body(ApiResponse.error(404, "Post not found."));
+        }
+        postService.removeById(postId);
+        return ResponseEntity.ok(ApiResponse.message("Post deleted successfully."));
+    }
+
+    private Long getCurrentAdminId() {
+        return (Long) SecurityContextHolder.getContext()
+                .getAuthentication().getPrincipal();
+    }
+
 }
\ No newline at end of file
diff --git a/src/main/java/com/example/g8backend/controller/AuthController.java b/src/main/java/com/example/g8backend/controller/AuthController.java
index 538d433..48376f2 100644
--- a/src/main/java/com/example/g8backend/controller/AuthController.java
+++ b/src/main/java/com/example/g8backend/controller/AuthController.java
@@ -94,6 +94,10 @@
             return ApiResponse.error(400, "用户名或密码错误");
         }
 
+        if (existingUser.getIsBanned()) {
+            return ApiResponse.error(403, "账号已被封禁,请联系管理员");
+        }
+
         String token = jwtUtil.generateToken(existingUser.getUserId());
         Map<String, String> response = new HashMap<>();
         response.put("token", token);
diff --git a/src/main/java/com/example/g8backend/controller/PostController.java b/src/main/java/com/example/g8backend/controller/PostController.java
index 6800666..41be09a 100644
--- a/src/main/java/com/example/g8backend/controller/PostController.java
+++ b/src/main/java/com/example/g8backend/controller/PostController.java
@@ -51,26 +51,12 @@
 
     @GetMapping("/{postId}")
     public ResponseEntity<ApiResponse<Post>> getPost(@PathVariable Long postId) {
+        Post post = postService.getById(postId);
         long userId = (long) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
         postService.recordViewHistory(userId, postId);
-        Post post = postService.getById(postId);
         return ResponseEntity.ok(ApiResponse.success(post));
     }
 
-    @DeleteMapping("/{postId}")
-    public ResponseEntity<ApiResponse<String>> deletePost(@PathVariable Long postId) {
-        long userId = (long) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
-        Post post = postService.getById(postId);
-        if (post == null) {
-            return ResponseEntity.status(404).body(ApiResponse.error(404, "Post not found."));
-        }
-        if (post.getUserId() != userId) {
-            return ResponseEntity.status(403).body(ApiResponse.error(403, "You are not authorized to delete this post."));
-        }
-        postService.removeById(postId);
-        return ResponseEntity.ok(ApiResponse.message("Post deleted successfully."));
-    }
-
     @GetMapping("/getAll")
     public ResponseEntity<ApiResponse<List<Post>>> getAllPosts() {
         return ResponseEntity.ok(ApiResponse.success(postService.list()));
@@ -209,4 +195,18 @@
         }
     }
 
+    @DeleteMapping("/{postId}")
+    public ResponseEntity<ApiResponse<String>> deletePost(@PathVariable Long postId) {
+        long userId = (long) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
+        Post post = postService.getById(postId);
+        if (post == null) {
+            return ResponseEntity.status(404).body(ApiResponse.error(404, "Post not found."));
+        }
+        if (post.getUserId() != userId) {
+            return ResponseEntity.status(403).body(ApiResponse.error(403, "You are not authorized to delete this post."));
+        }
+        postService.removeById(postId);
+        return ResponseEntity.ok(ApiResponse.message("Post deleted successfully."));
+    }
+
 }
diff --git a/src/main/java/com/example/g8backend/entity/Post.java b/src/main/java/com/example/g8backend/entity/Post.java
index 351e24d..e0f2a79 100644
--- a/src/main/java/com/example/g8backend/entity/Post.java
+++ b/src/main/java/com/example/g8backend/entity/Post.java
@@ -5,6 +5,9 @@
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import java.sql.Timestamp;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
 import lombok.Data;
 import lombok.experimental.Accessors;
 
@@ -20,6 +23,18 @@
     private String postContent;
     private Timestamp createdAt;
     private String postType;
+    // 新增锁定相关字段
+    @TableField("is_locked")
+    private Boolean isLocked = false;
+
+    @TableField("locked_reason")
+    private String lockedReason;
+
+    @TableField("locked_at")
+    private LocalDateTime lockedAt;
+
+    @TableField("locked_by")
+    private Long lockedBy;
 
     @TableField("view_count")
     private Integer viewCount = 0;
diff --git a/src/main/java/com/example/g8backend/entity/User.java b/src/main/java/com/example/g8backend/entity/User.java
index 0645824..aea6335 100644
--- a/src/main/java/com/example/g8backend/entity/User.java
+++ b/src/main/java/com/example/g8backend/entity/User.java
@@ -1,12 +1,14 @@
 package com.example.g8backend.entity;
 
 import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.Data;
 import lombok.experimental.Accessors;
 
 import java.time.LocalDate;
+import java.time.LocalDateTime;
 
 @Data
 @TableName("users")
@@ -23,6 +25,18 @@
     private Integer signinCount;
     private LocalDate lastSigninDate;
     private String role;
+    @TableField("is_banned")
+    private Boolean isBanned = false;
+
+    @TableField("banned_reason")
+    private String bannedReason;
+
+    @TableField("banned_at")
+    private LocalDateTime bannedAt;
+
+    @TableField("banned_by")
+    private Long bannedBy;
+
 
     @Override
     public String toString() {
diff --git a/src/main/java/com/example/g8backend/service/AdminService.java b/src/main/java/com/example/g8backend/service/AdminService.java
index 1f84eec..da672f5 100644
--- a/src/main/java/com/example/g8backend/service/AdminService.java
+++ b/src/main/java/com/example/g8backend/service/AdminService.java
@@ -2,4 +2,8 @@
 
 public interface AdminService {
     boolean grantVip(Long targetUserId);
+    boolean banUser(Long userId, String reason, Long adminId);
+    boolean unbanUser(Long userId, Long adminId);
+    boolean lockPost(Long postId, String reason, Long adminId);
+    boolean unlockPost(Long postId, Long adminId);
 }
\ No newline at end of file
diff --git a/src/main/java/com/example/g8backend/service/impl/AdminServiceImpl.java b/src/main/java/com/example/g8backend/service/impl/AdminServiceImpl.java
index d3be9fe..24434f4 100644
--- a/src/main/java/com/example/g8backend/service/impl/AdminServiceImpl.java
+++ b/src/main/java/com/example/g8backend/service/impl/AdminServiceImpl.java
@@ -1,12 +1,16 @@
 package com.example.g8backend.service.impl;
 
+import com.example.g8backend.entity.Post;
 import com.example.g8backend.entity.User;
 import com.example.g8backend.mapper.UserMapper;
+import com.example.g8backend.mapper.PostMapper;
 import com.example.g8backend.service.AdminService;
 import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.time.LocalDateTime;
+
 @Service
 @RequiredArgsConstructor
 public class AdminServiceImpl implements AdminService {
@@ -23,4 +27,62 @@
         userMapper.updateById(user);
         return true;
     }
+
+    private final PostMapper postMapper;
+
+    @Override
+    @Transactional
+    public boolean banUser(Long userId, String reason, Long adminId) {
+        User user = userMapper.selectById(userId);
+        if (user == null) return false;
+
+        user.setIsBanned(true);
+        user.setBannedReason(reason);
+        user.setBannedAt(LocalDateTime.now());
+        user.setBannedBy(adminId);
+
+        return userMapper.updateById(user) > 0;
+    }
+
+    @Override
+    @Transactional
+    public boolean unbanUser(Long userId, Long adminId) {
+        User user = userMapper.selectById(userId);
+        if (user == null) return false;
+
+        user.setIsBanned(false);
+        user.setBannedReason(null);
+        user.setBannedAt(null);
+        user.setBannedBy(null);
+
+        return userMapper.updateById(user) > 0;
+    }
+
+    @Override
+    @Transactional
+    public boolean lockPost(Long postId, String reason, Long adminId) {
+        Post post = postMapper.selectById(postId);
+        if (post == null) return false;
+
+        post.setIsLocked(true);
+        post.setLockedReason(reason);
+        post.setLockedAt(LocalDateTime.now());
+        post.setLockedBy(adminId);
+
+        return postMapper.updateById(post) > 0;
+    }
+
+    @Override
+    @Transactional
+    public boolean unlockPost(Long postId, Long adminId) {
+        Post post = postMapper.selectById(postId);
+        if (post == null) return false;
+
+        post.setIsLocked(false);
+        post.setLockedReason(null);
+        post.setLockedAt(null);
+        post.setLockedBy(null);
+
+        return postMapper.updateById(post) > 0;
+    }
 }
\ No newline at end of file
diff --git a/src/main/java/com/example/g8backend/service/impl/PostServiceImpl.java b/src/main/java/com/example/g8backend/service/impl/PostServiceImpl.java
index 1fda1e2..2a43439 100644
--- a/src/main/java/com/example/g8backend/service/impl/PostServiceImpl.java
+++ b/src/main/java/com/example/g8backend/service/impl/PostServiceImpl.java
@@ -178,7 +178,6 @@
                 .orderByDesc("hot_score"); // 确保排序条件正确
         return postMapper.selectPage(new Page<>(page, size), queryWrapper);
     }
-
     @Override
     public List<PostHistoryDTO> getViewHistoryWithTitles(Long userId) {
         // 1. 查询浏览记录(按时间倒序)
@@ -187,15 +186,12 @@
                         .eq("user_id", userId)
                         .orderByDesc("view_time")
         );
-
-
         // 2. 转换为DTO并填充标题
         return history.stream().map(view -> {
             PostHistoryDTO dto = new PostHistoryDTO();
             dto.setViewId(view.getViewId());
             dto.setPostId(view.getPostId());
             dto.setViewTime(view.getViewTime());
-
             // 3. 查询帖子标题
             Post post = postMapper.selectById(view.getPostId());
             if (post != null) {
diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql
index e5faa3f..8a2387c 100644
--- a/src/main/resources/schema.sql
+++ b/src/main/resources/schema.sql
@@ -9,6 +9,10 @@
   `signin_count` INT DEFAULT 0,
   `last_signin_date` DATE,
   `role` ENUM('USER', 'ADMIN') DEFAULT 'USER' COMMENT '用户角色',
+  `is_banned` BOOLEAN DEFAULT FALSE COMMENT '是否被封禁',
+  `banned_reason` VARCHAR(255) COMMENT '封禁原因',
+  `banned_at` DATETIME COMMENT '封禁时间',
+  `banned_by` BIGINT COMMENT '操作管理员ID',
   INDEX `idx_user_level` (`user_level`)  -- 按等级查询优化
 );
 -- 用户统计表
@@ -58,6 +62,10 @@
   `last_calculated` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后热度计算时间',
   `average_rating` DECIMAL(3,2) DEFAULT 0.00 COMMENT '帖子平均评分',
   `rating_count` INT DEFAULT 0 COMMENT '总评分人数',
+  `is_locked` BOOLEAN DEFAULT FALSE COMMENT '是否被锁定',
+  `locked_reason` VARCHAR(255) COMMENT '锁定原因',
+  `locked_at` DATETIME COMMENT '锁定时间',
+  `locked_by` BIGINT COMMENT '操作管理员ID',
   FOREIGN KEY (`user_id`) REFERENCES `users`(`user_id`),
   FOREIGN KEY (`torrent_id`) REFERENCES `torrents`(`torrent_id`),
   INDEX `idx_hot_score` (`hot_score`),          -- 新增热度索引
diff --git a/src/test/java/com/example/g8backend/service/AdminServiceImplTest.java b/src/test/java/com/example/g8backend/service/AdminServiceImplTest.java
index f8e6229..a8983ba 100644
--- a/src/test/java/com/example/g8backend/service/AdminServiceImplTest.java
+++ b/src/test/java/com/example/g8backend/service/AdminServiceImplTest.java
@@ -1,5 +1,6 @@
 package com.example.g8backend.service;
 
+import com.example.g8backend.entity.Post;
 import com.example.g8backend.entity.User;
 import com.example.g8backend.mapper.UserMapper;
 import com.example.g8backend.service.impl.AdminServiceImpl;
@@ -8,6 +9,10 @@
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
+import com.example.g8backend.mapper.PostMapper;
+
+import java.time.LocalDateTime;
+
 import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.Mockito.*;
 
@@ -17,6 +22,8 @@
     private UserMapper userMapper;
     @InjectMocks
     private AdminServiceImpl adminService;
+    @Mock
+    private PostMapper postMapper;
 
     @Test
     public void testGrantVip_Success() {
@@ -35,4 +42,106 @@
         boolean result = adminService.grantVip(1L);
         assertFalse(result);
     }
+
+    void testBanUser_Success() {
+        User user = new User().setUserId(1L);
+        when(userMapper.selectById(1L)).thenReturn(user);
+        when(userMapper.updateById(any(User.class))).thenReturn(1);
+
+        boolean result = adminService.banUser(1L, "违规操作", 1001L);
+        assertTrue(result);
+
+        assertTrue(user.getIsBanned());
+        assertEquals("违规操作", user.getBannedReason());
+        assertNotNull(user.getBannedAt());
+        assertEquals(1001L, user.getBannedBy());
+        verify(userMapper).updateById(any(User.class));
+    }
+
+    @Test
+    void testBanUser_UserNotFound() {
+        when(userMapper.selectById(1L)).thenReturn(null);
+        boolean result = adminService.banUser(1L, "原因", 1001L);
+        assertFalse(result);
+        verify(userMapper, never()).updateById(any(User.class));
+    }
+
+    @Test
+    void testUnbanUser_Success() {
+        User user = new User().setUserId(1L)
+                .setIsBanned(true)
+                .setBannedReason("原因")
+                .setBannedAt(LocalDateTime.now())
+                .setBannedBy(1001L);
+        when(userMapper.selectById(1L)).thenReturn(user);
+        when(userMapper.updateById(any(User.class))).thenReturn(1);
+
+        boolean result = adminService.unbanUser(1L, 1001L);
+        assertTrue(result);
+
+        assertFalse(user.getIsBanned());
+        assertNull(user.getBannedReason());
+        assertNull(user.getBannedAt());
+        assertNull(user.getBannedBy());
+        verify(userMapper).updateById(any(User.class));
+    }
+
+    @Test
+    void testUnbanUser_UserNotFound() {
+        when(userMapper.selectById(1L)).thenReturn(null);
+        boolean result = adminService.unbanUser(1L, 1001L);
+        assertFalse(result);
+        verify(userMapper, never()).updateById(any(User.class));
+    }
+
+    @Test
+    void testLockPost_Success() {
+        Post post = new Post().setPostId(1L);
+        when(postMapper.selectById(1L)).thenReturn(post);
+        when(postMapper.updateById(any(Post.class))).thenReturn(1);
+
+        boolean result = adminService.lockPost(1L, "违规内容", 1001L);
+        assertTrue(result);
+
+        assertTrue(post.getIsLocked());
+        assertEquals("违规内容", post.getLockedReason());
+        assertNotNull(post.getLockedAt());
+        assertEquals(1001L, post.getLockedBy());
+        verify(postMapper).updateById(any(Post.class));
+    }
+
+    @Test
+    void testLockPost_PostNotFound() {
+        when(postMapper.selectById(1L)).thenReturn(null);
+        boolean result = adminService.lockPost(1L, "原因", 1001L);
+        assertFalse(result);
+        verify(postMapper, never()).updateById(any(Post.class));
+    }
+
+    @Test
+    void testUnlockPost_Success() {
+        Post post = new Post().setPostId(1L)
+                .setIsLocked(true)
+                .setLockedReason("原因")
+                .setLockedAt(LocalDateTime.now())
+                .setLockedBy(1001L);
+        when(postMapper.selectById(1L)).thenReturn(post);
+        when(postMapper.updateById(any(Post.class))).thenReturn(1);
+
+        boolean result = adminService.unlockPost(1L, 1001L);
+        assertTrue(result);
+
+        assertFalse(post.getIsLocked());
+        assertNull(post.getLockedReason());
+        assertNull(post.getLockedAt());
+        assertNull(post.getLockedBy());
+        verify(postMapper).updateById(any(Post.class));
+    }
+    @Test
+    void testUnlockPost_PostNotFound() {
+        when(postMapper.selectById(1L)).thenReturn(null);
+        boolean result = adminService.unlockPost(1L, 1001L);
+        assertFalse(result);
+        verify(postMapper, never()).updateById(any(Post.class));
+    }
 }
\ No newline at end of file