post_report
Change-Id: I191da6e9ea7371e66dadead873abd057f5d08c74
diff --git a/src/main/java/com/example/g8backend/controller/PostController.java b/src/main/java/com/example/g8backend/controller/PostController.java
index e5e4eab..1e0adf4 100644
--- a/src/main/java/com/example/g8backend/controller/PostController.java
+++ b/src/main/java/com/example/g8backend/controller/PostController.java
@@ -16,9 +16,12 @@
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
+import com.example.g8backend.entity.Report;
+import com.example.g8backend.service.IReportService;
import java.util.List;
+
@RestController
@RequestMapping("/post")
@Validated
@@ -29,6 +32,8 @@
@Autowired
private PostViewMapper postViewMapper;
+ @Autowired
+ private IReportService reportService;
@PostMapping("")
public ResponseEntity<ApiResponse<Void>> createPost(@RequestBody PostCreateDTO postCreateDTO) {
@@ -191,4 +196,41 @@
Long count = postRatingService.getRatingUserCount(postId);
return ResponseEntity.ok(ApiResponse.success(count));
}
+ @PostMapping("/{postId}/report")
+ public ResponseEntity<ApiResponse<String>> reportPost(
+ @PathVariable Long postId,
+ @RequestParam String reason) {
+ long userId = (long) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
+ try {
+ reportService.submitReport(userId, postId, reason);
+ return ResponseEntity.ok(ApiResponse.message("举报已提交"));
+ } catch (IllegalArgumentException e) {
+ return ResponseEntity.badRequest().body(ApiResponse.error(400, e.getMessage()));
+ }
+ }
+
+
+ @GetMapping("/reports")
+ public ResponseEntity<ApiResponse<List<Report>>> getReports(
+ @RequestParam(required = false) String status) {
+ List<Report> reports = reportService.getReports(status);
+ return ResponseEntity.ok(ApiResponse.success(reports));
+ }
+
+ @PutMapping("/report/{reportId}")
+ public ResponseEntity<ApiResponse<String>> resolveReport(
+ @PathVariable Long reportId,
+ @RequestParam Long adminId, // 实际部署时可从 token 解析或改为登录信息中获取
+ @RequestParam String status,
+ @RequestParam(required = false) String notes) {
+ try {
+ reportService.resolveReport(reportId, adminId, status, notes);
+ return ResponseEntity.ok(ApiResponse.message("举报处理完成"));
+ } catch (IllegalArgumentException e) {
+ return ResponseEntity.badRequest().body(ApiResponse.error(400, e.getMessage()));
+ }
+ }
+
+
+
}
diff --git a/src/main/java/com/example/g8backend/entity/Report.java b/src/main/java/com/example/g8backend/entity/Report.java
new file mode 100644
index 0000000..9ddfa5b
--- /dev/null
+++ b/src/main/java/com/example/g8backend/entity/Report.java
@@ -0,0 +1,23 @@
+package com.example.g8backend.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+@TableName("reports")
+public class Report {
+ @TableId(type = IdType.AUTO)
+ private Long reportId;
+ private Long postId;
+ private Long userId;
+ private String reason;
+ private String status;
+ private LocalDateTime createdAt;
+ private Long resolvedBy;
+ private LocalDateTime resolvedAt;
+ private String resolutionNotes;
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/g8backend/mapper/ReportMapper.java b/src/main/java/com/example/g8backend/mapper/ReportMapper.java
new file mode 100644
index 0000000..01e5630
--- /dev/null
+++ b/src/main/java/com/example/g8backend/mapper/ReportMapper.java
@@ -0,0 +1,16 @@
+package com.example.g8backend.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.example.g8backend.entity.Report;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+@Mapper
+public interface ReportMapper extends BaseMapper<Report> {
+ // 自定义查询方法(如按状态查询)
+ @Select("SELECT * FROM reports WHERE status = #{status}")
+ List<Report> selectByStatus(@Param("status") String status);
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/g8backend/service/IReportService.java b/src/main/java/com/example/g8backend/service/IReportService.java
new file mode 100644
index 0000000..c1cf775
--- /dev/null
+++ b/src/main/java/com/example/g8backend/service/IReportService.java
@@ -0,0 +1,15 @@
+package com.example.g8backend.service;
+
+import com.example.g8backend.entity.Report;
+import java.util.List;
+
+public interface IReportService {
+ // 用户提交举报
+ boolean submitReport(Long userId, Long postId, String reason);
+
+ // 管理员处理举报
+ boolean resolveReport(Long reportId, Long adminUserId, String status, String notes);
+
+ // 获取举报列表(按状态过滤)
+ List<Report> getReports(String status);
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/g8backend/service/impl/ReportServiceImpl.java b/src/main/java/com/example/g8backend/service/impl/ReportServiceImpl.java
new file mode 100644
index 0000000..f27de25
--- /dev/null
+++ b/src/main/java/com/example/g8backend/service/impl/ReportServiceImpl.java
@@ -0,0 +1,64 @@
+package com.example.g8backend.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.example.g8backend.entity.Report;
+import com.example.g8backend.mapper.ReportMapper;
+import com.example.g8backend.service.IReportService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Service
+@RequiredArgsConstructor
+public class ReportServiceImpl implements IReportService {
+ private final ReportMapper reportMapper;
+
+ @Override
+ @Transactional
+ public boolean submitReport(Long userId, Long postId, String reason) {
+ // 检查是否已存在未处理的举报
+ Long count = reportMapper.selectCount(
+ new QueryWrapper<Report>()
+ .eq("user_id", userId)
+ .eq("post_id", postId)
+ .eq("status", "pending")
+ );
+ if (count > 0) {
+ throw new IllegalArgumentException("您已举报过该帖子,请勿重复提交");
+ }
+
+ // 创建举报记录
+ Report report = new Report();
+ report.setPostId(postId);
+ report.setUserId(userId);
+ report.setReason(reason);
+ report.setStatus("pending");
+ report.setCreatedAt(LocalDateTime.now());
+ return reportMapper.insert(report) > 0;
+ }
+
+ @Override
+ @Transactional
+ public boolean resolveReport(Long reportId, Long adminUserId, String status, String notes) {
+ Report report = reportMapper.selectById(reportId);
+ if (report == null) {
+ throw new IllegalArgumentException("举报记录不存在");
+ }
+ report.setStatus(status);
+ report.setResolvedBy(adminUserId);
+ report.setResolvedAt(LocalDateTime.now());
+ report.setResolutionNotes(notes);
+ return reportMapper.updateById(report) > 0;
+ }
+
+ @Override
+ public List<Report> getReports(String status) {
+ return reportMapper.selectList(
+ new QueryWrapper<Report>().eq("status", status)
+ );
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql
index f6d061b..1ee0c87 100644
--- a/src/main/resources/schema.sql
+++ b/src/main/resources/schema.sql
@@ -143,4 +143,19 @@
FOREIGN KEY (`user_id`) REFERENCES `users`(`user_id`),
FOREIGN KEY (`post_id`) REFERENCES `posts`(`post_id`),
INDEX idx_post_ratings_post_id ON post_ratings (post_id)
+);
+
+CREATE TABLE IF NOT EXISTS `reports` (
+ `report_id` INT AUTO_INCREMENT PRIMARY KEY COMMENT '举报ID',
+ `post_id` INT NOT NULL COMMENT '被举报的帖子ID',
+ `user_id` INT NOT NULL COMMENT '举报人ID',
+ `reason` TEXT NOT NULL COMMENT '举报原因',
+ `status` ENUM('pending', 'resolved', 'rejected') DEFAULT 'pending' COMMENT '处理状态(待处理/已解决/已驳回)',
+ `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '举报时间',
+ `resolved_by` INT DEFAULT NULL COMMENT '处理人ID(管理员)',
+ `resolved_at` TIMESTAMP DEFAULT NULL COMMENT '处理时间',
+ `resolution_notes` TEXT DEFAULT NULL COMMENT '处理备注',
+ FOREIGN KEY (`post_id`) REFERENCES `posts`(`post_id`),
+ FOREIGN KEY (`user_id`) REFERENCES `users`(`user_id`),
+ FOREIGN KEY (`resolved_by`) REFERENCES `users`(`user_id`)
);
\ No newline at end of file
diff --git a/src/test/java/com/example/g8backend/service/ReportServiceImplTest.java b/src/test/java/com/example/g8backend/service/ReportServiceImplTest.java
new file mode 100644
index 0000000..acd2730
--- /dev/null
+++ b/src/test/java/com/example/g8backend/service/ReportServiceImplTest.java
@@ -0,0 +1,113 @@
+package com.example.g8backend.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.example.g8backend.entity.Report;
+import com.example.g8backend.mapper.ReportMapper;
+import com.example.g8backend.service.impl.ReportServiceImpl;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.time.LocalDateTime;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+class ReportServiceImplTest {
+
+ private ReportMapper reportMapper;
+ private ReportServiceImpl reportService;
+
+ @BeforeEach
+ void setUp() {
+ reportMapper = mock(ReportMapper.class);
+ reportService = new ReportServiceImpl(reportMapper);
+ }
+
+ @Test
+ void submitReport_shouldSucceedIfNoPendingExists() {
+ Long userId = 1L;
+ Long postId = 2L;
+ String reason = "Spam content";
+
+ // 模拟:没有已有 pending 状态的举报
+ when(reportMapper.selectCount(any(QueryWrapper.class))).thenReturn(0L);
+ when(reportMapper.insert(any(Report.class))).thenReturn(1);
+
+ boolean result = reportService.submitReport(userId, postId, reason);
+
+ assertTrue(result);
+ verify(reportMapper).insert(any(Report.class));
+ }
+
+ @Test
+ void submitReport_shouldThrowIfPendingExists() {
+ Long userId = 1L;
+ Long postId = 2L;
+ String reason = "重复举报";
+
+ when(reportMapper.selectCount(any(QueryWrapper.class))).thenReturn(1L);
+
+ IllegalArgumentException exception = assertThrows(
+ IllegalArgumentException.class,
+ () -> reportService.submitReport(userId, postId, reason)
+ );
+
+ assertEquals("您已举报过该帖子,请勿重复提交", exception.getMessage());
+ verify(reportMapper, never()).insert(any(Report.class));
+ }
+
+ @Test
+ void resolveReport_shouldUpdateExistingReport() {
+ Long reportId = 10L;
+ Long adminId = 100L;
+ String status = "resolved";
+ String notes = "已处理";
+
+ Report mockReport = new Report();
+ mockReport.setReportId(reportId);
+ mockReport.setStatus("pending");
+
+ when(reportMapper.selectById(reportId)).thenReturn(mockReport);
+ when(reportMapper.updateById(any(Report.class))).thenReturn(1);
+
+ boolean result = reportService.resolveReport(reportId, adminId, status, notes);
+
+ assertTrue(result);
+ verify(reportMapper).updateById(any(Report.class));
+ }
+
+ @Test
+ void resolveReport_shouldThrowIfNotFound() {
+ Long reportId = 999L;
+
+ when(reportMapper.selectById(reportId)).thenReturn(null);
+
+ IllegalArgumentException exception = assertThrows(
+ IllegalArgumentException.class,
+ () -> reportService.resolveReport(reportId, 1L, "resolved", "无效举报")
+ );
+
+ assertEquals("举报记录不存在", exception.getMessage());
+ verify(reportMapper, never()).updateById(any(Report.class));
+ }
+
+ @Test
+ void getReports_shouldReturnMatchingStatus() {
+ Report r1 = new Report();
+ r1.setStatus("pending");
+
+ Report r2 = new Report();
+ r2.setStatus("pending");
+
+ when(reportMapper.selectList(any(QueryWrapper.class)))
+ .thenReturn(Arrays.asList(r1, r2));
+
+ List<Report> reports = reportService.getReports("pending");
+
+ assertEquals(2, reports.size());
+ assertEquals("pending", reports.get(0).getStatus());
+ verify(reportMapper).selectList(any(QueryWrapper.class));
+ }
+}