463 lines
19 KiB
Go
463 lines
19 KiB
Go
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
|
||
}
|