From 392d9ecfe89eb8eb87e71147ea3f0cf15a5aa8dc Mon Sep 17 00:00:00 2001 From: ziin Date: Thu, 29 Jan 2026 19:38:13 +0800 Subject: [PATCH] =?UTF-8?q?feat(ai-companion):=20=E6=96=B0=E5=A2=9EAI?= =?UTF-8?q?=E8=A7=92=E8=89=B2=E4=B8=BE=E6=8A=A5=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增举报接口 POST /ai-companion/report,支持多选举报类型 - 引入 KeyboardAiCompanionReportService 处理举报业务 - 补充举报相关错误码:类型无效、角色ID为空、类型为空 - 新增实体、DTO、Mapper、Service 及 XML 配置,完成举报数据持久化 --- .../com/yolo/keyborad/common/ErrorCode.java | 5 +- .../controller/AiCompanionController.java | 13 +++ .../KeyboardAiCompanionReportMapper.java | 12 +++ .../dto/companion/CompanionReportReq.java | 30 ++++++ .../entity/KeyboardAiCompanionReport.java | 99 +++++++++++++++++++ .../KeyboardAiCompanionReportService.java | 21 ++++ .../KeyboardAiCompanionReportServiceImpl.java | 77 +++++++++++++++ .../KeyboardAiCompanionReportMapper.xml | 24 +++++ 8 files changed, 280 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/yolo/keyborad/mapper/KeyboardAiCompanionReportMapper.java create mode 100644 src/main/java/com/yolo/keyborad/model/dto/companion/CompanionReportReq.java create mode 100644 src/main/java/com/yolo/keyborad/model/entity/KeyboardAiCompanionReport.java create mode 100644 src/main/java/com/yolo/keyborad/service/KeyboardAiCompanionReportService.java create mode 100644 src/main/java/com/yolo/keyborad/service/impl/KeyboardAiCompanionReportServiceImpl.java create mode 100644 src/main/resources/mapper/KeyboardAiCompanionReportMapper.xml diff --git a/src/main/java/com/yolo/keyborad/common/ErrorCode.java b/src/main/java/com/yolo/keyborad/common/ErrorCode.java index d4f5ac7..94d28c2 100644 --- a/src/main/java/com/yolo/keyborad/common/ErrorCode.java +++ b/src/main/java/com/yolo/keyborad/common/ErrorCode.java @@ -75,7 +75,10 @@ public enum ErrorCode { AUDIO_FILE_EMPTY(40016, "音频文件不能为空"), AUDIO_FILE_TOO_LARGE(40017, "音频文件过大"), AUDIO_FORMAT_NOT_SUPPORTED(40018, "音频格式不支持"), - STT_SERVICE_ERROR(50031, "语音转文字服务异常"); + STT_SERVICE_ERROR(50031, "语音转文字服务异常"), + REPORT_TYPE_INVALID(40020, "举报类型无效"), + REPORT_COMPANION_ID_EMPTY(40021, "被举报的AI角色ID不能为空"), + REPORT_TYPE_EMPTY(40022, "举报类型不能为空"); /** * 状态码 diff --git a/src/main/java/com/yolo/keyborad/controller/AiCompanionController.java b/src/main/java/com/yolo/keyborad/controller/AiCompanionController.java index a979c2f..cfc2554 100644 --- a/src/main/java/com/yolo/keyborad/controller/AiCompanionController.java +++ b/src/main/java/com/yolo/keyborad/controller/AiCompanionController.java @@ -8,8 +8,10 @@ import com.yolo.keyborad.common.ResultUtils; import com.yolo.keyborad.exception.BusinessException; import com.yolo.keyborad.model.dto.PageDTO; import com.yolo.keyborad.model.dto.companion.CompanionLikeReq; +import com.yolo.keyborad.model.dto.companion.CompanionReportReq; import com.yolo.keyborad.model.vo.AiCompanionVO; import com.yolo.keyborad.service.KeyboardAiCompanionLikeService; +import com.yolo.keyborad.service.KeyboardAiCompanionReportService; import com.yolo.keyborad.service.KeyboardAiCompanionService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -35,6 +37,9 @@ public class AiCompanionController { @Resource private KeyboardAiCompanionLikeService aiCompanionLikeService; + @Resource + private KeyboardAiCompanionReportService aiCompanionReportService; + @PostMapping("/page") @Operation(summary = "分页查询AI陪聊角色", description = "分页查询已上线的AI陪聊角色列表,包含点赞数、评论数和当前用户点赞状态") public BaseResponse> pageList(@RequestBody PageDTO pageDTO) { @@ -81,4 +86,12 @@ public class AiCompanionController { AiCompanionVO result = aiCompanionService.getCompanionById(userId, companionId); return ResultUtils.success(result); } + + @PostMapping("/report") + @Operation(summary = "举报AI角色", description = "举报AI角色,支持多种举报类型(可多选):1=色情低俗, 2=政治敏感, 3=暴力恐怖, 4=侵权/冒充, 5=价值观问题, 99=其他") + public BaseResponse reportCompanion(@RequestBody CompanionReportReq req) { + Long userId = StpUtil.getLoginIdAsLong(); + Long reportId = aiCompanionReportService.reportCompanion(userId, req); + return ResultUtils.success(reportId); + } } diff --git a/src/main/java/com/yolo/keyborad/mapper/KeyboardAiCompanionReportMapper.java b/src/main/java/com/yolo/keyborad/mapper/KeyboardAiCompanionReportMapper.java new file mode 100644 index 0000000..25fda96 --- /dev/null +++ b/src/main/java/com/yolo/keyborad/mapper/KeyboardAiCompanionReportMapper.java @@ -0,0 +1,12 @@ +package com.yolo.keyborad.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yolo.keyborad.model.entity.KeyboardAiCompanionReport; + +/* +* @author: ziin +* @date: 2026/1/29 16:17 +*/ + +public interface KeyboardAiCompanionReportMapper extends BaseMapper { +} \ No newline at end of file diff --git a/src/main/java/com/yolo/keyborad/model/dto/companion/CompanionReportReq.java b/src/main/java/com/yolo/keyborad/model/dto/companion/CompanionReportReq.java new file mode 100644 index 0000000..46426ce --- /dev/null +++ b/src/main/java/com/yolo/keyborad/model/dto/companion/CompanionReportReq.java @@ -0,0 +1,30 @@ +package com.yolo.keyborad.model.dto.companion; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +/* + * @author: ziin + * @date: 2026/1/29 + */ +@Data +@Schema(description = "AI角色举报请求") +public class CompanionReportReq { + + @Schema(description = "AI角色ID", requiredMode = Schema.RequiredMode.REQUIRED) + private Long companionId; + + @Schema(description = "举报类型列表:1=色情低俗, 2=政治敏感, 3=暴力恐怖, 4=侵权/冒充, 5=价值观问题, 99=其他,支持多选", requiredMode = Schema.RequiredMode.REQUIRED) + private List reportTypes; + + @Schema(description = "详细描述") + private String reportDesc; + + @Schema(description = "聊天上下文快照JSON") + private String chatContext; + + @Schema(description = "图片证据URL") + private String evidenceImageUrl; +} diff --git a/src/main/java/com/yolo/keyborad/model/entity/KeyboardAiCompanionReport.java b/src/main/java/com/yolo/keyborad/model/entity/KeyboardAiCompanionReport.java new file mode 100644 index 0000000..63138ce --- /dev/null +++ b/src/main/java/com/yolo/keyborad/model/entity/KeyboardAiCompanionReport.java @@ -0,0 +1,99 @@ +package com.yolo.keyborad.model.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 io.swagger.v3.oas.annotations.media.Schema; +import java.util.Date; +import lombok.Data; + +/* +* @author: ziin +* @date: 2026/1/29 16:17 +*/ + +/** + * AI角色举报记录表 + */ +@Schema(description="AI角色举报记录表") +@Data +@TableName(value = "keyboard_ai_companion_report") +public class KeyboardAiCompanionReport { + /** + * 举报记录唯一ID + */ + @TableId(value = "id", type = IdType.AUTO) + @Schema(description="举报记录唯一ID") + private Long id; + + /** + * 被举报的AI角色ID(逻辑关联 keyboard_ai_companion.id,无物理外键) + */ + @TableField(value = "companion_id") + @Schema(description="被举报的AI角色ID(逻辑关联 keyboard_ai_companion.id,无物理外键)") + private Long companionId; + + /** + * 发起举报的用户ID(逻辑关联用户表) + */ + @TableField(value = "user_id") + @Schema(description="发起举报的用户ID(逻辑关联用户表)") + private Long userId; + + /** + * 举报类型:1=色情低俗, 2=政治敏感, 3=暴力恐怖, 4=侵权/冒充, 5=价值观问题, 99=其他,多选时逗号分隔 + */ + @TableField(value = "report_type") + @Schema(description="举报类型:1=色情低俗, 2=政治敏感, 3=暴力恐怖, 4=侵权/冒充, 5=价值观问题, 99=其他,多选时逗号分隔") + private String reportType; + + /** + * 用户填写的详细举报描述 + */ + @TableField(value = "report_desc") + @Schema(description="用户填写的详细举报描述") + private String reportDesc; + + /** + * 违规现场:举报时的聊天上下文快照(建议存JSON字符串),用于审核取证 + */ + @TableField(value = "chat_context") + @Schema(description="违规现场:举报时的聊天上下文快照(建议存JSON字符串),用于审核取证") + private String chatContext; + + /** + * 图片证据:用户上传的截图URL + */ + @TableField(value = "evidence_image_url") + @Schema(description="图片证据:用户上传的截图URL") + private String evidenceImageUrl; + + /** + * 处理状态:0=待处理, 1=违规确立(已处罚), 2=无效举报/已驳回, 3=已忽略 + */ + @TableField(value = "\"status\"") + @Schema(description="处理状态:0=待处理, 1=违规确立(已处罚), 2=无效举报/已驳回, 3=已忽略") + private Short status; + + /** + * 管理员处理备注(记录处理理由或处罚措施) + */ + @TableField(value = "admin_remark") + @Schema(description="管理员处理备注(记录处理理由或处罚措施)") + private String adminRemark; + + /** + * 举报提交时间 + */ + @TableField(value = "created_at") + @Schema(description="举报提交时间") + private Date createdAt; + + /** + * 最后更新时间 + */ + @TableField(value = "updated_at") + @Schema(description="最后更新时间") + private Date updatedAt; +} \ No newline at end of file diff --git a/src/main/java/com/yolo/keyborad/service/KeyboardAiCompanionReportService.java b/src/main/java/com/yolo/keyborad/service/KeyboardAiCompanionReportService.java new file mode 100644 index 0000000..fe7e6a2 --- /dev/null +++ b/src/main/java/com/yolo/keyborad/service/KeyboardAiCompanionReportService.java @@ -0,0 +1,21 @@ +package com.yolo.keyborad.service; + +import com.yolo.keyborad.model.dto.companion.CompanionReportReq; +import com.yolo.keyborad.model.entity.KeyboardAiCompanionReport; +import com.baomidou.mybatisplus.extension.service.IService; + /* +* @author: ziin +* @date: 2026/1/29 16:17 +*/ + +public interface KeyboardAiCompanionReportService extends IService{ + + /** + * 举报AI角色 + * @param userId 用户ID + * @param req 举报请求 + * @return 举报记录ID + */ + Long reportCompanion(Long userId, CompanionReportReq req); + +} diff --git a/src/main/java/com/yolo/keyborad/service/impl/KeyboardAiCompanionReportServiceImpl.java b/src/main/java/com/yolo/keyborad/service/impl/KeyboardAiCompanionReportServiceImpl.java new file mode 100644 index 0000000..3aeb151 --- /dev/null +++ b/src/main/java/com/yolo/keyborad/service/impl/KeyboardAiCompanionReportServiceImpl.java @@ -0,0 +1,77 @@ +package com.yolo.keyborad.service.impl; + +import com.yolo.keyborad.common.ErrorCode; +import com.yolo.keyborad.exception.BusinessException; +import com.yolo.keyborad.model.dto.companion.CompanionReportReq; +import com.yolo.keyborad.model.entity.KeyboardAiCompanion; +import com.yolo.keyborad.service.KeyboardAiCompanionService; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.beans.factory.annotation.Autowired; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yolo.keyborad.mapper.KeyboardAiCompanionReportMapper; +import com.yolo.keyborad.model.entity.KeyboardAiCompanionReport; +import com.yolo.keyborad.service.KeyboardAiCompanionReportService; +/* +* @author: ziin +* @date: 2026/1/29 16:17 +*/ + +@Service +public class KeyboardAiCompanionReportServiceImpl extends ServiceImpl implements KeyboardAiCompanionReportService{ + + @Resource + private KeyboardAiCompanionService aiCompanionService; + + @Override + public Long reportCompanion(Long userId, CompanionReportReq req) { + // 校验 companionId 不为空 + if (req.getCompanionId() == null) { + throw new BusinessException(ErrorCode.REPORT_COMPANION_ID_EMPTY); + } + + // 校验 reportTypes 不为空 + if (req.getReportTypes() == null || req.getReportTypes().isEmpty()) { + throw new BusinessException(ErrorCode.REPORT_TYPE_EMPTY); + } + + // 校验每个 reportType 在有效范围内(1,2,3,4,5,99) + List validTypes = List.of((short) 1, (short) 2, (short) 3, (short) 4, (short) 5, (short) 99); + for (Short type : req.getReportTypes()) { + if (!validTypes.contains(type)) { + throw new BusinessException(ErrorCode.REPORT_TYPE_INVALID); + } + } + + // 校验 AI 角色是否存在 + KeyboardAiCompanion companion = aiCompanionService.getById(req.getCompanionId()); + if (companion == null) { + throw new BusinessException(ErrorCode.COMPANION_NOT_FOUND); + } + + // 创建举报记录 + KeyboardAiCompanionReport report = new KeyboardAiCompanionReport(); + report.setUserId(userId); + report.setCompanionId(req.getCompanionId()); + + // 将 List 转换为逗号分隔的字符串 + String reportTypeStr = req.getReportTypes().stream() + .map(String::valueOf) + .collect(Collectors.joining(",")); + report.setReportType(reportTypeStr); + + report.setReportDesc(req.getReportDesc()); + report.setChatContext(req.getChatContext()); + report.setEvidenceImageUrl(req.getEvidenceImageUrl()); + report.setStatus((short) 0); // 待处理 + report.setCreatedAt(new Date()); + + // 保存并返回 ID + this.save(report); + return report.getId(); + } + +} diff --git a/src/main/resources/mapper/KeyboardAiCompanionReportMapper.xml b/src/main/resources/mapper/KeyboardAiCompanionReportMapper.xml new file mode 100644 index 0000000..cab616f --- /dev/null +++ b/src/main/resources/mapper/KeyboardAiCompanionReportMapper.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + id, companion_id, user_id, report_type, report_desc, chat_context, evidence_image_url, + "status", admin_remark, created_at, updated_at + + \ No newline at end of file