Преглед на файлове

fix:重新对接ai新接口,修改端口和路径配置

李亚非 преди 2 месеца
родител
ревизия
44441bbf89

+ 26 - 15
alien-store-platform/src/main/java/shop/alien/storeplatform/feign/AlienAIFeign.java

@@ -33,27 +33,38 @@ public interface AlienAIFeign {
                  produces = MediaType.APPLICATION_JSON_VALUE)
     JsonNode generatePromotionImage(@RequestHeader("Authorization") String authorization, @RequestBody JsonNode requestBody);
 
-    /**
-     * 使用 JsonNode 灵活调用接口 - 发起AI审核(多模态)
-     *
-     * @param authorization Bearer token,格式为 "Bearer {access_token}"
-     * @param requestBody JsonNode 请求体,可以灵活构建
-     * @return JsonNode 响应体,可以灵活解析
-     */
-    @PostMapping(value = "/ai/auto-review/api/v1/multimodal_audit_task/submit",
-            consumes = MediaType.APPLICATION_JSON_VALUE,
-            produces = MediaType.APPLICATION_JSON_VALUE)
-    JsonNode multiModelAudit(@RequestHeader("Authorization") String authorization, @RequestBody JsonNode requestBody);
-
+//    /**
+//     * 使用 JsonNode 灵活调用接口 - 发起AI审核(多模态)
+//     *
+//     * @param authorization Bearer token,格式为 "Bearer {access_token}"
+//     * @param requestBody JsonNode 请求体,可以灵活构建
+//     * @return JsonNode 响应体,可以灵活解析
+//     */
+//    @PostMapping(value = "/ai/auto-review/api/v1/multimodal_audit_task/submit",
+//            consumes = MediaType.APPLICATION_JSON_VALUE,
+//            produces = MediaType.APPLICATION_JSON_VALUE)
+//    JsonNode multiModelAudit(@RequestHeader("Authorization") String authorization, @RequestBody JsonNode requestBody);
+//
+//
+//    /**
+//     * 获取 AI审核(多模态) 结果
+//     *
+//     * @param authorization Bearer token,格式为 "Bearer {access_token}"
+//     * @param taskId 任务ID
+//     * @return JsonNode 响应体,包含审核结果
+//     */
+//    @GetMapping(value = "/ai/auto-review/api/v1/multimodal_audit_task/getResult",
+//            produces = MediaType.APPLICATION_JSON_VALUE)
+//    JsonNode getMultiModelAuditResult(@RequestHeader("Authorization") String authorization, @RequestParam("task_id") String taskId);
 
     /**
      * 获取 AI审核(多模态) 结果
      *
      * @param authorization Bearer token,格式为 "Bearer {access_token}"
-     * @param taskId 任务ID
+     * @param requestBody
      * @return JsonNode 响应体,包含审核结果
      */
-    @GetMapping(value = "/ai/auto-review/api/v1/multimodal_audit_task/getResult",
+    @GetMapping(value = "/api/v1/moderate",
             produces = MediaType.APPLICATION_JSON_VALUE)
-    JsonNode getMultiModelAuditResult(@RequestHeader("Authorization") String authorization, @RequestParam("task_id") String taskId);
+    JsonNode syncAudit(@RequestHeader("Authorization") String authorization, @RequestBody JsonNode requestBody);
 }

+ 27 - 24
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/OperationalActivityServiceImpl.java

@@ -26,12 +26,15 @@ import shop.alien.mapper.StoreImgMapper;
 import shop.alien.mapper.storePlantform.StoreOperationalActivityMapper;
 import shop.alien.storeplatform.feign.AlienAIFeign;
 import shop.alien.storeplatform.service.OperationalActivityService;
+import shop.alien.storeplatform.util.AiContentModerationUtil;
 
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
 import java.util.concurrent.TimeUnit;
 
 
@@ -62,6 +65,7 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
 
     private final RedissonClient redissonClient;
 
+    private final AiContentModerationUtil aiContentModerationUtil;
     @Value("${ai.aiAccount}")
     private String aiAccount;
 
@@ -109,31 +113,30 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
                     // AI登录成功
                     // 先调用AI进行运营名称和图片的审核,同步调用
                     String authorization = "Bearer " + accessToken;
-                    JsonNode audioResponse = alienAIFeign.multiModelAudit(authorization, dto.getAuditParam());
-                    // 如果审核失败,不进行文字生成海报图片,提前短路。
-                    if (audioResponse.has("data")) {
-                        String taskId = audioResponse.get("data").get("task_id").asText();
-                        JsonNode audioResResponse = alienAIFeign.getMultiModelAuditResult(authorization, taskId);
-                        String status=audioResResponse.get("data").get("status").asText();
-                        for (int i = 0; i < 60; i++) { // AI审核接口速度还在优化中 todo
-                            if (status.equals("completed")||status.equals("failed")) {
-                                break;
-                            }
-                            audioResResponse = alienAIFeign.getMultiModelAuditResult(authorization, taskId);
-                            status = audioResResponse.get("data").get("status").asText();
-                            Thread.sleep(1000);
-                        }
-                        String auditRes = audioResResponse.get("data").get("audit_result").asText();
-                        if (!status.equals("failed")&&auditRes != null && auditRes.equals("compliant")) {
-                            activity.setStatus(8);
-                            result = activityMapper.insert(activity);
-                        } else {
-                            String failureReason = audioResResponse.get("data").get("failure_reason").asText();
-                            failureReasonHolder.set(failureReason);
-                            // 审核不成功 不生成任何记录,返回原因
-                            return 2;
-                        }
+                    
+                    JsonNode auditParam = dto.getAuditParam();
+                    String auditText = (auditParam != null && auditParam.has("text")) ? auditParam.get("text").asText() : "";
+                    JsonNode imagesNode = (auditParam != null) ? auditParam.get("image_urls") : null;
+                    
+                    List<String> imageUrls = (imagesNode != null && imagesNode.isArray())
+                            ? StreamSupport.stream(imagesNode.spliterator(), false)
+                                .map(JsonNode::asText)
+                                .collect(Collectors.toList())
+                            : new ArrayList<>();
+
+                    // 调用同步审核工具类
+                    AiContentModerationUtil.AuditResult auditResult = aiContentModerationUtil.auditContent(auditText, imageUrls);
+                    
+                    // 如果审核不通过,记录原因并提前结束
+                    if (!auditResult.isPassed()) {
+                        log.warn("AI内容审核未通过: {}", auditResult.getFailureReason());
+                        failureReasonHolder.set(auditResult.getFailureReason());
+                        return 2; // 返回2表示审核失败
                     }
+                    
+                    // 审核通过,执行入库操作(状态设为8:待生成海报)
+                    activity.setStatus(8);
+                    result = activityMapper.insert(activity);
                     // 使用用户描述和页面输入框其他信息,让AI生成海报图片。
                     if (dto.getUploadImgType() == 2) {
                         // 格式化输入AI参数

+ 209 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/util/AiContentModerationUtil.java

@@ -0,0 +1,209 @@
+package shop.alien.storeplatform.util;
+
+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.http.*;
+import org.springframework.stereotype.Component;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.util.StringUtils;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 通用图文审核工具类
+ * 调用AI图文审核接口审核文本和图片内容
+ */
+@Slf4j
+@Component
+@RefreshScope
+@RequiredArgsConstructor
+public class AiContentModerationUtil {
+
+    private final RestTemplate restTemplate;
+
+    /**
+     * AI审核接口地址
+     */
+    @Value("${ai.service.moderate-url}")
+    private String moderateUrl;
+
+    /**
+     * 审核结果类
+     */
+    public static class AuditResult {
+        private boolean passed;
+        private String failureReason;
+
+        public AuditResult(boolean passed, String failureReason) {
+            this.passed = passed;
+            this.failureReason = failureReason;
+        }
+
+        public boolean isPassed() {
+            return passed;
+        }
+
+        public String getFailureReason() {
+            return failureReason;
+        }
+    }
+
+    /**
+     * 审核文本和图片内容
+     *
+     * @param text 文本内容
+     * @param imageUrls 图片URL列表
+     * @return 审核结果
+     */
+    public AuditResult auditContent(String text, List<String> imageUrls) {
+        log.info("开始审核内容:text={}, imageCount={}",
+                text, imageUrls != null ? imageUrls.size() : 0);
+
+        try {
+            // 如果没有任何内容,直接返回审核通过,避免400错误
+            if (!StringUtils.hasText(text) && (imageUrls == null || imageUrls.isEmpty())) {
+                log.info("审核内容为空,自动跳过审核");
+                return new AuditResult(true, null);
+            }
+
+            // 调用审核接口
+            return callModerateApi(text, imageUrls);
+
+        } catch (Exception e) {
+            log.error("审核内容异常", e);
+            return new AuditResult(false, "审核异常");
+        }
+    }
+
+    /**
+     * 调用AI审核接口
+     *
+     * @param text 文本内容
+     * @param imageUrls 图片URL列表
+     * @return 审核结果
+     */
+    private AuditResult callModerateApi(String text, List<String> imageUrls) {
+        try {
+            // 构建 form-data 请求头
+            HttpHeaders headers = new HttpHeaders();
+            headers.setContentType(MediaType.MULTIPART_FORM_DATA);
+
+            // 构建 form-data 请求体
+            MultiValueMap<String, Object> formData = new LinkedMultiValueMap<>();
+            if (StringUtils.hasText(text)) {
+                formData.add("text", text);
+            }
+            if (imageUrls != null) {
+                for (String url : imageUrls) {
+                    if (StringUtils.hasText(url)) {
+                        // 支持多张图片:同一个 key 重复多次
+                        formData.add("image_urls", url);
+                    }
+                }
+            }
+
+            HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(formData, headers);
+
+            log.info("调用AI审核接口:url={}, text={}, imageCount={}",
+                    moderateUrl, text, imageUrls != null ? imageUrls.size() : 0);
+
+            // 发送请求
+            ResponseEntity<String> response = restTemplate.postForEntity(moderateUrl, requestEntity, String.class);
+
+            if (response.getStatusCode() == HttpStatus.OK) {
+                String responseBody = response.getBody();
+                log.info("AI审核接口响应:{}", responseBody);
+
+                if (StringUtils.hasText(responseBody)) {
+                    JSONObject jsonResponse = JSONObject.parseObject(responseBody);
+                    return parseAuditResult(jsonResponse);
+                } else {
+                    log.error("AI审核接口返回空响应");
+                    return new AuditResult(false, "审核异常");
+                }
+            } else {
+                log.error("AI审核接口调用失败,状态码:{}", response.getStatusCode());
+                return new AuditResult(false, "审核异常");
+            }
+
+        } catch (Exception e) {
+            log.error("调用AI审核接口异常", e);
+            return new AuditResult(false, "审核异常");
+        }
+    }
+
+    /**
+     * 解析审核结果
+     *
+     * @param jsonResponse API响应JSON
+     * @return 审核结果
+     */
+    private AuditResult parseAuditResult(JSONObject jsonResponse) {
+        try {
+            // API返回格式:
+            // {
+            //     "results": [
+            //         {
+            //             "filename": "[TEXT INPUT]" 或 URL,
+            //             "flagged": true/false,
+            //             "risk_level": "high"/"safe"等,
+            //             "violation_categories": [...],
+            //             "reason": "原因描述"
+            //         }
+            //     ],
+            //     "summary": "处理摘要"
+            // }
+
+            com.alibaba.fastjson2.JSONArray results = jsonResponse.getJSONArray("results");
+            if (results == null || results.isEmpty()) {
+                log.warn("AI审核接口返回结果为空");
+                return new AuditResult(false, "审核异常");
+            }
+
+            // 检查是否有任何项目被标记为违规
+            List<String> violationReasons = new ArrayList<>();
+            boolean hasViolations = false;
+
+            for (int i = 0; i < results.size(); i++) {
+                JSONObject result = results.getJSONObject(i);
+                if (result != null) {
+                    Boolean flagged = result.getBoolean("flagged");
+                    String filename = result.getString("filename");
+
+                    if (flagged != null && flagged) {
+                        hasViolations = true;
+                        // 根据filename判断是文本还是图片违规
+                        if ("[TEXT INPUT]".equals(filename)) {
+                            violationReasons.add("含违规词汇");
+                        } else if (StringUtils.hasText(filename) && filename.startsWith("http")) {
+                            violationReasons.add("图片内容违规");
+                        } else {
+                            violationReasons.add("内容违规");
+                        }
+                    }
+                }
+            }
+
+            if (hasViolations) {
+                // 有违规内容,审核失败
+                String failureReason = String.join("; ", violationReasons);
+                log.warn("AI审核失败:{}", failureReason);
+                return new AuditResult(false, failureReason);
+            } else {
+                // 所有内容都安全,审核通过
+                log.info("AI审核通过:所有内容安全");
+                return new AuditResult(true, null);
+            }
+
+        } catch (Exception e) {
+            log.error("解析审核结果异常", e);
+            return new AuditResult(false, "审核异常");
+        }
+    }
+}