feat: 06 互动与内容治理落地(评论 / 点赞 / 举报 / 敏感词 / 审核留证)
为什么: 1) 产品侧要先把内容治理闭环跑通才能谈上线:评论可发布、可翻页、可删除, 违规内容能在后台被审核与驳回,举报要有工单可追溯。本提交把这套补齐。 2) 审核不能只靠人肉:敏感词按 BLOCK/REPLACE/REVIEW 三档处置, 机审通过即发布(可配置为 PRE_REVIEW 全部先审后发),人审与机审的结论 都写进 content_audit_record,争议时可回答"当时凭什么放行/驳回"。 3) 举报处理必须与处置打通:结论成立会联动驳回被举报评论,并可选同时封禁被举报用户, 避免"工单处理完了但违规内容还挂着"。 4) 顺带修复一个真实缺陷:user_favorite / content_like 上有唯一索引,而全局配置会把 deleteById 改写成逻辑删除,导致取消收藏/点赞后旧的 deleted 行仍占位, 再次收藏或点赞直接撞唯一索引报错。关系类数据没有留证价值,改为显式物理删除, 并补了"取消后再来一次"的回归自测。
This commit is contained in:
1 parent
b3179fc836
commit
af1dc48494
48 files changed
+1752
-14
No files matched your search
@@ -35,6 +35,10 @@
|
||||
<groupId>com.fruitshorts</groupId>
|
||||
<artifactId>fruitshorts-module-playback</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fruitshorts</groupId>
|
||||
<artifactId>fruitshorts-module-interaction</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package com.fruitshorts.admin.controller;
|
||||
|
||||
import com.fruitshorts.common.pojo.PageResult;
|
||||
import com.fruitshorts.common.pojo.Result;
|
||||
import com.fruitshorts.framework.log.OperationLog;
|
||||
import com.fruitshorts.framework.security.SecurityUtils;
|
||||
import com.fruitshorts.interaction.dal.entity.ContentAuditRecord;
|
||||
import com.fruitshorts.interaction.service.CommentService;
|
||||
import com.fruitshorts.interaction.vo.CommentAuditReqVO;
|
||||
import com.fruitshorts.interaction.vo.CommentQueryReqVO;
|
||||
import com.fruitshorts.interaction.vo.CommentRespVO;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** 后台评论审核。 */
|
||||
@RestController
|
||||
@RequestMapping("/admin/comments")
|
||||
@RequiredArgsConstructor
|
||||
public class CommentAdminController {
|
||||
|
||||
private final CommentService commentService;
|
||||
|
||||
/** 检索评论:默认按状态过滤,`status=pending` 即审核队列。 */
|
||||
@GetMapping("/page")
|
||||
@PreAuthorize("@ss.hasPermission('comment:list')")
|
||||
public Result<PageResult<CommentRespVO>> page(CommentQueryReqVO req) {
|
||||
return Result.ok(commentService.adminPage(req));
|
||||
}
|
||||
|
||||
/** 审核留证:机审与人审的完整链路。 */
|
||||
@GetMapping("/{id}/audit-records")
|
||||
@PreAuthorize("@ss.hasPermission('comment:list')")
|
||||
public Result<List<ContentAuditRecord>> auditRecords(@PathVariable Long id) {
|
||||
return Result.ok(commentService.auditRecords(id));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}/audit")
|
||||
@PreAuthorize("@ss.hasPermission('comment:audit')")
|
||||
@OperationLog(module = "内容治理", action = "审核评论")
|
||||
public Result<Void> audit(@PathVariable Long id, @Valid @RequestBody CommentAuditReqVO req) {
|
||||
commentService.audit(id, req, SecurityUtils.getUserId());
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("@ss.hasPermission('comment:audit')")
|
||||
@OperationLog(module = "内容治理", action = "删除评论")
|
||||
public Result<Void> delete(@PathVariable Long id) {
|
||||
commentService.adminDelete(id);
|
||||
return Result.ok();
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package com.fruitshorts.admin.controller;
|
||||
|
||||
import com.fruitshorts.common.pojo.PageResult;
|
||||
import com.fruitshorts.common.pojo.Result;
|
||||
import com.fruitshorts.framework.log.OperationLog;
|
||||
import com.fruitshorts.framework.security.SecurityUtils;
|
||||
import com.fruitshorts.interaction.dal.entity.ContentReport;
|
||||
import com.fruitshorts.interaction.service.ReportService;
|
||||
import com.fruitshorts.interaction.vo.ReportHandleReqVO;
|
||||
import com.fruitshorts.interaction.vo.ReportQueryReqVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/** 后台举报工单。 */
|
||||
@RestController
|
||||
@RequestMapping("/admin/reports")
|
||||
@RequiredArgsConstructor
|
||||
public class ReportAdminController {
|
||||
|
||||
private final ReportService reportService;
|
||||
|
||||
@GetMapping("/page")
|
||||
@PreAuthorize("@ss.hasPermission('report:list')")
|
||||
public Result<PageResult<ContentReport>> page(ReportQueryReqVO req) {
|
||||
return Result.ok(reportService.page(req));
|
||||
}
|
||||
|
||||
/** 处理工单:结论成立会联动驳回被举报评论,可选择同时封禁被举报用户。 */
|
||||
@PutMapping("/{id}/handle")
|
||||
@PreAuthorize("@ss.hasPermission('report:handle')")
|
||||
@OperationLog(module = "内容治理", action = "处理举报")
|
||||
public Result<Void> handle(@PathVariable Long id, @RequestBody ReportHandleReqVO req) {
|
||||
reportService.handle(id, req, SecurityUtils.getUserId());
|
||||
return Result.ok();
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package com.fruitshorts.admin.controller;
|
||||
|
||||
import com.fruitshorts.common.pojo.PageResult;
|
||||
import com.fruitshorts.common.pojo.Result;
|
||||
import com.fruitshorts.framework.log.OperationLog;
|
||||
import com.fruitshorts.interaction.dal.entity.SensitiveWord;
|
||||
import com.fruitshorts.interaction.service.SensitiveWordService;
|
||||
import com.fruitshorts.interaction.vo.SensitiveWordReqVO;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/** 后台敏感词库。改动即时生效(服务端词库缓存会失效重建)。 */
|
||||
@RestController
|
||||
@RequestMapping("/admin/sensitive-words")
|
||||
@RequiredArgsConstructor
|
||||
public class SensitiveWordAdminController {
|
||||
|
||||
private final SensitiveWordService sensitiveWordService;
|
||||
|
||||
@GetMapping("/page")
|
||||
@PreAuthorize("@ss.hasPermission('sensitive-word:list')")
|
||||
public Result<PageResult<SensitiveWord>> page(@RequestParam(required = false) String keyword,
|
||||
@RequestParam(required = false) String category,
|
||||
@RequestParam(required = false) String level,
|
||||
@RequestParam(defaultValue = "1") Integer pageIndex,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize) {
|
||||
return Result.ok(sensitiveWordService.page(keyword, category, level, pageIndex, pageSize));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@PreAuthorize("@ss.hasPermission('sensitive-word:update')")
|
||||
@OperationLog(module = "内容治理", action = "新增敏感词")
|
||||
public Result<Long> create(@Valid @RequestBody SensitiveWordReqVO req) {
|
||||
return Result.ok(sensitiveWordService.create(req));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("@ss.hasPermission('sensitive-word:update')")
|
||||
@OperationLog(module = "内容治理", action = "修改敏感词")
|
||||
public Result<Void> update(@PathVariable Long id, @Valid @RequestBody SensitiveWordReqVO req) {
|
||||
sensitiveWordService.update(id, req);
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("@ss.hasPermission('sensitive-word:update')")
|
||||
@OperationLog(module = "内容治理", action = "删除敏感词")
|
||||
public Result<Void> delete(@PathVariable Long id) {
|
||||
sensitiveWordService.delete(id);
|
||||
return Result.ok();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user