Files
cid/service/check/video_detection_service.go
2026-08-25 10:14:32 +08:00

243 lines
8.5 KiB
Go

package check
import (
"context"
"errors"
"fmt"
"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"
callbackresponse "github.com/yidun/yidun-golang-sdk/yidun/service/antispam/videosolution/callback/v2/response"
queryrequest "github.com/yidun/yidun-golang-sdk/yidun/service/antispam/videosolution/query/v2/request"
vsrequest "github.com/yidun/yidun-golang-sdk/yidun/service/antispam/videosolution/submit/v2/request"
)
// VideoDetectionService 视频检测服务
type VideoDetectionService struct{}
var VideoDetection = new(VideoDetectionService)
var (
ErrVideoStillProcessing = errors.New("视频仍在检测中,请稍后重试")
ErrVideoResultNotFound = errors.New("未找到视频检测结果")
)
// VideoSubmitResult 视频检测提交结果
type VideoSubmitResult struct {
TaskID string `json:"taskId"`
DataID string `json:"dataId"`
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, dataID: %s", videoURL, dataID)
req := vsrequest.NewVideoSolutionSubmitV2Req()
req.SetURL(videoURL)
req.SetDataID(dataID)
req.SetUniqueKey(dataID)
if callbackURL != "" {
req.SetCallbackURL(callbackURL)
}
req.SetSubProduct("videoStream")
response, err := DefaultClients.VideoClient.Submit(req)
if err != nil {
g.Log().Errorf(ctx, "视频检测提交HTTP错误: %v", err)
return nil, fmt.Errorf("视频检测提交HTTP错误: %w", err)
}
if response.GetCode() != 200 {
g.Log().Errorf(ctx, "视频检测API错误: code=%d, msg=%s", response.GetCode(), response.GetMsg())
errMsg := fmt.Sprintf("视频检测API错误: code=%d, msg=%s", response.GetCode(), response.GetMsg())
switch response.GetCode() {
case 417:
errMsg += " (可能原因: 视频URL无法访问或业务配置问题)"
case 400:
errMsg += " (可能原因: 请求参数错误)"
case 403:
errMsg += " (可能原因: 鉴权失败,检查secretId和secretKey)"
}
return nil, fmt.Errorf("%s", errMsg)
}
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 视频检测完整结果
type VideoResult struct {
TaskID string `json:"taskId"`
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"`
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"`
Ocr *videocallback.VideoCallbackOcrV4Response `json:"ocr,omitempty"`
Discern *videocallback.VideoCallbackDiscernV4Response `json:"discern,omitempty"`
Logo *videocallback.VideoCallbackLogoV4Response `json:"logo,omitempty"`
Face *videocallback.VideoCallbackFaceV4Response `json:"face,omitempty"`
Aigc *videocallback.VideoCallbackAigcV4Response `json:"aigc,omitempty"`
Quality *callbackresponse.VideoSolutionQualityCallbackV2Response `json:"quality,omitempty"`
}
// queryCheckStatusToStatus 将易盾 checkStatus 映射为内部 Status
func queryCheckStatusToStatus(checkStatus int) int {
switch checkStatus {
case 0:
return 1 // Processing
case 1:
return 2 // Success
case 2:
return 3 // Failed
default:
return 0 // NotStart
}
}
// queryResultToSuggestion 将易盾 result 映射为内部 suggestion
// result: 1=正常, 2=异常, 3=疑似 → suggestion: 0=通过, 1=嫌疑, 2=不通过
func queryResultToSuggestion(result int) int {
switch result {
case 1:
return 0 // 通过
case 2:
return 2 // 不通过
case 3:
return 1 // 嫌疑
default:
return 0
}
}
// GetVideoResult 获取视频检测结果(轮询模式)
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 := queryrequest.NewVideoSolutionQueryTaskV2Request()
req.SetTaskIds([]string{taskID})
response, err := DefaultClients.VideoClient.QueryTaskV2(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, ErrVideoResultNotFound
}
for _, item := range *response.Result {
if item.TaskID != nil && *item.TaskID == taskID {
// status: 0=检测完成/失败, 20=非7天内, 30=不存在, 40=检测中
if item.Status != nil {
if *item.Status == 30 {
g.Log().Warningf(ctx, "视频检测结果不存在, taskID: %s", taskID)
return nil, ErrVideoResultNotFound
}
if *item.Status == 40 {
g.Log().Infof(ctx, "视频仍在检测中, taskID: %s", taskID)
return nil, ErrVideoStillProcessing
}
}
result := &VideoResult{TaskID: taskID}
if item.DataID != nil {
result.DataID = *item.DataID
}
if item.TaskID != nil {
result.TaskID = *item.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
}
// 完整证据信息
result.Antispam = &item.Antispam
g.Log().Infof(ctx, "视频检测结果查询成功, taskID: %s, suggestion=%d, label=%d",
taskID, result.Suggestion, result.Label)
return result, nil
}
}
g.Log().Warningf(ctx, "未找到指定的taskID: %s", taskID)
return nil, ErrVideoResultNotFound
}
// VideoCallbackData 推送模式回调数据
type VideoCallbackData struct {
Antispam *VideoCallbackAntispam `json:"antispam"`
}
// VideoCallbackAntispam 视频回调反垃圾结果
type VideoCallbackAntispam struct {
TaskID string `json:"taskId"`
DataID string `json:"dataId"`
Suggestion int `json:"suggestion"`
Status int `json:"status"`
ResultType int `json:"resultType"`
Label int `json:"label"`
SecondLabel string `json:"secondLabel"`
ThirdLabel string `json:"thirdLabel"`
RiskDescription string `json:"riskDescription"`
CensorSource int `json:"censorSource"`
CensorTime int64 `json:"censorTime"`
CheckTime int64 `json:"checkTime"`
Duration int64 `json:"duration"`
}