Files
cid/service/yidun/image_detection_service.go
2026-05-15 10:28:17 +08:00

463 lines
19 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}