|
|
@@ -0,0 +1,546 @@
|
|
|
+package shop.alien.second.service.impl;
|
|
|
+
|
|
|
+import cn.hutool.core.collection.CollectionUtil;
|
|
|
+import com.alibaba.fastjson2.JSON;
|
|
|
+import com.alibaba.fastjson2.JSONArray;
|
|
|
+import com.alibaba.fastjson2.JSONObject;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
|
+import com.google.common.collect.Lists;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.util.StringUtils;
|
|
|
+import shop.alien.entity.SecondVideoTask;
|
|
|
+import shop.alien.entity.second.SecondGoods;
|
|
|
+import shop.alien.entity.second.SecondGoodsAudit;
|
|
|
+import shop.alien.entity.second.SecondGoodsRecord;
|
|
|
+import shop.alien.entity.second.enums.SecondGoodsStatusEnum;
|
|
|
+import shop.alien.entity.second.vo.SecondGoodsVo;
|
|
|
+import shop.alien.entity.store.SecondAiTask;
|
|
|
+import shop.alien.entity.store.StoreImg;
|
|
|
+import shop.alien.mapper.SecondAiTaskMapper;
|
|
|
+import shop.alien.mapper.StoreImgMapper;
|
|
|
+import shop.alien.mapper.second.SecondGoodsAuditMapper;
|
|
|
+import shop.alien.mapper.second.SecondGoodsMapper;
|
|
|
+import shop.alien.mapper.second.SecondGoodsRecordMapper;
|
|
|
+import shop.alien.second.service.*;
|
|
|
+import shop.alien.second.util.AiTaskUtils;
|
|
|
+import shop.alien.util.common.Constants;
|
|
|
+import shop.alien.util.common.safe.ImageModerationResultVO;
|
|
|
+import shop.alien.util.common.safe.ImageModerationUtil;
|
|
|
+import shop.alien.util.common.safe.ImageReviewServiceEnum;
|
|
|
+import shop.alien.util.common.safe.TextModerationResultVO;
|
|
|
+import shop.alien.util.common.safe.TextModerationUtil;
|
|
|
+import shop.alien.util.common.safe.TextReviewServiceEnum;
|
|
|
+
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Collections;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.List;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 二手商品审核服务实现类
|
|
|
+ * 负责商品的图片、文本、视频审核相关业务逻辑
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+@RequiredArgsConstructor
|
|
|
+public class SecondGoodsAuditServiceImpl implements SecondGoodsAuditService {
|
|
|
+
|
|
|
+ private final SecondAiTaskMapper secondAiTaskMapper;
|
|
|
+ private final StoreImgMapper storeImgMapper;
|
|
|
+ /**
|
|
|
+ * 视频审核功能是否启用的配置项
|
|
|
+ */
|
|
|
+ @Value("${video.moderation.enabled}")
|
|
|
+ private boolean videoModerationEnabled;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 视频审核失败时是否阻塞商品发布的配置项
|
|
|
+ */
|
|
|
+ @Value("${video.moderation.block-on-failure}")
|
|
|
+ private boolean videoModerationBlockOnFailure;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 视频审核服务,用于处理商品中视频内容的审核
|
|
|
+ */
|
|
|
+ private final VideoModerationService videoModerationService;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 文本审核工具,用于审核商品标题、描述等文本内容
|
|
|
+ */
|
|
|
+ private final TextModerationUtil textModerationUtil;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 图片审核工具,用于审核商品图片内容
|
|
|
+ */
|
|
|
+ private final ImageModerationUtil imageModerationUtil;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 二手商品Mapper
|
|
|
+ */
|
|
|
+ private final SecondGoodsMapper secondGoodsMapper;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 二手商品审核Mapper
|
|
|
+ */
|
|
|
+ private final SecondGoodsAuditMapper secondGoodsAuditMapper;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 商品操作历史记录Mapper
|
|
|
+ */
|
|
|
+ private final SecondGoodsRecordMapper secondGoodsRecordMapper;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 消息通知服务
|
|
|
+ */
|
|
|
+ private final SecondGoodsNotificationService notificationService;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 操作历史记录服务
|
|
|
+ */
|
|
|
+ private final SecondGoodsOperationRecordService operationRecordService;
|
|
|
+
|
|
|
+ private final AiTaskUtils aiTaskUtil;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 执行内容审核
|
|
|
+ * @param goodsDTO 商品信息
|
|
|
+ * @param goods 商品实体
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void performContentReview(SecondGoodsVo goodsDTO, SecondGoods goods) throws Exception {
|
|
|
+ // 图片审核
|
|
|
+ boolean imageAuditResult = performImageReviews(goods, goodsDTO);
|
|
|
+
|
|
|
+ // 审核失败,直接返回
|
|
|
+ if (!imageAuditResult) {
|
|
|
+ // 图片审核不通过,记录操作历史
|
|
|
+ operationRecordService.recordGoodsOperation(goods, "图片审核失败");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 文本审核
|
|
|
+ boolean textAuditResult = performTextReview(goods, goodsDTO);
|
|
|
+
|
|
|
+ // 审核失败,直接返回
|
|
|
+ if (!textAuditResult) {
|
|
|
+ // 文本审核不通过,记录操作历史
|
|
|
+ operationRecordService.recordGoodsOperation(goods, "文本审核失败");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 视频审核
|
|
|
+ List<String> taskIds = performVideoReviews(goods, goodsDTO);
|
|
|
+
|
|
|
+ // 如果成功提交了视频审核任务,设置商品状态为审核中
|
|
|
+ if (!taskIds.isEmpty()) {
|
|
|
+ goods.setGoodsStatus(SecondGoodsStatusEnum.UNDER_REVIEW.getCode()); // 审核中
|
|
|
+ goods.setVideoTaskId(taskIds.get(0)); // 保存第一个任务ID到商品表
|
|
|
+ goods.setFailedReason("");
|
|
|
+ secondGoodsMapper.updateById(goods);
|
|
|
+ // 审核中审核记录
|
|
|
+ createGoodsAudit(goods, "", Constants.AuditStatus.UNDER_REVIEW);
|
|
|
+ } else {
|
|
|
+ // 第二轮AI审核
|
|
|
+ boolean b = performSecondRoundReview(goods, goodsDTO);
|
|
|
+ if (!b) {
|
|
|
+ // 第二轮审核失败,记录操作历史
|
|
|
+ operationRecordService.recordGoodsOperation(goods, "第二轮审核失败");
|
|
|
+ goods.setGoodsStatus(2);
|
|
|
+ goods.setFailedReason("调用AI接口失败,未获取到task_id");
|
|
|
+ secondGoodsMapper.updateById(goods);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 审核通过后上架商品
|
|
|
+// approveAndListGoods(goods);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 第二轮审核(AI)
|
|
|
+ @Override
|
|
|
+ public boolean performSecondRoundReview(SecondGoods goods, SecondGoodsVo goodsDTO) {
|
|
|
+ try {
|
|
|
+ // 参数校验
|
|
|
+ if (goodsDTO == null || CollectionUtil.isEmpty(goodsDTO.getImgUrl())) {
|
|
|
+ log.warn("Second round review skipped: empty image URLs for goods id={}",
|
|
|
+ goods != null ? goods.getId() : "unknown");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取访问令牌
|
|
|
+ String accessToken = aiTaskUtil.getAccessToken();
|
|
|
+ if (StringUtils.isEmpty(accessToken)) {
|
|
|
+ log.error("Failed to obtain access token for second round review, goods id={}", goods.getId());
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建AI任务
|
|
|
+ String taskId = aiTaskUtil.createTask(accessToken, goods.getDescription(), goodsDTO.getImgUrl());
|
|
|
+ if (StringUtils.isEmpty(taskId)) {
|
|
|
+ log.warn("Failed to create AI task for second round review, goods id={}", goods.getId());
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ goods.setAiTaskId(taskId);
|
|
|
+ goods.setGoodsStatus(SecondGoodsStatusEnum.UNDER_REVIEW.getCode());
|
|
|
+ secondGoodsMapper.updateById(goods);
|
|
|
+
|
|
|
+ // 保存任务信息
|
|
|
+ SecondAiTask secondAiTask = new SecondAiTask();
|
|
|
+ secondAiTask.setTaskId(taskId);
|
|
|
+ secondAiTask.setStatus("PROCESSING");
|
|
|
+ Date currentTime = new Date();
|
|
|
+ secondAiTask.setCreateTime(currentTime);
|
|
|
+ secondAiTask.setUpdateTime(currentTime);
|
|
|
+ secondAiTaskMapper.insert(secondAiTask);
|
|
|
+
|
|
|
+ log.info("Successfully created second round AI review task, taskId={}, goodsId={}",
|
|
|
+ taskId, goods.getId());
|
|
|
+ return true;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("Error during second round review for goods id={}",
|
|
|
+ goods != null ? goods.getId() : "unknown", e);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 执行图片审核
|
|
|
+ * @param goods 商品信息
|
|
|
+ * @param goodsDTO 商品DTO信息
|
|
|
+ * @return 审核结果
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public boolean performImageReviews(SecondGoods goods, SecondGoodsVo goodsDTO) throws Exception {
|
|
|
+ // 图片审核(循环处理)
|
|
|
+ List<String> imageUrls = goodsDTO.getImgUrl();
|
|
|
+ // 根据imageUrls过滤不是图片的url
|
|
|
+ imageUrls = imageUrls.stream()
|
|
|
+ .filter(url -> url.toLowerCase().endsWith(".jpg")
|
|
|
+ || url.toLowerCase().endsWith(".png")
|
|
|
+ || url.toLowerCase().endsWith(".jpeg")
|
|
|
+ || url.toLowerCase().endsWith(".gif"))
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ // 图片审核
|
|
|
+ if (imageUrls != null && !imageUrls.isEmpty()) {
|
|
|
+ for (String imageUrl : imageUrls) {
|
|
|
+ List<String> imgServicesList = Lists.newArrayList();
|
|
|
+ // 内容治理检测 + AIGC图片风险检测
|
|
|
+ imgServicesList.add(ImageReviewServiceEnum.TONALITY_IMPROVE.getService());
|
|
|
+ imgServicesList.add(ImageReviewServiceEnum.AIGC_CHECK.getService());
|
|
|
+
|
|
|
+ ImageModerationResultVO response = imageModerationUtil.productPublishCheck(imageUrl, imgServicesList);
|
|
|
+ if ("high".equals(response.getRiskLevel())) {
|
|
|
+ // 图片审核不通过或存在高风险
|
|
|
+ goods.setGoodsStatus(SecondGoodsStatusEnum.REVIEW_FAILED.getCode()); // 审核失败
|
|
|
+ String failReason = "图片审核不通过:图片中包含" +
|
|
|
+ (response.getDescriptions() != null ? response.getDescriptions() : "高风险内容");
|
|
|
+ goods.setFailedReason(failReason);
|
|
|
+ // 插入审核记录
|
|
|
+ createGoodsAudit(goods, failReason, Constants.AuditStatus.FAILED);
|
|
|
+ // 发送审核失败消息
|
|
|
+ notificationService.sendFailedMsg(goods);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 执行文本审核
|
|
|
+ * @param goods 商品信息
|
|
|
+ * @param goodsDTO 商品DTO信息
|
|
|
+ * @return 审核结果
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public boolean performTextReview(SecondGoods goods, SecondGoodsVo goodsDTO) throws Exception {
|
|
|
+ List<String> servicesList = Lists.newArrayList();
|
|
|
+ servicesList.add(TextReviewServiceEnum.AD_COMPLIANCE_DETECTION_PRO.getService());
|
|
|
+ servicesList.add(TextReviewServiceEnum.LLM_QUERY_MODERATION.getService());
|
|
|
+
|
|
|
+ // 使用商品发布场景的审核服务
|
|
|
+ String test = goodsDTO.getDescription() + goodsDTO.getTitle() + goods.getLabel() + goods.getTopic();
|
|
|
+ TextModerationResultVO textCheckResult = textModerationUtil.invokeFunction(test, servicesList);
|
|
|
+
|
|
|
+ if ("high".equals(textCheckResult.getRiskLevel())) {
|
|
|
+ // 文本审核不通过或存在高风险
|
|
|
+ goods.setGoodsStatus(SecondGoodsStatusEnum.REVIEW_FAILED.getCode()); // 审核失败
|
|
|
+ String failReason = "文本审核不通过:" +
|
|
|
+ (textCheckResult.getRiskWords() != null ? textCheckResult.getRiskWords() : "存在高风险内容");
|
|
|
+ goods.setFailedReason(failReason);
|
|
|
+ // 插入审核记录
|
|
|
+ createGoodsAudit(goods, failReason, Constants.AuditStatus.FAILED);
|
|
|
+ // 发送审核失败消息
|
|
|
+ notificationService.sendFailedMsg(goods);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 执行视频审核
|
|
|
+ * @param goods 商品信息
|
|
|
+ * @param goodsDTO 商品DTO信息
|
|
|
+ * @return 审核结果
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public List<String> performVideoReviews(SecondGoods goods, SecondGoodsVo goodsDTO) {
|
|
|
+ List<String> videoUrls = extractVideoUrls(goodsDTO.getImgUrl());
|
|
|
+ List<String> taskIds = new ArrayList<>();
|
|
|
+
|
|
|
+ // 视频审核
|
|
|
+ if (videoModerationEnabled) {
|
|
|
+ if (!videoUrls.isEmpty()) {
|
|
|
+ // 提交视频审核任务
|
|
|
+ for (String videoUrl : videoUrls) {
|
|
|
+ try {
|
|
|
+ String taskId = videoModerationService.submitVideoModerationTask(videoUrl);
|
|
|
+ taskIds.add(taskId);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("提交视频审核任务失败,视频URL: {}", videoUrl, e);
|
|
|
+ if (videoModerationBlockOnFailure) {
|
|
|
+ // 视频审核提交失败,设置为审核失败状态
|
|
|
+ goods.setGoodsStatus(SecondGoodsStatusEnum.REVIEW_FAILED.getCode());
|
|
|
+ goods.setFailedReason("视频审核提交失败: " + e.getMessage());
|
|
|
+ createGoodsAudit(goods, "视频审核提交失败", Constants.AuditStatus.FAILED);
|
|
|
+ notificationService.sendFailedMsg(goods);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return taskIds;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理视频审核结果
|
|
|
+ * @param task 视频审核任务
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void processVideoModerationResult(SecondVideoTask task) {
|
|
|
+ try {
|
|
|
+ // 查找关联的商品
|
|
|
+ QueryWrapper<SecondGoods> queryWrapper = new QueryWrapper<>();
|
|
|
+ queryWrapper.eq("video_task_id", task.getTaskId());
|
|
|
+ SecondGoods goods = secondGoodsMapper.selectOne(queryWrapper);
|
|
|
+
|
|
|
+ if (goods == null) {
|
|
|
+ log.warn("未找到关联的商品,任务ID: {}", task.getTaskId());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // 根据审核结果更新商品状态
|
|
|
+ if ("none".equals(task.getRiskLevel())) {
|
|
|
+ QueryWrapper<StoreImg> imgQueryWrapper = new QueryWrapper<>();
|
|
|
+ imgQueryWrapper.eq("store_id", goods.getId());
|
|
|
+ imgQueryWrapper.eq("img_type", 19);
|
|
|
+ List<StoreImg> storeImgs = storeImgMapper.selectList(imgQueryWrapper);
|
|
|
+ List<String> imgUrls = storeImgs.stream()
|
|
|
+ .map(StoreImg::getImgUrl)
|
|
|
+ .filter(imgUrl -> StringUtils.hasText(imgUrl))
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ SecondGoodsVo goodsDTO = new SecondGoodsVo();
|
|
|
+ goodsDTO.setImgUrl(imgUrls);
|
|
|
+
|
|
|
+ // 开始第二轮审核
|
|
|
+ boolean b = performSecondRoundReview(goods, goodsDTO);
|
|
|
+
|
|
|
+ // 审核通过
|
|
|
+// approveAndListGoods(goods);
|
|
|
+ } else {
|
|
|
+ // 审核不通过
|
|
|
+ goods.setGoodsStatus(SecondGoodsStatusEnum.REVIEW_FAILED.getCode());
|
|
|
+
|
|
|
+ // 解析审核结果,生成具体的失败原因
|
|
|
+ String failedReason = parseVideoModerationFailureReason(task);
|
|
|
+ goods.setFailedReason(failedReason);
|
|
|
+ secondGoodsMapper.updateById(goods);
|
|
|
+
|
|
|
+ // 更新审核记录
|
|
|
+ createGoodsAudit(goods, failedReason, Constants.AuditStatus.FAILED);
|
|
|
+
|
|
|
+ // 记录操作历史
|
|
|
+ operationRecordService.recordGoodsOperation(goods, "视频审核失败");
|
|
|
+ // 发送审核失败消息
|
|
|
+ notificationService.sendFailedMsg(goods);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("处理视频审核结果时发生异常,任务ID: {}", task.getTaskId(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 审核通过后上架商品
|
|
|
+ * @param goods 商品信息
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void approveAndListGoods(SecondGoods goods) {
|
|
|
+ boolean isNotified = false;
|
|
|
+ try {
|
|
|
+ // 如果所有审核都通过,设置为上架状态
|
|
|
+ goods.setGoodsStatus(SecondGoodsStatusEnum.LISTED.getCode()); // 上架
|
|
|
+ goods.setFailedReason("");
|
|
|
+ goods.setReleaseTime(new Date()); // 上架时间
|
|
|
+ secondGoodsMapper.updateById(goods);
|
|
|
+ // 插入审核记录
|
|
|
+ createGoodsAudit(goods, "", Constants.AuditStatus.PASSED);
|
|
|
+ // 发送审核成功消息
|
|
|
+ notificationService.sendMessage(goods);
|
|
|
+ isNotified = true; // 标记通知已发送
|
|
|
+
|
|
|
+ // 上架 记录商品操作历史
|
|
|
+ String operationName = "";
|
|
|
+ QueryWrapper<SecondGoodsRecord> queryWrapper = new QueryWrapper<>();
|
|
|
+ queryWrapper.eq("goods_id", goods.getId());
|
|
|
+ List<SecondGoodsRecord> recordList = secondGoodsRecordMapper.selectList(queryWrapper);
|
|
|
+ if (CollectionUtil.isNotEmpty(recordList)) {
|
|
|
+ operationName = "重新发布";
|
|
|
+ } else {
|
|
|
+ operationName = "首次发布";
|
|
|
+ }
|
|
|
+ operationRecordService.recordGoodsOperation(goods, operationName);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("商品上架过程中发生异常,执行回滚", e);
|
|
|
+ // 如果通知已发送但后续操作失败,需要补偿
|
|
|
+ if (isNotified) {
|
|
|
+ // 发送补偿消息,比如撤销通知或标记为异常状态
|
|
|
+ }
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建商品审核记录
|
|
|
+ * @param goods 商品信息
|
|
|
+ * @param failReason 审核失败原因
|
|
|
+ * @param goodsStatus 商品状态
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void createGoodsAudit(SecondGoods goods, String failReason, Integer goodsStatus) {
|
|
|
+ // 保存审核结果
|
|
|
+ secondGoodsMapper.updateById(goods);
|
|
|
+ // 插入审核记录
|
|
|
+ SecondGoodsAudit auditRecord = new SecondGoodsAudit();
|
|
|
+ auditRecord.setGoodsId(goods.getId());
|
|
|
+ auditRecord.setGoodsStatus(goodsStatus); // 审核状态
|
|
|
+ if (Constants.AuditStatus.FAILED.equals(goodsStatus)) {
|
|
|
+ auditRecord.setFailedReason(failReason);
|
|
|
+ }
|
|
|
+ auditRecord.setCreatedUserId(goods.getUserId());
|
|
|
+ auditRecord.setUpdatedUserId(goods.getUserId());
|
|
|
+ auditRecord.setCreatedTime(new Date());
|
|
|
+ auditRecord.setUpdatedTime(new Date());
|
|
|
+ secondGoodsAuditMapper.insert(auditRecord);
|
|
|
+ goods.setAuditRecordId(auditRecord.getId());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从图片URL列表中提取视频URL
|
|
|
+ * @param imageUrls 图片URL列表
|
|
|
+ * @return 视频URL列表
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public List<String> extractVideoUrls(List<String> imageUrls) {
|
|
|
+ if (CollectionUtil.isEmpty(imageUrls)) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+
|
|
|
+ List<String> videoUrls = new ArrayList<>();
|
|
|
+ for (String url : imageUrls) {
|
|
|
+ if (isVideoUrl(url)) {
|
|
|
+ videoUrls.add(url);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return videoUrls;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断URL是否为视频地址
|
|
|
+ * @param url 输入URL
|
|
|
+ * @return 是否为视频地址
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public boolean isVideoUrl(String url) {
|
|
|
+ if (url == null) return false;
|
|
|
+ url = url.toLowerCase();
|
|
|
+ return url.endsWith(".mp4") ||
|
|
|
+ url.endsWith(".avi") ||
|
|
|
+ url.endsWith(".mov") ||
|
|
|
+ url.endsWith(".flv") ||
|
|
|
+ url.endsWith(".wmv") ||
|
|
|
+ url.endsWith(".mkv");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析视频审核失败原因
|
|
|
+ * @param task 视频审核任务
|
|
|
+ * @return 失败原因
|
|
|
+ */
|
|
|
+ private String parseVideoModerationFailureReason(SecondVideoTask task) {
|
|
|
+ StringBuilder reasonBuilder = new StringBuilder("视频审核不通过,风险等级: " + task.getRiskLevel());
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 解析审核结果JSON
|
|
|
+ JSONObject resultJson = JSON.parseObject(task.getResult());
|
|
|
+ if (resultJson != null && resultJson.containsKey("data")) {
|
|
|
+ JSONObject data = resultJson.getJSONObject("data");
|
|
|
+
|
|
|
+ // 处理帧结果(视频画面)
|
|
|
+ if (data.containsKey("FrameResult")) {
|
|
|
+ JSONObject frameResult = data.getJSONObject("FrameResult");
|
|
|
+ if (frameResult != null && frameResult.containsKey("FrameSummarys")) {
|
|
|
+ JSONArray frameSummarys = frameResult.getJSONArray("FrameSummarys");
|
|
|
+ if (frameSummarys != null && !frameSummarys.isEmpty()) {
|
|
|
+ reasonBuilder.append("。检测到违规内容:");
|
|
|
+ for (int i = 0; i < frameSummarys.size(); i++) {
|
|
|
+ JSONObject summary = frameSummarys.getJSONObject(i);
|
|
|
+ String label = summary.getString("Label");
|
|
|
+ String description = summary.getString("Description");
|
|
|
+ Integer labelSum = summary.getInteger("LabelSum");
|
|
|
+
|
|
|
+ if (label != null && !label.isEmpty()) {
|
|
|
+ reasonBuilder.append("[").append(description != null ? description : label)
|
|
|
+ .append("]出现").append(labelSum != null ? labelSum : 1).append("次;");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理音频结果
|
|
|
+ if (data.containsKey("AudioResult")) {
|
|
|
+ JSONObject audioResult = data.getJSONObject("AudioResult");
|
|
|
+ if (audioResult != null && audioResult.containsKey("AudioSummarys")) {
|
|
|
+ JSONArray audioSummarys = audioResult.getJSONArray("AudioSummarys");
|
|
|
+ if (audioSummarys != null && !audioSummarys.isEmpty()) {
|
|
|
+ reasonBuilder.append("。检测到违规音频:");
|
|
|
+ for (int i = 0; i < audioSummarys.size(); i++) {
|
|
|
+ JSONObject summary = audioSummarys.getJSONObject(i);
|
|
|
+ String label = summary.getString("Label");
|
|
|
+ String description = summary.getString("Description");
|
|
|
+ Integer labelSum = summary.getInteger("LabelSum");
|
|
|
+
|
|
|
+ if (label != null && !label.isEmpty()) {
|
|
|
+ reasonBuilder.append("[").append(description != null ? description : label)
|
|
|
+ .append("]出现").append(labelSum != null ? labelSum : 1).append("次;");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("解析视频审核结果失败,使用默认原因,任务ID: {}", task.getTaskId(), e);
|
|
|
+ }
|
|
|
+
|
|
|
+ return reasonBuilder.toString();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|