package yidun import ( dataengineService "cid/service/dataengine" "context" "fmt" "gitea.com/red-future/common/beans" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" ) // YidunCallbackController 易盾回调控制器 // 用于接收易盾检测结果的主动推送或手动轮询查询 type YidunCallbackController struct{} // YidunCallback 易盾回调控制器单例 var YidunCallback = new(YidunCallbackController) // CallbackResult 通用回调响应 type CallbackResult struct { Code int `json:"code"` Msg string `json:"msg"` Data interface{} `json:"data,omitempty"` } // PollResult 轮询结果 type PollResult struct { SuccessCount int `json:"success_count"` FailCount int `json:"fail_count"` PendingCount int `json:"pending_count"` } // ============================================================================= // 易盾主动推送模式回调接口 // 易盾会在检测完成后主动 POST 数据到这些接口 // ============================================================================= // ReceiveImageCallback 接收易盾图片检测结果推送 // 易盾回调格式: POST /yidun/callback/receiveImage // Body: callbackData={"antispam":{...}} func (c *YidunCallbackController) ReceiveImageCallback(r *ghttp.Request) { ctx := r.Context() ctx = context.WithValue(ctx, "user", &beans.User{UserName: "yidun_callback"}) // 易盾推送的数据在请求体中 var callbackData string // 尝试从表单数据获取 callbackData = r.GetForm("callbackData", "").String() if callbackData == "" { // 尝试从请求体JSON获取 var reqBody map[string]interface{} if err := r.Parse(&reqBody); err == nil { if v, ok := reqBody["callbackData"]; ok { callbackData = toString(v) } } } // 尝试直接从请求体获取原始数据 if callbackData == "" { callbackData = string(r.GetBody()) } if callbackData == "" { g.Log().Warningf(ctx, "图片回调数据为空") r.Response.WriteJson(CallbackResult{Code: 400, Msg: "callbackData不能为空"}) return } g.Log().Infof(ctx, "收到易盾图片回调, data长度: %d", len(callbackData)) // 处理回调 - 更新 material_verify_log 和 tencent_image 表 err := dataengineService.MaterialVerify.ProcessImageCallback(ctx, callbackData) if err != nil { g.Log().Errorf(ctx, "处理易盾图片回调失败: %v", err) r.Response.WriteJson(CallbackResult{Code: 500, Msg: err.Error()}) return } r.Response.WriteJson(CallbackResult{Code: 200, Msg: "success"}) } // ReceiveVideoCallback 接收易盾视频检测结果推送 // 易盾回调格式: POST /yidun/callback/receiveVideo // Body: callbackData={"antispam":{...}} func (c *YidunCallbackController) ReceiveVideoCallback(r *ghttp.Request) { ctx := r.Context() ctx = context.WithValue(ctx, "user", &beans.User{UserName: "yidun_callback"}) // 易盾推送的数据在请求体中 var callbackData string // 尝试从表单数据获取 callbackData = r.GetForm("callbackData", "").String() if callbackData == "" { // 尝试从请求体JSON获取 var reqBody map[string]interface{} if err := r.Parse(&reqBody); err == nil { if v, ok := reqBody["callbackData"]; ok { callbackData = toString(v) } } } // 尝试直接从请求体获取原始数据 if callbackData == "" { callbackData = string(r.GetBody()) } if callbackData == "" { g.Log().Warningf(ctx, "视频回调数据为空") r.Response.WriteJson(CallbackResult{Code: 400, Msg: "callbackData不能为空"}) return } g.Log().Infof(ctx, "收到易盾视频回调, data长度: %d", len(callbackData)) // 处理回调 - 更新 material_verify_log 和 tencent_video 表 err := dataengineService.MaterialVerify.ProcessVideoCallback(ctx, callbackData) if err != nil { g.Log().Errorf(ctx, "处理易盾视频回调失败: %v", err) r.Response.WriteJson(CallbackResult{Code: 500, Msg: err.Error()}) return } r.Response.WriteJson(CallbackResult{Code: 200, Msg: "success"}) } // ============================================================================= // 轮询模式 - 手动查询检测结果 // ============================================================================= // PollAllResults 轮询所有待查询的检测结果(图片+视频) // 格式: POST /yidun/callback/poll func (c *YidunCallbackController) PollAllResults(r *ghttp.Request) { ctx := r.Context() ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"}) g.Log().Info(ctx, "开始轮询所有待查询的检测结果...") // 先获取待处理数量 pendingCount, _ := dataengineService.MaterialVerify.GetPendingResultsCount(ctx) // 执行轮询 successCount, failCount, err := dataengineService.MaterialVerify.PollPendingResults(ctx) result := PollResult{ SuccessCount: successCount, FailCount: failCount, PendingCount: pendingCount - successCount, } if err != nil { r.Response.WriteJson(CallbackResult{ Code: 500, Msg: fmt.Sprintf("轮询完成但有错误: %v", err), Data: result, }) return } r.Response.WriteJson(CallbackResult{ Code: 200, Msg: fmt.Sprintf("轮询完成,成功处理 %d 条,失败 %d 条", successCount, failCount), Data: result, }) } // PollImageResults 轮询图片待查询的检测结果 // 格式: POST /yidun/callback/pollImage func (c *YidunCallbackController) PollImageResults(r *ghttp.Request) { ctx := r.Context() ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"}) g.Log().Info(ctx, "开始轮询图片待查询的检测结果...") successCount, failCount, err := dataengineService.MaterialVerify.PollPendingImageResults(ctx) if err != nil { r.Response.WriteJson(CallbackResult{ Code: 500, Msg: fmt.Sprintf("轮询失败: %v", err), Data: PollResult{SuccessCount: successCount, FailCount: failCount}, }) return } r.Response.WriteJson(CallbackResult{ Code: 200, Msg: fmt.Sprintf("轮询完成,成功处理 %d 条,失败 %d 条", successCount, failCount), Data: PollResult{SuccessCount: successCount, FailCount: failCount}, }) } // PollVideoResults 轮询视频待查询的检测结果 // 格式: POST /yidun/callback/pollVideo func (c *YidunCallbackController) PollVideoResults(r *ghttp.Request) { ctx := r.Context() ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"}) g.Log().Info(ctx, "开始轮询视频待查询的检测结果...") successCount, failCount, err := dataengineService.MaterialVerify.PollPendingVideoResults(ctx) if err != nil { r.Response.WriteJson(CallbackResult{ Code: 500, Msg: fmt.Sprintf("轮询失败: %v", err), Data: PollResult{SuccessCount: successCount, FailCount: failCount}, }) return } r.Response.WriteJson(CallbackResult{ Code: 200, Msg: fmt.Sprintf("轮询完成,成功处理 %d 条,失败 %d 条", successCount, failCount), Data: PollResult{SuccessCount: successCount, FailCount: failCount}, }) } // PollByTaskID 根据任务ID查询单个检测结果 // 格式: POST /yidun/callback/pollTask func (c *YidunCallbackController) PollByTaskID(r *ghttp.Request) { ctx := r.Context() ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"}) taskID := r.Get("taskId", "").String() taskType := r.Get("type", "").String() // image 或 video if taskID == "" { r.Response.WriteJson(CallbackResult{Code: 400, Msg: "taskId不能为空"}) return } g.Log().Infof(ctx, "查询单个检测结果, taskId=%s, type=%s", taskID, taskType) var err error if taskType == "video" || taskType == "" { // 尝试视频 err = dataengineService.MaterialVerify.ProcessVideoResultByTask(ctx, taskID) if err != nil { // 如果失败且没有指定类型,尝试图片 if taskType == "" { err = dataengineService.MaterialVerify.ProcessImageResultByTask(ctx, taskID) } } } else if taskType == "image" { err = dataengineService.MaterialVerify.ProcessImageResultByTask(ctx, taskID) } if err != nil { r.Response.WriteJson(CallbackResult{Code: 500, Msg: err.Error()}) return } r.Response.WriteJson(CallbackResult{Code: 200, Msg: "查询并处理成功"}) } // GetPendingCount 获取待查询结果的数量 // 格式: GET /yidun/callback/pendingCount func (c *YidunCallbackController) GetPendingCount(r *ghttp.Request) { ctx := r.Context() ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"}) count, err := dataengineService.MaterialVerify.GetPendingResultsCount(ctx) if err != nil { r.Response.WriteJson(CallbackResult{Code: 500, Msg: err.Error()}) return } r.Response.WriteJson(g.Map{ "code": 200, "data": g.Map{ "pending_count": count, "description": "待查询结果的日志数量(状态为pending且有taskID)", }, }) } // ============================================================================= // 兼容旧接口(手动触发回调处理) // ============================================================================= // ProcessImageCallback 手动处理图片回调(兼容旧接口) // 格式: POST /yidun/callback/processImage func (c *YidunCallbackController) ProcessImageCallback(r *ghttp.Request) { ctx := r.Context() ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"}) var req struct { CallbackData string `json:"callbackData" v:"required#回调数据不能为空"` } if err := r.Parse(&req); err != nil { r.Response.WriteJson(CallbackResult{Code: 400, Msg: err.Error()}) return } err := dataengineService.MaterialVerify.ProcessImageCallback(ctx, req.CallbackData) if err != nil { g.Log().Errorf(ctx, "处理图片回调失败: %v", err) r.Response.WriteJson(CallbackResult{Code: 500, Msg: err.Error()}) return } r.Response.WriteJson(CallbackResult{Code: 200, Msg: "success"}) } // ProcessVideoCallback 手动处理视频回调(兼容旧接口) // 格式: POST /yidun/callback/processVideo func (c *YidunCallbackController) ProcessVideoCallback(r *ghttp.Request) { ctx := r.Context() ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"}) var req struct { CallbackData string `json:"callbackData" v:"required#回调数据不能为空"` } if err := r.Parse(&req); err != nil { r.Response.WriteJson(CallbackResult{Code: 400, Msg: err.Error()}) return } err := dataengineService.MaterialVerify.ProcessVideoCallback(ctx, req.CallbackData) if err != nil { g.Log().Errorf(ctx, "处理视频回调失败: %v", err) r.Response.WriteJson(CallbackResult{Code: 500, Msg: err.Error()}) return } r.Response.WriteJson(CallbackResult{Code: 200, Msg: "success"}) } // toString 转换interface{}为string func toString(v interface{}) string { if s, ok := v.(string); ok { return s } return "" }