diff --git a/YIDUN_API_DOCUMENTATION.md b/YIDUN_API_DOCUMENTATION.md new file mode 100644 index 0000000..6f8147b --- /dev/null +++ b/YIDUN_API_DOCUMENTATION.md @@ -0,0 +1,875 @@ +# 易盾内容安全 API 调用文档 + +## 📋 目录 + +- [概述](#概述) +- [配置说明](#配置说明) +- [API接口列表](#api接口列表) +- [文本检测](#1-文本检测) +- [图片检测](#2-图片检测) +- [视频检测](#3-视频检测) +- [错误码说明](#错误码说明) + +--- + +## 概述 + +本项目集成了网易易盾内容安全SDK,提供文本、图片、视频的内容安全检测服务。 + +### 产品凭证 + +| 检测类型 | SecretID | SecretKey | BusinessID | +|---------|----------|-----------|------------| +| **音视频检测** | f58a38341ca6227014df7c3bf0e6f16f | 526aa631ba5d518aedeb70b5a3b67371 | 需从易盾控制台获取 | +| **图片检测** | 9a82f90bfec61eb40d1c95605b894817 | f73a78954417a3713c36ec2d14eb2b5f | 需从易盾控制台获取 | +| **文本检测** | 待配置 | 待配置 | 待配置 | + +### 服务地址 + +``` +http://localhost:3002 +``` + +--- + +## 配置说明 + +配置文件位置:`config.yml` + +```yaml +yidun: + # 音视频检测配置 + video: + business_id: "YOUR_VIDEO_BUSINESS_ID" + secret_id: "f58a38341ca6227014df7c3bf0e6f16f" + secret_key: "526aa631ba5d518aedeb70b5a3b67371" + + # 图片检测配置 + image: + business_id: "YOUR_IMAGE_BUSINESS_ID" + secret_id: "9a82f90bfec61eb40d1c95605b894817" + secret_key: "f73a78954417a3713c36ec2d14eb2b5f" + + # 文本检测配置 + text: + business_id: "YOUR_TEXT_BUSINESS_ID" + secret_id: "YOUR_TEXT_SECRET_ID" + secret_key: "YOUR_TEXT_SECRET_KEY" +``` + +--- + +## API接口列表 + +| 接口名称 | 请求方法 | 路径 | 说明 | +|---------|---------|------|------| +| 文本检测提交 | POST | `/yidun/detect-text` | 提交文本进行异步检测 | +| 图片检测提交 | POST | `/yidun/detect-image` | 提交图片进行异步检测 | +| 视频检测提交 | POST | `/yidun/detect-video` | 提交视频进行检测 | +| 图片结果查询 | POST | `/yidun/GetImageResult` | 查询图片检测结果(轮询模式) | +| 图片检测回调 | POST | `/yidun/receive-image-callback` | 接收图片检测结果推送(推送模式) | +| 视频结果查询 | POST | `/yidun/GetVideoResult` | 查询视频检测结果(轮询模式) | +| 视频检测回调 | POST | `/yidun/receive-video-callback` | 接收视频检测结果推送(推送模式) | + +**注意**:检测结果是异步的,提交接口只返回 `task_id`,需要通过回调或轮询获取结果。 + +--- + +## 1. 文本检测 + +### 提交文本检测任务 + +**接口地址**: `POST /yidun/detect-text` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| data_id | string | 是 | 数据唯一标识 | +| content | string | 是 | 待检测的文本内容 | +| ip | string | 否 | 用户IP地址 | +| token | string | 否 | 用户token(反作弊) | + +**请求示例**: + +```bash +curl -X POST http://localhost:3002/yidun/detect-text \ + -H "Content-Type: application/json" \ + -d '{ + "data_id": "text-001", + "content": "这是一段需要检测的文本内容", + "ip": "192.168.1.100" + }' +``` + +**响应示例**: + +```json +{ + "code": 0, + "message": "", + "data": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6" +} +``` + +**响应说明**: + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| data | string | 任务ID (task_id),用于后续查询检测结果 | + +--- + +## 2. 图片检测 + +### 2.1 提交图片检测任务 + +**接口地址**: `POST /yidun/detect-image` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| data_id | string | 是 | 数据唯一标识 | +| image_url | string | 是 | 图片URL地址 | +| callback_url | string | 否 | 回调地址 | + +**请求示例**: + +```bash +curl -X POST http://localhost:3002/yidun/detect-image \ + -H "Content-Type: application/json" \ + -d '{ + "data_id": "img-001", + "image_url": "https://example.com/image.jpg" + }' +``` + +**响应示例**: + +```json +{ + "code": 0, + "message": "", + "data": { + "taskId": "b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7", + "name": "img-001", + "dataId": "img-001", + "dealingCount": 0 + } +} +``` + +**响应说明**: + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| taskId | string | 任务ID,用于后续查询检测结果 | +| name | string | 图片唯一标识 | +| dataId | string | 客户图片唯一标识 | +| dealingCount | int64 | 缓冲池排队待处理数据量 | + +### 2.2 查询图片检测结果(轮询模式) + +**接口地址**: `POST /yidun/GetImageResult` + +**SDK调用信息**: +- SDK方法: `ImageClient.ImageCallback(request)` +- 请求地址: `http://as.dun.163.com/v5/image/callback/results` +- 请求参数: `yidunRequestId` (taskId) + +**请求示例**: + +```bash +curl -X POST "http://localhost:3002/yidun/GetImageResult" \ + -H "Content-Type: application/json" \ + -d '{"taskId": "b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7"}' +``` + +**响应示例**: + +```json +{ + "code": 0, + "message": "", + "data": { + "taskId": "b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7", + "status": 2, + "suggestion": 0, + "label": 100, + "resultType": 2, + "dataId": "img-001", + "name": "img-001", + "censorTime": 1629426386079, + "url": "https://example.com/image.jpg", + "antispam": { + "taskId": "b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7", + "name": "img-001", + "dataId": "img-001", + "status": 2, + "suggestion": 0, + "label": 100, + "resultType": 2, + "censorTime": 1629426386079, + "censorSource": 0, + "censorRound": 1, + "labels": [ + { + "label": 400, + "level": 2, + "rate": 1.0 + } + ], + "censorLabels": [ + { + "code": "审核标签编码", + "customCode": "自定义标签编码", + "name": "审核标签名称", + "desc": "风景" + } + ], + "remark": "人审备注信息" + }, + "ocr": { ... }, + "face": { ... }, + "quality": { ... }, + "logo": { ... }, + "discern": { ... }, + "ad": { ... }, + "userRisk": { ... }, + "anticheat": { ... }, + "riskControl": { ... }, + "aigc": { ... }, + "llmCheckInfo": [ ... ] + } +} +``` + +**antispam 字段完整说明**: + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| taskId | string | 任务ID | +| name | string | 图片名称 | +| dataId | string | 客户数据ID | +| status | int | 检测状态:0=未开始,1=检测中,2=检测成功,3=检测失败 | +| suggestion | int | 处置建议:0=通过,1=嫌疑,2=不通过 | +| label | int | 一级分类 | +| secondLabel | string | 二级分类 | +| thirdLabel | string | 三级分类 | +| riskDescription | string | 风险描述 | +| resultType | int | 结果类型:1=机器结果,2=人审结果 | +| censorTime | int64 | 审核完成时间(毫秒时间戳) | +| censorSource | int | 审核来源 | +| censorRound | int | 审核轮数 | +| censorLabels | array | 审核标签数组 | +| remark | string | 审核备注 | +| overAllMarkDesc | string | 整体审核备注 | +| detailMarks | array | 细节标注 | +| labels | array | 分类标签详情 | +| url | string | 图片URL | +| imgMd5 | string | 图片MD5 | +| frameSize | int | 分帧数 | +| customLabels | array | 客户自定义标签 | +| censorExtension | object | 人审拓展字段 | +| strategyVersions | array | 策略版本 | +| hitType | int | 命中策略类型 | +| strategyType | int | 策略类型:1=公有策略,2=私有策略 | +| hitResult | string | 命中结果 | +| hitSource | int | 特征添加来源 | +| hidden | bool | 是否有隐藏文件 | +| hiddenFormat | string | 隐藏文件格式 | +| publicOpinionInfo | string | 舆情信息 | + +**labels 数组结构**: + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| label | int | 标签类型 | +| level | int | 判断结果:0=正常,1=不确定,2=确定 | +| rate | float | 置信度 | +| subLabels | array | 二级分类详情 | +| explain | string | LLM解释说明 | +| isLlmCheck | bool | 是否LLM检测命中 | + +**censorLabels 数组结构**: + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| code | string | 审核标签编码 | +| customCode | string | 自定义标签编码 | +| name | string | 审核标签名称 | +| desc | string | 审核标签描述 | +| parentLabelId | string | 父标签ID | +| depth | int | 标签深度 | + +### 2.3 接收图片检测回调(推送模式) + +**接口地址**: `POST /yidun/receive-image-callback` + +**易盾回调配置**: +- HTTP URL: 回调地址 `callbackUrl` +- HTTP Method: POST +- Content-Type: application/x-www-form-urlencoded + +**请求参数** (form-data): + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| secretId | string | 是 | 产品密钥ID | +| businessId | string | 是 | 业务ID | +| signature | string | 是 | 请求签名 | +| callbackData | string | 是 | 回调数据(JSON字符串) | + +**请求体示例**: + +``` +callbackData={"antispam":{...}}&signature=xxx&secretId=xxx&businessId=xxx +``` + +**回调数据格式**: + +```json +{ + "antispam": { + "taskId": "a2e53718f9c348d09206e4671304ca7c", + "suggestion": 2, + "remark": "人审备注信息,填写才会返回", + "dataId": "dataId", + "name": "name", + "labels": [ + { + "label": 400, + "level": 2, + "rate": 1.0 + }, + { + "label": 500, + "level": 0, + "rate": 1.0 + } + ], + "censorLabels": [ + { + "code": "审核标签编码", + "customCode": "自定义标签编码", + "name": "审核标签名称", + "desc": "风景" + } + ], + "resultType": 2, + "censorTime": 1622036595467, + "censorSource": 0, + "censorRound": 1 + } +} +``` + +**Suggestion值说明**: + +| 值 | 含义 | +|----|------| +| 0 | 通过 | +| 1 | 嫌疑,需人工审核 | +| 2 | 不通过 | + +**Status值说明**: + +| 值 | 含义 | +|----|------| +| 0 | 未开始 | +| 1 | 检测中 | +| 2 | 检测成功 | +| 3 | 检测失败 | + +--- + +## 3. 视频检测 + +### 提交视频检测任务 + +**接口地址**: `POST /yidun/detect-video` + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| data_id | string | 是 | 数据唯一标识 | +| video_url | string | 是 | 视频URL地址 | +| callback_url | string | 否 | 回调地址 | + +**请求示例**: + +```bash +curl -X POST http://localhost:3002/yidun/detect-video \ + -H "Content-Type: application/json" \ + -d '{ + "data_id": "video-001", + "video_url": "https://example.com/video.mp4" + }' +``` + +**响应示例**: + +```json +{ + "code": 0, + "message": "", + "data": { + "taskId": "c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8", + "dataId": "video-001", + "dealingCount": 0 + } +} +``` + +**响应说明**: + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| taskId | string | 任务ID,用于后续查询检测结果 | +| dataId | string | 调用检测时传递的数据ID | +| dealingCount | int64 | 缓冲池排队待处理数据量 | + +**注意**: 视频检测是异步的,检测时间取决于视频长度(几分钟到几十分钟)。建议配置回调地址接收结果。 + +### 3.2 查询视频检测结果(轮询模式) + +**接口地址**: `POST /yidun/GetVideoResult` + +**SDK调用信息**: +- SDK方法: `VideoClient.Callback(request)` +- 请求地址: `http://as.dun.163.com/v2/videosolution/callback/results` +- 请求参数: `yidunRequestId` (taskId) + +**请求示例**: + +```bash +curl -X POST "http://localhost:3002/yidun/GetVideoResult" \ + -H "Content-Type: application/json" \ + -d '{"taskId": "c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8"}' +``` + +**响应示例**: + +```json +{ + "code": 0, + "message": "", + "data": { + "taskId": "18xrunm6fr35t49nnv506ukg02009q66", + "status": 2, + "suggestion": 2, + "label": 100, + "resultType": 1, + "dataId": "video-001", + "censorTime": 1633759853762, + "duration": 71339, + "antispam": { + "taskId": "18xrunm6fr35t49nnv506ukg02009q66", + "suggestion": 2, + "status": 2, + "resultType": 1, + "censorSource": 2, + "checkTime": 1633759861164, + "censorTime": 1633759853762, + "duration": 71339, + "censorLabels": [ + { + "code": "101", + "desc": "自定义标签", + "customCode": "68684" + } + ], + "evidences": { + "images": [...], + "text": {...}, + "audio": {...}, + "video": {...} + } + }, + "language": { ... }, + "voice": { ... }, + "asr": { ... }, + "ocr": { ... }, + "discern": { ... }, + "logo": { ... }, + "face": { ... }, + "aigc": { ... }, + "quality": { ... } + } +} +``` + +**antispam 字段完整说明**: + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| taskId | string | 任务ID | +| dataId | string | 客户数据ID | +| callback | string | 回调参数 | +| suggestion | int | 处置建议:0=通过,1=嫌疑,2=不通过 | +| status | int | 检测状态:0=未开始,1=检测中,2=检测成功,3=检测失败 | +| resultType | int | 结果类型:1=机器结果,2=人审结果 | +| censorRound | int | 人审轮次 | +| censor | string | 人审操作人 | +| censorSource | int | 审核来源:0=易盾人审,1=客户人审,2=易盾机审 | +| checkTime | int64 | 机器检测结束时间 | +| censorTime | int64 | 人工审核完成时间 | +| duration | int64 | 音视频时长(毫秒) | +| durationMs | int64 | 音频时长(毫秒) | +| label | int | 一级垃圾类型 | +| secondLabel | string | 二级垃圾类型 | +| thirdLabel | string | 三级垃圾类型 | +| riskDescription | string | 风险描述 | +| picCount | int64 | 截图数量 | +| censorLabels | array | 审核标签数组 | +| censorExtension | object | 质检扩展结果 | +| evidences | object | 机器检测证据信息 | +| solutionExtra | object | 额外信息 | +| reviewEvidences | object | 人审证据信息 | + +**evidences 对象说明**: + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| text | object | 文本证据(标题、简介等) | +| images | array | 图片证据(封面、截图中的图片) | +| audio | object | 音频证据(人声内容) | +| video | object | 视频证据(视频画面) | + +**CensorSource值说明**: + +| 值 | 含义 | +|----|------| +| 0 | 易盾人审 | +| 1 | 客户人审 | +| 2 | 易盾机审 | + +### 3.3 接收视频检测回调(推送模式) + +**接口地址**: `POST /yidun/receive-video-callback` + +**易盾回调配置**: +- HTTP URL: 回调地址 `callbackUrl` +- HTTP Method: POST +- Content-Type: application/x-www-form-urlencoded + +**请求参数** (form-data): + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| secretId | string | 是 | 产品密钥ID | +| signature | string | 是 | 请求签名 | +| callbackData | string | 是 | 回调数据(JSON字符串) | + +**请求体示例**: + +``` +callbackData={"antispam":{...}}&signature=xxx&secretId=xxx +``` + +**回调数据格式**: + +```json +{ + "antispam": { + "taskId": "18xrunm6fr35t49nnv506ukg02009q66", + "suggestion": 2, + "status": 2, + "resultType": 1, + "censorSource": 2, + "checkTime": 1633759861164, + "censorTime": 1633759853762, + "duration": 71339, + "censorLabels": [ + { + "code": "101", + "desc": "自定义标签", + "customCode": "68684" + } + ], + "evidences": { + "images": [ + { + "censorType": 0, + "labels": [ + { + "label": 100, + "level": 2, + "rate": 0.9999, + "subLabels": [ + { + "details": { + "hitInfos": [ + { + "group": "色情部位分组", + "value": "女胸" + } + ] + }, + "rate": 1, + "subLabel": 10002 + } + ] + } + ], + "name": "gpqis0e92czvfliemjpa3neg02009q6a", + "resultType": 1, + "status": 2, + "suggestion": 2, + "taskId": "0b2dd0e561a8457fbfcf7d79378eec37" + } + ], + "text": { + "taskId": "44i3effo1j5w5for9l8xtdjg02009q6a", + "suggestion": 2, + "resultType": 1, + "censorType": 0, + "isRelatedHit": false, + "labels": [ + { + "label": 200, + "level": 2, + "subLabels": [ + { + "subLabel": "200009", + "details": { + "hitInfos": [ + { + "value": "加我", + "positions": [ + { + "fieldName": "content", + "startPos": 5, + "endPos": 8 + } + ] + } + ] + } + } + ] + } + ] + }, + "audio": { + "taskId": "18xrunm6fr35t49nnv506ukg02009q66", + "dataId": "", + "status": 2, + "suggestion": 2, + "label": 100, + "resultType": 1, + "callback": "", + "censorSource": 2, + "censorTime": 1633759861065, + "segments": [ + { + "startTime": 0, + "endTime": 6, + "content": "第一次用这个软件上了半天,全是长得漂亮呢?", + "type": 0, + "labels": [ + { + "label": 100, + "level": 2, + "subLabels": [ + { + "subLabel": "100001", + "details": { + "hitInfos": [ + { + "value": "120", + "startTime": 1684201, + "endTime": 1684201 + } + ], + "keywords": [ + { + "word": "自定义敏感词" + } + ], + "libInfos": [ + { + "listType": 1, + "entity": "127.0.0.1" + } + ] + } + } + ] + } + ] + } + ], + "duration": 71 + }, + "video": { + "taskId": "18xrunm6fr35t49nnv506ukg02009q66", + "dataId": "", + "status": 2, + "suggestion": 2, + "resultType": 1, + "censorSource": 2, + "censorTime": 1633759858920, + "pictures": [ + { + "type": 1, + "url": "https://xxx.nosdn.127.net/xxx.jpg", + "startTime": 5000, + "endTime": 5000, + "labels": [ + { + "label": 100, + "level": 2, + "rate": 0.95157, + "subLabels": [ + { + "subLabel": 10007, + "rate": 0.926, + "details": { + "hitInfos": [ + { + "value": "卡通色情", + "group": "色情部位分组" + } + ] + } + } + ] + } + ], + "censorSource": 2, + "frontPics": [ + { + "url": "https://xxx.nosdn.127.net/xxx_0_0.jpg" + } + ], + "backPics": [ + { + "url": "https://xxx.nosdn.127.net/xxx_2_10000.jpg" + } + ] + } + ] + } + } + } +} +``` + +**Suggestion值说明**: + +| 值 | 含义 | +|----|------| +| 0 | 通过 | +| 1 | 嫌疑,需人工审核 | +| 2 | 不通过 | + +**Status值说明**: + +| 值 | 含义 | +|----|------| +| 0 | 未开始 | +| 1 | 检测中 | +| 2 | 检测成功 | +| 3 | 检测失败 | + +**images/images.labels 结构说明**: + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| label | int | 标签类型 | +| level | int | 级别:0=正常,1=嫌疑,2=确定违规 | +| rate | float | 置信度 | +| subLabels | array | 二级分类详情 | +| subLabels.details.hitInfos | array | 命中信息 | +| subLabels.details.hitInfos.group | string | 分组名称 | +| subLabels.details.hitInfos.value | string | 命中值 | + +**audio.segments 结构说明**: + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| startTime | int | 开始时间(秒) | +| endTime | int | 结束时间(秒) | +| content | string | 语音识别原文 | +| type | int | 片段类型:0=语音识别,1=声纹检测 | +| labels | array | 分类标签 | +| labels.details.keywords | array | 自定义敏感词 | +| labels.details.libInfos | array | 自定义名单 | +| labels.details.hitInfos | array | 命中内容 | + +**video.pictures 结构说明**: + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| type | int | 截图类型 | +| url | string | 截图URL | +| startTime | int64 | 开始时间(毫秒) | +| endTime | int64 | 结束时间(毫秒) | +| labels | array | 分类标签 | +| frontPics | array | 关联前帧图片 | +| backPics | array | 关联后帧图片 | + +--- + +## 错误码说明 + +| code | 说明 | +|------|------| +| 0 | 成功 | +| 400 | 请求参数错误 | +| 500 | 服务器内部错误 | + +**易盾API错误码**: + +| 错误码 | 说明 | +|--------|------| +| 200 | 成功 | +| 401 | 认证失败(SecretID/SecretKey错误) | +| 403 | 权限不足 | +| 429 | 请求频率超限 | + +--- + +## Go代码调用示例 + +```go +// 文本检测 +func detectText() (string, error) { + req := map[string]interface{}{ + "data_id": "text-001", + "content": "这是一段测试文本", + } + jsonData, _ := json.Marshal(req) + resp, err := http.Post("http://localhost:3002/yidun/detect-text", "application/json", bytes.NewBuffer(jsonData)) + // ... +} + +// 图片检测 +func detectImage() (string, error) { + req := map[string]interface{}{ + "data_id": "img-001", + "image_url": "https://example.com/image.jpg", + } + jsonData, _ := json.Marshal(req) + resp, err := http.Post("http://localhost:3002/yidun/detect-image", "application/json", bytes.NewBuffer(jsonData)) + // ... +} + +// 视频检测 +func detectVideo() (string, error) { + req := map[string]interface{}{ + "data_id": "video-001", + "video_url": "https://example.com/video.mp4", + } + jsonData, _ := json.Marshal(req) + resp, err := http.Post("http://localhost:3002/yidun/detect-video", "application/json", bytes.NewBuffer(jsonData)) + // ... +} +``` + +--- + +**最后更新**: 2026-05-07 diff --git a/config.yml b/config.yml index 15448e8..1a160de 100644 --- a/config.yml +++ b/config.yml @@ -46,4 +46,31 @@ consul: address: 116.204.74.41:8500 # pass: jiahui8888 jaeger: #链路追踪 - addr: 116.204.74.41:4318 \ No newline at end of file + addr: 116.204.74.41:4318 + +yidun: + # 音视频检测配置 + video: + secret_id: "f58a38341ca6227014df7c3bf0e6f16f" + secret_key: "526aa631ba5d518aedeb70b5a3b67371" + region: "cn-hangzhou" + protocol: "https" + max_retry_count: 3 + + # 图片检测配置 + image: + business_id: "20acccc525cbc5cb6bed3d8f6f6b2f77" + secret_id: "9a82f90bfec61eb40d1c95605b894817" + secret_key: "f73a78954417a3713c36ec2d14eb2b5f" + region: "cn-hangzhou" + protocol: "https" + max_retry_count: 3 + + # 文本检测配置(如需要请补充) + text: + business_id: "YOUR_TEXT_BUSINESS_ID" + secret_id: "YOUR_TEXT_SECRET_ID" + secret_key: "YOUR_TEXT_SECRET_KEY" + region: "cn-hangzhou" + protocol: "https" + max_retry_count: 3 \ No newline at end of file diff --git a/controller/yidun/yidun_controller.go b/controller/yidun/yidun_controller.go new file mode 100644 index 0000000..3bafe0c --- /dev/null +++ b/controller/yidun/yidun_controller.go @@ -0,0 +1,164 @@ +package yidun + +import ( + "cid/service/yidun" + "context" + + "gitea.com/red-future/common/beans" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/yidun/yidun-golang-sdk/yidun/service/antispam/text/v5/check/async/single" +) + +type yidunController struct{} + +// YidunController 易盾控制器 +var YidunController = new(yidunController) + +// DetectTextReq 文本检测请求 +type DetectTextReq struct { + DataID string `json:"data_id"` + Content string `json:"content" v:"required#待检测文本不能为空"` + IP string `json:"ip"` + Token string `json:"token"` +} + +// DetectImageReq 图片检测请求 +type DetectImageReq struct { + DataID string `json:"data_id"` + ImageURL string `json:"image_url" v:"required#图片URL不能为空"` + CallbackURL string `json:"callback_url"` +} + +// DetectVideoReq 视频检测请求 +type DetectVideoReq struct { + DataID string `json:"data_id"` + VideoURL string `json:"video_url" v:"required#视频URL不能为空"` + CallbackURL string `json:"callback_url"` +} + +// DetectText 文本检测 +func (c *yidunController) DetectText(ctx context.Context, req *DetectTextReq) (string, error) { + ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"}) + + businessId := g.Cfg().MustGet(ctx, "yidun.text.business_id").String() + sdkReq := single.NewTextAsyncCheckRequest(businessId) + sdkReq.SetDataID(req.DataID) + sdkReq.SetContent(req.Content) + if req.IP != "" { + sdkReq.SetIP(req.IP) + } + if req.Token != "" { + sdkReq.SetToken(req.Token) + } + + return yidun.TextDetection.DetectText(ctx, sdkReq) +} + +// DetectImage 图片检测 +func (c *yidunController) DetectImage(ctx context.Context, req *DetectImageReq) (*yidun.ImageSubmitResult, error) { + ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"}) + return yidun.ImageDetection.DetectImage(ctx, req.ImageURL, req.DataID, req.CallbackURL) +} + +// DetectVideo 视频检测 +func (c *yidunController) DetectVideo(ctx context.Context, req *DetectVideoReq) (*yidun.VideoSubmitResult, error) { + ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"}) + return yidun.VideoDetection.DetectVideo(ctx, req.VideoURL, req.DataID, req.CallbackURL) +} + +// ImageCallbackResult 图片检测回调响应 +type ImageCallbackResult struct { + Code int `json:"code"` + Msg string `json:"msg"` +} + +// ReceiveImageCallback 接收图片检测结果推送 +func (c *yidunController) ReceiveImageCallback(r *ghttp.Request) { + ctx := r.Context() + ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"}) + + callbackData := r.GetForm("callbackData", "").String() + if callbackData == "" { + r.Response.WriteJson(ImageCallbackResult{Code: 400, Msg: "callbackData不能为空"}) + return + } + + err := yidun.ImageDetection.ProcessImageCallback(ctx, callbackData) + if err != nil { + g.Log().Errorf(ctx, "处理图片检测回调失败: %v", err) + r.Response.WriteJson(ImageCallbackResult{Code: 500, Msg: err.Error()}) + return + } + + r.Response.WriteJson(ImageCallbackResult{Code: 200, Msg: "success"}) +} + +// VideoCallbackResult 视频检测回调响应 +type VideoCallbackResult struct { + Code int `json:"code"` + Msg string `json:"msg"` +} + +// ReceiveVideoCallback 接收视频检测结果推送 +func (c *yidunController) ReceiveVideoCallback(r *ghttp.Request) { + ctx := r.Context() + ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"}) + + callbackData := r.GetForm("callbackData", "").String() + if callbackData == "" { + r.Response.WriteJson(VideoCallbackResult{Code: 400, Msg: "callbackData不能为空"}) + return + } + + err := yidun.VideoDetection.ProcessVideoCallback(ctx, callbackData) + if err != nil { + g.Log().Errorf(ctx, "处理视频检测回调失败: %v", err) + r.Response.WriteJson(VideoCallbackResult{Code: 500, Msg: err.Error()}) + return + } + + r.Response.WriteJson(VideoCallbackResult{Code: 200, Msg: "success"}) +} + +// GetVideoResult 获取视频检测结果 +func (c *yidunController) GetVideoResult(r *ghttp.Request) { + ctx := r.Context() + ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"}) + + taskId := r.Get("taskId", "").String() + if taskId == "" { + r.Response.WriteJson(g.Map{"code": 400, "msg": "taskId不能为空"}) + return + } + + result, err := yidun.VideoDetection.GetVideoResult(ctx, taskId) + if err != nil { + g.Log().Errorf(ctx, "查询视频检测结果失败: %v", err) + r.Response.WriteJson(g.Map{"code": 500, "msg": err.Error()}) + return + } + + r.Response.WriteJson(result) +} + +// GetImageResult 获取图片检测结果 +func (c *yidunController) GetImageResult(r *ghttp.Request) { + ctx := r.Context() + ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"}) + + taskId := r.Get("taskId", "").String() + if taskId == "" { + r.Response.WriteJson(g.Map{"code": 400, "msg": "taskId不能为空"}) + return + } + + result, err := yidun.ImageDetection.GetImageResult(ctx, taskId) + if err != nil { + g.Log().Errorf(ctx, "查询图片检测结果失败: %v", err) + r.Response.WriteJson(g.Map{"code": 500, "msg": err.Error()}) + return + } + + r.Response.WriteJson(result) +} diff --git a/go.mod b/go.mod index db5b8f0..810a974 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.5 github.com/gogf/gf/contrib/nosql/redis/v2 v2.9.5 github.com/gogf/gf/v2 v2.9.5 + github.com/yidun/yidun-golang-sdk v1.0.38 golang.org/x/net v0.47.0 ) @@ -26,6 +27,7 @@ require ( github.com/emirpasic/gods v1.18.1 // indirect github.com/fatih/color v1.18.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/go-ego/gse v1.0.2 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-sql-driver/mysql v1.7.1 // indirect @@ -61,14 +63,15 @@ require ( github.com/olekukonko/errors v1.1.0 // indirect github.com/olekukonko/ll v0.0.9 // indirect github.com/olekukonko/tablewriter v1.1.0 // indirect + github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/r3labs/diff/v2 v2.15.1 // indirect github.com/redis/go-redis/v9 v9.12.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/tiger1103/gfast-token v1.0.10 // indirect - github.com/xdg-go/pbkdf2 v1.0.0 // indirect - github.com/xdg-go/scram v1.1.2 // indirect - github.com/xdg-go/stringprep v1.0.4 // indirect - github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect + github.com/tjfoc/gmsm v1.4.1 // indirect + github.com/vcaesar/cedar v0.30.0 // indirect + github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect go.mongodb.org/mongo-driver/v2 v2.4.0 // indirect go.opencensus.io v0.23.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect @@ -79,11 +82,10 @@ require ( go.opentelemetry.io/otel/sdk v1.38.0 // indirect go.opentelemetry.io/otel/trace v1.38.0 // indirect go.opentelemetry.io/proto/otlp v1.7.1 // indirect - golang.org/x/crypto v0.44.0 // indirect golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect - golang.org/x/sync v0.18.0 // indirect golang.org/x/sys v0.38.0 // indirect golang.org/x/text v0.31.0 // indirect + google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect google.golang.org/grpc v1.75.0 // indirect diff --git a/go.sum b/go.sum index 9dffd9c..7567df0 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,4 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -gitea.com/red-future/common v0.0.6 h1:2Otksfcy5V5JCBcqd2eRKh4WwZ/iAiIhJZMr6uM1x+Q= -gitea.com/red-future/common v0.0.6/go.mod h1:UI9N5UUjilbMPF7+/lypZSnqDVHigt14300oSRrAyZg= -gitea.com/red-future/common v0.0.7/go.mod h1:JgBczlUF8xA8S0YbvDaMLxvvpmBGgjJA6po+EbodzZA= -gitea.com/red-future/common v0.0.9/go.mod h1:JgBczlUF8xA8S0YbvDaMLxvvpmBGgjJA6po+EbodzZA= -gitea.com/red-future/common v0.0.11/go.mod h1:B8syUI4XbLCDQSeRHURYxEwnWw8mEFgmqCxjC+lM+NU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= @@ -68,6 +63,8 @@ github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/go-ego/gse v1.0.2 h1:+27lYFPhQEhA9igtdOsJPRKYL/k3TwYsxBF5jr6KFv4= +github.com/go-ego/gse v1.0.2/go.mod h1:Fy35G+q7VV7Et1zIKO8o/sW1kkugV3znXap/lF/11zc= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= @@ -105,12 +102,14 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= @@ -133,8 +132,7 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= -github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= github.com/grokify/html-strip-tags-go v0.1.0 h1:03UrQLjAny8xci+R+qjCce/MYnpNXCtgzltlQbOBae4= github.com/grokify/html-strip-tags-go v0.1.0/go.mod h1:ZdzgfHEzAfz9X6Xe5eBLVblWIxXfYSQ40S/VKrAOGpc= @@ -243,6 +241,8 @@ github.com/olekukonko/tablewriter v1.1.0/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfB github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -264,6 +264,8 @@ github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8b github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/r3labs/diff/v2 v2.15.1 h1:EOrVqPUzi+njlumoqJwiS/TgGgmZo83619FNDB9xQUg= +github.com/r3labs/diff/v2 v2.15.1/go.mod h1:I8noH9Fc2fjSaMxqF3G2lhDdC0b+JXCfyx85tWFM9kc= github.com/redis/go-redis/v9 v9.12.1 h1:k5iquqv27aBtnTm2tIkROUDp8JBXhXZIVu1InSgvovg= github.com/redis/go-redis/v9 v9.12.1/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -283,24 +285,26 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tiger1103/gfast-token v1.0.10 h1:fNiBE/Dq5iTHvTGlCx3DmXa2o4hr0NtumFpffZ39k6s= github.com/tiger1103/gfast-token v1.0.10/go.mod h1:a/21mxmj7zFeNvjhZSC0XpEAFHfb1aT2k6DXnufFU1s= +github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= -github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= -github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= -github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= -github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= -github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= -github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= -github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= -github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= +github.com/vcaesar/cedar v0.30.0 h1:9fSDpM7FTjjUdPiBUUa0MWYMRGSEcqgFXvppZcZ4d7Y= +github.com/vcaesar/cedar v0.30.0/go.mod h1:lyuGvALuZZDPNXwpzv/9LyxW+8Y6faN7zauFezNsnik= +github.com/vcaesar/tt v0.20.1 h1:D/jUeeVCNbq3ad8M7hhtB3J9x5RZ6I1n1eZ0BJp7M+4= +github.com/vcaesar/tt v0.20.1/go.mod h1:cH2+AwGAJm19Wa6xvEa+0r+sXDJBT0QgNQey6mwqLeU= +github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= +github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/yidun/yidun-golang-sdk v1.0.38 h1:4NjQdt2GGMgLToB2+zTA0L4YRpqY3ZQjVpl2ot1gwfk= +github.com/yidun/yidun-golang-sdk v1.0.38/go.mod h1:+JGdWbkUvLi9uKTtHI+nrxajulfZKA7BXDPlzt1RCsU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.mongodb.org/mongo-driver/v2 v2.4.0 h1:Oq6BmUAAFTzMeh6AonuDlgZMuAuEiUxoAD1koK5MuFo= go.mongodb.org/mongo-driver/v2 v2.4.0/go.mod h1:jHeEDJHJq7tm6ZF45Issun9dbogjfnPySb1vXA7EeAI= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= @@ -330,19 +334,15 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= -golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20250128144449-3edf0e91c1ae h1:COZdc9Ut6wLq7MO9GIYxfZl4n4ScmgqQLoHocKXrxco= -golang.org/x/exp v0.0.0-20250128144449-3edf0e91c1ae/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -351,15 +351,16 @@ golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -370,7 +371,6 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -391,25 +391,19 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -421,7 +415,6 @@ golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -432,6 +425,9 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= @@ -443,6 +439,7 @@ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= diff --git a/main.go b/main.go index 4380308..6ab55db 100644 --- a/main.go +++ b/main.go @@ -4,12 +4,13 @@ import ( "cid/controller/app" "cid/controller/data" "cid/controller/mapping" + controllerYidun "cid/controller/yidun" + serviceYidun "cid/service/yidun" + "fmt" - _ "gitea.com/red-future/common/config" - + _ "gitea.com/red-future/common/consul" "gitea.com/red-future/common/http" "gitea.com/red-future/common/jaeger" - _ "gitea.com/red-future/common/ragflow" // RAGFlow 客户端自动初始化 _ "github.com/gogf/gf/contrib/drivers/mysql/v2" _ "github.com/gogf/gf/contrib/nosql/redis/v2" "golang.org/x/net/context" @@ -19,6 +20,11 @@ func main() { ctx := context.Background() defer jaeger.ShutDown(ctx) + // 初始化易盾客户端 + if err := serviceYidun.InitYidunClients(ctx); err != nil { + panic(fmt.Sprintf("初始化易盾客户端失败: %v", err)) + } + http.RouteRegister([]interface{}{ // 平台管理 data.Platform, @@ -30,6 +36,8 @@ func main() { mapping.DataMapping, // 应用管理 app.Application, + // 易盾内容安全 + controllerYidun.YidunController, }) select {} } diff --git a/service/yidun/image_detection_service.go b/service/yidun/image_detection_service.go new file mode 100644 index 0000000..50e4fcd --- /dev/null +++ b/service/yidun/image_detection_service.go @@ -0,0 +1,389 @@ +package yidun + +import ( + "context" + "encoding/json" + "errors" + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/yidun/yidun-golang-sdk/yidun/service/antispam/image/v5/callback" + "github.com/yidun/yidun-golang-sdk/yidun/service/antispam/image/v5/check" + "github.com/yidun/yidun-golang-sdk/yidun/service/antispam/image/v5/check/async" + imagesync "github.com/yidun/yidun-golang-sdk/yidun/service/antispam/image/v5/check/sync" +) + +// ImageDetectionService 图片检测服务 +type ImageDetectionService struct{} + +var ImageDetection = new(ImageDetectionService) + +var ( + ErrImageStillProcessing = errors.New("图片仍在检测中,请稍后重试") + ErrImageResultNotFound = errors.New("未找到图片检测结果") +) + +// ImageSubmitResult 图片检测提交结果 +type ImageSubmitResult struct { + TaskID string `json:"taskId"` // 任务ID + Name string `json:"name"` // 图片唯一标识 + DataID string `json:"dataId"` // 客户图片唯一标识 + DealingCount int64 `json:"dealingCount"` // 缓冲池排队待处理数据量 +} + +// DetectImage 提交图片异步检测任务,返回完整响应 +func (s *ImageDetectionService) DetectImage(ctx context.Context, imageURL, dataID string, callbackURL string) (*ImageSubmitResult, error) { + if DefaultClients == nil || DefaultClients.ImageClient == nil { + return nil, fmt.Errorf("易盾图片检测客户端未初始化") + } + + if imageURL == "" { + return nil, fmt.Errorf("图片URL不能为空") + } + + businessId := g.Cfg().MustGet(ctx, "yidun.image.business_id").String() + g.Log().Infof(ctx, "图片检测任务提交, url: %s, business_id: %s", imageURL, businessId) + + // 创建请求 + request := async.NewImageV5AsyncCheckRequest(businessId) + imageBean := check.NewImageBeanRequest() + imageBean.SetData(imageURL) + imageBean.SetName(dataID) + imageBean.SetType(1) // 1: 图片URL + if callbackURL != "" { + imageBean.SetCallbackUrl(callbackURL) + } + request.SetImages([]check.ImageBeanRequest{*imageBean}) + + // 调用API + response, err := DefaultClients.ImageClient.ImageAsyncCheck(request) + if err != nil { + g.Log().Errorf(ctx, "图片检测提交失败: %v", err) + return nil, fmt.Errorf("图片检测提交失败: %w", err) + } + + if response.GetCode() != 200 { + g.Log().Errorf(ctx, "图片检测API错误: code=%d, msg=%s", response.GetCode(), response.GetMsg()) + return nil, fmt.Errorf("图片检测API错误: code=%d, msg=%s", response.GetCode(), response.GetMsg()) + } + + result := &ImageSubmitResult{ + DealingCount: 0, + } + if response.Result != nil { + if response.Result.DealingCount != nil { + result.DealingCount = *response.Result.DealingCount + } + if response.Result.CheckImages != nil && len(*response.Result.CheckImages) > 0 { + detail := (*response.Result.CheckImages)[0] + if detail.TaskId != nil { + result.TaskID = *detail.TaskId + } + if detail.Name != nil { + result.Name = *detail.Name + } + if detail.DataId != nil { + result.DataID = *detail.DataId + } + } + } + + g.Log().Infof(ctx, "图片检测任务提交成功, taskID: %s, dealingCount: %d", result.TaskID, result.DealingCount) + return result, nil +} + +// ImageResult 图片检测完整结果 +type ImageResult struct { + TaskID string `json:"taskId"` // 任务ID + Status int `json:"status"` // 检测状态:0=未开始,1=检测中,2=检测成功,3=检测失败 + Suggestion int `json:"suggestion"` // 处置建议:0=通过,1=嫌疑,2=不通过 + Label int `json:"label"` // 垃圾类型 + ResultType int `json:"resultType"` // 结果类型:1=机器结果,2=人审结果 + DataID string `json:"dataId"` // 数据ID + Name string `json:"name"` // 图片标识 + CensorTime int64 `json:"censorTime"` // 审核完成时间(毫秒) + Url string `json:"url"` // 图片URL + + // 完整证据信息 + Antispam *imagesync.ImageV5AntispamResp `json:"antispam,omitempty"` // 反垃圾检测结果 + Ocr *imagesync.ImageV5OcrResp `json:"ocr,omitempty"` // OCR文字识别结果 + Face *imagesync.ImageV5FaceResp `json:"face,omitempty"` // 人脸检测结果 + Quality *imagesync.ImageV5QualityResp `json:"quality,omitempty"` // 图片质量结果 + Logo *imagesync.ImageV5LogoResp `json:"logo,omitempty"` // Logo识别结果 + Discern *imagesync.ImageV5DiscernResp `json:"discern,omitempty"` // 图片识别结果 + Ad *imagesync.ImageV5AdResp `json:"ad,omitempty"` // 广告识别结果 + UserRisk *imagesync.ImageV5UserRiskResp `json:"userRisk,omitempty"` // 用户画像结果 + Anticheat *imagesync.ImageAnticheatV5Resp `json:"anticheat,omitempty"` // 反作弊结果 + RiskControl *imagesync.ImageRiskControlV5Resp `json:"riskControl,omitempty"` // 智能风控结果 + Aigc *imagesync.ImageV5AigcResp `json:"aigc,omitempty"` // AIGC识别结果 + LlmCheckInfo *[]imagesync.LlmCheckInfo `json:"llmCheckInfo,omitempty"` // 大模型检测结果 +} + +// GetImageResult 获取图片检测结果(轮询模式) +func (s *ImageDetectionService) GetImageResult(ctx context.Context, taskID string) (*ImageResult, error) { + if DefaultClients == nil || DefaultClients.ImageClient == nil { + return nil, fmt.Errorf("易盾图片检测客户端未初始化") + } + + businessId := g.Cfg().MustGet(ctx, "yidun.image.business_id").String() + g.Log().Infof(ctx, "查询图片检测结果, taskID: %s", taskID) + + req := callback.NewImageCallbackRequest(businessId) + req.SetYidunRequestId(taskID) + + response, err := DefaultClients.ImageClient.ImageCallback(req) + if err != nil { + g.Log().Errorf(ctx, "查询图片检测结果失败: %v", err) + return nil, fmt.Errorf("查询图片检测结果失败: %w", err) + } + + if response.GetCode() != 200 { + g.Log().Errorf(ctx, "查询图片检测结果API错误: code=%d, msg=%s", response.GetCode(), response.GetMsg()) + return nil, fmt.Errorf("查询图片检测结果API错误: code=%d, msg=%s", response.GetCode(), response.GetMsg()) + } + + if response.Result == nil || len(*response.Result) == 0 { + g.Log().Warningf(ctx, "未找到图片检测结果, taskID: %s", taskID) + return nil, ErrImageStillProcessing + } + + // 查找指定taskID的结果 + for _, item := range *response.Result { + if item.Antispam != nil && item.Antispam.TaskId != nil && *item.Antispam.TaskId == taskID { + result := &ImageResult{ + Antispam: item.Antispam, + Ocr: item.Ocr, + Face: item.Face, + Quality: item.Quality, + Logo: item.Logo, + Discern: item.Discern, + Ad: item.Ad, + UserRisk: item.UserRisk, + Anticheat: item.Anticheat, + RiskControl: item.RiskControl, + Aigc: item.Aigc, + LlmCheckInfo: item.LlmCheckInfo, + } + + result.TaskID = taskID + if item.Antispam.Status != nil { + result.Status = *item.Antispam.Status + } + if item.Antispam.Suggestion != nil { + result.Suggestion = *item.Antispam.Suggestion + } + if item.Antispam.Label != nil { + result.Label = *item.Antispam.Label + } + if item.Antispam.ResultType != nil { + result.ResultType = *item.Antispam.ResultType + } + if item.Antispam.DataId != nil { + result.DataID = *item.Antispam.DataId + } + if item.Antispam.Name != nil { + result.Name = *item.Antispam.Name + } + if item.Antispam.CensorTime != nil { + result.CensorTime = *item.Antispam.CensorTime + } + if item.Antispam.Url != nil { + result.Url = *item.Antispam.Url + } + + return result, nil + } + } + + g.Log().Warningf(ctx, "未找到指定的taskID: %s", taskID) + return nil, ErrImageResultNotFound +} + +// ImageCallbackData 推送模式回调数据完整结构 +type ImageCallbackData struct { + Antispam *ImageCallbackAntispam `json:"antispam"` // 反垃圾检测结果 +} + +type ImageCallbackAntispam struct { + TaskId string `json:"taskId"` // 任务ID + Name string `json:"name"` // 图片名称 + DataId string `json:"dataId"` // 客户数据ID + Suggestion int `json:"suggestion"` // 处置建议:0=通过,1=嫌疑,2=不通过 + Label int `json:"label"` // 一级分类 + SecondLabel string `json:"secondLabel"` // 二级分类 + ThirdLabel string `json:"thirdLabel"` // 三级分类 + RiskDescription string `json:"riskDescription"` // 风险描述 + Status int `json:"status"` // 检测状态:0=未开始,1=检测中,2=检测成功,3=检测失败 + ResultType int `json:"resultType"` // 结果类型:1=机器结果,2=人审结果 + CensorTime int64 `json:"censorTime"` // 审核完成时间 + CensorSource int `json:"censorSource"` // 审核来源 + CensorRound int `json:"censorRound"` // 审核轮数 + CensorLabels []*CensorLabel `json:"censorLabels"` // 审核标签 + Remark string `json:"remark"` // 审核备注 + OverAllMarkDesc string `json:"overAllMarkDesc"` // 整体审核备注 + DetailMarks []*DetailMarkInfo `json:"detailMarks"` // 细节标注 + Labels []*ImageLabelInfo `json:"labels"` // 分类标签详情 + Url string `json:"url"` // 图片URL + ImgMd5 string `json:"imgMd5"` // 图片MD5 + FrameSize int `json:"frameSize"` // 分帧数 + CustomLabels []*CustomLabelInfo `json:"customLabels"` // 客户自定义标签 + CensorExtension *CensorExtensionInfo `json:"censorExtension"` // 人审拓展字段 + StrategyVersions []*StrategyVersionInfo `json:"strategyVersions"` // 策略版本 + HitType int `json:"hitType"` // 命中策略类型 + StrategyType int `json:"strategyType"` // 策略类型:1=公有策略,2=私有策略 + HitResult string `json:"hitResult"` // 命中结果 + HitSource int `json:"hitSource"` // 特征添加来源 + Hidden bool `json:"hidden"` // 是否有隐藏文件 + HiddenFormat string `json:"hiddenFormat"` // 隐藏文件格式 + PublicOpinionInfo string `json:"publicOpinionInfo"` // 舆情信息 +} + +// CensorLabel 审核标签信息 +type CensorLabel struct { + Code string `json:"code"` // 审核标签编码 + CustomCode string `json:"customCode"` // 自定义标签编码 + Name string `json:"name"` // 审核标签名称 + Desc string `json:"desc"` // 审核标签描述 + ParentLabelId string `json:"parentLabelId"` // 父标签ID + Depth int `json:"depth"` // 标签深度 +} + +// ImageLabelInfo 分类标签详情 +type ImageLabelInfo struct { + Label int `json:"label"` // 标签类型 + Level int `json:"level"` // 判断结果:0=正常,1=不确定,2=确定 + Rate float32 `json:"rate"` // 置信度 + SubLabels []*SubLabelDetailInfo `json:"subLabels"` // 二级分类详情 + Explain string `json:"explain"` // LLM解释说明 + IsLlmCheck bool `json:"isLlmCheck"` // 是否LLM检测命中 +} + +// SubLabelDetailInfo 二级分类详情 +type SubLabelDetailInfo struct { + SubLabel string `json:"subLabel"` // 二级分类标签 + Level int `json:"level"` // 级别 + SubLabelDepth int `json:"subLabelDepth"` // 细分类层级 + SecondLabel string `json:"secondLabel"` // 二级分类 + ThirdLabel string `json:"thirdLabel"` // 三级分类 + RiskDescription string `json:"riskDescription"` // 风险描述 + HitStrategy int `json:"hitStrategy"` // 命中标识 + Rate float32 `json:"rate"` // 置信度 + Explain string `json:"explain"` // 解释说明 + IsLlmCheck bool `json:"isLlmCheck"` // 是否LLM命中 + Details *SubLabelHitDetails `json:"details"` // 命中详情 +} + +// SubLabelHitDetails 二级分类命中详情 +type SubLabelHitDetails struct { + Keywords []*HitKeywordInfo `json:"keywords"` // 敏感词命中 + LibInfos []*HitLibInfo `json:"libInfos"` // 图片名单命中 + HitInfos []*HitInfo `json:"hitInfos"` // 其他命中信息 + Anticheat *AnticheatHitInfo `json:"anticheat"` // 反作弊命中 + Llm *LlmKeywordInfo `json:"llm"` // 大模型关键词 +} + +// HitKeywordInfo 敏感词命中信息 +type HitKeywordInfo struct { + Type int `json:"type"` // 命中类型 + Word string `json:"word"` // 敏感词 + Entity string `json:"entity"` // 图片名单URL + HitCount int `json:"hitCount"` // 命中次数 + Value string `json:"value"` // 值 + Group string `json:"group"` // 分组 + X1 float32 `json:"x1"` // 坐标 + Y1 float32 `json:"y1"` // 坐标 + X2 float32 `json:"x2"` // 坐标 + Y2 float32 `json:"y2"` // 坐标 + ReleaseTime int64 `json:"releaseTime"` // 释放时间 + StrategyGroupName string `json:"strategyGroupName"` // 策略组名称 + StrategyGroupId int64 `json:"strategyGroupId"` // 策略组ID +} + +// HitLibInfo 图片名单命中信息 +type HitLibInfo struct { + Type int `json:"type"` // 命中类型 + Entity string `json:"entity"` // 图片名单URL + HitCount int `json:"hitCount"` // 命中次数 + ReleaseTime int64 `json:"releaseTime"` // 释放时间 + StrategyGroupName string `json:"strategyGroupName"` // 策略组名称 + StrategyGroupId int64 `json:"strategyGroupId"` // 策略组ID +} + +// HitInfo 其他命中信息 +type HitInfo struct { + Type int `json:"type"` // 命中类型 + Value string `json:"value"` // 值 + Group string `json:"group"` // 分组 + X1 float32 `json:"x1"` // 坐标 + Y1 float32 `json:"y1"` // 坐标 + X2 float32 `json:"x2"` // 坐标 + Y2 float32 `json:"y2"` // 坐标 +} + +// AnticheatHitInfo 反作弊命中信息 +type AnticheatHitInfo struct { + HitType int `json:"hitType"` // 命中类型 +} + +// LlmKeywordInfo 大模型关键词信息 +type LlmKeywordInfo struct { + Keyword string `json:"keyword"` // 关键词 +} + +// DetailMarkInfo 细节标注信息 +type DetailMarkInfo struct { + Position []*MarkPointInfo `json:"position"` // 标注位置 + CensorLabels []*CensorLabel `json:"censorLabels"` // 标注标签 + Desc string `json:"desc"` // 标注备注 +} + +// MarkPointInfo 标注点坐标 +type MarkPointInfo struct { + X float32 `json:"x"` // X坐标 + Y float32 `json:"y"` // Y坐标 +} + +// CustomLabelInfo 客户自定义标签 +type CustomLabelInfo struct { + Name string `json:"name"` // 名称 + Code string `json:"code"` // 编码 + Depth int `json:"depth"` // 深度 +} + +// CensorExtensionInfo 人审拓展字段 +type CensorExtensionInfo struct { + QualityInspectionTaskId string `json:"qualityInspectionTaskId"` // 质检任务ID + InspTaskCreateTime float64 `json:"inspTaskCreateTime"` // 质检任务创建时间 + QualityInspectionType float64 `json:"qualityInspectionType"` // 质检类型 +} + +// StrategyVersionInfo 策略版本信息 +type StrategyVersionInfo struct { + Label int `json:"label"` // 垃圾类别 + Version string `json:"version"` // 版本号 +} + +// ProcessImageCallback 处理图片检测回调(推送模式) +func (s *ImageDetectionService) ProcessImageCallback(ctx context.Context, callbackData string) error { + if callbackData == "" { + return fmt.Errorf("回调数据不能为空") + } + + var data ImageCallbackData + if err := json.Unmarshal([]byte(callbackData), &data); err != nil { + g.Log().Errorf(ctx, "解析回调数据失败: %v", err) + return fmt.Errorf("解析回调数据失败: %w", err) + } + + if data.Antispam == nil { + return fmt.Errorf("回调数据格式错误:缺少antispam字段") + } + + g.Log().Infof(ctx, "处理图片检测结果 - taskId: %s, suggestion: %d, resultType: %d", + data.Antispam.TaskId, data.Antispam.Suggestion, data.Antispam.ResultType) + + // TODO: 业务逻辑,如保存数据库、触发后续流程等 + // 可使用完整字段:data.Antispam.Labels, data.Antispam.CensorLabels, data.Antispam.Remark 等 + + return nil +} diff --git a/service/yidun/text_detection_service.go b/service/yidun/text_detection_service.go new file mode 100644 index 0000000..c479654 --- /dev/null +++ b/service/yidun/text_detection_service.go @@ -0,0 +1,51 @@ +package yidun + +import ( + "context" + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/yidun/yidun-golang-sdk/yidun/service/antispam/text/v5/check/async/single" +) + +// TextDetectionService 文本检测服务 +type TextDetectionService struct{} + +var TextDetection = new(TextDetectionService) + +// DetectText 提交文本异步检测任务 +func (s *TextDetectionService) DetectText(ctx context.Context, req *single.TextAsyncCheckRequest) (string, error) { + if DefaultClients == nil || DefaultClients.TextClient == nil { + return "", fmt.Errorf("易盾文本检测客户端未初始化") + } + + response, err := DefaultClients.TextClient.AsyncCheckText(req) + if err != nil { + g.Log().Errorf(ctx, "文本检测提交失败: %v", err) + return "", fmt.Errorf("文本检测提交失败: %w", err) + } + + if response.GetCode() != 200 { + g.Log().Errorf(ctx, "文本检测API错误: code=%d, msg=%s", response.GetCode(), response.GetMsg()) + return "", fmt.Errorf("文本检测API错误: code=%d, msg=%s", response.GetCode(), response.GetMsg()) + } + + var taskID string + if response.Result != nil && len(response.Result.CheckTexts) > 0 { + taskID = *response.Result.CheckTexts[0].TaskID + } + + g.Log().Infof(ctx, "文本检测任务提交成功, taskID: %s", taskID) + return taskID, nil +} + +// GetTextResult 获取文本检测结果 +func (s *TextDetectionService) GetTextResult(ctx context.Context, taskID string) (interface{}, error) { + if DefaultClients == nil || DefaultClients.TextClient == nil { + return nil, fmt.Errorf("易盾文本检测客户端未初始化") + } + + g.Log().Infof(ctx, "查询文本检测结果, taskID: %s", taskID) + // TODO: 根据实际业务需求实现查询逻辑 + return nil, nil +} diff --git a/service/yidun/video_detection_service.go b/service/yidun/video_detection_service.go new file mode 100644 index 0000000..f290170 --- /dev/null +++ b/service/yidun/video_detection_service.go @@ -0,0 +1,714 @@ +package yidun + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "time" + + "github.com/gogf/gf/v2/frame/g" + audiocallback "github.com/yidun/yidun-golang-sdk/yidun/service/antispam/audio/callback/v4/response" + videocallback "github.com/yidun/yidun-golang-sdk/yidun/service/antispam/video/callback/v4/response" + callbackrequest "github.com/yidun/yidun-golang-sdk/yidun/service/antispam/videosolution/callback/v2/request" + callbackresponse "github.com/yidun/yidun-golang-sdk/yidun/service/antispam/videosolution/callback/v2/response" + vsrequest "github.com/yidun/yidun-golang-sdk/yidun/service/antispam/videosolution/submit/v2/request" + submitresponse "github.com/yidun/yidun-golang-sdk/yidun/service/antispam/videosolution/submit/v2/response" +) + +// VideoDetectionService 视频检测服务 +type VideoDetectionService struct{} + +var VideoDetection = new(VideoDetectionService) + +var ( + ErrVideoStillProcessing = errors.New("视频仍在检测中,请稍后重试") + ErrVideoResultNotFound = errors.New("未找到视频检测结果") +) + +// VideoSubmitResult 视频检测提交结果 +type VideoSubmitResult struct { + TaskID string `json:"taskId"` // 任务ID + DataID string `json:"dataId"` // 数据ID + DealingCount int64 `json:"dealingCount"` // 缓冲池排队待处理数据量 +} + +// DetectVideo 提交视频检测任务,返回完整响应 +func (s *VideoDetectionService) DetectVideo(ctx context.Context, videoURL, dataID string, callbackURL string) (*VideoSubmitResult, error) { + if DefaultClients == nil || DefaultClients.VideoClient == nil { + return nil, fmt.Errorf("易盾视频检测客户端未初始化") + } + + if videoURL == "" { + return nil, fmt.Errorf("视频URL不能为空") + } + + g.Log().Infof(ctx, "视频检测任务提交, url: %s", videoURL) + + // 创建请求 + req := vsrequest.NewVideoSolutionSubmitV2Req() + req.SetURL(videoURL) + req.SetDataID(dataID) + req.SetUniqueKey(dataID) + if callbackURL != "" { + req.SetCallbackURL(callbackURL) + } + + // 调用API + response, err := DefaultClients.VideoClient.Submit(req) + if err != nil { + g.Log().Errorf(ctx, "视频检测提交失败: %v", err) + return nil, fmt.Errorf("视频检测提交失败: %w", err) + } + + if response.GetCode() != 200 { + g.Log().Errorf(ctx, "视频检测API错误: code=%d, msg=%s", response.GetCode(), response.GetMsg()) + return nil, fmt.Errorf("视频检测API错误: code=%d, msg=%s", response.GetCode(), response.GetMsg()) + } + + result := &VideoSubmitResult{} + if response.Result != nil { + if response.Result.TaskID != nil { + result.TaskID = *response.Result.TaskID + } + if response.Result.DataID != nil { + result.DataID = *response.Result.DataID + } + if response.Result.DealingCount != nil { + result.DealingCount = *response.Result.DealingCount + } + } + + g.Log().Infof(ctx, "视频检测任务提交成功, taskID: %s, dealingCount: %d", result.TaskID, result.DealingCount) + return result, nil +} + +// VideoResult 视频检测完整结果 +// 包含易盾返回的完整检测信息:文本、图片、音频、ASR等 +type VideoResult struct { + TaskID string `json:"taskId"` // 任务ID + Status int `json:"status"` // 检测状态:0=未开始,1=检测中,2=检测完成 + Suggestion int `json:"suggestion"` // 处置建议:0=通过,1=嫌疑,2=不通过 + Label int `json:"label"` // 垃圾类型 + ResultType int `json:"resultType"` // 结果类型:1=机器结果,2=人审结果 + DataID string `json:"dataId"` // 数据ID + CensorTime int64 `json:"censorTime"` // 审核完成时间(毫秒) + Duration int64 `json:"duration"` // 视频时长(毫秒) + + // 完整证据信息 + Antispam *callbackresponse.VideoSolutionAntispamCallbackV2Response `json:"antispam,omitempty"` // 反垃圾检测结果 + Language *audiocallback.AudioLanguageCallbackV4Response `json:"language,omitempty"` // 语言检测结果 + Voice *audiocallback.AudioVoiceCallbackV4Response `json:"voice,omitempty"` // 语音识别结果 + Asr *audiocallback.AudioAsrCallbackV4Response `json:"asr,omitempty"` // ASR语音转文字结果 + Ocr *videocallback.VideoCallbackOcrV4Response `json:"ocr,omitempty"` // OCR文字识别结果 + Discern *videocallback.VideoCallbackDiscernV4Response `json:"discern,omitempty"` // 画面识别结果 + Logo *videocallback.VideoCallbackLogoV4Response `json:"logo,omitempty"` // Logo识别结果 + Face *videocallback.VideoCallbackFaceV4Response `json:"face,omitempty"` // 人脸识别结果 + Aigc *videocallback.VideoCallbackAigcV4Response `json:"aigc,omitempty"` // AIGC识别结果 + Quality *callbackresponse.VideoSolutionQualityCallbackV2Response `json:"quality,omitempty"` // 音视频质量检测结果 +} + +// GetVideoResult 获取视频检测结果(轮询模式) +// 返回完整的检测详情,包括文本、图片、音频、ASR等 +func (s *VideoDetectionService) GetVideoResult(ctx context.Context, taskID string) (*VideoResult, error) { + if DefaultClients == nil || DefaultClients.VideoClient == nil { + return nil, fmt.Errorf("易盾视频检测客户端未初始化") + } + + g.Log().Infof(ctx, "查询视频检测结果, taskID: %s", taskID) + + req := callbackrequest.NewVideoSolutionCallbackV2Request() + req.SetYidunRequestId(taskID) + response, err := DefaultClients.VideoClient.Callback(req) + if err != nil { + g.Log().Errorf(ctx, "查询视频检测结果失败: %v", err) + return nil, fmt.Errorf("查询视频检测结果失败: %w", err) + } + + if response.GetCode() != 200 { + g.Log().Errorf(ctx, "查询视频检测结果API错误: code=%d, msg=%s", response.GetCode(), response.GetMsg()) + return nil, fmt.Errorf("查询视频检测结果API错误: code=%d, msg=%s", response.GetCode(), response.GetMsg()) + } + + if response.Result == nil || len(*response.Result) == 0 { + g.Log().Warningf(ctx, "未找到视频检测结果, taskID: %s", taskID) + return nil, ErrVideoStillProcessing + } + + // 查找指定taskID的结果 + for _, item := range *response.Result { + if item.Antispam != nil && item.Antispam.TaskID != nil && *item.Antispam.TaskID == taskID { + result := &VideoResult{ + Antispam: item.Antispam, + Language: item.Language, + Voice: item.Voice, + Asr: item.Asr, + Ocr: item.Ocr, + Discern: item.Discern, + Logo: item.Logo, + Face: item.Face, + Aigc: item.Aigc, + Quality: item.Quality, + } + + result.TaskID = taskID + if item.Antispam.Status != nil { + result.Status = *item.Antispam.Status + } + if item.Antispam.Suggestion != nil { + result.Suggestion = *item.Antispam.Suggestion + } + if item.Antispam.Label != nil { + result.Label = *item.Antispam.Label + } + if item.Antispam.ResultType != nil { + result.ResultType = *item.Antispam.ResultType + } + if item.Antispam.DataID != nil { + result.DataID = *item.Antispam.DataID + } + if item.Antispam.CensorTime != nil { + result.CensorTime = *item.Antispam.CensorTime + } + if item.Antispam.Duration != nil { + result.Duration = *item.Antispam.Duration + } + + return result, nil + } + } + + g.Log().Warningf(ctx, "未找到指定的taskID: %s", taskID) + return nil, ErrVideoResultNotFound +} + +// PollVideoResult 轮询获取视频检测结果 +func (s *VideoDetectionService) PollVideoResult(ctx context.Context, taskID string, interval, maxWait time.Duration) (*VideoResult, error) { + if interval <= 0 { + interval = 1 * time.Second + } + if maxWait <= 0 { + maxWait = 5 * time.Minute + } + + startTime := time.Now() + g.Log().Infof(ctx, "开始轮询视频检测结果, taskID: %s, interval: %v, maxWait: %v", taskID, interval, maxWait) + + for time.Since(startTime) < maxWait { + select { + case <-ctx.Done(): + return nil, fmt.Errorf("轮询取消: %w", ctx.Err()) + default: + } + + result, err := s.GetVideoResult(ctx, taskID) + if err == nil { + g.Log().Infof(ctx, "轮询成功, taskID: %s, elapsed: %v", taskID, time.Since(startTime)) + return result, nil + } + + if errors.Is(err, ErrVideoResultNotFound) { + g.Log().Infof(ctx, "视频仍在检测中,继续轮询, taskID: %s, elapsed: %v", taskID, time.Since(startTime)) + } else { + g.Log().Warningf(ctx, "查询出错,继续重试, taskID: %s, error: %v", taskID, err) + } + + time.Sleep(interval) + } + + return nil, fmt.Errorf("轮询超时,已等待 %v", time.Since(startTime)) +} + +// GetSDKSubmitResponse 获取SDK原始提交响应(包含完整易盾返回信息) +func (s *VideoDetectionService) GetSDKSubmitResponse(ctx context.Context, videoURL, dataID string, callbackURL string) (*submitresponse.VideoSolutionSubmitV2Response, error) { + if DefaultClients == nil || DefaultClients.VideoClient == nil { + return nil, fmt.Errorf("易盾视频检测客户端未初始化") + } + + req := vsrequest.NewVideoSolutionSubmitV2Req() + req.SetURL(videoURL) + req.SetDataID(dataID) + req.SetUniqueKey(dataID) + if callbackURL != "" { + req.SetCallbackURL(callbackURL) + } + + response, err := DefaultClients.VideoClient.Submit(req) + if err != nil { + return nil, err + } + + return response, nil +} + +// GetSDKCallbackResponse 获取SDK原始回调响应(包含完整易盾返回信息) +func (s *VideoDetectionService) GetSDKCallbackResponse(ctx context.Context, taskID string) (*callbackresponse.VideoSolutionCallbackV2Response, error) { + if DefaultClients == nil || DefaultClients.VideoClient == nil { + return nil, fmt.Errorf("易盾视频检测客户端未初始化") + } + + req := callbackrequest.NewVideoSolutionCallbackV2Request() + req.SetYidunRequestId(taskID) + + response, err := DefaultClients.VideoClient.Callback(req) + if err != nil { + return nil, err + } + + return response, nil +} + +// ============================================================================= +// 视频检测结果推送模式(易盾主动回调) +// ============================================================================= + +// VideoCallbackData 推送模式回调数据完整结构 +type VideoCallbackData struct { + Antispam *VideoCallbackAntispam `json:"antispam"` // 反垃圾检测结果 +} + +type VideoCallbackAntispam struct { + TaskID string `json:"taskId"` // 任务ID + DataID string `json:"dataId"` // 客户数据ID + Callback string `json:"callback"` // 回调参数 + Suggestion int `json:"suggestion"` // 处置建议:0=通过,1=嫌疑,2=不通过 + Status int `json:"status"` // 检测状态:0=未开始,1=检测中,2=检测成功,3=检测失败 + ResultType int `json:"resultType"` // 结果类型:1=机器结果,2=人审结果 + CensorRound int `json:"censorRound"` // 人审轮次 + Censor string `json:"censor"` // 人审操作人 + CensorSource int `json:"censorSource"` // 审核来源:0=易盾人审,1=客户人审,2=易盾机审 + CheckTime int64 `json:"checkTime"` // 机器检测结束时间 + CensorTime int64 `json:"censorTime"` // 人工审核完成时间 + Duration int64 `json:"duration"` // 音视频时长(毫秒) + DurationMs int64 `json:"durationMs"` // 音频时长(毫秒) + Label int `json:"label"` // 一级垃圾类型 + SecondLabel string `json:"secondLabel"` // 二级垃圾类型 + ThirdLabel string `json:"thirdLabel"` // 三级垃圾类型 + RiskDescription string `json:"riskDescription"` // 风险描述 + PicCount int64 `json:"picCount"` // 截图数量 + CensorLabels []*VideoCensorLabel `json:"censorLabels"` // 审核标签 + CensorExtension *VideoCensorExtension `json:"censorExtension"` // 质检扩展结果 + Evidences *VideoCallbackEvidence `json:"evidences"` // 机器检测证据信息 + SolutionExtra *VideoSolutionExtra `json:"solutionExtra"` // 额外信息 + ReviewEvidences *VideoReviewEvidence `json:"reviewEvidences"` // 人审证据信息 +} + +// VideoCensorLabel 审核标签 +type VideoCensorLabel struct { + Code string `json:"code"` // 审核标签编码 + Desc string `json:"desc"` // 审核标签描述 + Name string `json:"name"` // 审核标签名称 + CustomCode string `json:"customCode"` // 自定义标签编码 + ParentLabelId string `json:"parentLabelId"` // 父标签ID + Depth int `json:"depth"` // 标签深度 +} + +// VideoCensorExtension 质检扩展结果 +type VideoCensorExtension struct { + QualityInspectionTaskId string `json:"qualityInspectionTaskId"` // 质检任务ID + QualityInspectionType int `json:"qualityInspectionType"` // 质检类型 + InspTaskCreateTime int64 `json:"inspTaskCreateTime"` // 质检任务创建时间 +} + +// VideoCallbackEvidence 机器检测证据信息 +type VideoCallbackEvidence struct { + Text *VideoTextEvidence `json:"text,omitempty"` // 文本证据 + Images *[]VideoImageEvidence `json:"images,omitempty"` // 图片证据 + Audio *VideoAudioEvidence `json:"audio,omitempty"` // 音频证据 + Video *VideoVideoEvidence `json:"video,omitempty"` // 视频证据 +} + +// VideoTextEvidence 文本证据 +type VideoTextEvidence struct { + TaskID string `json:"taskId"` // 任务ID + DataID string `json:"dataId"` // 数据ID + Suggestion int `json:"suggestion"` // 处置建议 + ResultType int `json:"resultType"` // 结果类型 + CensorType int `json:"censorType"` // 审核类型 + IsRelatedHit bool `json:"isRelatedHit"` // 是否关联命中 + Labels []*VideoTextLabel `json:"labels"` // 分类标签 +} + +// VideoTextLabel 文本分类标签 +type VideoTextLabel struct { + Label int `json:"label"` // 标签类型 + Level int `json:"level"` // 级别 + SubLabels []*VideoTextSubLabel `json:"subLabels"` // 二级分类 +} + +// VideoTextSubLabel 文本二级分类 +type VideoTextSubLabel struct { + SubLabel string `json:"subLabel"` // 二级分类 + SubLabelDepth int `json:"subLabelDepth"` // 细分类层级 + SecondLabel string `json:"secondLabel"` // 二级分类 + ThirdLabel string `json:"thirdLabel"` // 三级分类 + Details *VideoTextSubLabelDetail `json:"details"` // 命中详情 +} + +// VideoTextSubLabelDetail 文本二级分类命中详情 +type VideoTextSubLabelDetail struct { + Keywords *[]VideoTextKeyword `json:"keywords,omitempty"` // 敏感词 + LibInfos *[]VideoTextLibInfo `json:"libInfos,omitempty"` // 名单信息 + Anticheat *VideoTextAnticheat `json:"anticheat,omitempty"` // 反作弊 + HitInfos *[]VideoTextHitInfo `json:"hitInfos,omitempty"` // 其他命中 +} + +// VideoTextKeyword 敏感词信息 +type VideoTextKeyword struct { + Word string `json:"word"` // 敏感词 + StrategyGroupId int64 `json:"strategyGroupId"` // 策略组ID + StrategyGroupName string `json:"strategyGroupName"` // 策略组名称 +} + +// VideoTextLibInfo 名单信息 +type VideoTextLibInfo struct { + Type int `json:"type"` // 名单类型 + Entity string `json:"entity"` // 名单内容 + ReleaseTime int64 `json:"releaseTime"` // 释放时间 +} + +// VideoTextAnticheat 反作弊信息 +type VideoTextAnticheat struct { + HitType int `json:"hitType"` // 命中类型 +} + +// VideoTextHitInfo 其他命中信息 +type VideoTextHitInfo struct { + Value string `json:"value"` // 命中值 + Positions *[]VideoTextHitPosition `json:"positions"` // 命中位置 +} + +// VideoTextHitPosition 命中位置 +type VideoTextHitPosition struct { + FieldName string `json:"fieldName"` // 字段名 + StartPos int `json:"startPos"` // 开始位置 + EndPos int `json:"endPos"` // 结束位置 +} + +// VideoImageEvidence 图片证据 +type VideoImageEvidence struct { + Name string `json:"name"` // 图片名称 + DataID string `json:"dataId"` // 数据ID + Suggestion int `json:"suggestion"` // 处置建议 + ResultType int `json:"resultType"` // 结果类型 + Status int `json:"status"` // 状态 + CensorType int `json:"censorType"` // 审核类型 + Labels []*VideoImageLabel `json:"labels"` // 分类标签 + TaskId string `json:"taskId"` // 任务ID +} + +// VideoImageLabel 图片分类标签 +type VideoImageLabel struct { + Label int `json:"label"` // 标签类型 + Level int `json:"level"` // 级别 + Rate float32 `json:"rate"` // 置信度 + SubLabels []*VideoImageSubLabel `json:"subLabels"` // 二级分类 +} + +// VideoImageSubLabel 图片二级分类 +type VideoImageSubLabel struct { + SubLabel interface{} `json:"subLabel"` // 二级分类标签 + SubLabelDepth int `json:"subLabelDepth"` // 细分类层级 + SecondLabel string `json:"secondLabel"` // 二级分类 + ThirdLabel string `json:"thirdLabel"` // 三级分类 + HitStrategy int `json:"hitStrategy"` // 命中策略 + Rate float32 `json:"rate"` // 置信度 + Details *VideoImageSubDetail `json:"details"` // 命中详情 + Explain string `json:"explain"` // 解释 + IsLlmCheck bool `json:"isLlmCheck"` // 是否LLM命中 +} + +// VideoImageSubDetail 图片二级分类命中详情 +type VideoImageSubDetail struct { + Keywords *[]VideoImageHitInfo `json:"keywords,omitempty"` // 敏感词 + LibInfos *[]VideoImageLibInfo `json:"libInfos,omitempty"` // 名单信息 + HitInfos *[]VideoImageHitInfo `json:"hitInfos,omitempty"` // 其他命中 + Anticheat *VideoImageAnticheat `json:"anticheat,omitempty"` // 反作弊 + Llm *VideoImageLlm `json:"llm,omitempty"` // 大模型 +} + +// VideoImageHitInfo 图片命中信息 +type VideoImageHitInfo struct { + Type int `json:"type"` // 命中类型 + Word string `json:"word"` // 敏感词 + Entity string `json:"entity"` // 名单URL + HitCount int `json:"hitCount"` // 命中次数 + Value string `json:"value"` // 值 + Group string `json:"group"` // 分组 + X1 float32 `json:"x1"` // 坐标 + Y1 float32 `json:"y1"` // 坐标 + X2 float32 `json:"x2"` // 坐标 + Y2 float32 `json:"y2"` // 坐标 + StrategyGroupName string `json:"strategyGroupName"` // 策略组名称 + StrategyGroupId int64 `json:"strategyGroupId"` // 策略组ID +} + +// VideoImageLibInfo 图片名单信息 +type VideoImageLibInfo struct { + Type int `json:"type"` // 名单类型 + Entity string `json:"entity"` // 名单URL + HitCount int `json:"hitCount"` // 命中次数 + ReleaseTime int64 `json:"releaseTime"` // 释放时间 + StrategyGroupName string `json:"strategyGroupName"` // 策略组名称 + StrategyGroupId int64 `json:"strategyGroupId"` // 策略组ID +} + +// VideoImageAnticheat 反作弊信息 +type VideoImageAnticheat struct { + HitType int `json:"hitType"` // 命中类型 +} + +// VideoImageLlm 大模型信息 +type VideoImageLlm struct { + Keyword string `json:"keyword"` // 关键词 +} + +// VideoAudioEvidence 音频证据 +type VideoAudioEvidence struct { + TaskID string `json:"taskId"` // 任务ID + DataID string `json:"dataId"` // 数据ID + Status int `json:"status"` // 状态 + Suggestion int `json:"suggestion"` // 处置建议 + Label int `json:"label"` // 标签 + ResultType int `json:"resultType"` // 结果类型 + Callback string `json:"callback"` // 回调参数 + CensorSource int `json:"censorSource"` // 审核来源 + CensorTime int64 `json:"censorTime"` // 审核时间 + Duration int64 `json:"duration"` // 音频时长 + Segments []*VideoAudioSegment `json:"segments"` // 音频片段 +} + +// VideoAudioSegment 音频片段 +type VideoAudioSegment struct { + StartTime int `json:"startTime"` // 开始时间(秒) + EndTime int `json:"endTime"` // 结束时间(秒) + StartTimeMillis int64 `json:"startTimeMillis"` // 开始时间(毫秒) + EndTimeMillis int64 `json:"endTimeMillis"` // 结束时间(毫秒) + Content string `json:"content"` // 语音识别原文 + Type int `json:"type"` // 片段类型:0=语音识别,1=声纹检测 + LeaderName string `json:"leaderName"` // 声纹检测人名 + Labels []*VideoAudioLabel `json:"labels"` // 分类标签 + Url string `json:"url"` // 音频URL +} + +// VideoAudioLabel 音频分类标签 +type VideoAudioLabel struct { + Label int `json:"label"` // 标签类型 + Level int `json:"level"` // 级别 + SubLabels []*VideoAudioSubLabel `json:"subLabels"` // 二级分类 +} + +// VideoAudioSubLabel 音频二级分类 +type VideoAudioSubLabel struct { + SubLabel string `json:"subLabel"` // 细分类 + SubLabelDepth int `json:"subLabelDepth"` // 细分类层级 + SecondLabel string `json:"secondLabel"` // 二级分类 + ThirdLabel string `json:"thirdLabel"` // 三级分类 + SuggestionRiskLevel int `json:"suggestionRiskLevel"` // 嫌疑级别 + Rate float64 `json:"rate"` // 置信度 + RiskDescription string `json:"riskDescription"` // 风险描述 + Details *VideoAudioSubLabelDetail `json:"details"` // 命中详情 +} + +// VideoAudioSubLabelDetail 音频二级分类命中详情 +type VideoAudioSubLabelDetail struct { + HitInfos *[]VideoAudioHitInfo `json:"hitInfos,omitempty"` // 命中内容 + Keywords *[]VideoAudioKeyword `json:"keywords,omitempty"` // 自定义敏感词 + LibInfos *[]VideoAudioLibInfo `json:"libInfos,omitempty"` // 自定义名单 +} + +// VideoAudioHitInfo 音频命中内容 +type VideoAudioHitInfo struct { + Value string `json:"value"` // 命中敏感词或声纹检测分值 + StartTime int `json:"startTime"` // 开始时间 + EndTime int `json:"endTime"` // 结束时间 +} + +// VideoAudioKeyword 自定义敏感词 +type VideoAudioKeyword struct { + Word string `json:"word"` // 敏感词 + StrategyGroupName string `json:"strategyGroupName"` // 策略组名称 + StrategyGroupId int64 `json:"strategyGroupId"` // 策略组ID +} + +// VideoAudioLibInfo 自定义名单 +type VideoAudioLibInfo struct { + ListType int `json:"listType"` // 名单类型 + Entity string `json:"entity"` // 名单内容 +} + +// VideoVideoEvidence 视频证据 +type VideoVideoEvidence struct { + TaskID string `json:"taskId"` // 任务ID + DataID string `json:"dataId"` // 数据ID + Status int `json:"status"` // 状态 + Suggestion int `json:"suggestion"` // 处置建议 + ResultType int `json:"resultType"` // 结果类型 + CensorSource int `json:"censorSource"` // 审核来源 + CensorTime int64 `json:"censorTime"` // 审核时间 + Pictures []*VideoPicture `json:"pictures"` // 截图列表 +} + +// VideoPicture 截图信息 +type VideoPicture struct { + Type int `json:"type"` // 类型 + URL string `json:"url"` // 图片URL + StartTime int64 `json:"startTime"` // 开始时间 + EndTime int64 `json:"endTime"` // 结束时间 + Labels []*VideoPictureLabel `json:"labels"` // 分类标签 + CensorSource int `json:"censorSource"` // 审核来源 + FrontPics []*VideoRelatedPic `json:"frontPics"` // 关联前帧 + BackPics []*VideoRelatedPic `json:"backPics"` // 关联后帧 + PictureID string `json:"pictureId"` // 图片ID +} + +// VideoPictureLabel 截图分类标签 +type VideoPictureLabel struct { + Label int `json:"label"` // 标签类型 + Level int `json:"level"` // 级别 + Rate float32 `json:"rate"` // 置信度 + SubLabels []*VideoPictureSubLabel `json:"subLabels"` // 二级分类 +} + +// VideoPictureSubLabel 截图二级分类 +type VideoPictureSubLabel struct { + SubLabel interface{} `json:"subLabel"` // 二级分类标签 + SubLabelDepth int `json:"subLabelDepth"` // 细分类层级 + SecondLabel string `json:"secondLabel"` // 二级分类 + ThirdLabel string `json:"thirdLabel"` // 三级分类 + HitStrategy int `json:"hitStrategy"` // 命中策略 + Rate float32 `json:"rate"` // 置信度 + Details *VideoPictureSubDetail `json:"details"` // 命中详情 + Explain string `json:"explain"` // 解释 + IsLlmCheck bool `json:"isLlmCheck"` // 是否LLM命中 +} + +// VideoPictureSubDetail 截图二级分类命中详情 +type VideoPictureSubDetail struct { + Keywords *[]VideoPictureHitInfo `json:"keywords,omitempty"` // 敏感词 + LibInfos *[]VideoPictureLibInfo `json:"libInfos,omitempty"` // 名单信息 + HitInfos *[]VideoPictureHitInfo `json:"hitInfos,omitempty"` // 其他命中 + Anticheat *VideoPictureAnticheat `json:"anticheat,omitempty"` // 反作弊 + Llm *VideoPictureLlm `json:"llm,omitempty"` // 大模型 +} + +// VideoPictureHitInfo 截图命中信息 +type VideoPictureHitInfo struct { + Value string `json:"value"` // 命中值 + Group string `json:"group"` // 分组 + Type int `json:"type"` // 类型 + X1 float32 `json:"x1"` // 坐标 + Y1 float32 `json:"y1"` // 坐标 + X2 float32 `json:"x2"` // 坐标 + Y2 float32 `json:"y2"` // 坐标 +} + +// VideoPictureLibInfo 截图名单信息 +type VideoPictureLibInfo struct { + Type int `json:"type"` // 名单类型 + Entity string `json:"entity"` // 名单URL + HitCount int `json:"hitCount"` // 命中次数 + ReleaseTime int64 `json:"releaseTime"` // 释放时间 + StrategyGroupName string `json:"strategyGroupName"` // 策略组名称 + StrategyGroupId int64 `json:"strategyGroupId"` // 策略组ID +} + +// VideoPictureAnticheat 反作弊信息 +type VideoPictureAnticheat struct { + HitType int `json:"hitType"` // 命中类型 +} + +// VideoPictureLlm 大模型信息 +type VideoPictureLlm struct { + Keyword string `json:"keyword"` // 关键词 +} + +// VideoRelatedPic 关联截图 +type VideoRelatedPic struct { + URL string `json:"url"` // 图片URL +} + +// VideoSolutionExtra 额外信息 +type VideoSolutionExtra struct { + FailUnit *VideoFailUnit `json:"failUnit,omitempty"` // 失败单元 +} + +// VideoFailUnit 失败单元 +type VideoFailUnit struct { + Images *[]VideoImageFailUnit `json:"images,omitempty"` // 图片失败单元 + Audio *VideoTargetFailUnit `json:"audio,omitempty"` // 音频失败单元 + Video *VideoTargetFailUnit `json:"video,omitempty"` // 视频失败单元 +} + +// VideoImageFailUnit 图片失败单元 +type VideoImageFailUnit struct { + FailureReason int `json:"failureReason"` // 失败原因 + Name string `json:"name"` // 名称 +} + +// VideoTargetFailUnit 目标失败单元 +type VideoTargetFailUnit struct { + FailureReason int `json:"failureReason"` // 失败原因 +} + +// VideoReviewEvidence 人审证据信息 +type VideoReviewEvidence struct { + Description string `json:"description"` // 描述 + Detail string `json:"detail"` // 详情 + Texts *[]VideoReviewText `json:"texts"` // 文本证据 + Images *[]VideoReviewImage `json:"images"` // 图片证据 + Audios *[]VideoReviewAudio `json:"audios"` // 音频证据 + Videos *[]VideoReviewVideo `json:"videos"` // 视频证据 +} + +// VideoReviewText 人审文本证据 +type VideoReviewText struct { + Snippet string `json:"snippet"` // 片段 + Description string `json:"description"` // 描述 +} + +// VideoReviewImage 人审图片证据 +type VideoReviewImage struct { + URL string `json:"url"` // 图片URL + Description string `json:"description"` // 描述 +} + +// VideoReviewAudio 人审音频证据 +type VideoReviewAudio struct { + StartTime int64 `json:"startTime"` // 开始时间 + EndTime int64 `json:"endTime"` // 结束时间 + Description string `json:"description"` // 描述 +} + +// VideoReviewVideo 人审视频证据 +type VideoReviewVideo struct { + StartTime int64 `json:"startTime"` // 开始时间 + EndTime int64 `json:"endTime"` // 结束时间 + URL string `json:"url"` // 视频URL + Description string `json:"description"` // 描述 +} + +// ProcessVideoCallback 处理视频检测回调(推送模式) +func (s *VideoDetectionService) ProcessVideoCallback(ctx context.Context, callbackData string) error { + if callbackData == "" { + return fmt.Errorf("回调数据不能为空") + } + + var data VideoCallbackData + if err := json.Unmarshal([]byte(callbackData), &data); err != nil { + g.Log().Errorf(ctx, "解析视频回调数据失败: %v", err) + return fmt.Errorf("解析视频回调数据失败: %w", err) + } + + if data.Antispam == nil { + return fmt.Errorf("视频回调数据格式错误:缺少antispam字段") + } + + g.Log().Infof(ctx, "处理视频检测结果 - taskId: %s, suggestion: %d, resultType: %d, status: %d", + data.Antispam.TaskID, data.Antispam.Suggestion, data.Antispam.ResultType, data.Antispam.Status) + + // TODO: 业务逻辑,如保存数据库、触发后续流程等 + // 可使用完整字段:data.Antispam.Evidences, data.Antispam.CensorLabels, data.Antispam.Duration 等 + + return nil +} diff --git a/service/yidun/yidun_client.go b/service/yidun/yidun_client.go new file mode 100644 index 0000000..2adc04c --- /dev/null +++ b/service/yidun/yidun_client.go @@ -0,0 +1,53 @@ +package yidun + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" + "github.com/yidun/yidun-golang-sdk/yidun/service/antispam/image/v5" + text "github.com/yidun/yidun-golang-sdk/yidun/service/antispam/text" + "github.com/yidun/yidun-golang-sdk/yidun/service/antispam/videosolution" +) + +// YidunClients 易盾客户端集合(直接使用SDK客户端) +type YidunClients struct { + TextClient *text.TextClient + ImageClient *image.ImageClient + VideoClient *videosolution.VideoSolutionClient +} + +var DefaultClients *YidunClients + +// InitYidunClients 初始化所有易盾客户端 +func InitYidunClients(ctx context.Context) error { + clients := &YidunClients{} + + // 文本检测 + secretId := g.Cfg().MustGet(ctx, "yidun.text.secret_id").String() + secretKey := g.Cfg().MustGet(ctx, "yidun.text.secret_key").String() + if secretId != "" && secretKey != "" { + clients.TextClient = text.NewTextClientWithAccessKey(secretId, secretKey) + g.Log().Info(ctx, "文本检测客户端初始化成功") + } + + // 图片检测 + secretId = g.Cfg().MustGet(ctx, "yidun.image.secret_id").String() + secretKey = g.Cfg().MustGet(ctx, "yidun.image.secret_key").String() + businessId := g.Cfg().MustGet(ctx, "yidun.image.business_id").String() + if secretId != "" && secretKey != "" { + clients.ImageClient = image.NewImageClientWithAccessKey(secretId, secretKey) + g.Log().Infof(ctx, "图片检测客户端初始化成功, business_id: %s", businessId) + } + + // 视频检测 + secretId = g.Cfg().MustGet(ctx, "yidun.video.secret_id").String() + secretKey = g.Cfg().MustGet(ctx, "yidun.video.secret_key").String() + businessId = g.Cfg().MustGet(ctx, "yidun.video.business_id").String() + if secretId != "" && secretKey != "" { + clients.VideoClient = videosolution.NewVideoSolutionClientWithAccessKey(secretId, secretKey) + g.Log().Infof(ctx, "视频检测客户端初始化成功, business_id: %s", businessId) + } + + DefaultClients = clients + return nil +}