|
|
@@ -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, "审核异常");
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|