浏览代码

阿里视频审核工具V1

wxd 4 月之前
父节点
当前提交
dc23ae6486

+ 125 - 0
VIDEO_MODERATION_DATABASE_DESIGN.md

@@ -0,0 +1,125 @@
+# 视频审核系统数据库设计文档
+
+## 1. 概述
+
+本文档详细描述了视频审核系统的数据库设计方案,包括表结构设计、字段说明、状态管理、索引设计以及落库逻辑等内容。
+
+## 2. 表结构设计
+
+### 2.1 表名
+`video_moderation_task` - 视频审核任务表
+
+### 2.2 表结构
+
+```sql
+CREATE TABLE `video_moderation_task` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `data_id` varchar(64) NOT NULL COMMENT '数据ID',
+  `video_url` varchar(512) NOT NULL COMMENT '视频URL',
+  `task_id` varchar(64) NOT NULL COMMENT '任务ID',
+  `status` varchar(20) NOT NULL DEFAULT 'SUBMITTED' COMMENT '任务状态 (SUBMITTED-已提交, PROCESSING-处理中, SUCCESS-成功, FAILED-失败)',
+  `risk_level` varchar(10) DEFAULT NULL COMMENT '风险级别 (none-无风险, low-低风险, medium-中风险, high-高风险)',
+  `result` text COMMENT '审核结果(JSON格式)',
+  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  `retry_count` int(11) NOT NULL DEFAULT '0' COMMENT '重试次数',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_task_id` (`task_id`),
+  KEY `idx_status` (`status`),
+  KEY `idx_create_time` (`create_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='视频审核任务表';
+```
+
+### 2.3 字段说明
+
+| 字段名 | 类型 | 允许空 | 默认值 | 说明 |
+|-------|------|-------|--------|------|
+| id | bigint(20) | 否 | 无 | 主键ID,自增 |
+| data_id | varchar(64) | 否 | 无 | 数据ID,用于标识审核的数据 |
+| video_url | varchar(512) | 否 | 无 | 视频URL地址 |
+| task_id | varchar(64) | 否 | 无 | 阿里云审核任务ID |
+| status | varchar(20) | 否 | SUBMITTED | 任务状态 |
+| risk_level | varchar(10) | 是 | NULL | 风险级别 |
+| result | text | 是 | NULL | 审核结果,JSON格式存储 |
+| create_time | datetime | 否 | CURRENT_TIMESTAMP | 创建时间 |
+| update_time | datetime | 否 | CURRENT_TIMESTAMP | 更新时间 |
+| retry_count | int(11) | 否 | 0 | 重试次数 |
+
+## 3. 状态管理
+
+系统中任务的状态流转如下:
+
+- **SUBMITTED**: 任务已提交到阿里云,等待处理
+- **PROCESSING**: 任务正在处理中
+- **SUCCESS**: 任务处理成功,审核完成
+- **FAILED**: 任务处理失败
+
+状态流转图:
+```
+SUBMITTED -> PROCESSING -> SUCCESS
+      |          |-> FAILED
+      |-> FAILED (提交失败)
+```
+
+## 4. 索引设计
+
+为了提高查询效率,表中设计了以下索引:
+
+1. **主键索引**: `id` 字段,用于唯一标识每条记录
+2. **唯一索引**: `task_id` 字段,确保阿里云任务ID的唯一性
+3. **普通索引**: `status` 字段,提高按状态查询任务的效率
+4. **普通索引**: `create_time` 字段,提高按创建时间查询任务的效率
+
+## 5. 落库逻辑
+
+### 5.1 提交任务时的落库逻辑
+
+当用户提交视频审核任务时,系统会执行以下落库操作:
+
+1. 生成唯一的数据ID (`data_id`)
+2. 调用阿里云接口提交视频审核任务
+3. 获取阿里云返回的`task_id`
+4. 将任务信息保存到数据库中,初始状态为`SUBMITTED`
+
+### 5.2 异步拉取结果时的落库逻辑
+
+通过XXL-JOB定时任务定期拉取审核结果:
+
+1. 查询数据库中状态为`SUBMITTED`或`PROCESSING`的任务
+2. 调用阿里云接口获取审核结果
+3. 根据结果更新数据库中的任务状态:
+   - 如果审核完成,更新状态为`SUCCESS`,并保存审核结果到`result`字段
+   - 如果仍在处理中,保持状态为`PROCESSING`
+   - 如果审核失败,更新状态为`FAILED`
+4. 对于网络异常等情况,增加重试次数,超过3次后标记为`FAILED`
+
+### 5.3 重试机制
+
+系统实现了重试机制来处理临时性错误:
+
+1. 每次处理失败时,`retry_count`字段加1
+2. 当重试次数超过3次时,将任务状态更新为`FAILED`
+3. 重试机制避免了因网络波动等临时问题导致的任务永久失败
+
+## 6. 示例数据
+
+```sql
+INSERT INTO `video_moderation_task` (`data_id`, `video_url`, `task_id`, `status`, `risk_level`, `result`) 
+VALUES 
+('video_1234567890', 'https://example.com/video.mp4', 'task_0987654321', 'SUCCESS', 'low', '{\"riskLevel\":\"low\",\"frameResult\":{}}');
+```
+
+## 7. 相关代码文件
+
+1. 实体类: `alien-entity/src/main/java/shop/alien/entity/VideoModerationTask.java`
+2. Mapper接口: `alien-entity/src/main/java/shop/alien/entity/VideoModerationTaskMapper.java`
+3. Mapper XML: `alien-entity/src/main/resources/mapper/VideoModerationTaskMapper.xml`
+4. 业务服务类: `alien-util/src/main/java/shop/alien/util/common/safe/video/VideoModerationService.java`
+5. XXL-JOB任务类: `alien-job/src/main/java/shop/alien/job/jobhandler/VideoModerationJobHandler.java`
+
+## 8. 注意事项
+
+1. `result`字段存储的是JSON格式的审核结果,可根据需要进行解析
+2. `video_url`字段最大长度为512字符,应确保视频URL不超过此长度
+3. 定时任务会定期清理状态为`SUCCESS`或`FAILED`的旧任务,避免数据量过大
+4. 重试次数限制为3次,可根据实际需求调整

+ 265 - 0
VIDEO_MODERATION_USER_GUIDE.md

@@ -0,0 +1,265 @@
+# 视频审核系统使用文档
+
+## 1. 概述
+
+视频审核系统是基于阿里云内容安全服务构建的一套完整的视频内容审核解决方案。该系统支持视频内容的自动审核,包括暴力、色情、政治敏感等内容的检测,并提供了完整的任务管理、结果存储和异步处理机制。
+
+## 2. 系统架构
+
+视频审核系统采用微服务架构,主要包括以下组件:
+
+1. **视频审核工具类** - 封装阿里云视频审核API调用
+2. **视频审核服务类** - 处理业务逻辑,包括任务提交、结果查询等
+3. **数据库实体类** - 定义视频审核任务的数据结构
+4. **数据库Mapper接口** - 提供数据库操作方法
+5. **XXL-JOB任务处理器** - 异步拉取审核结果
+6. **控制器示例** - 提供HTTP接口示例
+
+## 3. 功能说明
+
+### 3.1 视频审核任务提交
+
+用户可以通过调用[VideoModerationService.submitVideoModerationTask](file:///D:/database/alien_cloud/alien-util/src/main/java/shop/alien/util/common/safe/video/VideoModerationService.java#L47-L76)方法提交视频审核任务。
+
+#### 3.1.1 功能描述
+
+该功能将视频URL提交给阿里云内容安全服务进行审核,并将任务信息存储到数据库中。
+
+#### 3.1.2 使用方法
+
+```java
+@Autowired
+private VideoModerationService videoModerationService;
+
+// 提交视频审核任务
+String taskId = videoModerationService.submitVideoModerationTask("https://example.com/video.mp4");
+```
+
+#### 3.1.3 处理流程
+
+1. 生成唯一的`dataId`标识
+2. 调用阿里云接口提交视频审核任务
+3. 获取阿里云返回的`taskId`
+4. 将任务信息保存到数据库,状态设置为`SUBMITTED`
+
+### 3.2 视频审核结果查询
+
+用户可以通过调用[VideoModerationService.queryVideoModerationResult](file:///D:/database/alien_cloud/alien-util/src/main/java/shop/alien/util/common/safe/video/VideoModerationService.java#L84-L118)方法查询视频审核结果。
+
+#### 3.2.1 功能描述
+
+该功能直接从阿里云获取指定任务的审核结果,并更新数据库中的任务状态和结果。
+
+#### 3.2.2 使用方法
+
+```java
+@Autowired
+private VideoModerationService videoModerationService;
+
+// 查询视频审核结果
+VideoModerationTask task = videoModerationService.queryVideoModerationResult("your_task_id");
+```
+
+#### 3.2.3 处理流程
+
+1. 从数据库查询任务信息
+2. 调用阿里云接口获取审核结果
+3. 更新数据库中的任务状态为`SUCCESS`,并保存审核结果
+4. 返回完整的任务信息
+
+### 3.3 异步审核结果拉取
+
+系统通过XXL-JOB定时任务自动拉取审核结果,无需用户手动查询。
+
+#### 3.3.1 功能描述
+
+定时任务会定期扫描数据库中状态为`SUBMITTED`或`PROCESSING`的任务,并拉取其审核结果。
+
+#### 3.3.2 配置方法
+
+在XXL-JOB管理界面中添加新的任务,配置如下:
+
+- 任务名称:`videoModerationResultJobHandler`
+- 任务描述:视频审核结果拉取任务
+- 调度时间:建议每30秒执行一次
+- 任务参数:无需填写
+- 负责人:根据实际情况填写
+- 报警邮件:根据实际情况填写
+
+#### 3.3.3 处理流程
+
+1. 查询数据库中状态为`SUBMITTED`或`PROCESSING`的任务(最多100条)
+2. 遍历每个任务,调用阿里云接口获取审核结果
+3. 根据结果更新数据库:
+   - 审核完成:更新状态为`SUCCESS`,保存结果
+   - 审核中:更新状态为`PROCESSING`
+   - 审核失败:更新状态为`FAILED`
+4. 对于网络异常等临时错误,增加重试次数,超过3次后标记为`FAILED`
+
+### 3.4 重试机制
+
+系统实现了自动重试机制,以处理网络波动等临时性问题。
+
+#### 3.4.1 功能描述
+
+当异步拉取审核结果失败时,系统会自动重试,最多重试3次。
+
+#### 3.4.2 处理流程
+
+1. 拉取审核结果失败时,增加任务的重试次数
+2. 如果重试次数未超过3次,继续保留任务等待下次拉取
+3. 如果重试次数超过3次,将任务状态更新为`FAILED`
+
+## 4. 数据库设计
+
+### 4.1 表结构
+
+视频审核任务信息存储在`video_moderation_task`表中,包含以下字段:
+
+- `id`: 主键ID
+- `data_id`: 数据ID
+- `video_url`: 视频URL
+- `task_id`: 阿里云任务ID
+- [status](file://D:\database\alien_cloud\alien-entity\src\main\java\shop\alien\entity\result\R.java#L202-L204): 任务状态
+- `risk_level`: 风险级别
+- [result](file://D:\database\alien_cloud\alien-entity\src\main\java\shop\alien\entity\VideoModerationTask.java#L53-L53): 审核结果
+- `create_time`: 创建时间
+- `update_time`: 更新时间
+- `retry_count`: 重试次数
+
+### 4.2 状态说明
+
+任务状态包括:
+
+- `SUBMITTED`: 任务已提交
+- `PROCESSING`: 任务处理中
+- `SUCCESS`: 任务处理成功
+- `FAILED`: 任务处理失败
+
+## 5. API接口
+
+系统提供了示例控制器[VideoModerationController](file:///D:/database/alien_cloud/alien-util/src/main/java/shop/alien/util/common/safe/video/VideoModerationController.java#L14-L52),包含以下HTTP接口:
+
+### 5.1 提交视频审核任务
+
+```
+POST /video/moderation/submit?videoUrl={videoUrl}
+```
+
+#### 请求参数
+
+| 参数名 | 类型 | 必需 | 说明 |
+|--------|------|------|------|
+| videoUrl | string | 是 | 视频URL |
+
+#### 响应示例
+
+```json
+{
+  "code": 200,
+  "message": "任务提交成功,任务ID: task_xxxxxx",
+  "data": null
+}
+```
+
+### 5.2 查询视频审核结果
+
+```
+GET /video/moderation/result?taskId={taskId}
+```
+
+#### 请求参数
+
+| 参数名 | 类型 | 必需 | 说明 |
+|--------|------|------|------|
+| taskId | string | 是 | 任务ID |
+
+#### 响应示例
+
+```json
+{
+  "code": 200,
+  "message": "操作成功",
+  "data": {
+    "id": 1,
+    "dataId": "video_xxxxxx",
+    "videoUrl": "https://example.com/video.mp4",
+    "taskId": "task_xxxxxx",
+    "status": "SUCCESS",
+    "riskLevel": "low",
+    "result": "{\"riskLevel\":\"low\",\"frameResult\":{}}",
+    "createTime": "2023-01-01 12:00:00",
+    "updateTime": "2023-01-01 12:05:00",
+    "retryCount": 0
+  }
+}
+```
+
+## 6. 配置说明
+
+### 6.1 阿里云配置
+
+在`application.yml`或Nacos配置中心中添加以下配置:
+
+```yaml
+ali:
+  yundun:
+    accessKeyID: your_access_key_id
+    secret: your_access_key_secret
+    imgEndpoint: green-cip.cn-shanghai.aliyuncs.com
+    regionId: cn-shanghai
+```
+
+### 6.2 数据库配置
+
+确保数据库连接配置正确,并且数据库中已创建`video_moderation_task`表。
+
+### 6.3 XXL-JOB配置
+
+确保XXL-JOB配置正确,并且任务处理器已注册到调度中心。
+
+## 7. 错误处理
+
+### 7.1 提交任务失败
+
+当提交任务失败时,系统会抛出运行时异常,需要在调用方进行捕获处理。
+
+### 7.2 查询结果失败
+
+当查询结果失败时,系统会抛出运行时异常,需要在调用方进行捕获处理。
+
+### 7.3 网络异常
+
+对于网络异常等临时性错误,系统会自动重试,最多重试3次。
+
+## 8. 性能优化建议
+
+1. **批量处理**: XXL-JOB任务每次最多处理100条任务,可根据实际需求调整
+2. **索引优化**: 数据库表已创建必要的索引,可根据查询模式进一步优化
+3. **任务清理**: 建议定期清理已完成的任务,避免数据量过大影响性能
+4. **重试机制**: 合理设置重试次数,避免无效重试占用系统资源
+
+## 9. 扩展性说明
+
+### 9.1 增加新的审核类型
+
+如需支持其他类型的审核(如直播流审核),可以扩展[VideoModerationUtil](file:///D:/database/alien_cloud/alien-util/src/main/java/shop/alien/util/common/safe/video/VideoModerationUtil.java#L16-L101)工具类,添加相应的方法。
+
+### 9.2 修改审核结果处理逻辑
+
+如需修改审核结果的处理逻辑,可以修改[VideoModerationService.processTask](file:///D:/database/alien_cloud/alien-util/src/main/java/shop/alien/util/common/safe/video/VideoModerationService.java#L126-L185)方法。
+
+### 9.3 调整异步处理策略
+
+如需调整异步处理策略,可以修改[VideoModerationJobHandler](file:///D:/database/alien_cloud/alien-job/src/main/java/shop/alien/job/jobhandler/VideoModerationJobHandler.java#L16-L59)类中的处理逻辑。
+
+## 10. 监控和日志
+
+系统集成了完整的日志记录,可以通过日志监控任务执行情况:
+
+1. 任务提交日志
+2. 任务处理日志
+3. 异常日志
+4. 重试日志
+
+建议通过日志分析工具对这些日志进行集中管理和分析。

+ 24 - 0
alien-config/src/main/java/shop/alien/config/properties/VideoModerationProperties.java

@@ -0,0 +1,24 @@
+package shop.alien.config.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * 视频审核配置属性
+ */
+@Data
+@Component
+@ConfigurationProperties(prefix = "video.moderation")
+public class VideoModerationProperties {
+    
+    /**
+     * 视频审核开关
+     */
+    private boolean enabled = false;
+    
+    /**
+     * 审核失败时是否阻止商品发布
+     */
+    private boolean blockOnFailure = true;
+}

+ 70 - 0
alien-entity/src/main/java/shop/alien/entity/VideoModerationTask.java

@@ -0,0 +1,70 @@
+package shop.alien.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 视频审核任务实体类
+ */
+@Data
+@TableName("video_moderation_task")
+public class VideoModerationTask implements Serializable {
+    
+    private static final long serialVersionUID = 1L;
+    
+    /**
+     * 主键ID
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+    
+    /**
+     * 数据ID
+     */
+    private String dataId;
+    
+    /**
+     * 视频URL
+     */
+    private String videoUrl;
+    
+    /**
+     * 任务ID
+     */
+    private String taskId;
+    
+    /**
+     * 任务状态 (SUBMITTED-已提交, PROCESSING-处理中, SUCCESS-成功, FAILED-失败)
+     */
+    private String status;
+    
+    /**
+     * 风险级别 (none-无风险, low-低风险, medium-中风险, high-高风险)
+     */
+    private String riskLevel;
+    
+    /**
+     * 审核结果(JSON格式)
+     */
+    private String result;
+    
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+    
+    /**
+     * 更新时间
+     */
+    private Date updateTime;
+    
+    /**
+     * 重试次数
+     */
+    private Integer retryCount = 0;
+}

+ 5 - 0
alien-entity/src/main/java/shop/alien/entity/second/SecondGoods.java

@@ -99,6 +99,11 @@ public class SecondGoods implements Serializable {
     @ApiModelProperty(value = "交易ID")
     private Integer tradeId;
 
+    /**
+     * 视频审核任务ID
+     */
+    private String videoTaskId;
+
     @TableField("goods_status")
     @ApiModelProperty(value = "商品状态 0:草稿 1:审核中 2:审核失败 3:已上架 4:已下架 5:已售出")
     private Integer goodsStatus;

+ 63 - 0
alien-entity/src/main/java/shop/alien/mapper/system/VideoModerationTaskMapper.java

@@ -0,0 +1,63 @@
+package shop.alien.mapper.system;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.Update;
+import org.springframework.stereotype.Repository;
+import shop.alien.entity.VideoModerationTask;
+
+import java.util.List;
+
+/**
+ * 视频审核任务Mapper接口
+ */
+@Mapper
+@Repository
+public interface VideoModerationTaskMapper extends BaseMapper<VideoModerationTask> {
+
+    /**
+     * 查询待处理的审核任务
+     * @return 审核任务列表
+     */
+    @Select("SELECT * FROM video_moderation_task WHERE status IN ('SUBMITTED', 'PROCESSING') ORDER BY create_time ASC LIMIT 100")
+    List<VideoModerationTask> selectPendingTasks();
+
+    /**
+     * 根据任务ID查询任务详情
+     * @param taskId 任务ID
+     * @return 任务详情
+     */
+    @Select("SELECT * FROM video_moderation_task WHERE task_id = #{taskId}")
+    VideoModerationTask selectByTaskId(@Param("taskId") String taskId);
+
+    /**
+     * 更新任务状态
+     * @param taskId 任务ID
+     * @param status 状态
+     * @return 更新记录数
+     */
+    @Update("UPDATE video_moderation_task SET status = #{status}, update_time = NOW() WHERE task_id = #{taskId}")
+    int updateTaskStatus(@Param("taskId") String taskId, @Param("status") String status);
+
+    /**
+     * 更新任务结果
+     * @param taskId 任务ID
+     * @param status 状态
+     * @param riskLevel 风险级别
+     * @param result 审核结果
+     * @return 更新记录数
+     */
+    @Update("UPDATE video_moderation_task SET status = #{status}, risk_level = #{riskLevel}, result = #{result}, update_time = NOW() WHERE task_id = #{taskId}")
+    int updateTaskResult(@Param("taskId") String taskId, @Param("status") String status,
+                         @Param("riskLevel") String riskLevel, @Param("result") String result);
+
+    /**
+     * 增加重试次数
+     * @param id 任务ID
+     * @return 更新记录数
+     */
+    @Update("UPDATE video_moderation_task SET retry_count = retry_count + 1, update_time = NOW() WHERE id = #{id}")
+    int incrementRetryCount(@Param("id") Long id);
+}

+ 25 - 0
alien-entity/src/main/resources/mapper/VideoModerationTaskMapper.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="shop.alien.mapper.system.VideoModerationTaskMapper">
+    
+    <!-- 创建视频审核任务表 -->
+    <update id="createTableIfNotExists">
+        CREATE TABLE IF NOT EXISTS `video_moderation_task` (
+          `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+          `data_id` varchar(64) NOT NULL COMMENT '数据ID',
+          `video_url` varchar(512) NOT NULL COMMENT '视频URL',
+          `task_id` varchar(64) NOT NULL COMMENT '任务ID',
+          `status` varchar(20) NOT NULL DEFAULT 'SUBMITTED' COMMENT '任务状态 (SUBMITTED-已提交, PROCESSING-处理中, SUCCESS-成功, FAILED-失败)',
+          `risk_level` varchar(10) DEFAULT NULL COMMENT '风险级别 (none-无风险, low-低风险, medium-中风险, high-高风险)',
+          `result` text COMMENT '审核结果(JSON格式)',
+          `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+          `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+          `retry_count` int(11) NOT NULL DEFAULT '0' COMMENT '重试次数',
+          PRIMARY KEY (`id`),
+          UNIQUE KEY `uk_task_id` (`task_id`),
+          KEY `idx_status` (`status`),
+          KEY `idx_create_time` (`create_time`)
+        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='视频审核任务表';
+    </update>
+    
+</mapper>

+ 5 - 0
alien-job/pom.xml

@@ -46,6 +46,11 @@
         </dependency>
 
         <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-openfeign</artifactId>
+        </dependency>
+
+        <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
         </dependency>

+ 27 - 0
alien-job/src/main/java/shop/alien/job/feign/SecondGoodsFeign.java

@@ -0,0 +1,27 @@
+package shop.alien.job.feign;
+
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+
+@FeignClient(name = "alien-second", url = "http://localhost:8888")
+public interface SecondGoodsFeign {
+    
+    /**
+     * 处理视频审核结果
+     * 
+     * @param taskId 任务ID
+     * @return 处理结果
+     */
+    @GetMapping("/secondGoods/processVideoResult")
+    boolean processVideoResult(@RequestParam("taskId") String taskId);
+
+    /**
+     * 处理单个视频审核任务
+     *
+     * @param taskId 任务ID
+     * @return 处理结果
+     */
+    @GetMapping("/video/moderation/processTask")
+    boolean processTask(@RequestParam("taskId") String taskId);
+}

+ 73 - 0
alien-job/src/main/java/shop/alien/job/jobhandler/VideoModerationJobHandler.java

@@ -0,0 +1,73 @@
+package shop.alien.job.jobhandler;
+
+import com.xxl.job.core.biz.model.ReturnT;
+import com.xxl.job.core.handler.annotation.XxlJob;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import shop.alien.entity.VideoModerationTask;
+import shop.alien.job.feign.SecondGoodsFeign;
+import shop.alien.mapper.system.VideoModerationTaskMapper;
+
+import java.util.List;
+
+/**
+ * 视频审核任务XXL-JOB处理器
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class VideoModerationJobHandler {
+
+    /**
+     * 视频审核任务Mapper
+     */
+    private final VideoModerationTaskMapper videoModerationTaskMapper;
+
+    /**
+     * 二级商品服务Feign客户端
+     */
+    private final SecondGoodsFeign secondGoodsFeign;
+    
+    /**
+     * 视频审核结果拉取任务
+     * 每30秒执行一次
+     */
+    @XxlJob("videoModerationResultJobHandler")
+    public ReturnT<String> videoModerationResultJobHandler(String param) {
+        try {
+            log.info("开始执行视频审核结果拉取任务");
+            
+            // 查询待处理的审核任务(状态为SUBMITTED或PROCESSING)
+            List<VideoModerationTask> pendingTasks = videoModerationTaskMapper.selectPendingTasks();
+            
+            if (pendingTasks.isEmpty()) {
+                log.info("没有待处理的视频审核任务");
+                return ReturnT.SUCCESS;
+            }
+            
+            log.info("共找到 {} 个待处理的视频审核任务", pendingTasks.size());
+            
+            // 处理每个任务
+            int successCount = 0;
+            for (VideoModerationTask task : pendingTasks) {
+                try {
+                    boolean success = secondGoodsFeign.processTask(task.getTaskId());
+                    if (success) {
+                        successCount++;
+                        // 处理视频审核结果,更新商品状态
+                        secondGoodsFeign.processVideoResult(task.getTaskId());
+                    }
+                } catch (Exception e) {
+                    log.error("处理视频审核任务时发生异常,任务ID: {}", task.getTaskId(), e);
+                }
+            }
+            
+            log.info("视频审核结果拉取任务执行完成,成功处理 {} 个任务", successCount);
+            return ReturnT.SUCCESS;
+        } catch (Exception e) {
+            log.error("执行视频审核结果拉取任务时发生异常", e);
+            return ReturnT.FAIL;
+        }
+    }
+}

+ 33 - 0
alien-second/src/main/java/shop/alien/second/controller/SecondGoodsController.java

@@ -9,12 +9,14 @@ import io.swagger.annotations.*;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.VideoModerationTask;
 import shop.alien.entity.result.R;
 import shop.alien.entity.second.SecondGoods;
 import shop.alien.entity.second.SecondGoodsAudit;
 import shop.alien.entity.second.vo.SecondGoodsVo;
 import shop.alien.mapper.second.SecondGoodsAuditMapper;
 import shop.alien.second.service.SecondGoodsService;
+import shop.alien.second.service.VideoModerationService;
 import shop.alien.util.common.JwtUtil;
 import shop.alien.config.filter.NoRepeatSubmit;
 import shop.alien.util.common.safe.ImageModerationUtil;
@@ -38,8 +40,18 @@ import java.util.List;
 @RequiredArgsConstructor
 public class SecondGoodsController {
 
+    /**
+     * 视频审核服务
+     */
+    private final VideoModerationService videoModerationService;
+    /**
+     * 二手商品服务
+     */
     private final SecondGoodsService secondGoodsService;
 
+    /**
+     * 二手商品审核表
+     */
     private final SecondGoodsAuditMapper secondGoodsAuditMapper;
 
     // 注入文本审核工具
@@ -241,4 +253,25 @@ public class SecondGoodsController {
             return R.fail("图片审核异常: " + e.getMessage());
         }
     }
+
+    /**
+     * 处理视频审核结果
+     * @param taskId 视频审核任务ID
+     * @return 处理结果
+     */
+    @ApiOperation("处理视频审核结果")
+    @GetMapping("/processVideoResult")
+    public R processVideoResult(String taskId) {
+        try {
+            VideoModerationTask task = videoModerationService.getTaskByTaskId(taskId);
+            if (task == null) {
+                return R.fail("未找到视频审核任务 ");
+            }
+            secondGoodsService.processVideoModerationResult(task);
+            return R.data(task, "审核成功");
+        } catch (Exception e) {
+            log.error("处理视频审核结果时发生异常,任务ID: {}", taskId, e);
+            return R.fail("处理失败: " + e.getMessage());
+        }
+    }
 }

+ 77 - 0
alien-second/src/main/java/shop/alien/second/controller/VideoModerationController.java

@@ -0,0 +1,77 @@
+package shop.alien.second.controller;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.VideoModerationTask;
+import shop.alien.second.service.VideoModerationService;
+
+/**
+ * 视频审核控制器示例
+ */
+@Slf4j
+@RestController
+@RequestMapping("/video/moderation")
+@RequiredArgsConstructor
+public class VideoModerationController {
+    
+    private final VideoModerationService videoModerationService;
+    
+    /**
+     * 提交视频审核任务
+     * 
+     * @param videoUrl 视频URL
+     * @return 任务ID
+     */
+    @PostMapping("/submit")
+    public String submitVideoModerationTask(@RequestParam String videoUrl) {
+        try {
+            String taskId = videoModerationService.submitVideoModerationTask(videoUrl);
+            return "任务提交成功,任务ID: " + taskId;
+        } catch (Exception e) {
+            log.error("提交视频审核任务失败", e);
+            return "任务提交失败: " + e.getMessage();
+        }
+    }
+    
+    /**
+     * 查询视频审核结果
+     * 
+     * @param taskId 任务ID
+     * @return 审核结果
+     */
+    @GetMapping("/result")
+    public Object getVideoModerationResult(@RequestParam String taskId) {
+        try {
+            return videoModerationService.queryVideoModerationResult(taskId);
+        } catch (Exception e) {
+            log.error("查询视频审核结果失败", e);
+            return "查询失败: " + e.getMessage();
+        }
+    }
+
+
+    /**
+     * 处理单个视频审核任务
+     *
+     * @param taskId 任务ID
+     * @return 处理结果
+     */
+    @GetMapping("/processTask")
+    public boolean processTask(@RequestParam("taskId") String taskId) {
+        try {
+            // 根据任务ID查询任务详情
+            VideoModerationTask task = videoModerationService.getTaskByTaskId(taskId);
+            if (task == null) {
+                log.error("未找到任务ID为 {} 的视频审核任务", taskId);
+                return false;
+            }
+
+            // 处理任务
+            return videoModerationService.processTask(task);
+        } catch (Exception e) {
+            log.error("处理视频审核任务时发生异常,任务ID: {}", taskId, e);
+            return false;
+        }
+    }
+}

+ 8 - 0
alien-second/src/main/java/shop/alien/second/service/SecondGoodsService.java

@@ -1,9 +1,11 @@
 package shop.alien.second.service;
 
+import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import io.swagger.models.auth.In;
 import org.springframework.transaction.annotation.Transactional;
+import shop.alien.entity.VideoModerationTask;
 import shop.alien.entity.second.SecondGoods;
 import shop.alien.entity.second.vo.SecondGoodsVo;
 import shop.alien.entity.second.vo.SellGoodsVo;
@@ -149,4 +151,10 @@ public interface SecondGoodsService extends IService<SecondGoods> {
     IPage<SellGoodsVo> getTransactionList(IPage<SellGoodsVo> page, Integer userId);
 
     List<SecondGoods> getGoodsListByUserId(Integer userId, Integer goodsStatus);
+
+    /**
+     * 处理视频审核结果
+     * @param task 视频审核任务
+     */
+    void processVideoModerationResult(VideoModerationTask task);
 }

+ 35 - 0
alien-second/src/main/java/shop/alien/second/service/VideoModerationService.java

@@ -0,0 +1,35 @@
+package shop.alien.second.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.VideoModerationTask;
+
+public interface VideoModerationService  extends IService<VideoModerationTask> {
+    /**
+     * 提交视频审核任务
+     *
+     * @param videoUrl 视频URL
+     * @return 任务ID
+     */
+    String submitVideoModerationTask(String videoUrl);
+    /**
+     * 查询视频审核结果
+     *
+     * @param taskId 任务ID
+     * @return 审核结果
+     */
+    VideoModerationTask queryVideoModerationResult(String taskId);
+    /**
+     * 处理视频审核结果
+     *
+     * @param task 视频审核任务
+     * @return 是否处理成功
+     */
+    boolean processTask(VideoModerationTask task);
+    /**
+     * 根据任务ID获取视频审核任务
+     *
+     * @param taskId 任务ID
+     * @return 视频审核任务
+     */
+    VideoModerationTask getTaskByTaskId(String taskId);
+}

+ 87 - 2
alien-second/src/main/java/shop/alien/second/service/impl/SecondGoodsServiceImpl.java

@@ -10,9 +10,12 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.google.common.collect.Lists;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.util.StringUtils;
+import shop.alien.entity.VideoModerationTask;
 import shop.alien.entity.second.SecondGoods;
 import shop.alien.entity.second.SecondGoodsAudit;
 import shop.alien.entity.second.enums.SecondGoodsStatusEnum;
@@ -26,10 +29,12 @@ import shop.alien.mapper.second.SecondGoodsAuditMapper;
 import shop.alien.mapper.second.SecondGoodsMapper;
 import shop.alien.second.feign.AlienStoreFeign;
 import shop.alien.second.service.SecondGoodsService;
+import shop.alien.second.service.VideoModerationService;
 import shop.alien.util.common.Constants;
 import shop.alien.util.common.VideoUtils;
 import shop.alien.util.common.safe.*;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
 import java.util.List;
@@ -39,10 +44,26 @@ import java.util.stream.Collectors;
 /**
  * 二手商品服务实现类
  */
+@Slf4j
 @Service
 @RequiredArgsConstructor
 public class SecondGoodsServiceImpl extends ServiceImpl<SecondGoodsMapper, SecondGoods> implements SecondGoodsService {
 
+
+    @Value("${video.moderation.enabled:false}")
+    private boolean videoModerationEnabled;
+    
+    @Value("${video.moderation.block-on-failure:true}")
+    private boolean videoModerationBlockOnFailure;
+
+    /**
+     * 视频审核服务
+     */
+    private final VideoModerationService videoModerationService;
+
+    /**
+     * 视频工具类
+     */
     private final VideoUtils videoUtils;
 
     /**
@@ -174,7 +195,7 @@ public class SecondGoodsServiceImpl extends ServiceImpl<SecondGoodsMapper, Secon
         }
     }
     /**
-     * 执行内容审核(图片和文本
+     * 执行内容审核(图片、文本和视频
      * @param goods 商品信息
      * @param goodsDTO 商品DTO信息
      * @return 审核结果
@@ -211,7 +232,7 @@ public class SecondGoodsServiceImpl extends ServiceImpl<SecondGoodsMapper, Secon
                 imgServicesList.add(ImageReviewServiceEnum.AIGC_CHECK.getService());
                 ImageModerationResultVO response = imageModerationUtil.productPublishCheck(imageUrl,imgServicesList);
                 if ("high".equals(response.getRiskLevel())) {
-                    // 文本审核不通过或存在高风险
+                    // 图片审核不通过或存在高风险
                     goods.setGoodsStatus(SecondGoodsStatusEnum.REVIEW_FAILED.getCode()); // 审核失败
                     goods.setFailedReason("图片审核不通过:图片中包含" + (response.getDescriptions() != null ? response.getDescriptions() : "高风险内容"));
                     // 插入审核记录
@@ -222,6 +243,41 @@ public class SecondGoodsServiceImpl extends ServiceImpl<SecondGoodsMapper, Secon
                 }
             }
         }
+        
+        // 视频审核
+        if (videoModerationEnabled) {
+            List<String> videoUrls = extractVideoUrls(goodsDTO.getImgUrl());
+            if (!videoUrls.isEmpty()) {
+                // 提交视频审核任务
+                List<String> taskIds = new ArrayList<>();
+                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);
+                            sendFailedMsg(goods);
+                            return false;
+                        }
+                    }
+                }
+                
+                // 如果成功提交了视频审核任务,设置商品状态为审核中
+                if (!taskIds.isEmpty()) {
+                    goods.setGoodsStatus(SecondGoodsStatusEnum.UNDER_REVIEW.getCode()); // 审核中
+                    goods.setVideoTaskId(taskIds.get(0)); // 保存第一个任务ID到商品表
+                    goods.setFailedReason("");
+                    updateById(goods);
+                    createGoodsAudit(goods, "", SecondGoodsStatusEnum.UNDER_REVIEW.getCode());
+                    return true; // 异步处理,直接返回
+                }
+            }
+        }
 
         // 如果所有审核都通过,设置为上架状态
         goods.setGoodsStatus(SecondGoodsStatusEnum.LISTED.getCode()); // 上架
@@ -234,6 +290,25 @@ public class SecondGoodsServiceImpl extends ServiceImpl<SecondGoodsMapper, Secon
         sendMessage(goods);
         return true;
     }
+    
+    /**
+     * 从图片URL列表中提取视频URL
+     * @param imageUrls 图片URL列表
+     * @return 视频URL列表
+     */
+    private 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;
+    }
 
     /**
      * 创建商品审核记录
@@ -745,6 +820,15 @@ public class SecondGoodsServiceImpl extends ServiceImpl<SecondGoodsMapper, Secon
     }
 
     /**
+     * 处理视频审核结果
+     * @param task 视频审核任务
+     */
+    @Override
+    public void processVideoModerationResult(VideoModerationTask task) {
+
+    }
+
+    /**
      * 查询搜索结果
      * @param page 分页参数
      * @param secondGoodsVo 查询参数
@@ -896,4 +980,5 @@ public class SecondGoodsServiceImpl extends ServiceImpl<SecondGoodsMapper, Secon
             }
         }
     }
+
 }

+ 208 - 0
alien-second/src/main/java/shop/alien/second/service/impl/VideoModerationServiceImpl.java

@@ -0,0 +1,208 @@
+package shop.alien.second.service.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.aliyun.green20220302.models.VideoModerationResponse;
+import com.aliyun.green20220302.models.VideoModerationResultResponse;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import shop.alien.entity.VideoModerationTask;
+import shop.alien.entity.second.vo.SecondGoodsRecommendVo;
+import shop.alien.mapper.second.SecondRecommendMapper;
+import shop.alien.mapper.system.VideoModerationTaskMapper;
+import shop.alien.second.service.SecondRecommendService;
+import shop.alien.second.service.VideoModerationService;
+import shop.alien.util.common.safe.video.VideoModerationUtil;
+import shop.alien.entity.second.SecondGoods;
+
+import java.util.Date;
+import java.util.UUID;
+
+/**
+ * 视频审核业务服务类
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class VideoModerationServiceImpl extends ServiceImpl<VideoModerationTaskMapper, VideoModerationTask> implements VideoModerationService {
+
+    /**
+     * 视频审核工具类
+     */
+    private final VideoModerationUtil videoModerationUtil;
+
+    /**
+     * 视频审核任务Mapper
+     */
+    private final VideoModerationTaskMapper videoModerationTaskMapper;
+
+    /**
+     * 提交视频审核任务
+     *
+     * @param videoUrl 视频URL
+     * @return 任务ID
+     */
+    public String submitVideoModerationTask(String videoUrl) {
+        try {
+            // 生成dataId
+            String dataId = "video_" + UUID.randomUUID().toString().replace("-", "");
+
+            // 调用阿里云接口提交任务
+            VideoModerationResponse response = videoModerationUtil.submitVideoModerationTask(videoUrl, dataId);
+
+            if (response.getStatusCode() == 200 && response.getBody() != null &&
+                    response.getBody().getCode() != null && response.getBody().getCode() == 200) {
+
+                // 保存任务信息到数据库
+                VideoModerationTask task = new VideoModerationTask();
+                task.setDataId(dataId);
+                task.setVideoUrl(videoUrl);
+                task.setTaskId(response.getBody().getData().getTaskId());
+                task.setStatus("SUBMITTED");
+                task.setCreateTime(new Date());
+                task.setUpdateTime(new Date());
+
+                videoModerationTaskMapper.insert(task);
+
+                log.info("视频审核任务提交成功,任务ID: {}", task.getTaskId());
+                return task.getTaskId();
+            } else {
+                log.error("提交视频审核任务失败,响应: {}", JSON.toJSONString(response));
+                throw new RuntimeException("提交视频审核任务失败: " + (response.getBody() != null ? response.getBody().getMessage() : "未知错误"));
+            }
+        } catch (Exception e) {
+            log.error("提交视频审核任务异常", e);
+            throw new RuntimeException("提交视频审核任务异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 根据任务ID查询任务详情
+     *
+     * @param taskId 任务ID
+     * @return 任务详情
+     */
+    public VideoModerationTask getTaskByTaskId(String taskId) {
+        try {
+            return videoModerationTaskMapper.selectByTaskId(taskId);
+        } catch (Exception e) {
+            log.error("查询视频审核任务详情异常,任务ID: {}", taskId, e);
+            throw new RuntimeException("查询视频审核任务详情异常: " + e.getMessage());
+        }
+    }
+    
+    /**
+     * 根据任务ID查询任务详情
+     * 
+     * @param taskId 任务ID
+     * @return 任务详情
+     */
+    public VideoModerationTask getTaskById(String taskId) {
+        try {
+            return videoModerationTaskMapper.selectByTaskId(taskId);
+        } catch (Exception e) {
+            log.error("查询视频审核任务详情异常,任务ID: {}", taskId, e);
+            throw new RuntimeException("查询视频审核任务详情异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 查询视频审核结果
+     *
+     * @param taskId 任务ID
+     * @return 审核结果
+     */
+    public VideoModerationTask queryVideoModerationResult(String taskId) {
+        try {
+            // 查询数据库中的任务
+            VideoModerationTask task = videoModerationTaskMapper.selectByTaskId(taskId);
+            if (task == null) {
+                throw new RuntimeException("未找到任务ID为 " + taskId + " 的审核任务");
+            }
+
+            // 调用阿里云接口获取审核结果
+            VideoModerationResultResponse response = videoModerationUtil.getVideoModerationResult(taskId);
+
+            if (response.getStatusCode() == 200 && response.getBody() != null &&
+                    response.getBody().getCode() != null && response.getBody().getCode() == 200) {
+
+                // 更新数据库中的任务结果
+                String status = "SUCCESS";
+                String riskLevel = response.getBody().getData().getRiskLevel();
+                String result = JSON.toJSONString(response.getBody().getData());
+
+                videoModerationTaskMapper.updateTaskResult(taskId, status, riskLevel, result);
+
+                // 更新任务对象并返回
+                task.setStatus(status);
+                task.setRiskLevel(riskLevel);
+                task.setResult(result);
+                task.setUpdateTime(new Date());
+
+                log.info("获取视频审核结果成功,任务ID: {}", taskId);
+                return task;
+            } else {
+                log.error("获取视频审核结果失败,响应: {}", JSON.toJSONString(response));
+                throw new RuntimeException("获取视频审核结果失败: " + (response.getBody() != null ? response.getBody().getMessage() : "未知错误"));
+            }
+        } catch (Exception e) {
+            log.error("获取视频审核结果异常,任务ID: {}", taskId, e);
+            throw new RuntimeException("获取视频审核结果异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 处理单个审核任务
+     *
+     * @param task 审核任务
+     * @return 是否处理成功
+     */
+    public boolean processTask(VideoModerationTask task) {
+        try {
+            // 调用阿里云接口获取审核结果
+            VideoModerationResultResponse response = videoModerationUtil.getVideoModerationResult(task.getTaskId());
+
+            if (response.getStatusCode() == 200 && response.getBody() != null) {
+                Integer code = response.getBody().getCode();
+                if (code != null && code == 200) {
+                    // 审核完成,更新任务结果
+                    String status = "SUCCESS";
+                    String riskLevel = response.getBody().getData().getRiskLevel();
+                    String result = JSON.toJSONString(response.getBody().getData());
+
+                    videoModerationTaskMapper.updateTaskResult(task.getTaskId(), status, riskLevel, result);
+
+                    log.info("视频审核任务处理成功,任务ID: {}", task.getTaskId());
+                    return true;
+                } else if (code != null && code == 500) {
+                    // 审核仍在进行中,更新状态
+                    videoModerationTaskMapper.updateTaskStatus(task.getTaskId(), "PROCESSING");
+                    log.info("视频审核任务仍在处理中,任务ID: {}", task.getTaskId());
+                    return false;
+                } else {
+                    // 审核失败
+                    videoModerationTaskMapper.updateTaskStatus(task.getTaskId(), "FAILED");
+                    log.error("视频审核任务处理失败,任务ID: {},错误信息: {}", task.getTaskId(), response.getBody().getMessage());
+                    return false;
+                }
+            } else {
+                // HTTP请求失败
+                log.error("获取视频审核结果HTTP请求失败,任务ID: {},状态码: {}", task.getTaskId(), response.getStatusCode());
+                return false;
+            }
+        } catch (Exception e) {
+            log.error("处理视频审核任务异常,任务ID: {}", task.getTaskId(), e);
+
+            // 增加重试次数
+            videoModerationTaskMapper.incrementRetryCount(task.getId());
+
+            // 如果重试次数超过3次,标记为失败
+            if (task.getRetryCount() >= 3) {
+                videoModerationTaskMapper.updateTaskStatus(task.getTaskId(), "FAILED");
+            }
+
+            return false;
+        }
+    }
+}

+ 1 - 1
alien-util/pom.xml

@@ -123,7 +123,7 @@
             <artifactId>easyexcel</artifactId>
             <version>1.1.2-beta5</version>
         </dependency>
-        <!--    阿里安全图片/文本检测    -->
+        <!-- 阿里云内容安全 -->
         <dependency>
             <groupId>com.aliyun</groupId>
             <artifactId>green20220302</artifactId>

+ 136 - 0
alien-util/src/main/java/shop/alien/util/common/safe/video/VideoModerationUtil.java

@@ -0,0 +1,136 @@
+package shop.alien.util.common.safe.video;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.aliyun.green20220302.Client;
+import com.aliyun.green20220302.models.*;
+import com.aliyun.teaopenapi.models.Config;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+
+@Slf4j
+@Component
+public class VideoModerationUtil {
+
+    @Value("${ali.yundun.accessKeyID}")
+    private String accessKeyId;
+
+    @Value("${ali.yundun.secret}")
+    private String accessKeySecret;
+
+    @Value("${ali.yundun.imgEndpoint}")
+    private String imgEndpoint;
+
+    @Value("${ali.yundun.regionId:cn-shanghai}")
+    private String regionId;
+
+    private Client client;
+
+    @PostConstruct
+    public void init() throws Exception {
+        this.client = createClient();
+    }
+
+    /**
+     * 创建请求客户端
+     *
+     * @return Client
+     * @throws Exception 创建客户端异常
+     */
+    public Client createClient() throws Exception {
+        Config config = new Config();
+        config.setAccessKeyId(accessKeyId);
+        config.setAccessKeySecret(accessKeySecret);
+        config.setEndpoint(imgEndpoint);
+        //接入区域和地址请根据实际情况修改。
+        config.setRegionId(regionId);
+        //连接时超时时间,单位毫秒(ms)。
+        config.setReadTimeout(6000);
+        //读取时超时时间,单位毫秒(ms)。
+        config.setConnectTimeout(3000);
+        return new Client(config);
+    }
+
+    /**
+     * 提交视频检测任务
+     *
+     * @param url  视频URL
+     * @param dataId 数据ID
+     * @return VideoModerationResponse
+     * @throws Exception 调用异常
+     */
+    public VideoModerationResponse submitVideoModerationTask(String url, String dataId) throws Exception {
+        JSONObject serviceParameters = new JSONObject();
+        serviceParameters.put("url", url);
+        if (dataId != null) {
+            serviceParameters.put("dataId", dataId);
+        }
+
+        VideoModerationRequest videoModerationRequest = new VideoModerationRequest();
+        // 检测类型:videoDetection
+        videoModerationRequest.setService("videoDetection");
+        videoModerationRequest.setServiceParameters(serviceParameters.toJSONString());
+
+        try {
+            VideoModerationResponse response = client.videoModeration(videoModerationRequest);
+            log.info("提交视频审核任务结果: {}", JSON.toJSONString(response));
+            return response;
+        } catch (Exception e) {
+            log.error("提交视频审核任务失败", e);
+            throw new Exception("提交视频审核任务失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 获取视频检测结果
+     *
+     * @param taskId 任务ID
+     * @return VideoModerationResultResponse
+     * @throws Exception 调用异常
+     */
+    public VideoModerationResultResponse getVideoModerationResult(String taskId) throws Exception {
+        JSONObject serviceParameters = new JSONObject();
+        serviceParameters.put("taskId", taskId);
+
+        VideoModerationResultRequest request = new VideoModerationResultRequest();
+        request.setService("videoDetection");
+        request.setServiceParameters(serviceParameters.toJSONString());
+
+        try {
+            VideoModerationResultResponse response = client.videoModerationResult(request);
+            log.info("获取视频审核结果: {}", JSON.toJSONString(response));
+            return response;
+        } catch (Exception e) {
+            log.error("获取视频审核结果失败", e);
+            throw new Exception("获取视频审核结果失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 取消视频检测任务
+     *
+     * @param taskId 任务ID
+     * @return VideoModerationCancelResponse
+     * @throws Exception 调用异常
+     */
+    public VideoModerationCancelResponse cancelVideoModerationTask(String taskId) throws Exception {
+        JSONObject serviceParameters = new JSONObject();
+        serviceParameters.put("taskId", taskId);
+
+        VideoModerationCancelRequest request = new VideoModerationCancelRequest();
+        request.setService("liveStreamDetection");
+        request.setServiceParameters(serviceParameters.toJSONString());
+
+        try {
+            VideoModerationCancelResponse response = client.videoModerationCancel(request);
+            log.info("取消视频审核任务结果: {}", JSON.toJSONString(response));
+            return response;
+        } catch (Exception e) {
+            log.error("取消视频审核任务失败", e);
+            throw new Exception("取消视频审核任务失败: " + e.getMessage());
+        }
+    }
+}