|
|
@@ -3,15 +3,13 @@ package shop.alien.job.store;
|
|
|
import com.alibaba.fastjson2.JSONArray;
|
|
|
import com.alibaba.fastjson2.JSONObject;
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
-import com.fasterxml.jackson.core.JsonProcessingException;
|
|
|
-import com.fasterxml.jackson.databind.JsonNode;
|
|
|
-import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
import com.xxl.job.core.context.XxlJobHelper;
|
|
|
import com.xxl.job.core.handler.annotation.XxlJob;
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
|
import org.springframework.http.*;
|
|
|
+import org.springframework.http.client.ClientHttpRequestInterceptor;
|
|
|
import org.springframework.stereotype.Component;
|
|
|
import org.springframework.util.CollectionUtils;
|
|
|
import org.springframework.util.LinkedMultiValueMap;
|
|
|
@@ -25,8 +23,6 @@ import java.time.LocalDate;
|
|
|
import java.time.format.DateTimeFormatter;
|
|
|
import java.util.*;
|
|
|
|
|
|
-import static com.alipay.api.internal.util.AlipayUtils.getFileSuffix;
|
|
|
-
|
|
|
/**
|
|
|
* 调用AI标签数据服务类
|
|
|
*
|
|
|
@@ -38,8 +34,6 @@ import static com.alipay.api.internal.util.AlipayUtils.getFileSuffix;
|
|
|
@RequiredArgsConstructor
|
|
|
public class AiTagJob {
|
|
|
|
|
|
- private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
|
|
-
|
|
|
private final RestTemplate restTemplate;
|
|
|
|
|
|
private final TagsMainMapper tagsMainMapper;
|
|
|
@@ -77,9 +71,12 @@ public class AiTagJob {
|
|
|
private String saveTagUrl;
|
|
|
|
|
|
// 第三方接口地址 内容合规检测接口
|
|
|
- @Value("${third-party-content-check.base-url:http://192.168.2.250:9000/ai/auto-review/api/v1/content_compliance/check}")
|
|
|
+ @Value("${third-party-contentcheck.base-url}")
|
|
|
private String contentComplianceUrl;
|
|
|
|
|
|
+ @Value("${third-party-getresult.base-url}")
|
|
|
+ private String getResultUrl;
|
|
|
+
|
|
|
//用户名
|
|
|
@Value("${third-party-user-name.base-url}")
|
|
|
private String userName;
|
|
|
@@ -582,13 +579,14 @@ public class AiTagJob {
|
|
|
List<LifeUserDynamics> lifeUserDynamics = lifeUserDynamicsMapper.selectList(new LambdaQueryWrapper<LifeUserDynamics>()
|
|
|
.eq(LifeUserDynamics::getCheckFlag, 0).eq(LifeUserDynamics::getDeleteFlag, 0));
|
|
|
|
|
|
+ // 只依赖数据库字段即可判定待审核的动态,避免重复提交
|
|
|
// 常见图片后缀(可按需添加,如 .heic、.svg 等)
|
|
|
HashSet<String> IMAGE_SUFFIXES = new HashSet<>(Arrays.asList(
|
|
|
- ".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".heic", ".svg", ".tiff"
|
|
|
+ "jpg", "jpeg", "png", "gif", "bmp", "webp", "heic", "svg", "tiff"
|
|
|
));
|
|
|
// 常见视频后缀(可按需添加,如 .avi、.flv 等)
|
|
|
HashSet<String> VIDEO_SUFFIXES = new HashSet<>(Arrays.asList(
|
|
|
- ".mp4", ".mov", ".mkv", ".avi", ".flv", ".wmv", ".mpeg", ".mpg", ".webm"
|
|
|
+ "mp4", "mov", "mkv", "avi", "flv", "wmv", "mpeg", "mpg", "webm"
|
|
|
));
|
|
|
|
|
|
for (LifeUserDynamics lifeUserDynamic : lifeUserDynamics) {
|
|
|
@@ -597,7 +595,7 @@ public class AiTagJob {
|
|
|
List<String> imageList = new ArrayList<>();
|
|
|
List<String> videoList = new ArrayList<>();
|
|
|
// 按分隔符拆分路径数组(处理连续分隔符如 ",," 产生的空字符串)
|
|
|
- String[] allPaths = imagePath.split(imagePath);
|
|
|
+ String[] allPaths = imagePath.split(",");
|
|
|
for (String path : allPaths) {
|
|
|
// 去除路径前后空格(避免 " a.jpg " 这类情况)
|
|
|
String trimmedPath = path.trim();
|
|
|
@@ -615,6 +613,7 @@ public class AiTagJob {
|
|
|
} else if (VIDEO_SUFFIXES.contains(suffix)) {
|
|
|
videoList.add(trimmedPath);
|
|
|
}
|
|
|
+ // 对每一条动态逐条申请 token 并提交,避免批量失败导致重试逻辑复杂化
|
|
|
try {
|
|
|
log.info("登录Ai服务获取token..." + loginUrl);
|
|
|
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
|
|
|
@@ -645,6 +644,7 @@ public class AiTagJob {
|
|
|
aiHeaders.set("Authorization", "Bearer " + accessToken);
|
|
|
|
|
|
Map<String, Object> jsonBody = new HashMap<>();
|
|
|
+ // text/img/video 三种维度一起传入 AI,便于一次完成合规审查
|
|
|
jsonBody.put("text", lifeUserDynamic.getContext());
|
|
|
jsonBody.put("imgUrl", imageList);
|
|
|
jsonBody.put("video", videoList);
|
|
|
@@ -656,21 +656,27 @@ public class AiTagJob {
|
|
|
if (response.getStatusCodeValue() != 200) {
|
|
|
log.error("AI内容审核接口调用失败 http状态:" + response.getStatusCode());
|
|
|
}
|
|
|
- JsonNode responseNode = OBJECT_MAPPER.readTree(response.getBody());
|
|
|
- int code = responseNode.has("code") ? responseNode.get("code").asInt() : 0;
|
|
|
- if (code != 200) {
|
|
|
- throw new RuntimeException("AI接口调用失败" + code);
|
|
|
+ JSONObject responseNode = JSONObject.parseObject(response.getBody());
|
|
|
+ if (responseNode == null) {
|
|
|
+ log.error("AI接口调用失败,响应内容为空");
|
|
|
}
|
|
|
- JsonNode dataNode = responseNode.get("data");
|
|
|
-
|
|
|
- if (dataNode.has("is_compliant")) {
|
|
|
- LifeUserDynamics dynamics = new LifeUserDynamics();
|
|
|
- dynamics.setId(lifeUserDynamic.getId());
|
|
|
- dynamics.setCheckFlag(1);
|
|
|
- lifeUserDynamicsMapper.updateById(dynamics);
|
|
|
+ Integer code = null;
|
|
|
+ if (responseNode != null) {
|
|
|
+ code = responseNode.getInteger("code");
|
|
|
+ if(code==200) {
|
|
|
+ JSONObject dataNode = JSONObject.from(responseNode.get("data"));
|
|
|
+ // 审核发起后仅标记 checkFlag=1 并保存任务号,等待回调或后续轮询更新
|
|
|
+ LifeUserDynamics dynamics = new LifeUserDynamics();
|
|
|
+ dynamics.setId(lifeUserDynamic.getId());
|
|
|
+ dynamics.setCheckFlag(1);
|
|
|
+ dynamics.setAiTaskId(dataNode.get("task_id").toString());
|
|
|
+ lifeUserDynamicsMapper.updateById(dynamics);
|
|
|
+ log.info("动态审核成功,AI返回内容: {}", response.getBody());
|
|
|
+ XxlJobHelper.handleSuccess("动态内容审核任务执行成功");
|
|
|
+ }
|
|
|
+ }else {
|
|
|
+ log.error("AI接口调用失败,错误码: " + code);
|
|
|
}
|
|
|
- log.info("动态审核成功,AI返回内容: {}", response.getBody());
|
|
|
- XxlJobHelper.handleSuccess("动态内容审核任务执行成功");
|
|
|
} catch (Exception e) {
|
|
|
log.error("调用AI内容审核接口失败", e);
|
|
|
}
|
|
|
@@ -685,6 +691,95 @@ public class AiTagJob {
|
|
|
return R.success("动态内容审核任务执行成功");
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 获取审核违规内容结果
|
|
|
+ */
|
|
|
+ @XxlJob("getCheckTask")
|
|
|
+ public R<String> getCheckTask() {
|
|
|
+ List<LifeUserDynamics> lifeUserDynamics = lifeUserDynamicsMapper.selectList(new LambdaQueryWrapper<LifeUserDynamics>()
|
|
|
+ .eq(LifeUserDynamics::getCheckFlag, 1).eq(LifeUserDynamics::getDeleteFlag, 0));
|
|
|
+
|
|
|
+ for (LifeUserDynamics lifeUserDynamic : lifeUserDynamics) {
|
|
|
+ // 针对已提交且未删除的动态轮询查询结果
|
|
|
+ try {
|
|
|
+ log.info("登录Ai服务获取token..." + loginUrl);
|
|
|
+ MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
|
|
|
+ formData.add("username", userName);
|
|
|
+ formData.add("password", passWord);
|
|
|
+
|
|
|
+ HttpHeaders headers = new HttpHeaders();
|
|
|
+ headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
|
|
+ HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(formData, headers);
|
|
|
+ ResponseEntity<String> postForEntity = null;
|
|
|
+ try {
|
|
|
+ log.info("请求Ai服务登录接口===================>");
|
|
|
+ postForEntity = restTemplate.postForEntity(loginUrl, requestEntity, String.class);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("请求AI服务登录接口失败", e);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (postForEntity != null && postForEntity.getStatusCodeValue() == 200) {
|
|
|
+ log.info("请求Ai服务登录成功 postForEntity.getBody()\t" + postForEntity.getBody());
|
|
|
+ String responseBody = postForEntity.getBody();
|
|
|
+ JSONObject jsonObject = JSONObject.parseObject(responseBody);
|
|
|
+ if (jsonObject != null) {
|
|
|
+ JSONObject dataJson = jsonObject.getJSONObject("data");
|
|
|
+ String accessToken = dataJson.getString("access_token");
|
|
|
+
|
|
|
+ // 创建带拦截器的RestTemplate
|
|
|
+ // 使用拦截器统一追加 Authorization,避免每次手动设置 header
|
|
|
+ RestTemplate restTemplateWithAuth = new RestTemplate();
|
|
|
+ List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
|
|
|
+ interceptors.add((request, body, execution) -> {
|
|
|
+ request.getHeaders().set("Authorization", "Bearer " + accessToken);
|
|
|
+ return execution.execute(request, body);
|
|
|
+ });
|
|
|
+ restTemplateWithAuth.setInterceptors(interceptors);
|
|
|
+
|
|
|
+ ResponseEntity<String> response = null;
|
|
|
+ try {
|
|
|
+ response = restTemplateWithAuth.getForEntity(getResultUrl + "?task_id="+lifeUserDynamic.getAiTaskId(), String.class);
|
|
|
+ if (response.getStatusCodeValue() != 200) {
|
|
|
+ log.error("AI内容审核结果获取接口调用失败 http状态:" + response.getStatusCode());
|
|
|
+ }
|
|
|
+ JSONObject responseNode = JSONObject.parseObject(response.getBody());
|
|
|
+ if (responseNode == null) {
|
|
|
+ log.error("AI接口调用失败,响应内容为空");
|
|
|
+ }
|
|
|
+ Integer code = null;
|
|
|
+ if (responseNode != null) {
|
|
|
+ code = responseNode.getInteger("code");
|
|
|
+ if(code==200) {
|
|
|
+ JSONObject dataNode = JSONObject.from(responseNode.get("data"));
|
|
|
+
|
|
|
+ if (!(boolean) dataNode.get("is_compliant")) {
|
|
|
+ // 只要 AI 判定不合规,立即禁用动态并记录原因
|
|
|
+ LifeUserDynamics dynamics = new LifeUserDynamics();
|
|
|
+ dynamics.setId(lifeUserDynamic.getId());
|
|
|
+ dynamics.setCheckFlag(2);
|
|
|
+ dynamics.setEnableStatus(1);
|
|
|
+ dynamics.setReason(String.valueOf(dataNode.get("failure_reason")));
|
|
|
+ lifeUserDynamicsMapper.updateById(dynamics);
|
|
|
+ }
|
|
|
+ log.info("动态审核结果获取成功,AI返回内容: {}", response.getBody());
|
|
|
+ XxlJobHelper.handleSuccess("动态内容审核任务结果获取执行成功");
|
|
|
+ } else {
|
|
|
+ log.error("AI接口调用失败,错误码: " + code);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("调用AI内容审核结果获取接口失败", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (RuntimeException ex) {
|
|
|
+ XxlJobHelper.handleFail("动态内容审核任务结果获取执行失败:" + ex.getMessage());
|
|
|
+ return R.fail("动态内容审核任务结果获取执行失败:" + ex.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return R.success("动态内容审核任务结果获取执行成功");
|
|
|
+ }
|
|
|
+
|
|
|
class AnalysisRequest {
|
|
|
private String start_time;
|
|
|
private String end_time;
|
|
|
@@ -705,5 +800,4 @@ public class AiTagJob {
|
|
|
this.end_time = end_time;
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
}
|