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
+64
@@ -0,0 +1,64 @@
|
||||
package com.fruitshorts.app.controller;
|
||||
|
||||
import com.fruitshorts.common.pojo.CursorResult;
|
||||
import com.fruitshorts.common.pojo.Result;
|
||||
import com.fruitshorts.framework.security.SecurityUtils;
|
||||
import com.fruitshorts.framework.web.WebUtils;
|
||||
import com.fruitshorts.interaction.service.CommentService;
|
||||
import com.fruitshorts.interaction.vo.CommentPublishReqVO;
|
||||
import com.fruitshorts.interaction.vo.CommentQueryReqVO;
|
||||
import com.fruitshorts.interaction.vo.CommentRespVO;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
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.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 评论。
|
||||
*
|
||||
* <p>列表走游标翻页:`cursor` 传上一页最后一条的 id,返回 `nextCursor` 与 `hasMore`。
|
||||
* 不传 `rootId` 时返回顶层评论,传了则返回该楼层的回复。
|
||||
* 列表允许匿名浏览;发布与删除需要登录。
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/app/comments")
|
||||
@RequiredArgsConstructor
|
||||
public class AppCommentController {
|
||||
|
||||
private final CommentService commentService;
|
||||
|
||||
@PostMapping
|
||||
public Result<CommentRespVO> publish(@Valid @RequestBody CommentPublishReqVO req,
|
||||
HttpServletRequest request) {
|
||||
return Result.ok(commentService.publish(SecurityUtils.getAppUserId(), req,
|
||||
WebUtils.getClientIp(request), request.getHeader("X-Device-Id")));
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public Result<CursorResult<CommentRespVO>> page(@RequestParam Long dramaId,
|
||||
@RequestParam(required = false) Long episodeId,
|
||||
@RequestParam(required = false) Long rootId,
|
||||
@RequestParam(required = false) Long cursor,
|
||||
@RequestParam(required = false) Integer size) {
|
||||
CommentQueryReqVO query = new CommentQueryReqVO();
|
||||
query.setDramaId(dramaId);
|
||||
query.setEpisodeId(episodeId);
|
||||
query.setRootId(rootId);
|
||||
query.setCursor(cursor);
|
||||
query.setSize(size);
|
||||
return Result.ok(commentService.page(SecurityUtils.getUserIdOrNull(), query));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public Result<Void> delete(@PathVariable Long id) {
|
||||
commentService.delete(SecurityUtils.getAppUserId(), id);
|
||||
return Result.ok();
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package com.fruitshorts.app.controller;
|
||||
|
||||
import com.fruitshorts.common.pojo.Result;
|
||||
import com.fruitshorts.framework.security.SecurityUtils;
|
||||
import com.fruitshorts.interaction.service.LikeService;
|
||||
import com.fruitshorts.interaction.service.ReportService;
|
||||
import com.fruitshorts.interaction.vo.LikeToggleReqVO;
|
||||
import com.fruitshorts.interaction.vo.LikeToggleRespVO;
|
||||
import com.fruitshorts.interaction.vo.ReportCreateReqVO;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/** 点赞与举报。 */
|
||||
@RestController
|
||||
@RequestMapping("/app")
|
||||
@RequiredArgsConstructor
|
||||
public class AppInteractionController {
|
||||
|
||||
private final LikeService likeService;
|
||||
private final ReportService reportService;
|
||||
|
||||
/** 点赞/取消点赞,返回切换后的状态与最新计数。 */
|
||||
@PostMapping("/likes/toggle")
|
||||
public Result<LikeToggleRespVO> toggleLike(@Valid @RequestBody LikeToggleReqVO req) {
|
||||
return Result.ok(likeService.toggle(SecurityUtils.getAppUserId(), req));
|
||||
}
|
||||
|
||||
/** 提交举报:同一用户对同一目标只保留一条待处理工单。 */
|
||||
@PostMapping("/reports")
|
||||
public Result<Long> report(@Valid @RequestBody ReportCreateReqVO req) {
|
||||
return Result.ok(reportService.create(SecurityUtils.getAppUserId(), req));
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,12 @@ mybatis-plus:
|
||||
logic-not-delete-value: 0
|
||||
|
||||
fruitshorts:
|
||||
interaction:
|
||||
# MACHINE_FIRST:机审通过即发布,命中 REVIEW 级敏感词才转人工
|
||||
# PRE_REVIEW :全部先审后发(生产建议)
|
||||
comment-audit-mode: MACHINE_FIRST
|
||||
comment-max-length: 500
|
||||
comment-per-minute: 5
|
||||
download:
|
||||
# 这些值会通过 /app/downloads/policy 下发给客户端,改这里不用发版
|
||||
max-per-device: 20
|
||||
|
||||
Reference in new issue
Block a user