|
@@ -9,8 +9,7 @@ import org.aspectj.lang.annotation.Aspect;
|
|
|
import org.aspectj.lang.annotation.Pointcut;
|
|
import org.aspectj.lang.annotation.Pointcut;
|
|
|
import org.springframework.core.annotation.Order;
|
|
import org.springframework.core.annotation.Order;
|
|
|
import org.springframework.stereotype.Component;
|
|
import org.springframework.stereotype.Component;
|
|
|
-import shop.alien.entity.store.StorePrice;
|
|
|
|
|
-import shop.alien.entity.store.dto.CuisineComboDto;
|
|
|
|
|
|
|
+import shop.alien.entity.store.AiAuditable;
|
|
|
import shop.alien.store.util.ai.AiContentModerationUtil;
|
|
import shop.alien.store.util.ai.AiContentModerationUtil;
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.ArrayList;
|
|
@@ -51,13 +50,14 @@ public class AiAuditAspect {
|
|
|
String payload = JSON.toJSONString(args);
|
|
String payload = JSON.toJSONString(args);
|
|
|
log.info("AI审核切面拦截方法: {}, payload={}", joinPoint.getSignature().toShortString(), payload);
|
|
log.info("AI审核切面拦截方法: {}, payload={}", joinPoint.getSignature().toShortString(), payload);
|
|
|
|
|
|
|
|
- List<String> imageUrls = extractImageUrls(args);
|
|
|
|
|
|
|
+ List<AiAuditable> auditTargets = extractAuditTargets(args);
|
|
|
|
|
+ List<String> imageUrls = extractImageUrls(auditTargets);
|
|
|
|
|
|
|
|
// AI审核:文本采用入参JSON,图片列表来自入参的 images 字符串字段
|
|
// AI审核:文本采用入参JSON,图片列表来自入参的 images 字符串字段
|
|
|
- boolean auditPassed = performAiAudit( payload, imageUrls, args);
|
|
|
|
|
|
|
+ boolean auditPassed = performAiAudit(payload, imageUrls, auditTargets);
|
|
|
|
|
|
|
|
// 将审核结果写入请求对象
|
|
// 将审核结果写入请求对象
|
|
|
- applyStatus(args, auditPassed);
|
|
|
|
|
|
|
+ applyStatus(auditTargets, auditPassed);
|
|
|
|
|
|
|
|
return joinPoint.proceed(args);
|
|
return joinPoint.proceed(args);
|
|
|
}
|
|
}
|
|
@@ -65,25 +65,25 @@ public class AiAuditAspect {
|
|
|
/**
|
|
/**
|
|
|
* AI审核调用:使用文本审核(图片列表暂为空)
|
|
* AI审核调用:使用文本审核(图片列表暂为空)
|
|
|
*/
|
|
*/
|
|
|
- private boolean performAiAudit(String payload, List<String> imageUrls, Object[] args) {
|
|
|
|
|
|
|
+ private boolean performAiAudit(String payload, List<String> imageUrls, List<AiAuditable> auditTargets) {
|
|
|
try {
|
|
try {
|
|
|
// token 目前仅预留,如后续需要可添加到header或payload
|
|
// token 目前仅预留,如后续需要可添加到header或payload
|
|
|
AiContentModerationUtil.AuditResult result = aiContentModerationUtil.auditContent(payload, imageUrls);
|
|
AiContentModerationUtil.AuditResult result = aiContentModerationUtil.auditContent(payload, imageUrls);
|
|
|
if (result == null) {
|
|
if (result == null) {
|
|
|
log.warn("AI审核返回为空,视为未通过");
|
|
log.warn("AI审核返回为空,视为未通过");
|
|
|
- applyFailureReason(args, "审核异常");
|
|
|
|
|
|
|
+ applyFailureReason(auditTargets, "审核异常");
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
boolean passed = result.isPassed();
|
|
boolean passed = result.isPassed();
|
|
|
if (!passed) {
|
|
if (!passed) {
|
|
|
String reason = result.getFailureReason();
|
|
String reason = result.getFailureReason();
|
|
|
log.warn("AI审核不通过,原因: {}", reason);
|
|
log.warn("AI审核不通过,原因: {}", reason);
|
|
|
- applyFailureReason(args, reason);
|
|
|
|
|
|
|
+ applyFailureReason(auditTargets, reason);
|
|
|
}
|
|
}
|
|
|
return passed;
|
|
return passed;
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
log.error("AI审核调用异常", e);
|
|
log.error("AI审核调用异常", e);
|
|
|
- applyFailureReason(args, "审核异常");
|
|
|
|
|
|
|
+ applyFailureReason(auditTargets, "审核异常");
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -91,50 +91,73 @@ public class AiAuditAspect {
|
|
|
/**
|
|
/**
|
|
|
* 将审核结果写入入参的status字段(通过=1,不通过=2)
|
|
* 将审核结果写入入参的status字段(通过=1,不通过=2)
|
|
|
*/
|
|
*/
|
|
|
- private void applyStatus(Object[] args, boolean passed) {
|
|
|
|
|
|
|
+ private void applyStatus(List<AiAuditable> auditTargets, boolean passed) {
|
|
|
int status = passed ? 1 : 2;
|
|
int status = passed ? 1 : 2;
|
|
|
- for (Object arg : args) {
|
|
|
|
|
- if (arg == null) {
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- if (arg instanceof CuisineComboDto) {
|
|
|
|
|
- ((CuisineComboDto) arg).setStatus(status);
|
|
|
|
|
- } else if (arg instanceof StorePrice) {
|
|
|
|
|
- ((StorePrice) arg).setStatus(status);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ for (AiAuditable target : auditTargets) {
|
|
|
|
|
+ target.setStatus(status);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 将审核失败原因写入入参
|
|
* 将审核失败原因写入入参
|
|
|
*/
|
|
*/
|
|
|
- private void applyFailureReason(Object[] args, String reason) {
|
|
|
|
|
|
|
+ private void applyFailureReason(List<AiAuditable> auditTargets, String reason) {
|
|
|
String safeReason = reason != null ? reason : "审核未通过";
|
|
String safeReason = reason != null ? reason : "审核未通过";
|
|
|
- for (Object arg : args) {
|
|
|
|
|
- if (arg == null) {
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- if (arg instanceof CuisineComboDto) {
|
|
|
|
|
- ((CuisineComboDto) arg).setRejectionReason(safeReason);
|
|
|
|
|
- } else if (arg instanceof StorePrice) {
|
|
|
|
|
- ((StorePrice) arg).setRejectionReason(safeReason);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ for (AiAuditable target : auditTargets) {
|
|
|
|
|
+ target.setRejectionReason(safeReason);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 从入参中提取图片URL列表;images字段为JSON字符串数组
|
|
* 从入参中提取图片URL列表;images字段为JSON字符串数组
|
|
|
*/
|
|
*/
|
|
|
- private List<String> extractImageUrls(Object[] args) {
|
|
|
|
|
|
|
+ private List<String> extractImageUrls(List<AiAuditable> auditTargets) {
|
|
|
|
|
+ if (auditTargets.isEmpty()) {
|
|
|
|
|
+ return Collections.emptyList();
|
|
|
|
|
+ }
|
|
|
|
|
+ List<String> urls = new ArrayList<>();
|
|
|
|
|
+ for (AiAuditable target : auditTargets) {
|
|
|
|
|
+ List<String> parsed = parseImages(target.getImages());
|
|
|
|
|
+ if (!parsed.isEmpty()) {
|
|
|
|
|
+ urls.addAll(parsed);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return urls;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 将参数中支持 AI 审核的对象提取出来,支持单个对象、集合、数组
|
|
|
|
|
+ */
|
|
|
|
|
+ private List<AiAuditable> extractAuditTargets(Object[] args) {
|
|
|
|
|
+ if (args == null || args.length == 0) {
|
|
|
|
|
+ return Collections.emptyList();
|
|
|
|
|
+ }
|
|
|
|
|
+ List<AiAuditable> targets = new ArrayList<>();
|
|
|
for (Object arg : args) {
|
|
for (Object arg : args) {
|
|
|
- if (arg instanceof CuisineComboDto) {
|
|
|
|
|
- return parseImages(((CuisineComboDto) arg).getImages());
|
|
|
|
|
|
|
+ collectAuditable(arg, targets);
|
|
|
|
|
+ }
|
|
|
|
|
+ return targets;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void collectAuditable(Object candidate, List<AiAuditable> targets) {
|
|
|
|
|
+ if (candidate == null) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (candidate instanceof AiAuditable) {
|
|
|
|
|
+ targets.add((AiAuditable) candidate);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (candidate instanceof Iterable<?>) {
|
|
|
|
|
+ for (Object item : (Iterable<?>) candidate) {
|
|
|
|
|
+ collectAuditable(item, targets);
|
|
|
}
|
|
}
|
|
|
- if (arg instanceof StorePrice) {
|
|
|
|
|
- return parseImages(((StorePrice) arg).getImages());
|
|
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (candidate instanceof Object[]) {
|
|
|
|
|
+ for (Object item : (Object[]) candidate) {
|
|
|
|
|
+ collectAuditable(item, targets);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- return Collections.emptyList();
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private List<String> parseImages(String images) {
|
|
private List<String> parseImages(String images) {
|