Explorar o código

价目表-ai审核切面类

liyafei hai 3 meses
pai
achega
1310b77a36

+ 1 - 1
alien-entity/src/main/java/shop/alien/entity/store/StoreCuisine.java

@@ -82,7 +82,7 @@ public class StoreCuisine {
     @TableField("shelf_status")
     private Integer shelfStatus;
 
-    @ApiModelProperty(value = "拒绝原因")
+    @ApiModelProperty(value = "拒绝原因(审核失败原因)")
     @TableField("rejection_reason")
     private String rejectionReason;
 

+ 1 - 1
alien-entity/src/main/java/shop/alien/entity/store/StorePrice.java

@@ -83,7 +83,7 @@ public class StorePrice {
     @TableField("shelf_status")
     private Integer shelfStatus;
 
-    @ApiModelProperty(value = "拒绝原因")
+    @ApiModelProperty(value = "拒绝原因(审核失败原因)")
     @TableField("rejection_reason")
     private String rejectionReason;
 

+ 8 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/CuisineComboDto.java

@@ -71,4 +71,12 @@ public class CuisineComboDto {
     @ApiModelProperty(value = "使用规则")
     @TableField("usage_rule")
     private String usageRule;
+
+    @ApiModelProperty(value = "状态:0-待审核 1-审核通过 2-审核拒绝")
+    @TableField("status")
+    private Integer status;
+
+    @ApiModelProperty(value = "拒绝原因(审核失败原因)")
+    @TableField("rejection_reason")
+    private String rejectionReason;
 }

+ 155 - 0
alien-store/src/main/java/shop/alien/store/aspect/AiAuditAspect.java

@@ -0,0 +1,155 @@
+package shop.alien.store.aspect;
+
+import com.alibaba.fastjson.JSON;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import shop.alien.entity.store.StorePrice;
+import shop.alien.entity.store.dto.CuisineComboDto;
+import shop.alien.store.util.ai.AiContentModerationUtil;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * AI 审核切面:拦截美食与通用价目新增/修改接口
+ */
+@Slf4j
+@Aspect
+@Component
+@Order(2)
+@RequiredArgsConstructor
+public class AiAuditAspect {
+
+    private final AiContentModerationUtil aiContentModerationUtil;
+
+    /**
+     * 仅拦截美食新增/修改与通用价目新增/修改接口
+     */
+    @Pointcut(
+            "execution(* shop.alien.store.controller.StoreCuisineController.addCuisineCombo(..))"
+                    + " || execution(* shop.alien.store.controller.StoreCuisineController.updateCuisineCombo(..))"
+                    + " || execution(* shop.alien.store.controller.StorePriceController.save(..))"
+                    + " || execution(* shop.alien.store.controller.StorePriceController.update(..))")
+    public void aiAuditPointcut() {
+        // pointcut definition
+    }
+
+    /**
+     * 环绕通知:组合请求参数,获取AI token,并设置审核状态
+     */
+    @Around("aiAuditPointcut()")
+    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
+        Object[] args = joinPoint.getArgs();
+
+        String payload = JSON.toJSONString(args);
+        log.info("AI审核切面拦截方法: {}, payload={}", joinPoint.getSignature().toShortString(), payload);
+
+        List<String> imageUrls = extractImageUrls(args);
+
+        // AI审核:文本采用入参JSON,图片列表来自入参的 images 字符串字段
+        boolean auditPassed = performAiAudit( payload, imageUrls, args);
+
+        // 将审核结果写入请求对象
+        applyStatus(args, auditPassed);
+
+        return joinPoint.proceed(args);
+    }
+
+    /**
+     * AI审核调用:使用文本审核(图片列表暂为空)
+     */
+    private boolean performAiAudit(String payload, List<String> imageUrls, Object[] args) {
+        try {
+            // token 目前仅预留,如后续需要可添加到header或payload
+            AiContentModerationUtil.AuditResult result = aiContentModerationUtil.auditContent(payload, imageUrls);
+            if (result == null) {
+                log.warn("AI审核返回为空,视为未通过");
+                applyFailureReason(args, "审核异常");
+                return false;
+            }
+            boolean passed = result.isPassed();
+            if (!passed) {
+                String reason = result.getFailureReason();
+                log.warn("AI审核不通过,原因: {}", reason);
+                applyFailureReason(args, reason);
+            }
+            return passed;
+        } catch (Exception e) {
+            log.error("AI审核调用异常", e);
+            applyFailureReason(args, "审核异常");
+            return false;
+        }
+    }
+
+    /**
+     * 将审核结果写入入参的status字段(通过=1,不通过=0)
+     */
+    private void applyStatus(Object[] args, boolean passed) {
+        int status = passed ? 1 : 0;
+        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);
+            }
+        }
+    }
+
+    /**
+     * 将审核失败原因写入入参
+     */
+    private void applyFailureReason(Object[] args, String 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);
+            }
+        }
+    }
+
+    /**
+     * 从入参中提取图片URL列表;images字段为JSON字符串数组
+     */
+    private List<String> extractImageUrls(Object[] args) {
+        for (Object arg : args) {
+            if (arg instanceof CuisineComboDto) {
+                return parseImages(((CuisineComboDto) arg).getImages());
+            }
+            if (arg instanceof StorePrice) {
+                return parseImages(((StorePrice) arg).getImages());
+            }
+        }
+        return Collections.emptyList();
+    }
+
+    private List<String> parseImages(String images) {
+        if (images == null || images.trim().isEmpty()) {
+            return Collections.emptyList();
+        }
+        try {
+            // 期望格式为 JSON 数组字符串
+            return JSON.parseArray(images, String.class);
+        } catch (Exception e) {
+            log.warn("解析图片列表失败,images={}", images, e);
+            List<String> single = new ArrayList<>();
+            single.add(images);
+            return single;
+        }
+    }
+}
+

+ 2 - 0
alien-store/src/main/java/shop/alien/store/util/ai/AiFeedbackAssignUtils.java

@@ -4,6 +4,7 @@ import com.alibaba.fastjson2.JSONObject;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
 import org.springframework.core.io.ByteArrayResource;
 import org.springframework.http.*;
 import org.springframework.stereotype.Component;
@@ -22,6 +23,7 @@ import java.util.Map;
 @Slf4j
 @Component
 @RequiredArgsConstructor
+@RefreshScope
 public class AiFeedbackAssignUtils {
 
     private final RestTemplate restTemplate;