Files
cid/controller/yidun/yidun_callback_controller.go

341 lines
10 KiB
Go
Raw Normal View History

2026-05-15 10:28:17 +08:00
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 ""
}