Browse Source

统一审核任务状态

glx_phase2
glx 1 week ago
parent
commit
bfef04ae63
  1. 55
      src/main/java/com/youlai/boot/admin/constant/AuditConstants.java
  2. 1
      src/main/java/com/youlai/boot/admin/controller/AdoptionApplicationManageController.java
  3. 33
      src/main/java/com/youlai/boot/admin/controller/ContentAuditConfigController.java
  4. 12
      src/main/java/com/youlai/boot/admin/job/AuditTimeoutJob.java
  5. 14
      src/main/java/com/youlai/boot/admin/model/entity/MiniContentAudit.java
  6. 4
      src/main/java/com/youlai/boot/admin/model/entity/MiniContentAuditConfig.java
  7. 10
      src/main/java/com/youlai/boot/admin/model/entity/MiniContentAuditTask.java
  8. 2
      src/main/java/com/youlai/boot/admin/model/form/AuditConfigForm.java
  9. 4
      src/main/java/com/youlai/boot/admin/model/vo/AuditConfigVO.java
  10. 2
      src/main/java/com/youlai/boot/admin/service/AuditExecutorService.java
  11. 2
      src/main/java/com/youlai/boot/admin/service/ContentAuditService.java
  12. 4
      src/main/java/com/youlai/boot/admin/service/ContentAuditTaskService.java
  13. 2
      src/main/java/com/youlai/boot/admin/service/OssCallbackService.java
  14. 478
      src/main/java/com/youlai/boot/admin/service/impl/AuditExecutorServiceImpl.java
  15. 6
      src/main/java/com/youlai/boot/admin/service/impl/ContentAuditAppealServiceImpl.java
  16. 5
      src/main/java/com/youlai/boot/admin/service/impl/ContentAuditServiceImpl.java
  17. 9
      src/main/java/com/youlai/boot/admin/service/impl/ContentAuditTaskServiceImpl.java
  18. 108
      src/main/java/com/youlai/boot/admin/service/impl/OssCallbackServiceImpl.java
  19. 50
      src/main/java/com/youlai/boot/common/util/AliyunContentAuditUtil.java
  20. 4
      src/main/java/com/youlai/boot/mini/service/impl/MiniContentAuditAppealServiceImpl.java
  21. 6
      src/main/java/com/youlai/boot/mini/service/impl/UserPostServiceImpl.java
  22. 83
      src/test/java/com/youlai/boot/admin/AliyunContentAuditTest.java
  23. BIN
      tmp/178065053524697cu0r6a

55
src/main/java/com/youlai/boot/admin/constant/AuditConstants.java

@ -7,53 +7,36 @@ public final class AuditConstants {
private AuditConstants() {} private AuditConstants() {}
/** 审核状态: 机审中 */ // ======================== 通用状态(task + audit 共用) ========================
public static final String AUDIT_REVIEWING = "reviewing"; /** 审核中 */
/** 审核状态: 通过 */ public static final String STATUS_REVIEWING = "reviewing";
public static final String AUDIT_PASSED = "passed"; /** 审核通过 */
/** 审核状态: 不通过 */ public static final String STATUS_PASSED = "passed";
public static final String AUDIT_FAILED = "failed"; /** 审核不通过 */
/** 审核状态: 待人工 */ public static final String STATUS_REJECTED = "rejected";
public static final String AUDIT_MANUAL_REVIEW = "manual_review"; /** 待人工审核 */
/** 审核状态: 申诉中 */ public static final String STATUS_MANUAL_REVIEW = "manual_review";
public static final String AUDIT_APPEALING = "appealing";
// ======================== audit 专用状态 ========================
/** 任务状态: 审核中 */ /** 申诉中 */
public static final String TASK_REVIEWING = "reviewing"; public static final String STATUS_APPEALING = "appealing";
/** 任务状态: 机审成功 */
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";
/** 风险等级: 无风险 */
public static final String RISK_NONE = "none"; public static final String RISK_NONE = "none";
/** 风险等级: 中等风险 */
public static final String RISK_MEDIUM = "medium"; public static final String RISK_MEDIUM = "medium";
/** 风险等级: 高风险 */
public static final String RISK_HIGH = "high"; public static final String RISK_HIGH = "high";
/** 审核策略: 机审自决 */ // ======================== 审核策略 ========================
public static final String STRATEGY_AUTO = "auto"; public static final String STRATEGY_AUTO = "auto";
/** 审核策略: 均衡 */
public static final String STRATEGY_NORMAL = "normal"; public static final String STRATEGY_NORMAL = "normal";
/** 审核策略: 保守 */
public static final String STRATEGY_CAUTIOUS = "cautious"; public static final String STRATEGY_CAUTIOUS = "cautious";
/** 审核类型: 机器审核(AI 自行裁决) */ // ======================== 审核类型 ========================
public static final String AUDIT_TYPE_MACHINE = "machine"; public static final String AUDIT_TYPE_MACHINE = "machine";
/** 审核类型: 人工审核(不调 AI,直接转人工) */
public static final String AUDIT_TYPE_MANUAL = "manual"; 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"; public static final String TRIGGER_REPORT = "report";
} }

1
src/main/java/com/youlai/boot/admin/controller/AdoptionApplicationManageController.java

@ -37,4 +37,5 @@ public class AdoptionApplicationManageController {
adoptionApplicationService.auditApplication(form); adoptionApplicationService.auditApplication(form);
return Result.success(); return Result.success();
} }
} }

33
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.enums.LogModuleEnum;
import com.youlai.boot.common.result.PageResult; import com.youlai.boot.common.result.PageResult;
import com.youlai.boot.common.result.Result; 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.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
@Tag(name = "管理端-审核配置相关接口") @Tag(name = "管理端-审核配置相关接口")
@RestController @RestController
@RequestMapping("/api/v1/admin/auditConfig") @RequestMapping("/api/v1/admin/auditConfig")
@RequiredArgsConstructor @RequiredArgsConstructor
@Slf4j
public class ContentAuditConfigController { public class ContentAuditConfigController {
private final ContentAuditConfigService contentAuditConfigService; private final ContentAuditConfigService contentAuditConfigService;
@ -63,32 +59,11 @@ public class ContentAuditConfigController {
return Result.success(); return Result.success();
} }
@Operation(summary = "阿里云OSS增量图片审核回调") @Hidden
@Operation(summary = "阿里云OSS增量图片审核回调-测试")
@PostMapping("/images/callback") @PostMapping("/images/callback")
public Result<Void> imageCallback(@RequestBody String body) { public Result<Void> imageCallback(@RequestBody String body) {
log.info("OSS图片审核回调, body={}", body); ossCallbackService.handleImageCallback(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);
}
return Result.success(); return Result.success();
} }

12
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); long thresholdTimestamp = System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(timeoutMinutes);
List<MiniContentAudit> stuckAudits = contentAuditService.lambdaQuery() List<MiniContentAudit> stuckAudits = contentAuditService.lambdaQuery()
.eq(MiniContentAudit::getStatus, AuditConstants.AUDIT_REVIEWING) .eq(MiniContentAudit::getStatus, AuditConstants.STATUS_REVIEWING)
.lt(MiniContentAudit::getCreateTimestamp, thresholdTimestamp) .lt(MiniContentAudit::getCreateTimestamp, thresholdTimestamp)
.list(); .list();
@ -54,15 +54,15 @@ public class AuditTimeoutJob {
for (MiniContentAudit audit : stuckAudits) { for (MiniContentAudit audit : stuckAudits) {
try { try {
// 更新 status 为 manual_review,final_result 留空等待人工判定 // 更新 status 为 manual_review
contentAuditService.updateAuditStatus(audit.getId(), contentAuditService.updateAuditStatus(audit.getId(),
AuditConstants.AUDIT_MANUAL_REVIEW, null); AuditConstants.STATUS_MANUAL_REVIEW);
// 将关联的 reviewing 状态任务统一更新为 to_manual // 将关联的 reviewing 状态任务统一更新为 manual_review
contentAuditTaskService.lambdaUpdate() contentAuditTaskService.lambdaUpdate()
.eq(MiniContentAuditTask::getContentAuditId, audit.getId()) .eq(MiniContentAuditTask::getContentAuditId, audit.getId())
.eq(MiniContentAuditTask::getStatus, AuditConstants.TASK_REVIEWING) .eq(MiniContentAuditTask::getStatus, AuditConstants.STATUS_REVIEWING)
.set(MiniContentAuditTask::getStatus, AuditConstants.TASK_TO_MANUAL) .set(MiniContentAuditTask::getStatus, AuditConstants.STATUS_MANUAL_REVIEW)
.update(); .update();
log.info("超时审核已转人工, auditId={}, moduleCode={}, bizId={}", log.info("超时审核已转人工, auditId={}, moduleCode={}, bizId={}",

14
src/main/java/com/youlai/boot/admin/model/entity/MiniContentAudit.java

@ -40,18 +40,10 @@ public class MiniContentAudit implements Serializable {
@Schema(description = "审核类型(从config快照)") @Schema(description = "审核类型(从config快照)")
private String auditType; private String auditType;
@TableField("trigger_type")
@Schema(description = "触发类型:auto自动 / report举报")
private String triggerType;
@TableField("status") @TableField("status")
@Schema(description = "审核状态:reviewing机审中 / passed通过 / failed不通过 / manual_review待人工 / appealing申诉中") @Schema(description = "审核状态:reviewing审核中 / passed通过 / manual_review 待人工审核 / rejected不通过 / appealing申诉中")
private String status; private String status;
@TableField("final_result")
@Schema(description = "最终结果:passed通过 / failed未通过")
private String finalResult;
@TableField("operator") @TableField("operator")
@Schema(description = "最后操作人ID") @Schema(description = "最后操作人ID")
private Long operator; private Long operator;
@ -91,5 +83,9 @@ public class MiniContentAudit implements Serializable {
@Schema(description = "逻辑删除标识(0-未删除 1-已删除)") @Schema(description = "逻辑删除标识(0-未删除 1-已删除)")
private Boolean deleted; private Boolean deleted;
@TableField("trigger_type")
@Schema(description = "触发来源:create发布/report举报")
private String triggerType;
} }

4
src/main/java/com/youlai/boot/admin/model/entity/MiniContentAuditConfig.java

@ -37,11 +37,11 @@ public class MiniContentAuditConfig implements Serializable {
private Boolean auditEnable; private Boolean auditEnable;
@TableField("audit_type") @TableField("audit_type")
@Schema(description = "审核类型: machine机器 / manual手动 / mixed混合") @Schema(description = "审核类型: machine机器 / manual手动")
private String auditType; private String auditType;
@TableField("risk_strategy") @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; private String riskStrategy;
@TableField("create_time") @TableField("create_time")

10
src/main/java/com/youlai/boot/admin/model/entity/MiniContentAuditTask.java

@ -41,11 +41,11 @@ public class MiniContentAuditTask implements Serializable {
private String contentValue; private String contentValue;
@TableField("audit_type") @TableField("audit_type")
@Schema(description = "审核类型: machine机器 / manual手动 / mixed混合") @Schema(description = "审核类型: machine机器 / manual手动 / mixed混合(来自mini_content_audit)")
private String auditType; private String auditType;
@TableField("status") @TableField("status")
@Schema(description = "审核状态:reviewing审核中 / success成功 /manual手动") @Schema(description = "审核状态:reviewing审核中 / passed通过 / manual_review待人工审核 / rejected 不通过(机审按策略判定 / 人工驳回)")
private String status; private String status;
@TableField("risk_level") @TableField("risk_level")
@ -58,7 +58,7 @@ public class MiniContentAuditTask implements Serializable {
@TableField("confidence") @TableField("confidence")
@Schema(description = "机审信任度") @Schema(description = "机审信任度")
private Float confidence; private Integer confidence;
@TableField("description") @TableField("description")
@Schema(description = "机审描述") @Schema(description = "机审描述")
@ -76,10 +76,6 @@ public class MiniContentAuditTask implements Serializable {
@Schema(description = "机审异步视频任务id(轮询设置结果)") @Schema(description = "机审异步视频任务id(轮询设置结果)")
private String taskId; private String taskId;
@TableField("result")
@Schema(description = "passed通过 / failed未通过")
private String result;
@TableField("operator") @TableField("operator")
@Schema(description = "人工审核操作人") @Schema(description = "人工审核操作人")
private Long operator; private Long operator;

2
src/main/java/com/youlai/boot/admin/model/form/AuditConfigForm.java

@ -22,7 +22,7 @@ public class AuditConfigForm {
@Schema(description = "审核类型: machine / manual / mixed") @Schema(description = "审核类型: machine / manual / mixed")
private String auditType; 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; private String riskStrategy;
} }

4
src/main/java/com/youlai/boot/admin/model/vo/AuditConfigVO.java

@ -24,10 +24,10 @@ public class AuditConfigVO {
@Schema(description = "是否开启审核, false-开启, true-关闭") @Schema(description = "是否开启审核, false-开启, true-关闭")
private Boolean auditEnable; private Boolean auditEnable;
@Schema(description = "审核类型: machine / manual / mixed") @Schema(description = "审核类型: machine / manual")
private String auditType; private String auditType;
@Schema(description = "机器审核风险策略: none / medium / high") @Schema(description = "机器审核风险策略: auto / normal / cautious")
private String riskStrategy; private String riskStrategy;
@Schema(description = "创建时间") @Schema(description = "创建时间")

2
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 moduleCode 业务模块: animal_note / adoption_diary / user_post / *_comment / user_avatar / user_nickname / user_introduction
* @param bizId 业务数据ID * @param bizId 业务数据ID
* @param content 待审核内容 {text[], images[], video[]} * @param content 待审核内容 {text[], images[], video[]}
* @param triggerType 触发类型: auto自动 / report举报 * @param triggerType 触发类型: create发布 / report举报
* @return {status: "passed"|"failed"|"manual_review", auditId: Long}配置关闭或无配置时返回null * @return {status: "passed"|"failed"|"manual_review", auditId: Long}配置关闭或无配置时返回null
*/ */
Map<String, Object> executeAudit(String moduleCode, Long bizId, AuditContentDTO content, String triggerType); Map<String, Object> executeAudit(String moduleCode, Long bizId, AuditContentDTO content, String triggerType);

2
src/main/java/com/youlai/boot/admin/service/ContentAuditService.java

@ -7,7 +7,7 @@ public interface ContentAuditService extends IService<MiniContentAudit> {
MiniContentAudit createAudit(String moduleCode, Long bizId, String auditType, String triggerType); 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); MiniContentAudit getByModuleAndBizId(String moduleCode, Long bizId);

4
src/main/java/com/youlai/boot/admin/service/ContentAuditTaskService.java

@ -11,8 +11,8 @@ public interface ContentAuditTaskService extends IService<MiniContentAuditTask>
void batchCreateTasks(Long auditId, String auditType, List<String> texts, List<String> images, List<String> videos); void batchCreateTasks(Long auditId, String auditType, List<String> texts, List<String> images, List<String> videos);
/** 更新单个任务的机审结果 */ /** 更新单个任务的机审结果 */
void updateTaskMachineResult(Long taskId, String machineResult, String riskLevel, String result, String status, void updateTaskMachineResult(Long taskId, String machineResult, String riskLevel, String status,
String label, Float confidence, String description, String requestId); String label, Integer confidence, String description, String requestId);
/** 查询某个审核汇总下的所有任务明细 */ /** 查询某个审核汇总下的所有任务明细 */
List<MiniContentAuditTask> listTasksByAuditId(Long auditId); List<MiniContentAuditTask> listTasksByAuditId(Long auditId);

2
src/main/java/com/youlai/boot/admin/service/OssCallbackService.java

@ -3,6 +3,6 @@ package com.youlai.boot.admin.service;
public interface OssCallbackService { public interface OssCallbackService {
/** 处理OSS图片审核增量回调 */ /** 处理OSS图片审核增量回调 */
void handleImageCallback(String checksum, String content); void handleImageCallback(String body);
} }

478
src/main/java/com/youlai/boot/admin/service/impl/AuditExecutorServiceImpl.java

@ -1,6 +1,7 @@
package com.youlai.boot.admin.service.impl; package com.youlai.boot.admin.service.impl;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.aliyun.green20220302.models.*; import com.aliyun.green20220302.models.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 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 moduleCode 业务模块编码
* @param bizId 业务数据ID日记ID / 评论ID * @param bizId 业务数据ID
* @param content 待审核内容 {texts, images, videos} * @param content 待审核内容 {texts, images, videos}
* @return {status, auditId}配置关闭或无配置时返回 null调用方以此判断是否跳过审核 * @return {status, auditId}配置关闭或无配置时返回 null
*/ */
@Override @Override
public Map<String, Object> executeAudit(String moduleCode, Long bizId, AuditContentDTO content, String triggerType) { public Map<String, Object> executeAudit(String moduleCode, Long bizId, AuditContentDTO content, String triggerType) {
// 1) 查询对应模块的审核配置 // 1) 查询审核配置
MiniContentAuditConfig config = findAuditConfig(moduleCode); MiniContentAuditConfig config = findAuditConfig(moduleCode);
if (config == null || isAuditDisabled(config)) { if (config == null || isAuditDisabled(config)) {
return null; return null;
@ -53,163 +54,45 @@ public class AuditExecutorServiceImpl implements AuditExecutorService {
String auditType = config.getAuditType(); String auditType = config.getAuditType();
// 2) 创建审核汇总记录 // 2) 创建审核汇总
MiniContentAudit audit = contentAuditService.createAudit(moduleCode, bizId, auditType, triggerType); MiniContentAudit audit = contentAuditService.createAudit(moduleCode, bizId, auditType, triggerType);
Long auditId = audit.getId(); Long auditId = audit.getId();
// 3) 拆解待审核内容,批量创建审核任务明细 // 3) 拆解内容,批量创建审核任务
contentAuditTaskService.batchCreateTasks(auditId, auditType, contentAuditTaskService.batchCreateTasks(auditId, auditType,
content.getTexts(), content.getImages(), content.getVideos()); content.getTexts(), content.getImages(), content.getVideos());
// 4) 根据审核类型执行不同策略 // 4) 按审核类型执行
if (AuditConstants.AUDIT_TYPE_MANUAL.equals(auditType)) { if (AuditConstants.AUDIT_TYPE_MANUAL.equals(auditType)) {
// 人工审核
return executeManualAudit(auditId); 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; String strictness = config.getRiskStrategy() != null ? config.getRiskStrategy() : AuditConstants.STRATEGY_NORMAL;
List<MiniContentAuditTask> tasks = contentAuditTaskService.listTasksByAuditId(auditId); List<MiniContentAuditTask> tasks = contentAuditTaskService.listTasksByAuditId(auditId);
executeBatchAuditByType(tasks, strictness); executeBatchAuditByType(tasks, strictness); // 进入机器审核
List<MiniContentAuditTask> updatedTasks = contentAuditTaskService.listTasksByAuditId(auditId); List<MiniContentAuditTask> updatedTasks = contentAuditTaskService.listTasksByAuditId(auditId);
return aggregateTaskResultsAndUpdateAudit(auditId, updatedTasks); return aggregateTaskResultsAndUpdateAudit(auditId, updatedTasks);
} }
/** /**
* manual 审核不调 AI所有任务直接标记为 manualaudit 状态设为 manual_review * manual 人工审核不调 AI所有任务直接标记为 manual_reviewaudit 状态设为 manual_review
*/ */
private Map<String, Object> executeManualAudit(Long auditId) { private Map<String, Object> executeManualAudit(Long auditId) {
contentAuditTaskService.lambdaUpdate() contentAuditTaskService.lambdaUpdate()
.eq(MiniContentAuditTask::getContentAuditId, auditId) .eq(MiniContentAuditTask::getContentAuditId, auditId)
.set(MiniContentAuditTask::getStatus, AuditConstants.TASK_TO_MANUAL) .set(MiniContentAuditTask::getStatus, AuditConstants.STATUS_MANUAL_REVIEW)
.update(); .update();
contentAuditService.updateAuditStatus(auditId, AuditConstants.AUDIT_MANUAL_REVIEW, null); contentAuditService.updateAuditStatus(auditId, AuditConstants.STATUS_MANUAL_REVIEW);
Map<String, Object> result = new HashMap<>(); Map<String, Object> result = new HashMap<>();
result.put("status", AuditConstants.AUDIT_MANUAL_REVIEW); result.put("status", AuditConstants.STATUS_MANUAL_REVIEW);
result.put("auditId", auditId); result.put("auditId", auditId);
return result; return result;
} }
/** /**
* mixed 审核 AI 仅记录 riskLevel + machineResult不应用策略裁决交由人工判定 * 按类型分组批量执行机审
*/
private Map<String, Object> executeMixedAudit(Long auditId) {
List<MiniContentAuditTask> tasks = contentAuditTaskService.listTasksByAuditId(auditId);
executeBatchMixedAuditByType(tasks);
contentAuditService.updateAuditStatus(auditId, AuditConstants.AUDIT_MANUAL_REVIEW, null);
Map<String, Object> result = new HashMap<>();
result.put("status", AuditConstants.AUDIT_MANUAL_REVIEW);
result.put("auditId", auditId);
return result;
}
/**
* 按类型分组批量执行 mixed 机审仅记录风险信息不裁决
*/
private void executeBatchMixedAuditByType(List<MiniContentAuditTask> tasks) {
Map<String, List<MiniContentAuditTask>> grouped = tasks.stream()
.collect(Collectors.groupingBy(MiniContentAuditTask::getContentType));
List<MiniContentAuditTask> textTasks = grouped.getOrDefault("text", List.of());
if (!textTasks.isEmpty()) {
try {
List<String> contents = textTasks.stream().map(MiniContentAuditTask::getContentValue).toList();
TextModerationPlusResponse r = aliyunContentAuditUtil.batchTextModerationPlus(contents);
processBatchTextResponseForMixed(textTasks, r);
} catch (Exception e) {
log.error("mixed批量文本审核失败", e);
}
}
List<MiniContentAuditTask> imageTasks = grouped.getOrDefault("image", List.of());
if (!imageTasks.isEmpty()) {
try {
List<String> urls = imageTasks.stream().map(MiniContentAuditTask::getContentValue).toList();
ImageModerationResponse r = aliyunContentAuditUtil.batchImageModeration(urls);
processBatchImageResponseForMixed(imageTasks, r);
} catch (Exception e) {
log.error("mixed批量图片审核失败", e);
}
}
List<MiniContentAuditTask> 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<MiniContentAuditTask> 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<MiniContentAuditTask> 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<MiniContentAuditConfig> 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<MiniContentAuditTask> tasks, String strictness) { private void executeBatchAuditByType(List<MiniContentAuditTask> tasks, String strictness) {
Map<String, List<MiniContentAuditTask>> grouped = tasks.stream() Map<String, List<MiniContentAuditTask>> grouped = tasks.stream()
@ -225,7 +108,7 @@ public class AuditExecutorServiceImpl implements AuditExecutorService {
log.error("批量文本审核失败", e); log.error("批量文本审核失败", e);
for (MiniContentAuditTask task : textTasks) { for (MiniContentAuditTask task : textTasks) {
contentAuditTaskService.updateTaskMachineResult( contentAuditTaskService.updateTaskMachineResult(
task.getId(), null, null, null, AuditConstants.TASK_TO_MANUAL, task.getId(), null, null, AuditConstants.STATUS_MANUAL_REVIEW,
null, null, null, null); null, null, null, null);
} }
} }
@ -233,15 +116,14 @@ public class AuditExecutorServiceImpl implements AuditExecutorService {
List<MiniContentAuditTask> imageTasks = grouped.getOrDefault("image", List.of()); List<MiniContentAuditTask> imageTasks = grouped.getOrDefault("image", List.of());
if (!imageTasks.isEmpty()) { if (!imageTasks.isEmpty()) {
for (MiniContentAuditTask task : imageTasks) {
try { try {
List<String> urls = imageTasks.stream().map(MiniContentAuditTask::getContentValue).toList(); ImageModerationResponse r = aliyunContentAuditUtil.imageModeration(task.getContentValue());
ImageModerationResponse r = aliyunContentAuditUtil.batchImageModeration(urls); processSingleImageResponse(task, r, strictness);
processBatchImageResponse(imageTasks, r, strictness);
} catch (Exception e) { } catch (Exception e) {
log.error("批量图片审核失败", e); log.error("图片审核失败, taskId={}", task.getId(), e);
for (MiniContentAuditTask task : imageTasks) {
contentAuditTaskService.updateTaskMachineResult( contentAuditTaskService.updateTaskMachineResult(
task.getId(), null, null, null, AuditConstants.TASK_TO_MANUAL, task.getId(), null, null, AuditConstants.STATUS_MANUAL_REVIEW,
null, null, null, null); null, null, null, null);
} }
} }
@ -255,29 +137,47 @@ public class AuditExecutorServiceImpl implements AuditExecutorService {
/** /**
* 批量文本审核结果处理machine 模式按策略裁决 * 批量文本审核结果处理machine 模式按策略裁决
* <p>
* body.data 结构{ riskLevel: "high", result: [{ label, confidence, description }, ...] }
*/ */
private void processBatchTextResponse(List<MiniContentAuditTask> tasks, TextModerationPlusResponse response, String strictness) { private void processBatchTextResponse(List<MiniContentAuditTask> tasks, TextModerationPlusResponse response, String strictness) {
String machineResultJson = JSON.toJSONString(response); String machineResultJson = JSON.toJSONString(response);
String requestId = extractRequestIdFromResponse(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++) { for (int i = 0; i < tasks.size(); i++) {
MiniContentAuditTask task = tasks.get(i); MiniContentAuditTask task = tasks.get(i);
if (i < (dataList != null ? dataList.size() : 0)) { if (resultArray != null && i < resultArray.size()) {
Object data = dataList.get(i); JSONObject item = resultArray.getJSONObject(i);
String riskLevel = extractRiskLevelFromDataItem(data); String label = item.getString("label");
String label = extractLabelFromDataItem(data); Integer confidence = item.getInteger("confidence");
Float confidence = extractConfidenceFromDataItem(data); String description = item.getString("description");
String description = extractDescriptionFromDataItem(data); log.info("文本审核[{}]: label={}, confidence={}, description={}", i, label, confidence, description);
if (riskLevel == null) {
if (overallRiskLevel == null) {
contentAuditTaskService.updateTaskMachineResult( contentAuditTaskService.updateTaskMachineResult(
task.getId(), machineResultJson, null, null, AuditConstants.TASK_TO_MANUAL, task.getId(), machineResultJson, null, AuditConstants.STATUS_MANUAL_REVIEW,
label, confidence, description, requestId); label, confidence, description, requestId);
} else { } else {
String result = applyStrategy(riskLevel, strictness); String status = applyStrategy(overallRiskLevel, strictness);
String taskStatus = AuditConstants.RESULT_PASSED.equals(result)
? AuditConstants.TASK_SUCCESS : AuditConstants.TASK_TO_MANUAL;
contentAuditTaskService.updateTaskMachineResult( contentAuditTaskService.updateTaskMachineResult(
task.getId(), machineResultJson, riskLevel, result, taskStatus, task.getId(), machineResultJson, overallRiskLevel, status,
label, confidence, description, requestId); label, confidence, description, requestId);
} }
} }
@ -285,35 +185,39 @@ public class AuditExecutorServiceImpl implements AuditExecutorService {
} }
/** /**
* 批量图片审核结果处理machine 模式按策略裁决 * 单张图片审核结果处理machine 模式按策略裁决
*/ */
private void processBatchImageResponse(List<MiniContentAuditTask> tasks, ImageModerationResponse response, String strictness) { private void processSingleImageResponse(MiniContentAuditTask task, ImageModerationResponse response, String strictness) {
String machineResultJson = JSON.toJSONString(response); String machineResultJson = JSON.toJSONString(response);
String requestId = extractRequestIdFromResponse(response); String requestId = extractRequestIdFromResponse(response);
List<?> dataList = extractDataListFromResponse(response);
for (int i = 0; i < tasks.size(); i++) { JSONObject dataObj = extractDataObjectFromResponse(response);
MiniContentAuditTask task = tasks.get(i); if (dataObj == null) {
if (i < (dataList != null ? dataList.size() : 0)) { log.info("图片审核返回data为空, taskId={}", task.getId());
Object data = dataList.get(i); contentAuditTaskService.updateTaskMachineResult(
String riskLevel = extractRiskLevelFromDataItem(data); task.getId(), machineResultJson, null, AuditConstants.STATUS_MANUAL_REVIEW,
String label = extractLabelFromDataItem(data); null, null, null, requestId);
Float confidence = extractConfidenceFromDataItem(data); return;
String description = extractDescriptionFromDataItem(data); }
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) { if (riskLevel == null) {
contentAuditTaskService.updateTaskMachineResult( contentAuditTaskService.updateTaskMachineResult(
task.getId(), machineResultJson, null, null, AuditConstants.TASK_TO_MANUAL, task.getId(), machineResultJson, null, AuditConstants.STATUS_MANUAL_REVIEW,
label, confidence, description, requestId); label, confidence, description, requestId);
} else { } else {
String result = applyStrategy(riskLevel, strictness); String status = applyStrategy(riskLevel, strictness);
String taskStatus = AuditConstants.RESULT_PASSED.equals(result)
? AuditConstants.TASK_SUCCESS : AuditConstants.TASK_TO_MANUAL;
contentAuditTaskService.updateTaskMachineResult( contentAuditTaskService.updateTaskMachineResult(
task.getId(), machineResultJson, riskLevel, result, taskStatus, task.getId(), machineResultJson, riskLevel, status,
label, confidence, description, requestId); label, confidence, description, requestId);
} }
} }
}
}
/** /**
* 视频审核异步 * 视频审核异步
@ -341,109 +245,62 @@ public class AuditExecutorServiceImpl implements AuditExecutorService {
return response.getBody().getData().getTaskId(); return response.getBody().getData().getTaskId();
} }
/** 从响应的 body 中提取 requestId */ /** 反射提取 body.requestId */
private String extractRequestIdFromResponse(Object response) { private String extractRequestIdFromResponse(Object response) {
try { try {
Object body = invokeGetter(response, "getBody"); Method getBody = response.getClass().getMethod("getBody");
Object body = getBody.invoke(response);
if (body == null) return null; 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; return requestId != null ? requestId.toString() : null;
} catch (Exception e) { } catch (Exception e) {
return null; return null;
} }
} }
/** 从响应的 body 中提取 data 列表 */ /** 反射提取 body.data,转为 JSONObject */
private List<?> extractDataListFromResponse(Object response) { private JSONObject extractDataObjectFromResponse(Object response) {
try { try {
Object body = invokeGetter(response, "getBody"); Method getBody = response.getClass().getMethod("getBody");
Object body = getBody.invoke(response);
if (body == null) return null; if (body == null) return null;
Object data = invokeGetter(body, "getData"); Method getData = body.getClass().getMethod("getData");
if (data instanceof List<?> list) return list; Object data = getData.invoke(body);
return null; if (data == null) return null;
} catch (Exception e) { return JSON.parseObject(JSON.toJSONString(data));
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;
} 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) { } catch (Exception e) {
return null; 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
* <pre> * <pre>
* auto: NONEpassed, MEDIUMfailed, HIGHfailed * auto: NONEpassed, MEDIUMrejected, HIGHrejected
* normal: NONEpassed, MEDIUMmanual, HIGHfailed * normal: NONEpassed, MEDIUMmanual_review, HIGHrejected
* cautious: NONEpassed, MEDIUMmanual, HIGHmanual * cautious: NONEpassed, MEDIUMmanual_review, HIGHmanual_review
* </pre> * </pre>
*/ */
private String applyStrategy(String riskLevel, String strictness) { private String applyStrategy(String riskLevel, String strictness) {
if (AuditConstants.RISK_NONE.equals(riskLevel)) { if (AuditConstants.RISK_NONE.equals(riskLevel)) {
return AuditConstants.RESULT_PASSED; return AuditConstants.STATUS_PASSED;
} }
if (AuditConstants.STRATEGY_CAUTIOUS.equals(strictness)) { if (AuditConstants.STRATEGY_CAUTIOUS.equals(strictness)) {
// cautious: 非 NONE 只更新审核状态 → 一律转人工 return AuditConstants.STATUS_MANUAL_REVIEW;
return null;
} }
if (AuditConstants.STRATEGY_AUTO.equals(strictness)) { if (AuditConstants.STRATEGY_AUTO.equals(strictness)) {
// auto: 非 NONE 一律不通过 return AuditConstants.STATUS_REJECTED;
return AuditConstants.RESULT_FAILED;
} }
// normal(默认):MEDIUM→ 审核状态转人工,HIGH→不通过 // normal(默认):MEDIUM→manual_review, HIGH→rejected
if (AuditConstants.RISK_MEDIUM.equals(riskLevel)) { 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
* <p> * <p>
* 汇总规则 * 汇总规则
* - 任一 task.result = failed audit.status = failed * - 任一 task.status = rejected audit.status = rejected
* - task.result = to_manual audit.status = manual_review * - task.status = manual_review audit.status = manual_review
* - 全部 task.result = passed audit.status = passed * - 全部 task.status = passed audit.status = passed
* - 视频任务未完成result null 保持 reviewing * - 视频任务未完成status = reviewing 保持 reviewing
* </pre>
* <p>
* MiniContentAudit 字段含义
* - status: reviewing(机审中) / passed(通过) / failed(不通过) / manual_review(待人工) / appealing(申诉中)
* - final_result: passed / failed仅终态manual_review 时留空等待人工判定
*/ */
private Map<String, Object> aggregateTaskResultsAndUpdateAudit(Long auditId, List<MiniContentAuditTask> tasks) { private Map<String, Object> aggregateTaskResultsAndUpdateAudit(Long auditId, List<MiniContentAuditTask> tasks) {
boolean hasFailedTask = false; boolean hasRejected = false;
boolean hasManualTask = false; boolean hasManualReview = false;
boolean allTasksPassed = true; boolean allPassed = true;
for (MiniContentAuditTask task : tasks) { for (MiniContentAuditTask task : tasks) {
String taskResult = task.getResult(); String taskStatus = task.getStatus();
if (AuditConstants.RESULT_FAILED.equals(taskResult)) { if (AuditConstants.STATUS_REJECTED.equals(taskStatus)) {
hasFailedTask = true; hasRejected = true;
allTasksPassed = false; allPassed = false;
break; // 已有 failed,无需继续遍历 break;
} }
if (AuditConstants.TASK_TO_MANUAL.equals(task.getStatus())) { if (AuditConstants.STATUS_MANUAL_REVIEW.equals(taskStatus)) {
hasManualTask = true; hasManualReview = true;
allTasksPassed = false; allPassed = false;
} else if (!AuditConstants.RESULT_PASSED.equals(taskResult)) { } else if (!AuditConstants.STATUS_PASSED.equals(taskStatus)) {
// result 为 null 或其他未知值 → 未完成 allPassed = false;
allTasksPassed = false;
} }
} }
// 视频任务 result 为 null 时, 不能算通过 // 视频任务未完成时不能算通过
for (MiniContentAuditTask task : tasks) { for (MiniContentAuditTask task : tasks) {
if ("video".equals(task.getContentType()) && task.getResult() == null) { if ("video".equals(task.getContentType()) && AuditConstants.STATUS_REVIEWING.equals(task.getStatus())) {
allTasksPassed = false; allPassed = false;
break; break;
} }
} }
// 根据汇总结果确定 audit 状态
String auditStatus; String auditStatus;
String finalResultValue; if (hasRejected) {
auditStatus = AuditConstants.STATUS_REJECTED;
if (hasFailedTask) { } else if (hasManualReview) {
auditStatus = AuditConstants.AUDIT_FAILED; auditStatus = AuditConstants.STATUS_MANUAL_REVIEW;
finalResultValue = "failed"; } else if (allPassed && !tasks.isEmpty()) {
} else if (hasManualTask) { auditStatus = AuditConstants.STATUS_PASSED;
auditStatus = AuditConstants.AUDIT_MANUAL_REVIEW;
finalResultValue = null; // 转人工不是终态,final_result 留空等待人工判定
} else if (allTasksPassed && !tasks.isEmpty()) {
auditStatus = AuditConstants.AUDIT_PASSED;
finalResultValue = "passed";
} else { } else {
// 有任务未完成(如视频异步等待中) auditStatus = AuditConstants.STATUS_REVIEWING;
auditStatus = AuditConstants.AUDIT_REVIEWING;
finalResultValue = null;
} }
// 非 reviewing 状态下更新 audit(manual_review 虽无 finalResult 也要更新 status) if (!AuditConstants.STATUS_REVIEWING.equals(auditStatus)) {
if (!AuditConstants.AUDIT_REVIEWING.equals(auditStatus)) { contentAuditService.updateAuditStatus(auditId, auditStatus);
contentAuditService.updateAuditStatus(auditId, auditStatus, finalResultValue);
} }
// 返回给业务层的审核结果
Map<String, Object> resultMap = new HashMap<>(); Map<String, Object> resultMap = new HashMap<>();
resultMap.put("status", auditStatus); resultMap.put("status", auditStatus);
resultMap.put("auditId", auditId); resultMap.put("auditId", auditId);
@ -529,12 +370,6 @@ public class AuditExecutorServiceImpl implements AuditExecutorService {
// 视频异步审核结果轮询 // 视频异步审核结果轮询
// ================================================================ // ================================================================
/**
* 轮询所有待处理的视频审核异步结果更新任务和汇总状态
* 供定时任务 {@code VideoAuditPollJob} 调用
*
* @return 本次成功处理的视频任务数量
*/
@Override @Override
public int pollVideoAuditResults() { public int pollVideoAuditResults() {
List<MiniContentAuditTask> pendingTasks = contentAuditTaskService.getPendingVideoTasks(); List<MiniContentAuditTask> pendingTasks = contentAuditTaskService.getPendingVideoTasks();
@ -557,7 +392,7 @@ public class AuditExecutorServiceImpl implements AuditExecutorService {
} }
} }
// machine 类型需要重新汇总,mixed 的 audit 已为 manual_review 不参与汇总 // machine 类型需要重新汇总
for (Long auditId : affectedAuditIds) { for (Long auditId : affectedAuditIds) {
try { try {
MiniContentAudit audit = contentAuditService.getById(auditId); MiniContentAudit audit = contentAuditService.getById(auditId);
@ -574,9 +409,6 @@ public class AuditExecutorServiceImpl implements AuditExecutorService {
return processedCount; return processedCount;
} }
/**
* 处理单条视频审核任务的异步结果返回 true 表示结果已回填完成
*/
private boolean processSingleVideoTask(MiniContentAuditTask task) { private boolean processSingleVideoTask(MiniContentAuditTask task) {
String asyncTaskId = task.getTaskId(); String asyncTaskId = task.getTaskId();
VideoModerationResultResponse response = aliyunContentAuditUtil.videoModerationResult(asyncTaskId); VideoModerationResultResponse response = aliyunContentAuditUtil.videoModerationResult(asyncTaskId);
@ -604,54 +436,35 @@ public class AuditExecutorServiceImpl implements AuditExecutorService {
return false; return false;
} }
// 从阿里云响应中直接提取 riskLevel(none/medium/high)
String riskLevel = extractVideoRiskLevel(dataJson); String riskLevel = extractVideoRiskLevel(dataJson);
if (riskLevel == null) { if (riskLevel == null) {
log.warn("视频审核结果中未找到riskLevel, taskId={}", task.getId()); log.warn("视频审核结果中未找到riskLevel, taskId={}", task.getId());
return false; return false;
} }
// 查找关联的审核记录
MiniContentAudit audit = contentAuditService.getById(task.getContentAuditId()); MiniContentAudit audit = contentAuditService.getById(task.getContentAuditId());
if (audit == null) { if (audit == null) {
log.warn("未找到关联审核记录, taskId={}", task.getId()); log.warn("未找到关联审核记录, taskId={}", task.getId());
return false; 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; String strictness = AuditConstants.STRATEGY_NORMAL;
MiniContentAuditConfig config = findAuditConfig(audit.getModuleCode()); MiniContentAuditConfig config = findAuditConfig(audit.getModuleCode());
if (config != null && config.getRiskStrategy() != null) { if (config != null && config.getRiskStrategy() != null) {
strictness = config.getRiskStrategy(); strictness = config.getRiskStrategy();
} }
String result = applyStrategy(riskLevel, strictness); String status = applyStrategy(riskLevel, strictness);
String taskStatus = AuditConstants.RESULT_PASSED.equals(result)
? AuditConstants.TASK_SUCCESS : AuditConstants.TASK_TO_MANUAL;
contentAuditTaskService.updateTaskMachineResult( contentAuditTaskService.updateTaskMachineResult(
task.getId(), dataJson, riskLevel, result, taskStatus, task.getId(), dataJson, riskLevel, status,
null, null, null, null); null, null, null, null);
log.info("视频任务审核完成, taskId={}, riskLevel={}, strictness={}, result={}", log.info("视频任务审核完成, taskId={}, riskLevel={}, strictness={}, status={}",
task.getId(), riskLevel, strictness, result); task.getId(), riskLevel, strictness, status);
return true; return true;
} }
/**
* 判断视频异步审核是否已完成
* 阿里云视频审核完成后 data 中会出现 RiskLevel 字段none/medium/high
* 未完成时该字段不存在
*/
private boolean isVideoAuditCompleted(String dataJson) { private boolean isVideoAuditCompleted(String dataJson) {
try { try {
JSONObject json = JSON.parseObject(dataJson); JSONObject json = JSON.parseObject(dataJson);
@ -662,10 +475,6 @@ public class AuditExecutorServiceImpl implements AuditExecutorService {
} }
} }
/**
* 从视频审核结果JSON中直接提取 riskLevelnone/medium/high
* 阿里云视频审核完成后 data 中包含小写 riskLevel 字段
*/
private String extractVideoRiskLevel(String dataJson) { private String extractVideoRiskLevel(String dataJson) {
try { try {
JSONObject json = JSON.parseObject(dataJson); JSONObject json = JSON.parseObject(dataJson);
@ -675,4 +484,23 @@ public class AuditExecutorServiceImpl implements AuditExecutorService {
return null; return null;
} }
} }
/**
* 查找审核配置
*/
private MiniContentAuditConfig findAuditConfig(String moduleCode) {
LambdaQueryWrapper<MiniContentAuditConfig> 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);
}
} }

6
src/main/java/com/youlai/boot/admin/service/impl/ContentAuditAppealServiceImpl.java

@ -83,12 +83,12 @@ public class ContentAuditAppealServiceImpl implements ContentAuditAppealService
if (audit != null) { if (audit != null) {
if ("approved".equals(form.getResult())) { if ("approved".equals(form.getResult())) {
contentAuditService.updateAuditStatus(appeal.getAuditId(), contentAuditService.updateAuditStatus(appeal.getAuditId(),
AuditConstants.AUDIT_PASSED, AuditConstants.RESULT_PASSED); AuditConstants.STATUS_PASSED);
log.info("申诉通过, auditId={}, 审核状态已改为passed", appeal.getAuditId()); log.info("申诉通过, auditId={}, 审核状态已改为passed", appeal.getAuditId());
} else { } else {
contentAuditService.updateAuditStatus(appeal.getAuditId(), contentAuditService.updateAuditStatus(appeal.getAuditId(),
AuditConstants.AUDIT_FAILED, AuditConstants.RESULT_FAILED); AuditConstants.STATUS_REJECTED);
log.info("申诉驳回, auditId={}, 审核状态维持failed", appeal.getAuditId()); log.info("申诉驳回, auditId={}, 审核状态维持rejected", appeal.getAuditId());
} }
} }
} }

5
src/main/java/com/youlai/boot/admin/service/impl/ContentAuditServiceImpl.java

@ -23,7 +23,7 @@ public class ContentAuditServiceImpl extends ServiceImpl<MiniContentAuditMapper,
entity.setBizId(bizId); entity.setBizId(bizId);
entity.setAuditType(auditType); entity.setAuditType(auditType);
entity.setTriggerType(triggerType); entity.setTriggerType(triggerType);
entity.setStatus(AuditConstants.AUDIT_REVIEWING); entity.setStatus(AuditConstants.STATUS_REVIEWING);
entity.setCreateBy(SecurityUtils.getUserId()); entity.setCreateBy(SecurityUtils.getUserId());
entity.setCreateTime(new Date()); entity.setCreateTime(new Date());
entity.setCreateTimestamp(System.currentTimeMillis()); entity.setCreateTimestamp(System.currentTimeMillis());
@ -32,11 +32,10 @@ public class ContentAuditServiceImpl extends ServiceImpl<MiniContentAuditMapper,
} }
@Override @Override
public void updateAuditStatus(Long auditId, String status, String finalResult) { public void updateAuditStatus(Long auditId, String status) {
MiniContentAudit entity = new MiniContentAudit(); MiniContentAudit entity = new MiniContentAudit();
entity.setId(auditId); entity.setId(auditId);
entity.setStatus(status); entity.setStatus(status);
entity.setFinalResult(finalResult);
entity.setUpdateBy(SecurityUtils.getUserId()); entity.setUpdateBy(SecurityUtils.getUserId());
entity.setUpdateTime(new Date()); entity.setUpdateTime(new Date());
entity.setUpdateTimestamp(System.currentTimeMillis()); entity.setUpdateTimestamp(System.currentTimeMillis());

9
src/main/java/com/youlai/boot/admin/service/impl/ContentAuditTaskServiceImpl.java

@ -67,20 +67,19 @@ public class ContentAuditTaskServiceImpl extends ServiceImpl<MiniContentAuditTas
task.setContentType(contentType); task.setContentType(contentType);
task.setContentValue(contentValue); task.setContentValue(contentValue);
task.setAuditType(auditType); task.setAuditType(auditType);
task.setStatus(AuditConstants.TASK_REVIEWING); task.setStatus(AuditConstants.STATUS_REVIEWING);
task.setCreateTime(now); task.setCreateTime(now);
task.setCreateTimestamp(nowTs); task.setCreateTimestamp(nowTs);
return task; return task;
} }
@Override @Override
public void updateTaskMachineResult(Long taskId, String machineResult, String riskLevel, String result, String status, public void updateTaskMachineResult(Long taskId, String machineResult, String riskLevel, String status,
String label, Float confidence, String description, String requestId) { String label, Integer confidence, String description, String requestId) {
MiniContentAuditTask entity = new MiniContentAuditTask(); MiniContentAuditTask entity = new MiniContentAuditTask();
entity.setId(taskId); entity.setId(taskId);
entity.setMachineResult(machineResult); entity.setMachineResult(machineResult);
entity.setRiskLevel(riskLevel); entity.setRiskLevel(riskLevel);
entity.setResult(result);
entity.setStatus(status); entity.setStatus(status);
entity.setLabel(label); entity.setLabel(label);
entity.setConfidence(confidence); entity.setConfidence(confidence);
@ -102,7 +101,7 @@ public class ContentAuditTaskServiceImpl extends ServiceImpl<MiniContentAuditTas
public List<MiniContentAuditTask> getPendingVideoTasks() { public List<MiniContentAuditTask> getPendingVideoTasks() {
return this.list(new LambdaQueryWrapper<MiniContentAuditTask>() return this.list(new LambdaQueryWrapper<MiniContentAuditTask>()
.eq(MiniContentAuditTask::getContentType, "video") .eq(MiniContentAuditTask::getContentType, "video")
.eq(MiniContentAuditTask::getStatus, AuditConstants.TASK_REVIEWING) .eq(MiniContentAuditTask::getStatus, AuditConstants.STATUS_REVIEWING)
.isNotNull(MiniContentAuditTask::getTaskId) .isNotNull(MiniContentAuditTask::getTaskId)
.ne(MiniContentAuditTask::getTaskId, "") .ne(MiniContentAuditTask::getTaskId, "")
.and(w -> w.isNull(MiniContentAuditTask::getMachineResult) .and(w -> w.isNull(MiniContentAuditTask::getMachineResult)

108
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.JSONArray;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 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.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.ContentAuditTaskService;
import com.youlai.boot.admin.service.OssCallbackService; import com.youlai.boot.admin.service.OssCallbackService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.util.List;
@Service @Service
@Slf4j @Slf4j
public class OssCallbackServiceImpl implements OssCallbackService { public class OssCallbackServiceImpl implements OssCallbackService {
private final ContentAuditTaskService contentAuditTaskService; private final ContentAuditTaskService contentAuditTaskService;
private final ContentAuditService contentAuditService;
private final ContentAuditConfigService contentAuditConfigService;
@Value("${audit.aliyun.oss.callbackUid}") @Value("${audit.aliyun.oss.callbackUid}")
private String callbackUid; private String callbackUid;
@ -34,16 +27,32 @@ public class OssCallbackServiceImpl implements OssCallbackService {
@Value("${audit.aliyun.oss.callbackSeed}") @Value("${audit.aliyun.oss.callbackSeed}")
private String callbackSeed; private String callbackSeed;
public OssCallbackServiceImpl(ContentAuditTaskService contentAuditTaskService, public OssCallbackServiceImpl(ContentAuditTaskService contentAuditTaskService) {
ContentAuditService contentAuditService,
ContentAuditConfigService contentAuditConfigService) {
this.contentAuditTaskService = contentAuditTaskService; this.contentAuditTaskService = contentAuditTaskService;
this.contentAuditService = contentAuditService;
this.contentAuditConfigService = contentAuditConfigService;
} }
@Override @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 验签 // 1. SHA256 验签
if (!verifyChecksum(content, checksum)) { if (!verifyChecksum(content, checksum)) {
log.warn("OSS回调验签失败, checksum={}, content={}", checksum, content); 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); log.info("OSS图片审核回调, object={}, riskLevel={}, requestId={}", ossObjectName, riskLevel, requestId);
// 3. 根据 ossObjectName 匹配审核任务 //TODO 这个接口回调的是 OSS增量图片数据,如果用OSS内容审核,那么审核任务将不会存在,暂时先打印一下数据就行,另外想方案; // 3. 根据 ossObjectName 匹配审核任务
//TODO ,如果上传图片就创建一个审核任务,这样确实能这样处理,暂时只讨论方案不要实现;
MiniContentAuditTask task = findTaskByImageName(ossObjectName); MiniContentAuditTask task = findTaskByImageName(ossObjectName);
if (task == null) { if (task == null) {
log.warn("未找到匹配的审核任务, ossObjectName={}", ossObjectName); log.warn("未找到匹配的审核任务, ossObjectName={}", ossObjectName);
@ -96,53 +104,8 @@ public class OssCallbackServiceImpl implements OssCallbackService {
} }
} }
// 5. 查找配置获取策略 log.info("OSS回调解析完成, taskId={}, ossObjectName={}, riskLevel={}, label={}, confidence={}, description={}, requestId={}",
MiniContentAudit audit = contentAuditService.getById(task.getContentAuditId()); task.getId(), ossObjectName, riskLevel, label, confidence, description, requestId);
String normalizedRisk = normalizeRiskLevel(riskLevel);
if (audit != null && AuditConstants.AUDIT_TYPE_MACHINE.equals(audit.getAuditType())) {
// machine:按策略裁决
MiniContentAuditConfig config = contentAuditConfigService.getOne(
new LambdaQueryWrapper<MiniContentAuditConfig>()
.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<MiniContentAuditTask> 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);
} }
/** SHA256(uid + seed + content) 验签,对应阿里云控制台配置的加密算法 */ /** 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 尾部 */ /** 通过 ossObjectName(如 v2-xxx_r.jpg)匹配审核任务中图片 URL 尾部 */
private MiniContentAuditTask findTaskByImageName(String ossObjectName) { private MiniContentAuditTask findTaskByImageName(String ossObjectName) {
if (ossObjectName == null) return null; if (ossObjectName == null) return null;
@ -181,12 +133,4 @@ public class OssCallbackServiceImpl implements OssCallbackService {
.like(MiniContentAuditTask::getContentValue, ossObjectName) .like(MiniContentAuditTask::getContentValue, ossObjectName)
.last("LIMIT 1")); .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;
}
} }

50
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_MAX = 100;
private static final int BATCH_TEXT_SINGLE_MAX_CHARS = 600; 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 {
}); });
} }
/** // 图片审核不支持批量,使用 imageModeration 逐个调用
* 批量图片审核上限100张
*/
public ImageModerationResponse batchImageModeration(List<String> 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);
});
}
// ===================== 视频审核(异步) ===================== // ===================== 视频审核(异步) =====================
//视频文件检测:videoDetection ; AI生成视频判定:videoAigcDetector ; 视频文件检测_大模型版:videoDetectionByVL //视频文件检测:videoDetection ; AI生成视频判定:videoAigcDetector ; 视频文件检测_大模型版:videoDetectionByVL
@ -253,29 +229,7 @@ public class AliyunContentAuditUtil {
}); });
} }
/** // 视频审核不支持批量,使用 videoModeration 逐个调用
* 批量视频审核异步上限10个
*/
public VideoModerationResponse batchVideoModeration(List<String> 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);
});
}
// ===================== 查询视频审核结果 ===================== // ===================== 查询视频审核结果 =====================
public VideoModerationResultResponse videoModerationResult(String taskId) { public VideoModerationResultResponse videoModerationResult(String taskId) {

4
src/main/java/com/youlai/boot/mini/service/impl/MiniContentAuditAppealServiceImpl.java

@ -35,7 +35,7 @@ public class MiniContentAuditAppealServiceImpl implements MiniContentAuditAppeal
if (audit == null) { if (audit == null) {
throw new RuntimeException("审核记录不存在"); throw new RuntimeException("审核记录不存在");
} }
if (!AuditConstants.AUDIT_FAILED.equals(audit.getStatus())) { if (!AuditConstants.STATUS_REJECTED.equals(audit.getStatus())) {
throw new RuntimeException("当前审核状态不允许申诉"); throw new RuntimeException("当前审核状态不允许申诉");
} }
@ -61,7 +61,7 @@ public class MiniContentAuditAppealServiceImpl implements MiniContentAuditAppeal
appealMapper.insert(appeal); 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); log.info("用户提交申诉成功, auditId={}, userId={}", form.getAuditId(), userId);
} }

6
src/main/java/com/youlai/boot/mini/service/impl/UserPostServiceImpl.java

@ -227,7 +227,7 @@ public class UserPostServiceImpl extends ServiceImpl<MiniUserPostMapper, MiniUse
Long postId = post.getId(); Long postId = post.getId();
try { try {
AuditContentDTO auditContent = buildAuditContent(formData); AuditContentDTO auditContent = buildAuditContent(formData);
Map<String, Object> auditResult = auditExecutorService.executeAudit("user_post", postId, auditContent, AuditConstants.TRIGGER_AUTO); Map<String, Object> auditResult = auditExecutorService.executeAudit("user_post", postId, auditContent, AuditConstants.TRIGGER_CREATE);
if (auditResult != null) { if (auditResult != null) {
log.info("用户作品审核任务已创建, postId={}, auditResult={}", postId, auditResult); log.info("用户作品审核任务已创建, postId={}, auditResult={}", postId, auditResult);
} }
@ -282,7 +282,7 @@ public class UserPostServiceImpl extends ServiceImpl<MiniUserPostMapper, MiniUse
audit.setModuleCode("user_post"); audit.setModuleCode("user_post");
audit.setBizId(postId); audit.setBizId(postId);
audit.setAuditType("manual"); // 失败时 全转人工 audit.setAuditType("manual"); // 失败时 全转人工
audit.setStatus(AuditConstants.AUDIT_MANUAL_REVIEW); // audit.setStatus(AuditConstants.AUDIT_MANUAL_REVIEW);
audit.setCreateBy(SecurityUtils.getUserId()); audit.setCreateBy(SecurityUtils.getUserId());
audit.setCreateTime(new Date()); audit.setCreateTime(new Date());
audit.setCreateTimestamp(System.currentTimeMillis()); audit.setCreateTimestamp(System.currentTimeMillis());
@ -295,7 +295,7 @@ public class UserPostServiceImpl extends ServiceImpl<MiniUserPostMapper, MiniUse
// 将任务状态统一更新为 to_manual,使人工审核员能看到 // 将任务状态统一更新为 to_manual,使人工审核员能看到
contentAuditTaskService.lambdaUpdate() contentAuditTaskService.lambdaUpdate()
.eq(MiniContentAuditTask::getContentAuditId, audit.getId()) .eq(MiniContentAuditTask::getContentAuditId, audit.getId())
.set(MiniContentAuditTask::getStatus, AuditConstants.TASK_TO_MANUAL) // .set(MiniContentAuditTask::getStatus, AuditConstants.TASK_TO_MANUAL)
.update(); .update();
log.info("降级人工审核记录已创建, postId={}, auditId={}", postId, audit.getId()); log.info("降级人工审核记录已创建, postId={}, auditId={}", postId, audit.getId());

83
src/test/java/com/youlai/boot/admin/AliyunContentAuditTest.java

@ -0,0 +1,83 @@
package com.youlai.boot.admin;
import com.alibaba.fastjson.JSON;
import com.aliyun.green20220302.models.*;
import com.youlai.boot.common.util.AliyunContentAuditUtil;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
/**
* 阿里云内容审核 API 在线测试直连真实 API查看返回结构
*/
@Slf4j
@SpringBootTest
class AliyunContentAuditTest {
@Autowired
private AliyunContentAuditUtil auditUtil;
// ==================== 文本 ====================
@Test
@DisplayName("文本审核Plus - 单条")
void textModerationPlusSingle() {
TextModerationPlusResponse r = auditUtil.textModerationPlus("我操你吗");
log.info("=== 文本Plus单条 完整响应 ===\n{}", JSON.toJSONString(r, true));
log.info("body: {}", JSON.toJSONString(r.getBody(), true));
}
@Test
@DisplayName("文本审核Plus - 批量")
void textModerationPlusBatch() {
List<String> 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<String> 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());
}
}
}

BIN
tmp/178065053524697cu0r6a

Binary file not shown.
Loading…
Cancel
Save