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 } // DetectImageSync 同步检测图片,提交并直接返回检测结果 // 适用于轮询模式(无回调),提交时实时返回结果,无需额外查询 func (s *ImageDetectionService) DetectImageSync(ctx context.Context, imageURL, dataID string) (*ImageResult, 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 := check.NewImageV5CheckRequest(businessId) imageBean := check.NewImageBeanRequest() imageBean.SetData(imageURL) imageBean.SetName(dataID) imageBean.SetType(1) // 1: 图片URL request.SetImages([]check.ImageBeanRequest{*imageBean}) // 调用同步检测API response, err := DefaultClients.ImageClient.ImageSyncCheck(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()) } if response.Result == nil || len(*response.Result) == 0 { g.Log().Warningf(ctx, "图片同步检测结果为空, url: %s", imageURL) return nil, ErrImageResultNotFound } // 提取结果 detail := (*response.Result)[0] result := &ImageResult{ TaskID: dataID, Name: dataID, Url: imageURL, } if detail.Antispam != nil { if detail.Antispam.TaskId != nil { result.TaskID = *detail.Antispam.TaskId } if detail.Antispam.Status != nil { result.Status = *detail.Antispam.Status } if detail.Antispam.Suggestion != nil { result.Suggestion = *detail.Antispam.Suggestion } if detail.Antispam.Label != nil { result.Label = *detail.Antispam.Label } if detail.Antispam.ResultType != nil { result.ResultType = *detail.Antispam.ResultType } if detail.Antispam.CensorTime != nil { result.CensorTime = *detail.Antispam.CensorTime } result.Antispam = detail.Antispam } g.Log().Infof(ctx, "图片同步检测完成, taskID: %s, suggestion: %d, status: %d", result.TaskID, result.Suggestion, result.Status) 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, ErrImageResultNotFound } // 查找指定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 }