diff --git a/src/main/java/com/youlai/boot/admin/constant/AuditConstants.java b/src/main/java/com/youlai/boot/admin/constant/AuditConstants.java index 6af23d2..bb051a7 100644 --- a/src/main/java/com/youlai/boot/admin/constant/AuditConstants.java +++ b/src/main/java/com/youlai/boot/admin/constant/AuditConstants.java @@ -7,53 +7,36 @@ public final class AuditConstants { private AuditConstants() {} - /** 审核状态: 机审中 */ - public static final String AUDIT_REVIEWING = "reviewing"; - /** 审核状态: 通过 */ - public static final String AUDIT_PASSED = "passed"; - /** 审核状态: 不通过 */ - public static final String AUDIT_FAILED = "failed"; - /** 审核状态: 待人工 */ - public static final String AUDIT_MANUAL_REVIEW = "manual_review"; - /** 审核状态: 申诉中 */ - public static final String AUDIT_APPEALING = "appealing"; - - /** 任务状态: 审核中 */ - public static final String TASK_REVIEWING = "reviewing"; - /** 任务状态: 机审成功 */ - public static final String TASK_SUCCESS = "success"; - /** 任务状态: 转人工 */ - public static final String TASK_TO_MANUAL = "manual"; - - /** 任务结果: 通过 */ - public static final String RESULT_PASSED = "passed"; - /** 任务结果: 未通过 */ - public static final String RESULT_FAILED = "failed"; - - /** 风险等级: 无风险 */ + // ======================== 通用状态(task + audit 共用) ======================== + /** 审核中 */ + public static final String STATUS_REVIEWING = "reviewing"; + /** 审核通过 */ + public static final String STATUS_PASSED = "passed"; + /** 审核不通过 */ + public static final String STATUS_REJECTED = "rejected"; + /** 待人工审核 */ + public static final String STATUS_MANUAL_REVIEW = "manual_review"; + + // ======================== audit 专用状态 ======================== + /** 申诉中 */ + public static final String STATUS_APPEALING = "appealing"; + + // ======================== 风险等级 ======================== public static final String RISK_NONE = "none"; - /** 风险等级: 中等风险 */ public static final String RISK_MEDIUM = "medium"; - /** 风险等级: 高风险 */ public static final String RISK_HIGH = "high"; - /** 审核策略: 机审自决 */ + // ======================== 审核策略 ======================== public static final String STRATEGY_AUTO = "auto"; - /** 审核策略: 均衡 */ public static final String STRATEGY_NORMAL = "normal"; - /** 审核策略: 保守 */ public static final String STRATEGY_CAUTIOUS = "cautious"; - /** 审核类型: 机器审核(AI 自行裁决) */ + // ======================== 审核类型 ======================== public static final String AUDIT_TYPE_MACHINE = "machine"; - /** 审核类型: 人工审核(不调 AI,直接转人工) */ public static final String AUDIT_TYPE_MANUAL = "manual"; - /** 审核类型: 混合审核(AI 给出风险提示,交由人工判定) */ - public static final String AUDIT_TYPE_MIXED = "mixed"; - /** 触发类型: 自动触发(内容创建/修改时) */ - public static final String TRIGGER_AUTO = "auto"; - /** 触发类型: 举报触发 */ + // ======================== 触发类型 ======================== + public static final String TRIGGER_CREATE = "create"; public static final String TRIGGER_REPORT = "report"; } diff --git a/src/main/java/com/youlai/boot/admin/controller/AdoptionApplicationManageController.java b/src/main/java/com/youlai/boot/admin/controller/AdoptionApplicationManageController.java index cb0839e..636818d 100644 --- a/src/main/java/com/youlai/boot/admin/controller/AdoptionApplicationManageController.java +++ b/src/main/java/com/youlai/boot/admin/controller/AdoptionApplicationManageController.java @@ -37,4 +37,5 @@ public class AdoptionApplicationManageController { adoptionApplicationService.auditApplication(form); return Result.success(); } + } diff --git a/src/main/java/com/youlai/boot/admin/controller/ContentAuditConfigController.java b/src/main/java/com/youlai/boot/admin/controller/ContentAuditConfigController.java index d0c03b6..93d42c8 100644 --- a/src/main/java/com/youlai/boot/admin/controller/ContentAuditConfigController.java +++ b/src/main/java/com/youlai/boot/admin/controller/ContentAuditConfigController.java @@ -11,21 +11,17 @@ import com.youlai.boot.common.enums.ActionTypeEnum; import com.youlai.boot.common.enums.LogModuleEnum; import com.youlai.boot.common.result.PageResult; import com.youlai.boot.common.result.Result; +import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; - @Tag(name = "管理端-审核配置相关接口") @RestController @RequestMapping("/api/v1/admin/auditConfig") @RequiredArgsConstructor -@Slf4j public class ContentAuditConfigController { private final ContentAuditConfigService contentAuditConfigService; @@ -63,32 +59,11 @@ public class ContentAuditConfigController { return Result.success(); } - @Operation(summary = "阿里云OSS增量图片审核回调") + @Hidden + @Operation(summary = "阿里云OSS增量图片审核回调-测试") @PostMapping("/images/callback") public Result imageCallback(@RequestBody String body) { - log.info("OSS图片审核回调, body={}", body); - try { - // 解析 form-urlencoded: checksum=xxx&content=xxx%7B...%7D - String checksum = null; - String content = null; - for (String pair : body.split("&")) { - String[] kv = pair.split("=", 2); - if (kv.length == 2) { - if ("checksum".equals(kv[0])) { - checksum = kv[1]; - } else if ("content".equals(kv[0])) { - content = URLDecoder.decode(kv[1], StandardCharsets.UTF_8); - } - } - } - if (content != null) { - ossCallbackService.handleImageCallback(checksum, content); - } else { - log.warn("OSS回调content为空"); - } - } catch (Exception e) { - log.error("OSS回调处理异常", e); - } + ossCallbackService.handleImageCallback(body); return Result.success(); } diff --git a/src/main/java/com/youlai/boot/admin/job/AuditTimeoutJob.java b/src/main/java/com/youlai/boot/admin/job/AuditTimeoutJob.java index 31eea2e..09f985f 100644 --- a/src/main/java/com/youlai/boot/admin/job/AuditTimeoutJob.java +++ b/src/main/java/com/youlai/boot/admin/job/AuditTimeoutJob.java @@ -42,7 +42,7 @@ public class AuditTimeoutJob { long thresholdTimestamp = System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(timeoutMinutes); List stuckAudits = contentAuditService.lambdaQuery() - .eq(MiniContentAudit::getStatus, AuditConstants.AUDIT_REVIEWING) + .eq(MiniContentAudit::getStatus, AuditConstants.STATUS_REVIEWING) .lt(MiniContentAudit::getCreateTimestamp, thresholdTimestamp) .list(); @@ -54,15 +54,15 @@ public class AuditTimeoutJob { for (MiniContentAudit audit : stuckAudits) { try { - // 更新 status 为 manual_review,final_result 留空等待人工判定 + // 更新 status 为 manual_review contentAuditService.updateAuditStatus(audit.getId(), - AuditConstants.AUDIT_MANUAL_REVIEW, null); + AuditConstants.STATUS_MANUAL_REVIEW); - // 将关联的 reviewing 状态任务统一更新为 to_manual + // 将关联的 reviewing 状态任务统一更新为 manual_review contentAuditTaskService.lambdaUpdate() .eq(MiniContentAuditTask::getContentAuditId, audit.getId()) - .eq(MiniContentAuditTask::getStatus, AuditConstants.TASK_REVIEWING) - .set(MiniContentAuditTask::getStatus, AuditConstants.TASK_TO_MANUAL) + .eq(MiniContentAuditTask::getStatus, AuditConstants.STATUS_REVIEWING) + .set(MiniContentAuditTask::getStatus, AuditConstants.STATUS_MANUAL_REVIEW) .update(); log.info("超时审核已转人工, auditId={}, moduleCode={}, bizId={}", diff --git a/src/main/java/com/youlai/boot/admin/model/entity/MiniContentAudit.java b/src/main/java/com/youlai/boot/admin/model/entity/MiniContentAudit.java index 8fd5788..49ebcd5 100644 --- a/src/main/java/com/youlai/boot/admin/model/entity/MiniContentAudit.java +++ b/src/main/java/com/youlai/boot/admin/model/entity/MiniContentAudit.java @@ -40,18 +40,10 @@ public class MiniContentAudit implements Serializable { @Schema(description = "审核类型(从config快照)") private String auditType; - @TableField("trigger_type") - @Schema(description = "触发类型:auto自动 / report举报") - private String triggerType; - @TableField("status") - @Schema(description = "审核状态:reviewing机审中 / passed通过 / failed不通过 / manual_review待人工 / appealing申诉中") + @Schema(description = "审核状态:reviewing审核中 / passed通过 / manual_review 待人工审核 / rejected不通过 / appealing申诉中") private String status; - @TableField("final_result") - @Schema(description = "最终结果:passed通过 / failed未通过") - private String finalResult; - @TableField("operator") @Schema(description = "最后操作人ID") private Long operator; @@ -91,5 +83,9 @@ public class MiniContentAudit implements Serializable { @Schema(description = "逻辑删除标识(0-未删除 1-已删除)") private Boolean deleted; + @TableField("trigger_type") + @Schema(description = "触发来源:create发布/report举报") + private String triggerType; + } diff --git a/src/main/java/com/youlai/boot/admin/model/entity/MiniContentAuditConfig.java b/src/main/java/com/youlai/boot/admin/model/entity/MiniContentAuditConfig.java index 5241cb6..bab40b1 100644 --- a/src/main/java/com/youlai/boot/admin/model/entity/MiniContentAuditConfig.java +++ b/src/main/java/com/youlai/boot/admin/model/entity/MiniContentAuditConfig.java @@ -37,11 +37,11 @@ public class MiniContentAuditConfig implements Serializable { private Boolean auditEnable; @TableField("audit_type") - @Schema(description = "审核类型: machine机器 / manual手动 / mixed混合") + @Schema(description = "审核类型: machine机器 / manual手动") private String auditType; @TableField("risk_strategy") - @Schema(description = "机审风险策略:auto--none转passed,medium转failed, high转failed;normal--none转passed,medium转to_manual, high转failed;cautious--none转passed,medium转to_manual, high转to_manual;") + @Schema(description = "机审风险策略:auto:NONE → passed,MEDIUM → rejected (AI否决),HIGH → rejected (AI否决);normal:NONE → passed,MEDIUM → manual_review (转人工) ,HIGH → rejected (高风险直接否决); cautious: NONE → passed,MEDIUM → manual_review,HIGH → manual_review (全部不确定,交人工);") private String riskStrategy; @TableField("create_time") diff --git a/src/main/java/com/youlai/boot/admin/model/entity/MiniContentAuditTask.java b/src/main/java/com/youlai/boot/admin/model/entity/MiniContentAuditTask.java index 1534092..55633d9 100644 --- a/src/main/java/com/youlai/boot/admin/model/entity/MiniContentAuditTask.java +++ b/src/main/java/com/youlai/boot/admin/model/entity/MiniContentAuditTask.java @@ -41,11 +41,11 @@ public class MiniContentAuditTask implements Serializable { private String contentValue; @TableField("audit_type") - @Schema(description = "审核类型: machine机器 / manual手动 / mixed混合") + @Schema(description = "审核类型: machine机器 / manual手动 / mixed混合(来自mini_content_audit)") private String auditType; @TableField("status") - @Schema(description = "审核状态:reviewing审核中 / success成功 /manual手动") + @Schema(description = "审核状态:reviewing审核中 / passed通过 / manual_review待人工审核 / rejected 不通过(机审按策略判定 / 人工驳回)") private String status; @TableField("risk_level") @@ -58,7 +58,7 @@ public class MiniContentAuditTask implements Serializable { @TableField("confidence") @Schema(description = "机审信任度") - private Float confidence; + private Integer confidence; @TableField("description") @Schema(description = "机审描述") @@ -76,10 +76,6 @@ public class MiniContentAuditTask implements Serializable { @Schema(description = "机审异步视频任务id(轮询设置结果)") private String taskId; - @TableField("result") - @Schema(description = "passed通过 / failed未通过") - private String result; - @TableField("operator") @Schema(description = "人工审核操作人") private Long operator; diff --git a/src/main/java/com/youlai/boot/admin/model/form/AuditConfigForm.java b/src/main/java/com/youlai/boot/admin/model/form/AuditConfigForm.java index c3e8b0b..16d9e8a 100644 --- a/src/main/java/com/youlai/boot/admin/model/form/AuditConfigForm.java +++ b/src/main/java/com/youlai/boot/admin/model/form/AuditConfigForm.java @@ -22,7 +22,7 @@ public class AuditConfigForm { @Schema(description = "审核类型: machine / manual / mixed") private String auditType; - @Schema(description = "机器审核风险策略: 机审风险策略:auto--none转passed,medium转failed, high转failed;normal--none转passed,medium转to_manual, high转failed;cautious--none转passed,medium转to_manual, high转to_manual;") + @Schema(description = "机器审核风险策略: auto / normal / cautious") private String riskStrategy; } diff --git a/src/main/java/com/youlai/boot/admin/model/vo/AuditConfigVO.java b/src/main/java/com/youlai/boot/admin/model/vo/AuditConfigVO.java index 95ed389..fbef5aa 100644 --- a/src/main/java/com/youlai/boot/admin/model/vo/AuditConfigVO.java +++ b/src/main/java/com/youlai/boot/admin/model/vo/AuditConfigVO.java @@ -24,10 +24,10 @@ public class AuditConfigVO { @Schema(description = "是否开启审核, false-开启, true-关闭") private Boolean auditEnable; - @Schema(description = "审核类型: machine / manual / mixed") + @Schema(description = "审核类型: machine / manual") private String auditType; - @Schema(description = "机器审核风险策略: none / medium / high") + @Schema(description = "机器审核风险策略: auto / normal / cautious") private String riskStrategy; @Schema(description = "创建时间") diff --git a/src/main/java/com/youlai/boot/admin/service/AuditExecutorService.java b/src/main/java/com/youlai/boot/admin/service/AuditExecutorService.java index 52fc599..cd52c54 100644 --- a/src/main/java/com/youlai/boot/admin/service/AuditExecutorService.java +++ b/src/main/java/com/youlai/boot/admin/service/AuditExecutorService.java @@ -12,7 +12,7 @@ public interface AuditExecutorService { * @param moduleCode 业务模块: animal_note / adoption_diary / user_post / *_comment / user_avatar / user_nickname / user_introduction * @param bizId 业务数据ID * @param content 待审核内容 {text[], images[], video[]} - * @param triggerType 触发类型: auto自动 / report举报 + * @param triggerType 触发类型: create发布 / report举报 * @return {status: "passed"|"failed"|"manual_review", auditId: Long},配置关闭或无配置时返回null */ Map executeAudit(String moduleCode, Long bizId, AuditContentDTO content, String triggerType); diff --git a/src/main/java/com/youlai/boot/admin/service/ContentAuditService.java b/src/main/java/com/youlai/boot/admin/service/ContentAuditService.java index a988cb6..bfd4877 100644 --- a/src/main/java/com/youlai/boot/admin/service/ContentAuditService.java +++ b/src/main/java/com/youlai/boot/admin/service/ContentAuditService.java @@ -7,7 +7,7 @@ public interface ContentAuditService extends IService { MiniContentAudit createAudit(String moduleCode, Long bizId, String auditType, String triggerType); - void updateAuditStatus(Long auditId, String status, String finalResult); + void updateAuditStatus(Long auditId, String status); MiniContentAudit getByModuleAndBizId(String moduleCode, Long bizId); diff --git a/src/main/java/com/youlai/boot/admin/service/ContentAuditTaskService.java b/src/main/java/com/youlai/boot/admin/service/ContentAuditTaskService.java index 3dd3ae9..fb1b554 100644 --- a/src/main/java/com/youlai/boot/admin/service/ContentAuditTaskService.java +++ b/src/main/java/com/youlai/boot/admin/service/ContentAuditTaskService.java @@ -11,8 +11,8 @@ public interface ContentAuditTaskService extends IService void batchCreateTasks(Long auditId, String auditType, List texts, List images, List videos); /** 更新单个任务的机审结果 */ - void updateTaskMachineResult(Long taskId, String machineResult, String riskLevel, String result, String status, - String label, Float confidence, String description, String requestId); + void updateTaskMachineResult(Long taskId, String machineResult, String riskLevel, String status, + String label, Integer confidence, String description, String requestId); /** 查询某个审核汇总下的所有任务明细 */ List listTasksByAuditId(Long auditId); diff --git a/src/main/java/com/youlai/boot/admin/service/OssCallbackService.java b/src/main/java/com/youlai/boot/admin/service/OssCallbackService.java index 189fee5..2d0218a 100644 --- a/src/main/java/com/youlai/boot/admin/service/OssCallbackService.java +++ b/src/main/java/com/youlai/boot/admin/service/OssCallbackService.java @@ -3,6 +3,6 @@ package com.youlai.boot.admin.service; public interface OssCallbackService { /** 处理OSS图片审核增量回调 */ - void handleImageCallback(String checksum, String content); + void handleImageCallback(String body); } diff --git a/src/main/java/com/youlai/boot/admin/service/impl/AuditExecutorServiceImpl.java b/src/main/java/com/youlai/boot/admin/service/impl/AuditExecutorServiceImpl.java index ce10705..7e6fb63 100644 --- a/src/main/java/com/youlai/boot/admin/service/impl/AuditExecutorServiceImpl.java +++ b/src/main/java/com/youlai/boot/admin/service/impl/AuditExecutorServiceImpl.java @@ -1,6 +1,7 @@ package com.youlai.boot.admin.service.impl; import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.aliyun.green20220302.models.*; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; @@ -38,14 +39,14 @@ public class AuditExecutorServiceImpl implements AuditExecutorService { /** * 执行内容审核(供业务层在内容创建/修改后调用)。 * - * @param moduleCode 业务模块编码,对应 mini_content_audit_config.module_code - * @param bizId 业务数据ID(日记ID / 评论ID 等) + * @param moduleCode 业务模块编码 + * @param bizId 业务数据ID * @param content 待审核内容 {texts, images, videos} - * @return {status, auditId};配置关闭或无配置时返回 null,调用方以此判断是否跳过审核 + * @return {status, auditId};配置关闭或无配置时返回 null */ @Override public Map executeAudit(String moduleCode, Long bizId, AuditContentDTO content, String triggerType) { - // 1) 查询对应模块的审核配置 + // 1) 查询审核配置 MiniContentAuditConfig config = findAuditConfig(moduleCode); if (config == null || isAuditDisabled(config)) { return null; @@ -53,163 +54,45 @@ public class AuditExecutorServiceImpl implements AuditExecutorService { String auditType = config.getAuditType(); - // 2) 创建审核汇总记录 + // 2) 创建审核汇总 MiniContentAudit audit = contentAuditService.createAudit(moduleCode, bizId, auditType, triggerType); Long auditId = audit.getId(); - // 3) 拆解待审核内容,批量创建审核任务明细 + // 3) 拆解内容,批量创建审核任务 contentAuditTaskService.batchCreateTasks(auditId, auditType, content.getTexts(), content.getImages(), content.getVideos()); - // 4) 根据审核类型执行不同策略 + // 4) 按审核类型执行 if (AuditConstants.AUDIT_TYPE_MANUAL.equals(auditType)) { + // 人工审核 return executeManualAudit(auditId); } - if (AuditConstants.AUDIT_TYPE_MIXED.equals(auditType)) { - return executeMixedAudit(auditId); - } - // machine(默认):调 AI 并按策略裁决 + // machine:调 AI 并按策略裁决 String strictness = config.getRiskStrategy() != null ? config.getRiskStrategy() : AuditConstants.STRATEGY_NORMAL; List tasks = contentAuditTaskService.listTasksByAuditId(auditId); - executeBatchAuditByType(tasks, strictness); + executeBatchAuditByType(tasks, strictness); // 进入机器审核 List updatedTasks = contentAuditTaskService.listTasksByAuditId(auditId); return aggregateTaskResultsAndUpdateAudit(auditId, updatedTasks); } /** - * manual 审核:不调 AI,所有任务直接标记为 manual,audit 状态设为 manual_review + * manual 人工审核:不调 AI,所有任务直接标记为 manual_review,audit 状态设为 manual_review */ private Map executeManualAudit(Long auditId) { contentAuditTaskService.lambdaUpdate() .eq(MiniContentAuditTask::getContentAuditId, auditId) - .set(MiniContentAuditTask::getStatus, AuditConstants.TASK_TO_MANUAL) + .set(MiniContentAuditTask::getStatus, AuditConstants.STATUS_MANUAL_REVIEW) .update(); - contentAuditService.updateAuditStatus(auditId, AuditConstants.AUDIT_MANUAL_REVIEW, null); + contentAuditService.updateAuditStatus(auditId, AuditConstants.STATUS_MANUAL_REVIEW); Map result = new HashMap<>(); - result.put("status", AuditConstants.AUDIT_MANUAL_REVIEW); + result.put("status", AuditConstants.STATUS_MANUAL_REVIEW); result.put("auditId", auditId); return result; } /** - * mixed 审核:调 AI 仅记录 riskLevel + machineResult,不应用策略裁决,交由人工判定 - */ - private Map executeMixedAudit(Long auditId) { - List tasks = contentAuditTaskService.listTasksByAuditId(auditId); - executeBatchMixedAuditByType(tasks); - contentAuditService.updateAuditStatus(auditId, AuditConstants.AUDIT_MANUAL_REVIEW, null); - Map result = new HashMap<>(); - result.put("status", AuditConstants.AUDIT_MANUAL_REVIEW); - result.put("auditId", auditId); - return result; - } - - /** - * 按类型分组批量执行 mixed 机审(仅记录风险信息,不裁决) - */ - private void executeBatchMixedAuditByType(List tasks) { - Map> grouped = tasks.stream() - .collect(Collectors.groupingBy(MiniContentAuditTask::getContentType)); - - List textTasks = grouped.getOrDefault("text", List.of()); - if (!textTasks.isEmpty()) { - try { - List contents = textTasks.stream().map(MiniContentAuditTask::getContentValue).toList(); - TextModerationPlusResponse r = aliyunContentAuditUtil.batchTextModerationPlus(contents); - processBatchTextResponseForMixed(textTasks, r); - } catch (Exception e) { - log.error("mixed批量文本审核失败", e); - } - } - - List imageTasks = grouped.getOrDefault("image", List.of()); - if (!imageTasks.isEmpty()) { - try { - List urls = imageTasks.stream().map(MiniContentAuditTask::getContentValue).toList(); - ImageModerationResponse r = aliyunContentAuditUtil.batchImageModeration(urls); - processBatchImageResponseForMixed(imageTasks, r); - } catch (Exception e) { - log.error("mixed批量图片审核失败", e); - } - } - - List videoTasks = grouped.getOrDefault("video", List.of()); - for (MiniContentAuditTask task : videoTasks) { - try { - handleVideoAudit(task, task.getContentValue()); - } catch (Exception e) { - log.error("mixed视频审核失败, taskId={}", task.getId(), e); - } - } - } - - /** - * 批量文本审核结果仅记录风险信息(mixed 模式) - */ - private void processBatchTextResponseForMixed(List tasks, TextModerationPlusResponse response) { - String machineResultJson = JSON.toJSONString(response); - String requestId = extractRequestIdFromResponse(response); - List dataList = extractDataListFromResponse(response); - for (int i = 0; i < tasks.size(); i++) { - MiniContentAuditTask task = tasks.get(i); - if (i < (dataList != null ? dataList.size() : 0)) { - Object data = dataList.get(i); - String riskLevel = extractRiskLevelFromDataItem(data); - String label = extractLabelFromDataItem(data); - Float confidence = extractConfidenceFromDataItem(data); - String description = extractDescriptionFromDataItem(data); - contentAuditTaskService.updateTaskMachineResult( - task.getId(), machineResultJson, riskLevel, null, null, - label, confidence, description, requestId); - } - } - } - - /** - * 批量图片审核结果仅记录风险信息(mixed 模式) - */ - private void processBatchImageResponseForMixed(List tasks, ImageModerationResponse response) { - String machineResultJson = JSON.toJSONString(response); - String requestId = extractRequestIdFromResponse(response); - List dataList = extractDataListFromResponse(response); - for (int i = 0; i < tasks.size(); i++) { - MiniContentAuditTask task = tasks.get(i); - if (i < (dataList != null ? dataList.size() : 0)) { - Object data = dataList.get(i); - String riskLevel = extractRiskLevelFromDataItem(data); - String label = extractLabelFromDataItem(data); - Float confidence = extractConfidenceFromDataItem(data); - String description = extractDescriptionFromDataItem(data); - contentAuditTaskService.updateTaskMachineResult( - task.getId(), machineResultJson, riskLevel, null, null, - label, confidence, description, requestId); - } - } - } - - /** - * 查找审核配置。 - */ - private MiniContentAuditConfig findAuditConfig(String moduleCode) { - LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); - queryWrapper.eq(MiniContentAuditConfig::getModuleCode, moduleCode); - queryWrapper.eq(MiniContentAuditConfig::getDeleted, false); - queryWrapper.last("LIMIT 1"); - return contentAuditConfigService.getOne(queryWrapper); - } - - /** - * 判断审核是否已关闭。 - * MiniContentAuditConfig.auditEnable: false=开启审核, true=关闭审核 - */ - private boolean isAuditDisabled(MiniContentAuditConfig config) { - Boolean auditEnable = config.getAuditEnable(); - return Boolean.TRUE.equals(auditEnable); - } - - /** - * 按类型分组批量执行机审(machine 模式:调 AI 并按策略裁决) + * 按类型分组批量执行机审 */ private void executeBatchAuditByType(List tasks, String strictness) { Map> grouped = tasks.stream() @@ -225,7 +108,7 @@ public class AuditExecutorServiceImpl implements AuditExecutorService { log.error("批量文本审核失败", e); for (MiniContentAuditTask task : textTasks) { contentAuditTaskService.updateTaskMachineResult( - task.getId(), null, null, null, AuditConstants.TASK_TO_MANUAL, + task.getId(), null, null, AuditConstants.STATUS_MANUAL_REVIEW, null, null, null, null); } } @@ -233,15 +116,14 @@ public class AuditExecutorServiceImpl implements AuditExecutorService { List imageTasks = grouped.getOrDefault("image", List.of()); if (!imageTasks.isEmpty()) { - try { - List urls = imageTasks.stream().map(MiniContentAuditTask::getContentValue).toList(); - ImageModerationResponse r = aliyunContentAuditUtil.batchImageModeration(urls); - processBatchImageResponse(imageTasks, r, strictness); - } catch (Exception e) { - log.error("批量图片审核失败", e); - for (MiniContentAuditTask task : imageTasks) { + for (MiniContentAuditTask task : imageTasks) { + try { + ImageModerationResponse r = aliyunContentAuditUtil.imageModeration(task.getContentValue()); + processSingleImageResponse(task, r, strictness); + } catch (Exception e) { + log.error("图片审核失败, taskId={}", task.getId(), e); contentAuditTaskService.updateTaskMachineResult( - task.getId(), null, null, null, AuditConstants.TASK_TO_MANUAL, + task.getId(), null, null, AuditConstants.STATUS_MANUAL_REVIEW, null, null, null, null); } } @@ -255,29 +137,47 @@ public class AuditExecutorServiceImpl implements AuditExecutorService { /** * 批量文本审核结果处理(machine 模式:按策略裁决) + *

+ * body.data 结构:{ riskLevel: "high", result: [{ label, confidence, description }, ...] } */ private void processBatchTextResponse(List tasks, TextModerationPlusResponse response, String strictness) { String machineResultJson = JSON.toJSONString(response); String requestId = extractRequestIdFromResponse(response); - List dataList = extractDataListFromResponse(response); + + // body.data 是对象,不是数组 + JSONObject dataObj = extractDataObjectFromResponse(response); + if (dataObj == null) { + log.info("文本审核返回data为空, requestId={}", requestId); + for (MiniContentAuditTask task : tasks) { + contentAuditTaskService.updateTaskMachineResult( + task.getId(), machineResultJson, null, AuditConstants.STATUS_MANUAL_REVIEW, + null, null, null, requestId); + } + return; + } + + String overallRiskLevel = dataObj.getString("riskLevel"); + JSONArray resultArray = dataObj.getJSONArray("result"); + log.info("文本审核结果: requestId={}, riskLevel={}, resultSize={}", + requestId, overallRiskLevel, resultArray != null ? resultArray.size() : 0); + for (int i = 0; i < tasks.size(); i++) { MiniContentAuditTask task = tasks.get(i); - if (i < (dataList != null ? dataList.size() : 0)) { - Object data = dataList.get(i); - String riskLevel = extractRiskLevelFromDataItem(data); - String label = extractLabelFromDataItem(data); - Float confidence = extractConfidenceFromDataItem(data); - String description = extractDescriptionFromDataItem(data); - if (riskLevel == null) { + if (resultArray != null && i < resultArray.size()) { + JSONObject item = resultArray.getJSONObject(i); + String label = item.getString("label"); + Integer confidence = item.getInteger("confidence"); + String description = item.getString("description"); + log.info("文本审核[{}]: label={}, confidence={}, description={}", i, label, confidence, description); + + if (overallRiskLevel == null) { contentAuditTaskService.updateTaskMachineResult( - task.getId(), machineResultJson, null, null, AuditConstants.TASK_TO_MANUAL, + task.getId(), machineResultJson, null, AuditConstants.STATUS_MANUAL_REVIEW, label, confidence, description, requestId); } else { - String result = applyStrategy(riskLevel, strictness); - String taskStatus = AuditConstants.RESULT_PASSED.equals(result) - ? AuditConstants.TASK_SUCCESS : AuditConstants.TASK_TO_MANUAL; + String status = applyStrategy(overallRiskLevel, strictness); contentAuditTaskService.updateTaskMachineResult( - task.getId(), machineResultJson, riskLevel, result, taskStatus, + task.getId(), machineResultJson, overallRiskLevel, status, label, confidence, description, requestId); } } @@ -285,33 +185,37 @@ public class AuditExecutorServiceImpl implements AuditExecutorService { } /** - * 批量图片审核结果处理(machine 模式:按策略裁决) + * 单张图片审核结果处理(machine 模式:按策略裁决) */ - private void processBatchImageResponse(List tasks, ImageModerationResponse response, String strictness) { + private void processSingleImageResponse(MiniContentAuditTask task, ImageModerationResponse response, String strictness) { String machineResultJson = JSON.toJSONString(response); String requestId = extractRequestIdFromResponse(response); - List dataList = extractDataListFromResponse(response); - for (int i = 0; i < tasks.size(); i++) { - MiniContentAuditTask task = tasks.get(i); - if (i < (dataList != null ? dataList.size() : 0)) { - Object data = dataList.get(i); - String riskLevel = extractRiskLevelFromDataItem(data); - String label = extractLabelFromDataItem(data); - Float confidence = extractConfidenceFromDataItem(data); - String description = extractDescriptionFromDataItem(data); - if (riskLevel == null) { - contentAuditTaskService.updateTaskMachineResult( - task.getId(), machineResultJson, null, null, AuditConstants.TASK_TO_MANUAL, - label, confidence, description, requestId); - } else { - String result = applyStrategy(riskLevel, strictness); - String taskStatus = AuditConstants.RESULT_PASSED.equals(result) - ? AuditConstants.TASK_SUCCESS : AuditConstants.TASK_TO_MANUAL; - contentAuditTaskService.updateTaskMachineResult( - task.getId(), machineResultJson, riskLevel, result, taskStatus, - label, confidence, description, requestId); - } - } + + JSONObject dataObj = extractDataObjectFromResponse(response); + if (dataObj == null) { + log.info("图片审核返回data为空, taskId={}", task.getId()); + contentAuditTaskService.updateTaskMachineResult( + task.getId(), machineResultJson, null, AuditConstants.STATUS_MANUAL_REVIEW, + null, null, null, requestId); + return; + } + + String riskLevel = dataObj.getString("riskLevel"); + String label = dataObj.getString("label"); + int confidence = dataObj.getIntValue("confidence"); + String description = dataObj.getString("description"); + log.info("图片审核结果: taskId={}, riskLevel={}, label={}, confidence={}, description={}", + task.getId(), riskLevel, label, confidence, description); + + if (riskLevel == null) { + contentAuditTaskService.updateTaskMachineResult( + task.getId(), machineResultJson, null, AuditConstants.STATUS_MANUAL_REVIEW, + label, confidence, description, requestId); + } else { + String status = applyStrategy(riskLevel, strictness); + contentAuditTaskService.updateTaskMachineResult( + task.getId(), machineResultJson, riskLevel, status, + label, confidence, description, requestId); } } @@ -341,109 +245,62 @@ public class AuditExecutorServiceImpl implements AuditExecutorService { return response.getBody().getData().getTaskId(); } - /** 从响应的 body 中提取 requestId */ + /** 反射提取 body.requestId */ private String extractRequestIdFromResponse(Object response) { try { - Object body = invokeGetter(response, "getBody"); + Method getBody = response.getClass().getMethod("getBody"); + Object body = getBody.invoke(response); if (body == null) return null; - Object requestId = invokeGetter(body, "getRequestId"); + Method getRequestId = body.getClass().getMethod("getRequestId"); + Object requestId = getRequestId.invoke(body); return requestId != null ? requestId.toString() : null; } catch (Exception e) { return null; } } - /** 从响应的 body 中提取 data 列表 */ - private List extractDataListFromResponse(Object response) { + /** 反射提取 body.data,转为 JSONObject */ + private JSONObject extractDataObjectFromResponse(Object response) { try { - Object body = invokeGetter(response, "getBody"); + Method getBody = response.getClass().getMethod("getBody"); + Object body = getBody.invoke(response); if (body == null) return null; - Object data = invokeGetter(body, "getData"); - if (data instanceof List list) return list; - return null; - } catch (Exception e) { - return null; - } - } - - /** 从 data 列表中单个元素提取 riskLevel */ - private String extractRiskLevelFromDataItem(Object dataItem) { - try { - Object riskLevel = invokeGetter(dataItem, "getRiskLevel"); - return riskLevel != null ? riskLevel.toString() : null; - } catch (Exception e) { - return null; - } - } - - /** 从 data 列表中单个元素提取 label */ - private String extractLabelFromDataItem(Object dataItem) { - try { - Object label = invokeGetter(dataItem, "getLabel"); - return label != null ? label.toString() : null; + Method getData = body.getClass().getMethod("getData"); + Object data = getData.invoke(body); + if (data == null) return null; + return JSON.parseObject(JSON.toJSONString(data)); } catch (Exception e) { return null; } } - /** 从 data 列表中单个元素提取 confidence */ - private Float extractConfidenceFromDataItem(Object dataItem) { - try { - Object confidence = invokeGetter(dataItem, "getConfidence"); - if (confidence instanceof Number n) return n.floatValue(); - return null; - } catch (Exception e) { - return null; - } - } - - /** 从 data 列表中单个元素提取 description */ - private String extractDescriptionFromDataItem(Object dataItem) { - try { - Object description = invokeGetter(dataItem, "getDescription"); - return description != null ? description.toString() : null; - } catch (Exception e) { - return null; - } - } - - /** - * 反射调用目标对象的 getter 方法 - */ - private Object invokeGetter(Object target, String methodName) throws Exception { - Method method = target.getClass().getMethod(methodName); - return method.invoke(target); - } - // ================================================================ // 策略映射 // ================================================================ /** - * TODO 根据 strictness 策略,将 riskLevel 映射为任务 result。 + * 根据 riskLevel + strictness,返回 task status。 *

-     *   auto:     NONE→passed, MEDIUM→failed,     HIGH→failed
-     *   normal:   NONE→passed, MEDIUM→manual,  HIGH→failed
-     *   cautious: NONE→passed, MEDIUM→manual,  HIGH→manual
+     *   auto:     NONE→passed, MEDIUM→rejected, HIGH→rejected
+     *   normal:   NONE→passed, MEDIUM→manual_review, HIGH→rejected
+     *   cautious: NONE→passed, MEDIUM→manual_review, HIGH→manual_review
      * 
*/ private String applyStrategy(String riskLevel, String strictness) { if (AuditConstants.RISK_NONE.equals(riskLevel)) { - return AuditConstants.RESULT_PASSED; + return AuditConstants.STATUS_PASSED; } if (AuditConstants.STRATEGY_CAUTIOUS.equals(strictness)) { - // cautious: 非 NONE 只更新审核状态 → 一律转人工 - return null; + return AuditConstants.STATUS_MANUAL_REVIEW; } if (AuditConstants.STRATEGY_AUTO.equals(strictness)) { - // auto: 非 NONE 一律不通过 - return AuditConstants.RESULT_FAILED; + return AuditConstants.STATUS_REJECTED; } - // normal(默认):MEDIUM→ 审核状态转人工,HIGH→不通过 + // normal(默认):MEDIUM→manual_review, HIGH→rejected if (AuditConstants.RISK_MEDIUM.equals(riskLevel)) { - return null; + return AuditConstants.STATUS_MANUAL_REVIEW; } - return AuditConstants.RESULT_FAILED; + return AuditConstants.STATUS_REJECTED; } // ================================================================ @@ -451,74 +308,58 @@ public class AuditExecutorServiceImpl implements AuditExecutorService { // ================================================================ /** - * 汇总所有任务结果,更新 audit 最终状态。 + * 汇总所有任务状态,更新 audit。 *

* 汇总规则: - * - 任一 task.result = failed → audit.status = failed - * - 有 task.result = to_manual → audit.status = manual_review - * - 全部 task.result = passed → audit.status = passed - * - 视频任务未完成(result 为 null)→ 保持 reviewing - * - *

- * MiniContentAudit 字段含义: - * - status: reviewing(机审中) / passed(通过) / failed(不通过) / manual_review(待人工) / appealing(申诉中) - * - final_result: passed / failed(仅终态;manual_review 时留空,等待人工判定) + * - 任一 task.status = rejected → audit.status = rejected + * - 有 task.status = manual_review → audit.status = manual_review + * - 全部 task.status = passed → audit.status = passed + * - 视频任务未完成(status = reviewing)→ 保持 reviewing */ private Map aggregateTaskResultsAndUpdateAudit(Long auditId, List tasks) { - boolean hasFailedTask = false; - boolean hasManualTask = false; - boolean allTasksPassed = true; + boolean hasRejected = false; + boolean hasManualReview = false; + boolean allPassed = true; for (MiniContentAuditTask task : tasks) { - String taskResult = task.getResult(); + String taskStatus = task.getStatus(); - if (AuditConstants.RESULT_FAILED.equals(taskResult)) { - hasFailedTask = true; - allTasksPassed = false; - break; // 已有 failed,无需继续遍历 + if (AuditConstants.STATUS_REJECTED.equals(taskStatus)) { + hasRejected = true; + allPassed = false; + break; } - if (AuditConstants.TASK_TO_MANUAL.equals(task.getStatus())) { - hasManualTask = true; - allTasksPassed = false; - } else if (!AuditConstants.RESULT_PASSED.equals(taskResult)) { - // result 为 null 或其他未知值 → 未完成 - allTasksPassed = false; + if (AuditConstants.STATUS_MANUAL_REVIEW.equals(taskStatus)) { + hasManualReview = true; + allPassed = false; + } else if (!AuditConstants.STATUS_PASSED.equals(taskStatus)) { + allPassed = false; } } - // 视频任务 result 为 null 时, 不能算通过 + // 视频任务未完成时不能算通过 for (MiniContentAuditTask task : tasks) { - if ("video".equals(task.getContentType()) && task.getResult() == null) { - allTasksPassed = false; + if ("video".equals(task.getContentType()) && AuditConstants.STATUS_REVIEWING.equals(task.getStatus())) { + allPassed = false; break; } } - // 根据汇总结果确定 audit 状态 String auditStatus; - String finalResultValue; - - if (hasFailedTask) { - auditStatus = AuditConstants.AUDIT_FAILED; - finalResultValue = "failed"; - } else if (hasManualTask) { - auditStatus = AuditConstants.AUDIT_MANUAL_REVIEW; - finalResultValue = null; // 转人工不是终态,final_result 留空等待人工判定 - } else if (allTasksPassed && !tasks.isEmpty()) { - auditStatus = AuditConstants.AUDIT_PASSED; - finalResultValue = "passed"; + if (hasRejected) { + auditStatus = AuditConstants.STATUS_REJECTED; + } else if (hasManualReview) { + auditStatus = AuditConstants.STATUS_MANUAL_REVIEW; + } else if (allPassed && !tasks.isEmpty()) { + auditStatus = AuditConstants.STATUS_PASSED; } else { - // 有任务未完成(如视频异步等待中) - auditStatus = AuditConstants.AUDIT_REVIEWING; - finalResultValue = null; + auditStatus = AuditConstants.STATUS_REVIEWING; } - // 非 reviewing 状态下更新 audit(manual_review 虽无 finalResult 也要更新 status) - if (!AuditConstants.AUDIT_REVIEWING.equals(auditStatus)) { - contentAuditService.updateAuditStatus(auditId, auditStatus, finalResultValue); + if (!AuditConstants.STATUS_REVIEWING.equals(auditStatus)) { + contentAuditService.updateAuditStatus(auditId, auditStatus); } - // 返回给业务层的审核结果 Map resultMap = new HashMap<>(); resultMap.put("status", auditStatus); resultMap.put("auditId", auditId); @@ -529,12 +370,6 @@ public class AuditExecutorServiceImpl implements AuditExecutorService { // 视频异步审核结果轮询 // ================================================================ - /** - * 轮询所有待处理的视频审核异步结果,更新任务和汇总状态。 - * 供定时任务 {@code VideoAuditPollJob} 调用。 - * - * @return 本次成功处理的视频任务数量 - */ @Override public int pollVideoAuditResults() { List pendingTasks = contentAuditTaskService.getPendingVideoTasks(); @@ -557,7 +392,7 @@ public class AuditExecutorServiceImpl implements AuditExecutorService { } } - // 仅 machine 类型需要重新汇总,mixed 的 audit 已为 manual_review 不参与汇总 + // machine 类型需要重新汇总 for (Long auditId : affectedAuditIds) { try { MiniContentAudit audit = contentAuditService.getById(auditId); @@ -574,9 +409,6 @@ public class AuditExecutorServiceImpl implements AuditExecutorService { return processedCount; } - /** - * 处理单条视频审核任务的异步结果。返回 true 表示结果已回填完成。 - */ private boolean processSingleVideoTask(MiniContentAuditTask task) { String asyncTaskId = task.getTaskId(); VideoModerationResultResponse response = aliyunContentAuditUtil.videoModerationResult(asyncTaskId); @@ -604,54 +436,35 @@ public class AuditExecutorServiceImpl implements AuditExecutorService { return false; } - // 从阿里云响应中直接提取 riskLevel(none/medium/high) String riskLevel = extractVideoRiskLevel(dataJson); if (riskLevel == null) { log.warn("视频审核结果中未找到riskLevel, taskId={}", task.getId()); return false; } - // 查找关联的审核记录 MiniContentAudit audit = contentAuditService.getById(task.getContentAuditId()); if (audit == null) { log.warn("未找到关联审核记录, taskId={}", task.getId()); return false; } - // mixed 类型:记录机审结果,更新审核状态 manual,不裁决 - if (AuditConstants.AUDIT_TYPE_MIXED.equals(audit.getAuditType())) { - contentAuditTaskService.updateTaskMachineResult( - task.getId(), dataJson, riskLevel, null, "manual", - null, null, null, null); - log.info("视频任务AI分析完成(mixed), taskId={}, riskLevel={}", task.getId(), riskLevel); - return true; - } - - // machine 类型:按策略裁决 + // 查找配置获取策略 String strictness = AuditConstants.STRATEGY_NORMAL; MiniContentAuditConfig config = findAuditConfig(audit.getModuleCode()); if (config != null && config.getRiskStrategy() != null) { strictness = config.getRiskStrategy(); } - String result = applyStrategy(riskLevel, strictness); - String taskStatus = AuditConstants.RESULT_PASSED.equals(result) - ? AuditConstants.TASK_SUCCESS : AuditConstants.TASK_TO_MANUAL; - + String status = applyStrategy(riskLevel, strictness); contentAuditTaskService.updateTaskMachineResult( - task.getId(), dataJson, riskLevel, result, taskStatus, + task.getId(), dataJson, riskLevel, status, null, null, null, null); - log.info("视频任务审核完成, taskId={}, riskLevel={}, strictness={}, result={}", - task.getId(), riskLevel, strictness, result); + log.info("视频任务审核完成, taskId={}, riskLevel={}, strictness={}, status={}", + task.getId(), riskLevel, strictness, status); return true; } - /** - * 判断视频异步审核是否已完成。 - * 阿里云视频审核完成后 data 中会出现 RiskLevel 字段(none/medium/high), - * 未完成时该字段不存在。 - */ private boolean isVideoAuditCompleted(String dataJson) { try { JSONObject json = JSON.parseObject(dataJson); @@ -662,10 +475,6 @@ public class AuditExecutorServiceImpl implements AuditExecutorService { } } - /** - * 从视频审核结果JSON中直接提取 riskLevel(none/medium/high)。 - * 阿里云视频审核完成后 data 中包含小写 riskLevel 字段。 - */ private String extractVideoRiskLevel(String dataJson) { try { JSONObject json = JSON.parseObject(dataJson); @@ -675,4 +484,23 @@ public class AuditExecutorServiceImpl implements AuditExecutorService { return null; } } + + /** + * 查找审核配置。 + */ + private MiniContentAuditConfig findAuditConfig(String moduleCode) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(MiniContentAuditConfig::getModuleCode, moduleCode); + queryWrapper.eq(MiniContentAuditConfig::getDeleted, false); + queryWrapper.last("LIMIT 1"); + return contentAuditConfigService.getOne(queryWrapper); + } + + /** + * 判断审核是否已关闭。 + */ + private boolean isAuditDisabled(MiniContentAuditConfig config) { + Boolean auditEnable = config.getAuditEnable(); + return Boolean.TRUE.equals(auditEnable); + } } diff --git a/src/main/java/com/youlai/boot/admin/service/impl/ContentAuditAppealServiceImpl.java b/src/main/java/com/youlai/boot/admin/service/impl/ContentAuditAppealServiceImpl.java index 4e568ec..eaaf116 100644 --- a/src/main/java/com/youlai/boot/admin/service/impl/ContentAuditAppealServiceImpl.java +++ b/src/main/java/com/youlai/boot/admin/service/impl/ContentAuditAppealServiceImpl.java @@ -83,12 +83,12 @@ public class ContentAuditAppealServiceImpl implements ContentAuditAppealService if (audit != null) { if ("approved".equals(form.getResult())) { contentAuditService.updateAuditStatus(appeal.getAuditId(), - AuditConstants.AUDIT_PASSED, AuditConstants.RESULT_PASSED); + AuditConstants.STATUS_PASSED); log.info("申诉通过, auditId={}, 审核状态已改为passed", appeal.getAuditId()); } else { contentAuditService.updateAuditStatus(appeal.getAuditId(), - AuditConstants.AUDIT_FAILED, AuditConstants.RESULT_FAILED); - log.info("申诉驳回, auditId={}, 审核状态维持failed", appeal.getAuditId()); + AuditConstants.STATUS_REJECTED); + log.info("申诉驳回, auditId={}, 审核状态维持rejected", appeal.getAuditId()); } } } diff --git a/src/main/java/com/youlai/boot/admin/service/impl/ContentAuditServiceImpl.java b/src/main/java/com/youlai/boot/admin/service/impl/ContentAuditServiceImpl.java index a16c6dd..1a09721 100644 --- a/src/main/java/com/youlai/boot/admin/service/impl/ContentAuditServiceImpl.java +++ b/src/main/java/com/youlai/boot/admin/service/impl/ContentAuditServiceImpl.java @@ -23,7 +23,7 @@ public class ContentAuditServiceImpl extends ServiceImpl getPendingVideoTasks() { return this.list(new LambdaQueryWrapper() .eq(MiniContentAuditTask::getContentType, "video") - .eq(MiniContentAuditTask::getStatus, AuditConstants.TASK_REVIEWING) + .eq(MiniContentAuditTask::getStatus, AuditConstants.STATUS_REVIEWING) .isNotNull(MiniContentAuditTask::getTaskId) .ne(MiniContentAuditTask::getTaskId, "") .and(w -> w.isNull(MiniContentAuditTask::getMachineResult) diff --git a/src/main/java/com/youlai/boot/admin/service/impl/OssCallbackServiceImpl.java b/src/main/java/com/youlai/boot/admin/service/impl/OssCallbackServiceImpl.java index c6565a6..ef85017 100644 --- a/src/main/java/com/youlai/boot/admin/service/impl/OssCallbackServiceImpl.java +++ b/src/main/java/com/youlai/boot/admin/service/impl/OssCallbackServiceImpl.java @@ -4,29 +4,22 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.youlai.boot.admin.constant.AuditConstants; -import com.youlai.boot.admin.model.entity.MiniContentAudit; -import com.youlai.boot.admin.model.entity.MiniContentAuditConfig; import com.youlai.boot.admin.model.entity.MiniContentAuditTask; -import com.youlai.boot.admin.service.ContentAuditConfigService; -import com.youlai.boot.admin.service.ContentAuditService; import com.youlai.boot.admin.service.ContentAuditTaskService; import com.youlai.boot.admin.service.OssCallbackService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; -import java.util.List; @Service @Slf4j public class OssCallbackServiceImpl implements OssCallbackService { private final ContentAuditTaskService contentAuditTaskService; - private final ContentAuditService contentAuditService; - private final ContentAuditConfigService contentAuditConfigService; @Value("${audit.aliyun.oss.callbackUid}") private String callbackUid; @@ -34,16 +27,32 @@ public class OssCallbackServiceImpl implements OssCallbackService { @Value("${audit.aliyun.oss.callbackSeed}") private String callbackSeed; - public OssCallbackServiceImpl(ContentAuditTaskService contentAuditTaskService, - ContentAuditService contentAuditService, - ContentAuditConfigService contentAuditConfigService) { + public OssCallbackServiceImpl(ContentAuditTaskService contentAuditTaskService) { this.contentAuditTaskService = contentAuditTaskService; - this.contentAuditService = contentAuditService; - this.contentAuditConfigService = contentAuditConfigService; } @Override - public void handleImageCallback(String checksum, String content) { + public void handleImageCallback(String body) { + log.info("OSS图片审核回调, body={}", body); + + // 解析 form-urlencoded: checksum=xxx&content=xxx%7B...%7D + String checksum = null; + String content = null; + for (String pair : body.split("&")) { + String[] kv = pair.split("=", 2); + if (kv.length == 2) { + if ("checksum".equals(kv[0])) { + checksum = kv[1]; + } else if ("content".equals(kv[0])) { + content = URLDecoder.decode(kv[1], StandardCharsets.UTF_8); + } + } + } + if (content == null) { + log.warn("OSS回调content为空"); + return; + } + // 1. SHA256 验签 if (!verifyChecksum(content, checksum)) { log.warn("OSS回调验签失败, checksum={}, content={}", checksum, content); @@ -71,8 +80,7 @@ public class OssCallbackServiceImpl implements OssCallbackService { log.info("OSS图片审核回调, object={}, riskLevel={}, requestId={}", ossObjectName, riskLevel, requestId); - // 3. 根据 ossObjectName 匹配审核任务 //TODO 这个接口回调的是 OSS增量图片数据,如果用OSS内容审核,那么审核任务将不会存在,暂时先打印一下数据就行,另外想方案; - //TODO ,如果上传图片就创建一个审核任务,这样确实能这样处理,暂时只讨论方案不要实现; + // 3. 根据 ossObjectName 匹配审核任务 MiniContentAuditTask task = findTaskByImageName(ossObjectName); if (task == null) { log.warn("未找到匹配的审核任务, ossObjectName={}", ossObjectName); @@ -96,53 +104,8 @@ public class OssCallbackServiceImpl implements OssCallbackService { } } - // 5. 查找配置获取策略 - MiniContentAudit audit = contentAuditService.getById(task.getContentAuditId()); - String normalizedRisk = normalizeRiskLevel(riskLevel); - - if (audit != null && AuditConstants.AUDIT_TYPE_MACHINE.equals(audit.getAuditType())) { - // machine:按策略裁决 - MiniContentAuditConfig config = contentAuditConfigService.getOne( - new LambdaQueryWrapper() - .eq(MiniContentAuditConfig::getModuleCode, audit.getModuleCode()) - .eq(MiniContentAuditConfig::getDeleted, false) - .last("LIMIT 1")); - String strictness = (config != null && config.getRiskStrategy() != null) - ? config.getRiskStrategy() : AuditConstants.STRATEGY_NORMAL; - String result = applyStrategy(normalizedRisk, strictness); - String taskStatus = AuditConstants.RESULT_PASSED.equals(result) - ? AuditConstants.TASK_SUCCESS : AuditConstants.TASK_TO_MANUAL; - - contentAuditTaskService.updateTaskMachineResult( - task.getId(), payload.toJSONString(), normalizedRisk, result, taskStatus, - label, confidence, description, requestId); - - // 重新汇总 audit - List tasks = contentAuditTaskService.listTasksByAuditId(task.getContentAuditId()); - boolean hasFailed = false, hasManual = false, allPassed = true; - for (MiniContentAuditTask t : tasks) { - if (AuditConstants.RESULT_FAILED.equals(t.getResult())) { hasFailed = true; break; } - if (AuditConstants.TASK_TO_MANUAL.equals(t.getStatus())) hasManual = true; - if (!AuditConstants.RESULT_PASSED.equals(t.getResult())) allPassed = false; - } - for (MiniContentAuditTask t : tasks) { - if ("video".equals(t.getContentType()) && t.getResult() == null) { allPassed = false; break; } - } - if (hasFailed) { - contentAuditService.updateAuditStatus(audit.getId(), AuditConstants.AUDIT_FAILED, "failed"); - } else if (hasManual) { - contentAuditService.updateAuditStatus(audit.getId(), AuditConstants.AUDIT_MANUAL_REVIEW, null); - } else if (allPassed && !tasks.isEmpty()) { - contentAuditService.updateAuditStatus(audit.getId(), AuditConstants.AUDIT_PASSED, "passed"); - } - } else { - // mixed / manual:仅记录机审信息 - contentAuditTaskService.updateTaskMachineResult( - task.getId(), payload.toJSONString(), normalizedRisk, null, null, - label, confidence, description, requestId); - } - - log.info("OSS回调处理完成, taskId={}, riskLevel={}", task.getId(), normalizedRisk); + log.info("OSS回调解析完成, taskId={}, ossObjectName={}, riskLevel={}, label={}, confidence={}, description={}, requestId={}", + task.getId(), ossObjectName, riskLevel, label, confidence, description, requestId); } /** SHA256(uid + seed + content) 验签,对应阿里云控制台配置的加密算法 */ @@ -162,17 +125,6 @@ public class OssCallbackServiceImpl implements OssCallbackService { } } - /** 阿里云 riskLevel 统一为小写 */ - private String normalizeRiskLevel(String riskLevel) { - if (riskLevel == null) return null; - return switch (riskLevel.toLowerCase()) { - case "none", "normal" -> AuditConstants.RISK_NONE; - case "medium", "review" -> AuditConstants.RISK_MEDIUM; - case "high", "block" -> AuditConstants.RISK_HIGH; - default -> riskLevel.toLowerCase(); - }; - } - /** 通过 ossObjectName(如 v2-xxx_r.jpg)匹配审核任务中图片 URL 尾部 */ private MiniContentAuditTask findTaskByImageName(String ossObjectName) { if (ossObjectName == null) return null; @@ -181,12 +133,4 @@ public class OssCallbackServiceImpl implements OssCallbackService { .like(MiniContentAuditTask::getContentValue, ossObjectName) .last("LIMIT 1")); } - - private String applyStrategy(String riskLevel, String strictness) { - if (AuditConstants.RISK_NONE.equals(riskLevel)) return AuditConstants.RESULT_PASSED; - if (AuditConstants.STRATEGY_CAUTIOUS.equals(strictness)) return null; - if (AuditConstants.STRATEGY_AUTO.equals(strictness)) return AuditConstants.RESULT_FAILED; - if (AuditConstants.RISK_MEDIUM.equals(riskLevel)) return null; - return AuditConstants.RESULT_FAILED; - } } diff --git a/src/main/java/com/youlai/boot/common/util/AliyunContentAuditUtil.java b/src/main/java/com/youlai/boot/common/util/AliyunContentAuditUtil.java index 14aa61c..7f349c2 100644 --- a/src/main/java/com/youlai/boot/common/util/AliyunContentAuditUtil.java +++ b/src/main/java/com/youlai/boot/common/util/AliyunContentAuditUtil.java @@ -47,8 +47,6 @@ public class AliyunContentAuditUtil { // 批量审核上限 private static final int BATCH_TEXT_MAX = 100; private static final int BATCH_TEXT_SINGLE_MAX_CHARS = 600; - private static final int BATCH_IMAGE_MAX = 100; - private static final int BATCH_VIDEO_MAX = 10; /** * 构建客户端 @@ -214,29 +212,7 @@ public class AliyunContentAuditUtil { }); } - /** - * 批量图片审核(上限100张) - */ - public ImageModerationResponse batchImageModeration(List imageUrls) { - if (imageUrls == null || imageUrls.isEmpty()) { - return null; - } - if (imageUrls.size() > BATCH_IMAGE_MAX) { - throw new MsgException("批量图片审核单次上限" + BATCH_IMAGE_MAX + "张,当前" + imageUrls.size() + "张"); - } - return executeWithFailover(client -> { - JSONObject params = new JSONObject(); - JSONArray arr = new JSONArray(); - arr.addAll(imageUrls); - params.put("images", arr); - - ImageModerationRequest request = new ImageModerationRequest() - .setService("oss_baselineCheck") - .setServiceParameters(params.toJSONString()); - - return client.imageModeration(request); - }); - } + // 图片审核不支持批量,使用 imageModeration 逐个调用 // ===================== 视频审核(异步) ===================== //视频文件检测:videoDetection ; AI生成视频判定:videoAigcDetector ; 视频文件检测_大模型版:videoDetectionByVL @@ -253,29 +229,7 @@ public class AliyunContentAuditUtil { }); } - /** - * 批量视频审核(异步,上限10个) - */ - public VideoModerationResponse batchVideoModeration(List videoUrls) { - if (videoUrls == null || videoUrls.isEmpty()) { - return null; - } - if (videoUrls.size() > BATCH_VIDEO_MAX) { - throw new MsgException("批量视频审核单次上限" + BATCH_VIDEO_MAX + "个,当前" + videoUrls.size() + "个"); - } - return executeWithFailover(client -> { - JSONObject params = new JSONObject(); - JSONArray arr = new JSONArray(); - arr.addAll(videoUrls); - params.put("urls", arr); - - VideoModerationRequest request = new VideoModerationRequest() - .setService("videoDetection") - .setServiceParameters(params.toJSONString()); - - return client.videoModeration(request); - }); - } + // 视频审核不支持批量,使用 videoModeration 逐个调用 // ===================== 查询视频审核结果 ===================== public VideoModerationResultResponse videoModerationResult(String taskId) { diff --git a/src/main/java/com/youlai/boot/mini/service/impl/MiniContentAuditAppealServiceImpl.java b/src/main/java/com/youlai/boot/mini/service/impl/MiniContentAuditAppealServiceImpl.java index c6990a9..5ad530c 100644 --- a/src/main/java/com/youlai/boot/mini/service/impl/MiniContentAuditAppealServiceImpl.java +++ b/src/main/java/com/youlai/boot/mini/service/impl/MiniContentAuditAppealServiceImpl.java @@ -35,7 +35,7 @@ public class MiniContentAuditAppealServiceImpl implements MiniContentAuditAppeal if (audit == null) { throw new RuntimeException("审核记录不存在"); } - if (!AuditConstants.AUDIT_FAILED.equals(audit.getStatus())) { + if (!AuditConstants.STATUS_REJECTED.equals(audit.getStatus())) { throw new RuntimeException("当前审核状态不允许申诉"); } @@ -61,7 +61,7 @@ public class MiniContentAuditAppealServiceImpl implements MiniContentAuditAppeal appealMapper.insert(appeal); // 更新审核状态为申诉中 - contentAuditService.updateAuditStatus(form.getAuditId(), AuditConstants.AUDIT_APPEALING, null); + contentAuditService.updateAuditStatus(form.getAuditId(), AuditConstants.STATUS_APPEALING); log.info("用户提交申诉成功, auditId={}, userId={}", form.getAuditId(), userId); } diff --git a/src/main/java/com/youlai/boot/mini/service/impl/UserPostServiceImpl.java b/src/main/java/com/youlai/boot/mini/service/impl/UserPostServiceImpl.java index 5b32408..c769c1a 100644 --- a/src/main/java/com/youlai/boot/mini/service/impl/UserPostServiceImpl.java +++ b/src/main/java/com/youlai/boot/mini/service/impl/UserPostServiceImpl.java @@ -227,7 +227,7 @@ public class UserPostServiceImpl extends ServiceImpl auditResult = auditExecutorService.executeAudit("user_post", postId, auditContent, AuditConstants.TRIGGER_AUTO); + Map auditResult = auditExecutorService.executeAudit("user_post", postId, auditContent, AuditConstants.TRIGGER_CREATE); if (auditResult != null) { log.info("用户作品审核任务已创建, postId={}, auditResult={}", postId, auditResult); } @@ -282,7 +282,7 @@ public class UserPostServiceImpl extends ServiceImpl texts = List.of("我操你吗", "狗日的"); + TextModerationPlusResponse r = auditUtil.batchTextModerationPlus(texts); + log.info("=== 文本Plus批量 完整响应 ===\n{}", JSON.toJSONString(r, true)); + if (r != null && r.getBody() != null) { + log.info("body.code={}", r.getBody().getCode()); + log.info("body.data class={}", r.getBody().getData() != null ? r.getBody().getData().getClass().getName() : "null"); + log.info("body.data JSON: {}", JSON.toJSONString(r.getBody().getData(), true)); + } + } + + // ==================== 图片 ==================== + + @Test + @DisplayName("图片审核 - 单张") + void imageModerationSingle() { + String url = "https://pet-map.oss-cn-beijing.aliyuncs.com/user_post/image/17806506996944g1vs980.png"; + ImageModerationResponse r = auditUtil.imageModeration(url); + log.info("=== 图片单张 完整响应 ===\n{}", JSON.toJSONString(r, true)); + if (r != null && r.getBody() != null) { + log.info("body.code={}", r.getBody().getCode()); + } + } + +// @Test +// @DisplayName("图片审核 - 批量(当前实现,预期400)") +// void imageModerationBatch() { +// List urls = List.of( +// "https://pet-map.oss-cn-beijing.aliyuncs.com/user_post/image/17806506996944g1vs980.png", +// "https://pet-map.oss-cn-beijing.aliyuncs.com/user_post/image/another.png" +// ); +// ImageModerationResponse r = auditUtil.batchImageModeration(urls); +// log.info("=== 图片批量 完整响应 ===\n{}", JSON.toJSONString(r, true)); +// } + + // ==================== 视频 ==================== + + @Test + @DisplayName("视频审核 - 单条(异步,返回taskId)") + void videoModerationSingle() { + String url = "https://pet-map.oss-cn-beijing.aliyuncs.com/user_post/video/1780650699694okhqt697.mp4"; + VideoModerationResponse r = auditUtil.videoModeration(url); + log.info("=== 视频单条 完整响应 ===\n{}", JSON.toJSONString(r, true)); + if (r != null && r.getBody() != null && r.getBody().getData() != null) { + log.info("taskId={}", r.getBody().getData().getTaskId()); + } + } +} diff --git a/tmp/178065053524697cu0r6a b/tmp/178065053524697cu0r6a new file mode 100644 index 0000000..9bcce47 Binary files /dev/null and b/tmp/178065053524697cu0r6a differ