调整目录
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
package check
|
||||
|
||||
import (
|
||||
dto "cid/model/dto/check"
|
||||
check2 "cid/service/check"
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||
)
|
||||
|
||||
// ContentCheckController 内容送检控制器
|
||||
type ContentCheckController struct{}
|
||||
|
||||
// ContentCheck 内容送检控制器单例
|
||||
var ContentCheck = new(ContentCheckController)
|
||||
|
||||
// StatusRes 状态响应
|
||||
type StatusRes struct {
|
||||
Running bool `json:"running"`
|
||||
Config check2.ContentCheckConfig `json:"config"`
|
||||
PendingStats map[string]int `json:"pending_stats"`
|
||||
}
|
||||
|
||||
// Start 启动送检服务
|
||||
func (c *ContentCheckController) Start(ctx context.Context, req *dto.StartCheckReq) (res *beans.ResponseEmpty, err error) {
|
||||
ctx = WithAdminUser(ctx)
|
||||
|
||||
if check2.TencentContentCheck.IsRunning() {
|
||||
return &beans.ResponseEmpty{}, nil
|
||||
}
|
||||
|
||||
// 如果有配置参数,更新配置
|
||||
if req.BatchSize > 0 || req.IntervalSeconds > 0 {
|
||||
config := check2.ContentCheckConfig{
|
||||
BatchSize: req.BatchSize,
|
||||
ImageEnabled: req.ImageEnabled,
|
||||
VideoEnabled: req.VideoEnabled,
|
||||
IntervalSeconds: req.IntervalSeconds,
|
||||
}
|
||||
check2.TencentContentCheck.SetConfig(config)
|
||||
}
|
||||
|
||||
err = check2.TencentContentCheck.Start(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &beans.ResponseEmpty{}, nil
|
||||
}
|
||||
|
||||
// Stop 停止送检服务
|
||||
func (c *ContentCheckController) Stop(ctx context.Context, req *dto.EmptyReq) (res *beans.ResponseEmpty, err error) {
|
||||
ctx = WithAdminUser(ctx)
|
||||
check2.TencentContentCheck.Stop(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
// Status 获取送检服务状态
|
||||
func (c *ContentCheckController) Status(ctx context.Context, req *dto.EmptyReq) (res *StatusRes, err error) {
|
||||
ctx = WithAdminUser(ctx)
|
||||
|
||||
res = &StatusRes{
|
||||
Running: check2.TencentContentCheck.IsRunning(),
|
||||
Config: check2.TencentContentCheck.GetConfig(),
|
||||
PendingStats: check2.TencentContentCheck.GetPendingStats(ctx),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ProcessImageCallback 处理图片检测回调
|
||||
func (c *ContentCheckController) ProcessImageCallback(ctx context.Context, req *dto.ProcessImageCallbackReq) (res *beans.ResponseEmpty, err error) {
|
||||
ctx = WithAdminUser(ctx)
|
||||
|
||||
if req.CallbackData == "" {
|
||||
return nil, fmt.Errorf("callbackData不能为空")
|
||||
}
|
||||
|
||||
err = check2.TencentContentCallback.ProcessImageCallback(ctx, req.CallbackData)
|
||||
return
|
||||
}
|
||||
|
||||
// ProcessVideoCallback 处理视频检测回调
|
||||
func (c *ContentCheckController) ProcessVideoCallback(ctx context.Context, req *dto.ProcessVideoCallbackReq) (res *beans.ResponseEmpty, err error) {
|
||||
ctx = WithAdminUser(ctx)
|
||||
|
||||
if req.CallbackData == "" {
|
||||
return nil, fmt.Errorf("callbackData不能为空")
|
||||
}
|
||||
|
||||
err = check2.TencentContentCallback.ProcessVideoCallback(ctx, req.CallbackData)
|
||||
return
|
||||
}
|
||||
|
||||
// ProcessImageResult 查询并处理图片检测结果(轮询模式)
|
||||
func (c *ContentCheckController) ProcessImageResult(ctx context.Context, req *dto.ProcessImageResultReq) (res *beans.ResponseEmpty, err error) {
|
||||
ctx = WithAdminUser(ctx)
|
||||
|
||||
if req.TaskID == "" {
|
||||
return nil, fmt.Errorf("taskId不能为空")
|
||||
}
|
||||
|
||||
err = check2.TencentContentCallback.ProcessImageResult(ctx, req.TaskID)
|
||||
return
|
||||
}
|
||||
|
||||
// ProcessVideoResult 查询并处理视频检测结果(轮询模式)
|
||||
func (c *ContentCheckController) ProcessVideoResult(ctx context.Context, req *dto.ProcessVideoResultReq) (res *beans.ResponseEmpty, err error) {
|
||||
ctx = WithAdminUser(ctx)
|
||||
|
||||
if req.TaskID == "" {
|
||||
return nil, fmt.Errorf("taskId不能为空")
|
||||
}
|
||||
|
||||
err = check2.TencentContentCallback.ProcessVideoResult(ctx, req.TaskID)
|
||||
return
|
||||
}
|
||||
|
||||
// ManualSubmitImageByID 根据图片ID手动提交送检
|
||||
func (c *ContentCheckController) ManualSubmitImageByID(ctx context.Context, req *dto.ManualSubmitImageByIDReq) (res *dto.ManualSubmitRes, err error) {
|
||||
ctx = WithAdminUser(ctx)
|
||||
|
||||
result, err := check2.TencentContentCheck.SubmitImageByID(ctx, req.ImageID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res = &dto.ManualSubmitRes{
|
||||
TaskID: result.TaskID,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ManualSubmitVideoByID 根据视频ID手动提交送检
|
||||
func (c *ContentCheckController) ManualSubmitVideoByID(ctx context.Context, req *dto.ManualSubmitVideoByIDReq) (res *dto.ManualSubmitRes, err error) {
|
||||
ctx = WithAdminUser(ctx)
|
||||
|
||||
result, err := check2.TencentContentCheck.SubmitVideoByID(ctx, req.VideoID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res = &dto.ManualSubmitRes{
|
||||
TaskID: result.TaskID,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetImageCheckLogs 获取图片的送检日志
|
||||
func (c *ContentCheckController) GetImageCheckLogs(ctx context.Context, req *dto.GetImageCheckLogsReq) (res *dto.GetCheckLogsRes, err error) {
|
||||
ctx = WithAdminUser(ctx)
|
||||
|
||||
logs, err := check2.TencentContentCallback.GetCheckLogsByImageID(ctx, req.ImageID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res = &dto.GetCheckLogsRes{
|
||||
List: logs,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetVideoCheckLogs 获取视频的送检日志
|
||||
func (c *ContentCheckController) GetVideoCheckLogs(ctx context.Context, req *dto.GetVideoCheckLogsReq) (res *dto.GetCheckLogsRes, err error) {
|
||||
ctx = WithAdminUser(ctx)
|
||||
|
||||
logs, err := check2.TencentContentCallback.GetCheckLogsByVideoID(ctx, req.VideoID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res = &dto.GetCheckLogsRes{
|
||||
List: logs,
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,634 @@
|
||||
package check
|
||||
|
||||
import (
|
||||
consts "cid/consts/check"
|
||||
dao "cid/dao/check"
|
||||
entity "cid/model/entity/check"
|
||||
serviceDataengine "cid/service/check"
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// MaterialVerifyController 素材校验控制器
|
||||
type MaterialVerifyController struct{}
|
||||
|
||||
// MaterialVerify 控制器单例
|
||||
var MaterialVerify = new(MaterialVerifyController)
|
||||
|
||||
// =============================================================================
|
||||
// 请求/响应结构体
|
||||
// =============================================================================
|
||||
|
||||
// ImageListReq 图片列表请求
|
||||
type ImageListReq struct {
|
||||
Status string `json:"status"`
|
||||
AccountID int64 `json:"accountId"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
StartTime int64 `json:"startTime"`
|
||||
EndTime int64 `json:"endTime"`
|
||||
}
|
||||
|
||||
// ImageListRes 图片列表响应
|
||||
type ImageListRes struct {
|
||||
List interface{} `json:"list"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
// StatsRes 统计响应
|
||||
type StatsRes struct {
|
||||
Pending int `json:"pending"`
|
||||
Verified int `json:"verified"`
|
||||
Rejected int `json:"rejected"`
|
||||
}
|
||||
|
||||
// VideoListReq 视频列表请求
|
||||
type VideoListReq struct {
|
||||
Status string `json:"status"`
|
||||
AccountID int64 `json:"accountId"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
StartTime int64 `json:"startTime"`
|
||||
EndTime int64 `json:"endTime"`
|
||||
}
|
||||
|
||||
// VideoListRes 视频列表响应
|
||||
type VideoListRes struct {
|
||||
List interface{} `json:"list"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
// LogListReq 日志列表请求
|
||||
type LogListReq struct {
|
||||
MaterialType string `json:"materialType"`
|
||||
MaterialID string `json:"materialId"`
|
||||
VerifyStatus string `json:"verifyStatus"`
|
||||
AccountID int64 `json:"accountId"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
StartTime int64 `json:"startTime"`
|
||||
EndTime int64 `json:"endTime"`
|
||||
}
|
||||
|
||||
// ManualVerifyReq 手动校验请求
|
||||
type ManualVerifyReq struct {
|
||||
MaterialID string `json:"materialId" v:"required#素材ID不能为空"`
|
||||
}
|
||||
|
||||
// TaskIDReq 任务ID请求
|
||||
type TaskIDReq struct {
|
||||
TaskID string `json:"taskId" v:"required#任务ID不能为空"`
|
||||
}
|
||||
|
||||
// ImageCallbackReq 图片回调请求
|
||||
type ImageCallbackReq struct {
|
||||
CallbackData string `json:"callbackData"`
|
||||
}
|
||||
|
||||
// VideoCallbackReq 视频回调请求
|
||||
type VideoCallbackReq struct {
|
||||
CallbackData string `json:"callbackData"`
|
||||
}
|
||||
|
||||
// BatchVerifyReq 批量校验请求
|
||||
type BatchVerifyReq struct {
|
||||
Limit int `json:"limit"`
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 图片素材接口
|
||||
// =============================================================================
|
||||
|
||||
// ListImage 图片素材列表
|
||||
func (c *MaterialVerifyController) ListImage(ctx context.Context, req *ImageListReq) (res *ImageListRes, err error) {
|
||||
ctx = WithAdminUser(ctx)
|
||||
|
||||
if req.Page == 0 {
|
||||
req.Page = 1
|
||||
}
|
||||
if req.PageSize == 0 {
|
||||
req.PageSize = 20
|
||||
}
|
||||
|
||||
condition := make(map[string]interface{})
|
||||
if req.Status != "" {
|
||||
condition[entity.TencentImageCols.VerifyStatus] = req.Status
|
||||
}
|
||||
if req.AccountID > 0 {
|
||||
condition[entity.TencentImageCols.AccountID] = req.AccountID
|
||||
}
|
||||
|
||||
data, total, err := dao.TencentImage.GetByCondition(ctx, condition, req.Page, req.PageSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ImageListRes{
|
||||
List: data,
|
||||
Total: total,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// StatsImage 图片素材统计
|
||||
func (c *MaterialVerifyController) StatsImage(ctx context.Context, req *ImageListReq) (res *StatsRes, err error) {
|
||||
ctx = WithAdminUser(ctx)
|
||||
// 使用实体中定义的正确状态值:PENDING=待校验, VERIFIED=校验通过, REJECTED=校验不通过
|
||||
pending, err := dao.TencentImage.CountByStatus(ctx, entity.VerifyStatusPending)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "统计待校验图片数量失败: %v", err)
|
||||
}
|
||||
verified, err := dao.TencentImage.CountByStatus(ctx, entity.VerifyStatusVerified)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "统计已通过图片数量失败: %v", err)
|
||||
}
|
||||
rejected, err := dao.TencentImage.CountByStatus(ctx, entity.VerifyStatusRejected)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "统计不通过图片数量失败: %v", err)
|
||||
}
|
||||
|
||||
return &StatsRes{
|
||||
Pending: pending,
|
||||
Verified: verified,
|
||||
Rejected: rejected,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 视频素材接口
|
||||
// =============================================================================
|
||||
|
||||
// ListVideo 视频素材列表
|
||||
func (c *MaterialVerifyController) ListVideo(ctx context.Context, req *VideoListReq) (res *VideoListRes, err error) {
|
||||
ctx = WithAdminUser(ctx)
|
||||
|
||||
if req.Page == 0 {
|
||||
req.Page = 1
|
||||
}
|
||||
if req.PageSize == 0 {
|
||||
req.PageSize = 20
|
||||
}
|
||||
|
||||
condition := make(map[string]interface{})
|
||||
if req.Status != "" {
|
||||
condition[entity.TencentVideoCols.VerifyStatus] = req.Status
|
||||
}
|
||||
if req.AccountID > 0 {
|
||||
condition[entity.TencentVideoCols.AccountID] = req.AccountID
|
||||
}
|
||||
|
||||
data, total, err := dao.TencentVideo.GetByCondition(ctx, condition, req.Page, req.PageSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &VideoListRes{
|
||||
List: data,
|
||||
Total: total,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// StatsVideo 视频素材统计
|
||||
func (c *MaterialVerifyController) StatsVideo(ctx context.Context, req *VideoListReq) (res *StatsRes, err error) {
|
||||
ctx = WithAdminUser(ctx)
|
||||
// 使用实体中定义的正确状态值:PENDING=待校验, VERIFIED=校验通过, REJECTED=校验不通过
|
||||
pending, err := dao.TencentVideo.CountByStatus(ctx, entity.VerifyStatusPending)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "统计待校验视频数量失败: %v", err)
|
||||
}
|
||||
verified, err := dao.TencentVideo.CountByStatus(ctx, entity.VerifyStatusVerified)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "统计已通过视频数量失败: %v", err)
|
||||
}
|
||||
rejected, err := dao.TencentVideo.CountByStatus(ctx, entity.VerifyStatusRejected)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "统计不通过视频数量失败: %v", err)
|
||||
}
|
||||
|
||||
return &StatsRes{
|
||||
Pending: pending,
|
||||
Verified: verified,
|
||||
Rejected: rejected,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 校验日志接口
|
||||
// =============================================================================
|
||||
|
||||
// ListLogRes 日志列表响应
|
||||
type ListLogRes struct {
|
||||
List interface{} `json:"list"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
// ListLog 日志列表
|
||||
func (c *MaterialVerifyController) ListLog(ctx context.Context, req *LogListReq) (res *ListLogRes, err error) {
|
||||
ctx = WithAdminUser(ctx)
|
||||
|
||||
if req.Page == 0 {
|
||||
req.Page = 1
|
||||
}
|
||||
if req.PageSize == 0 {
|
||||
req.PageSize = 20
|
||||
}
|
||||
|
||||
condition := make(map[string]interface{})
|
||||
if req.MaterialType != "" {
|
||||
condition[entity.MaterialVerifyLogCols.MaterialType] = req.MaterialType
|
||||
}
|
||||
if req.MaterialID != "" {
|
||||
condition[entity.MaterialVerifyLogCols.MaterialID] = req.MaterialID
|
||||
}
|
||||
if req.VerifyStatus != "" {
|
||||
condition[entity.MaterialVerifyLogCols.VerifyStatus] = req.VerifyStatus
|
||||
}
|
||||
if req.AccountID > 0 {
|
||||
condition[entity.MaterialVerifyLogCols.AccountID] = req.AccountID
|
||||
}
|
||||
|
||||
data, total, err := serviceDataengine.MaterialVerify.GetLogsByCondition(ctx, condition, req.Page, req.PageSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ListLogRes{
|
||||
List: data,
|
||||
Total: total,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// LogDetailRes 日志详情响应
|
||||
type LogDetailRes struct {
|
||||
*entity.MaterialVerifyLog
|
||||
PreviewURL string `json:"previewURL"`
|
||||
}
|
||||
|
||||
// GetLogDetailReq 日志详情请求
|
||||
type GetLogDetailReq struct {
|
||||
Id int64 `json:"id" v:"required#日志ID不能为空"`
|
||||
}
|
||||
|
||||
// GetLogDetail 日志详情
|
||||
func (c *MaterialVerifyController) GetLogDetail(ctx context.Context, req *GetLogDetailReq) (res *LogDetailRes, err error) {
|
||||
ctx = WithAdminUser(ctx)
|
||||
|
||||
log, err := serviceDataengine.MaterialVerify.GetLogByID(ctx, req.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if log == nil {
|
||||
return nil, fmt.Errorf("日志不存在")
|
||||
}
|
||||
|
||||
// 获取来源数据预览
|
||||
res = &LogDetailRes{
|
||||
MaterialVerifyLog: log,
|
||||
}
|
||||
if log.SourceTable == consts.SourceTableTencentImage {
|
||||
image, _ := dao.TencentImage.GetByID(ctx, log.SourceID)
|
||||
if image != nil {
|
||||
res.PreviewURL = image.PreviewURL
|
||||
}
|
||||
} else if log.SourceTable == consts.SourceTableTencentVideo {
|
||||
video, _ := dao.TencentVideo.GetByID(ctx, log.SourceID)
|
||||
if video != nil {
|
||||
res.PreviewURL = video.PreviewURL
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// StatsLogRes 日志统计响应
|
||||
type StatsLogRes struct {
|
||||
Total int `json:"total"`
|
||||
Pending int `json:"pending"`
|
||||
Verified int `json:"verified"`
|
||||
Rejected int `json:"rejected"`
|
||||
}
|
||||
|
||||
// StatsLog 日志统计
|
||||
func (c *MaterialVerifyController) StatsLog(ctx context.Context, req *LogListReq) (res *StatsLogRes, err error) {
|
||||
ctx = WithAdminUser(ctx)
|
||||
stats, err := serviceDataengine.MaterialVerify.GetStats(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &StatsLogRes{
|
||||
Total: stats["total"],
|
||||
Pending: stats["pending"],
|
||||
Verified: stats["verified"],
|
||||
Rejected: stats["rejected"],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 手动校验接口
|
||||
// =============================================================================
|
||||
|
||||
// ManualVerifyImageRes 手动校验响应
|
||||
type ManualVerifyImageRes struct {
|
||||
Id int64 `json:"id"`
|
||||
TaskID string `json:"taskId"`
|
||||
SourceID string `json:"sourceId"`
|
||||
}
|
||||
|
||||
// ManualVerifyImage 手动校验图片
|
||||
func (c *MaterialVerifyController) ManualVerifyImage(ctx context.Context, req *ManualVerifyReq) (res *ManualVerifyImageRes, err error) {
|
||||
ctx = WithAdminUser(ctx)
|
||||
|
||||
log, err := serviceDataengine.MaterialVerify.VerifyImageByID(ctx, req.MaterialID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ManualVerifyImageRes{
|
||||
Id: log.Id,
|
||||
TaskID: log.TaskID,
|
||||
SourceID: fmt.Sprintf("%d", log.SourceID),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ManualVerifyVideo 手动校验视频
|
||||
func (c *MaterialVerifyController) ManualVerifyVideo(ctx context.Context, req *ManualVerifyReq) (res *ManualVerifyImageRes, err error) {
|
||||
ctx = WithAdminUser(ctx)
|
||||
|
||||
log, err := serviceDataengine.MaterialVerify.VerifyVideoByID(ctx, req.MaterialID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ManualVerifyImageRes{
|
||||
Id: log.Id,
|
||||
TaskID: log.TaskID,
|
||||
SourceID: fmt.Sprintf("%d", log.SourceID),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 批量校验接口
|
||||
// =============================================================================
|
||||
|
||||
// BatchVerifyRes 批量校验响应
|
||||
type BatchVerifyRes struct {
|
||||
Success int `json:"success"`
|
||||
Fail int `json:"fail"`
|
||||
Total int `json:"total"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// BatchVerifyImage 批量校验图片
|
||||
func (c *MaterialVerifyController) BatchVerifyImage(ctx context.Context, req *BatchVerifyReq) (res *BatchVerifyRes, err error) {
|
||||
ctx = WithAdminUser(ctx)
|
||||
|
||||
if req.Limit <= 0 {
|
||||
req.Limit = 100
|
||||
}
|
||||
|
||||
images, err := dao.TencentImage.GetPendingList(ctx, req.Limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
successCount := 0
|
||||
failCount := 0
|
||||
|
||||
for _, image := range images {
|
||||
_, err := serviceDataengine.MaterialVerify.VerifyImageByID(ctx, image.ImageID)
|
||||
if err != nil {
|
||||
failCount++
|
||||
g.Log().Errorf(ctx, "图片校验失败: %s, error: %v", image.ImageID, err)
|
||||
} else {
|
||||
successCount++
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
return &BatchVerifyRes{
|
||||
Success: successCount,
|
||||
Fail: failCount,
|
||||
Total: len(images),
|
||||
Message: fmt.Sprintf("批量提交完成,成功: %d,失败: %d。请通过轮询接口获取检测结果", successCount, failCount),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// BatchVerifyVideo 批量校验视频
|
||||
func (c *MaterialVerifyController) BatchVerifyVideo(ctx context.Context, req *BatchVerifyReq) (res *BatchVerifyRes, err error) {
|
||||
ctx = WithAdminUser(ctx)
|
||||
|
||||
if req.Limit <= 0 {
|
||||
req.Limit = 100
|
||||
}
|
||||
|
||||
videos, err := dao.TencentVideo.GetPendingList(ctx, req.Limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
successCount := 0
|
||||
failCount := 0
|
||||
|
||||
for _, video := range videos {
|
||||
_, err := serviceDataengine.MaterialVerify.VerifyVideoByID(ctx, video.VideoID)
|
||||
if err != nil {
|
||||
failCount++
|
||||
g.Log().Errorf(ctx, "视频校验失败: %s, error: %v", video.VideoID, err)
|
||||
} else {
|
||||
successCount++
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
return &BatchVerifyRes{
|
||||
Success: successCount,
|
||||
Fail: failCount,
|
||||
Total: len(videos),
|
||||
Message: fmt.Sprintf("批量提交完成,成功: %d,失败: %d。请通过轮询接口获取检测结果", successCount, failCount),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 账户列表接口
|
||||
// =============================================================================
|
||||
|
||||
// ListAccountsReq 账户列表请求
|
||||
type ListAccountsReq struct{}
|
||||
|
||||
// AccountItem 账户列表项
|
||||
type AccountItem struct {
|
||||
AccountID int64 `json:"accountId"`
|
||||
CorporationName string `json:"corporationName"`
|
||||
}
|
||||
|
||||
// ListAccountsRes 账户列表响应
|
||||
type ListAccountsRes struct {
|
||||
List []AccountItem `json:"list"`
|
||||
}
|
||||
|
||||
// ListAccounts 获取所有启用的广告账户列表(用于前端下拉筛选)
|
||||
func (c *MaterialVerifyController) ListAccounts(ctx context.Context, req *ListAccountsReq) (res *ListAccountsRes, err error) {
|
||||
ctx = WithAdminUser(ctx)
|
||||
accounts, err := dao.TencentAccountRelation.GetAll(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var items []AccountItem
|
||||
for _, acc := range accounts {
|
||||
items = append(items, AccountItem{
|
||||
AccountID: acc.AccountID,
|
||||
CorporationName: acc.CorporationName,
|
||||
})
|
||||
}
|
||||
|
||||
return &ListAccountsRes{
|
||||
List: items,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 导出接口 - 不通过数据
|
||||
// =============================================================================
|
||||
|
||||
// ExportRejectedReq 导出不通过数据请求
|
||||
type ExportRejectedReq struct {
|
||||
MaterialType string `json:"materialType"` // IMAGE/VIDEO,为空则导出全部
|
||||
}
|
||||
|
||||
// ExportRejectedItem 导出的不通过数据项
|
||||
type ExportRejectedItem struct {
|
||||
ID int64 `json:"id"`
|
||||
MaterialID string `json:"materialId"`
|
||||
AccountID int64 `json:"accountId"`
|
||||
CorporationName string `json:"corporationName"`
|
||||
PreviewURL string `json:"previewUrl"`
|
||||
Description string `json:"description"`
|
||||
ErrorMsg string `json:"errorMsg"`
|
||||
MaterialType string `json:"materialType"`
|
||||
ImageUsage string `json:"imageUsage,omitempty"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
}
|
||||
|
||||
// ExportRejectedRes 导出不通过数据响应
|
||||
type ExportRejectedRes struct {
|
||||
Items []ExportRejectedItem `json:"items"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
// ExportRejected 导出不通过的图片/视频数据(含失败原因)
|
||||
func (c *MaterialVerifyController) ExportRejected(ctx context.Context, req *ExportRejectedReq) (res *ExportRejectedRes, err error) {
|
||||
ctx = WithAdminUser(ctx)
|
||||
|
||||
items, err := serviceDataengine.MaterialVerify.ExportRejectedData(ctx, req.MaterialType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为响应结构
|
||||
var respItems []ExportRejectedItem
|
||||
for _, item := range items {
|
||||
respItems = append(respItems, ExportRejectedItem{
|
||||
ID: item.ID,
|
||||
MaterialID: item.MaterialID,
|
||||
AccountID: item.AccountID,
|
||||
CorporationName: item.CorporationName,
|
||||
PreviewURL: item.PreviewURL,
|
||||
Description: item.Description,
|
||||
ErrorMsg: item.ErrorMsg,
|
||||
MaterialType: item.MaterialType,
|
||||
ImageUsage: item.ImageUsage,
|
||||
CreatedAt: item.CreatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
return &ExportRejectedRes{
|
||||
Items: respItems,
|
||||
Total: len(respItems),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 回调处理接口
|
||||
// =============================================================================
|
||||
|
||||
// CallbackRes 回调响应
|
||||
type CallbackRes struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
// ImageCallback 图片校验回调
|
||||
func (c *MaterialVerifyController) ImageCallback(ctx context.Context, req *ImageCallbackReq) (res *CallbackRes, err error) {
|
||||
if !CheckCallbackIP(ctx) {
|
||||
return &CallbackRes{Code: 403, Msg: "IP not allowed"}, nil
|
||||
}
|
||||
ctx = WithCallbackUser(ctx)
|
||||
if req.CallbackData == "" {
|
||||
return &CallbackRes{Code: 400, Msg: "callbackData不能为空"}, nil
|
||||
}
|
||||
|
||||
err = serviceDataengine.MaterialVerify.ProcessImageCallback(ctx, req.CallbackData)
|
||||
if err != nil {
|
||||
return &CallbackRes{Code: 500, Msg: err.Error()}, nil
|
||||
}
|
||||
|
||||
return &CallbackRes{Code: 0, Msg: "处理成功"}, nil
|
||||
}
|
||||
|
||||
// VideoCallback 视频校验回调
|
||||
func (c *MaterialVerifyController) VideoCallback(ctx context.Context, req *VideoCallbackReq) (res *CallbackRes, err error) {
|
||||
if !CheckCallbackIP(ctx) {
|
||||
return &CallbackRes{Code: 403, Msg: "IP not allowed"}, nil
|
||||
}
|
||||
ctx = WithCallbackUser(ctx)
|
||||
if req.CallbackData == "" {
|
||||
return &CallbackRes{Code: 400, Msg: "callbackData不能为空"}, nil
|
||||
}
|
||||
|
||||
err = serviceDataengine.MaterialVerify.ProcessVideoCallback(ctx, req.CallbackData)
|
||||
if err != nil {
|
||||
return &CallbackRes{Code: 500, Msg: err.Error()}, nil
|
||||
}
|
||||
|
||||
return &CallbackRes{Code: 0, Msg: "处理成功"}, nil
|
||||
}
|
||||
|
||||
// ResultRes 结果查询响应
|
||||
type ResultRes struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
// ImageResult 图片校验结果查询(轮询模式)
|
||||
func (c *MaterialVerifyController) ImageResult(ctx context.Context, req *TaskIDReq) (res *ResultRes, err error) {
|
||||
ctx = WithAdminUser(ctx)
|
||||
if req.TaskID == "" {
|
||||
return &ResultRes{Code: 400, Msg: "taskId不能为空"}, nil
|
||||
}
|
||||
|
||||
err = serviceDataengine.MaterialVerify.ProcessImageResultByTask(ctx, req.TaskID)
|
||||
if err != nil {
|
||||
return &ResultRes{Code: 500, Msg: err.Error()}, nil
|
||||
}
|
||||
|
||||
return &ResultRes{Code: 0, Msg: "处理成功"}, nil
|
||||
}
|
||||
|
||||
// VideoResult 视频校验结果查询(轮询模式)
|
||||
func (c *MaterialVerifyController) VideoResult(ctx context.Context, req *TaskIDReq) (res *ResultRes, err error) {
|
||||
ctx = WithAdminUser(ctx)
|
||||
if req.TaskID == "" {
|
||||
return &ResultRes{Code: 400, Msg: "taskId不能为空"}, nil
|
||||
}
|
||||
|
||||
err = serviceDataengine.MaterialVerify.ProcessVideoResultByTask(ctx, req.TaskID)
|
||||
if err != nil {
|
||||
return &ResultRes{Code: 500, Msg: err.Error()}, nil
|
||||
}
|
||||
|
||||
return &ResultRes{Code: 0, Msg: "处理成功"}, nil
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Package internal 提供 controller 层的共享工具函数
|
||||
package check
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
)
|
||||
|
||||
// WithAdminUser 在 context 中注入 admin 用户信息
|
||||
func WithAdminUser(ctx context.Context) context.Context {
|
||||
return context.WithValue(ctx, "user", &beans.User{UserName: "admin", TenantId: 1})
|
||||
}
|
||||
|
||||
// WithCallbackUser 在 context 中注入 yidun_callback 用户信息
|
||||
func WithCallbackUser(ctx context.Context) context.Context {
|
||||
return context.WithValue(ctx, "user", &beans.User{UserName: "yidun_callback", TenantId: 1})
|
||||
}
|
||||
|
||||
// CheckCallbackIP 校验回调请求 IP 是否在白名单内
|
||||
// 读取配置 check.callback_allowed_ips,若未配置则跳过校验
|
||||
func CheckCallbackIP(ctx context.Context) bool {
|
||||
r := ghttp.RequestFromCtx(ctx)
|
||||
if r == nil {
|
||||
return true
|
||||
}
|
||||
allowedIPs := g.Cfg().MustGet(ctx, "check.callback_allowed_ips", "").String()
|
||||
if allowedIPs == "" {
|
||||
return true
|
||||
}
|
||||
clientIP := r.GetClientIp()
|
||||
for _, ip := range strings.Split(allowedIPs, ",") {
|
||||
if strings.TrimSpace(ip) == clientIP {
|
||||
return true
|
||||
}
|
||||
}
|
||||
g.Log().Warningf(ctx, "回调IP不在白名单中, clientIP=%s, allowedIPs=%s", clientIP, allowedIPs)
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,382 @@
|
||||
package check
|
||||
|
||||
import (
|
||||
dataengineService "cid/service/check"
|
||||
"fmt"
|
||||
|
||||
"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 /check/callback/receiveImage
|
||||
// Body: callbackData={"antispam":{...}}
|
||||
func (c *YidunCallbackController) ReceiveImageCallback(r *ghttp.Request) {
|
||||
// IP 白名单校验
|
||||
if !CheckCallbackIP(r.Context()) {
|
||||
r.Response.WriteJson(CallbackResult{Code: 403, Msg: "IP not allowed"})
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
ctx = WithCallbackUser(ctx)
|
||||
|
||||
// 易盾推送的数据在请求体中
|
||||
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: 0, Msg: "success"})
|
||||
}
|
||||
|
||||
// ReceiveVideoCallback 接收易盾视频检测结果推送
|
||||
// 易盾回调格式: POST /check/callback/receiveVideo
|
||||
// Body: callbackData={"antispam":{...}}
|
||||
func (c *YidunCallbackController) ReceiveVideoCallback(r *ghttp.Request) {
|
||||
// IP 白名单校验
|
||||
if !CheckCallbackIP(r.Context()) {
|
||||
r.Response.WriteJson(CallbackResult{Code: 403, Msg: "IP not allowed"})
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
ctx = WithCallbackUser(ctx)
|
||||
|
||||
// 易盾推送的数据在请求体中
|
||||
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: 0, Msg: "success"})
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 轮询模式 - 手动查询检测结果
|
||||
// =============================================================================
|
||||
|
||||
// PollAllResults 轮询所有待查询的检测结果(图片+视频)
|
||||
// 格式: POST /check/callback/poll
|
||||
func (c *YidunCallbackController) PollAllResults(r *ghttp.Request) {
|
||||
ctx := r.Context()
|
||||
ctx = WithAdminUser(ctx)
|
||||
|
||||
g.Log().Info(ctx, "开始轮询所有待查询的检测结果...")
|
||||
|
||||
// 先获取待处理数量
|
||||
|
||||
// 执行轮询
|
||||
successCount, failCount, err := dataengineService.MaterialVerify.PollPendingResults(ctx)
|
||||
|
||||
// 轮询后再查一下剩余待处理的明细
|
||||
pendingItems, _ := dataengineService.MaterialVerify.GetPendingResultsDetail(ctx, 50)
|
||||
|
||||
msg := fmt.Sprintf("✅ 成功处理 %d 条 | ❌ 失败 %d 条 | ⏳ 还剩 %d 条待处理",
|
||||
successCount, failCount, len(pendingItems))
|
||||
|
||||
if err != nil {
|
||||
r.Response.WriteJson(CallbackResult{
|
||||
Code: 500,
|
||||
Msg: fmt.Sprintf("部分完成,但有错误: %v", err),
|
||||
Data: g.Map{
|
||||
"summary": g.Map{"success": successCount, "fail": failCount, "pending": len(pendingItems)},
|
||||
"pending_detail": pendingItems,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
r.Response.WriteJson(CallbackResult{
|
||||
Code: 0,
|
||||
Msg: msg,
|
||||
Data: g.Map{
|
||||
"summary": g.Map{"success": successCount, "fail": failCount, "pending": len(pendingItems)},
|
||||
"pending_detail": pendingItems,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// PollImageResults 轮询图片待查询的检测结果
|
||||
// 格式: POST /check/callback/pollImage
|
||||
func (c *YidunCallbackController) PollImageResults(r *ghttp.Request) {
|
||||
ctx := r.Context()
|
||||
ctx = WithAdminUser(ctx)
|
||||
|
||||
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: 0,
|
||||
Msg: fmt.Sprintf("轮询完成,成功处理 %d 条,失败 %d 条", successCount, failCount),
|
||||
Data: PollResult{SuccessCount: successCount, FailCount: failCount},
|
||||
})
|
||||
}
|
||||
|
||||
// PollVideoResults 轮询视频待查询的检测结果
|
||||
// 格式: POST /check/callback/pollVideo
|
||||
func (c *YidunCallbackController) PollVideoResults(r *ghttp.Request) {
|
||||
ctx := r.Context()
|
||||
ctx = WithAdminUser(ctx)
|
||||
|
||||
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: 0,
|
||||
Msg: fmt.Sprintf("轮询完成,成功处理 %d 条,失败 %d 条", successCount, failCount),
|
||||
Data: PollResult{SuccessCount: successCount, FailCount: failCount},
|
||||
})
|
||||
}
|
||||
|
||||
// PollByTaskID 根据任务ID查询单个检测结果
|
||||
// 格式: POST /check/callback/pollTask
|
||||
func (c *YidunCallbackController) PollByTaskID(r *ghttp.Request) {
|
||||
ctx := r.Context()
|
||||
ctx = WithAdminUser(ctx)
|
||||
|
||||
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: 0, Msg: "查询并处理成功"})
|
||||
}
|
||||
|
||||
// PendingListRes 待查询结果明细
|
||||
type PendingListRes struct {
|
||||
Total int `json:"total"`
|
||||
List []dataengineService.PendingResultItem `json:"list"`
|
||||
}
|
||||
|
||||
// GetPendingDetail 获取待查询结果的明细
|
||||
// 格式: GET /check/callback/pendingDetail
|
||||
func (c *YidunCallbackController) GetPendingDetail(r *ghttp.Request) {
|
||||
ctx := r.Context()
|
||||
ctx = WithAdminUser(ctx)
|
||||
|
||||
items, err := dataengineService.MaterialVerify.GetPendingResultsDetail(ctx, 50)
|
||||
if err != nil {
|
||||
r.Response.WriteJson(CallbackResult{Code: 500, Msg: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
r.Response.WriteJson(g.Map{
|
||||
"code": 0,
|
||||
"data": PendingListRes{
|
||||
Total: len(items),
|
||||
List: items,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// GetPendingCount 获取待查询结果的数量
|
||||
// 格式: GET /check/callback/pendingCount
|
||||
func (c *YidunCallbackController) GetPendingCount(r *ghttp.Request) {
|
||||
ctx := r.Context()
|
||||
ctx = WithAdminUser(ctx)
|
||||
|
||||
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": 0,
|
||||
"data": g.Map{
|
||||
"pending_count": count,
|
||||
"description": "待查询结果的日志数量(状态为pending且有taskID)",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 兼容旧接口(手动触发回调处理)
|
||||
// =============================================================================
|
||||
|
||||
// ProcessImageCallback 手动处理图片回调(兼容旧接口)
|
||||
// 格式: POST /check/callback/processImage
|
||||
func (c *YidunCallbackController) ProcessImageCallback(r *ghttp.Request) {
|
||||
ctx := r.Context()
|
||||
ctx = WithAdminUser(ctx)
|
||||
|
||||
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: 0, Msg: "success"})
|
||||
}
|
||||
|
||||
// ProcessVideoCallback 手动处理视频回调(兼容旧接口)
|
||||
// 格式: POST /check/callback/processVideo
|
||||
func (c *YidunCallbackController) ProcessVideoCallback(r *ghttp.Request) {
|
||||
ctx := r.Context()
|
||||
ctx = WithAdminUser(ctx)
|
||||
|
||||
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: 0, Msg: "success"})
|
||||
}
|
||||
|
||||
// toString 转换interface{}为string
|
||||
func toString(v interface{}) string {
|
||||
if s, ok := v.(string); ok {
|
||||
return s
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package check
|
||||
|
||||
import (
|
||||
"cid/service/check"
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/yidun/yidun-golang-sdk/yidun/service/antispam/text/v5/check/async/single"
|
||||
)
|
||||
|
||||
type yidunController struct{}
|
||||
|
||||
// YidunController 易盾控制器
|
||||
var YidunController = new(yidunController)
|
||||
|
||||
// DetectTextReq 文本检测请求
|
||||
type DetectTextReq struct {
|
||||
DataID string `json:"data_id"`
|
||||
Content string `json:"content" v:"required#待检测文本不能为空"`
|
||||
IP string `json:"ip"`
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
// DetectImageReq 图片检测请求
|
||||
type DetectImageReq struct {
|
||||
DataID string `json:"data_id"`
|
||||
ImageURL string `json:"image_url" v:"required#图片URL不能为空"`
|
||||
CallbackURL string `json:"callback_url"`
|
||||
}
|
||||
|
||||
// DetectVideoReq 视频检测请求
|
||||
type DetectVideoReq struct {
|
||||
DataID string `json:"data_id"`
|
||||
VideoURL string `json:"video_url" v:"required#视频URL不能为空"`
|
||||
CallbackURL string `json:"callback_url"`
|
||||
}
|
||||
|
||||
// DetectText 文本检测
|
||||
func (c *yidunController) DetectText(ctx context.Context, req *DetectTextReq) (string, error) {
|
||||
ctx = WithAdminUser(ctx)
|
||||
|
||||
businessId := g.Cfg().MustGet(ctx, "check.text.business_id").String()
|
||||
sdkReq := single.NewTextAsyncCheckRequest(businessId)
|
||||
sdkReq.SetDataID(req.DataID)
|
||||
sdkReq.SetContent(req.Content)
|
||||
if req.IP != "" {
|
||||
sdkReq.SetIP(req.IP)
|
||||
}
|
||||
if req.Token != "" {
|
||||
sdkReq.SetToken(req.Token)
|
||||
}
|
||||
|
||||
return check.TextDetection.DetectText(ctx, sdkReq)
|
||||
}
|
||||
|
||||
// DetectImage 图片检测
|
||||
func (c *yidunController) DetectImage(ctx context.Context, req *DetectImageReq) (*check.ImageSubmitResult, error) {
|
||||
ctx = WithAdminUser(ctx)
|
||||
return check.ImageDetection.DetectImage(ctx, req.ImageURL, req.DataID, req.CallbackURL)
|
||||
}
|
||||
|
||||
// DetectVideo 视频检测
|
||||
func (c *yidunController) DetectVideo(ctx context.Context, req *DetectVideoReq) (*check.VideoSubmitResult, error) {
|
||||
ctx = WithAdminUser(ctx)
|
||||
return check.VideoDetection.DetectVideo(ctx, req.VideoURL, req.DataID, req.CallbackURL)
|
||||
}
|
||||
|
||||
// ImageCallbackResult 图片检测回调响应
|
||||
type ImageCallbackResult struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
// ReceiveImageCallback 接收图片检测结果推送
|
||||
func (c *yidunController) ReceiveImageCallback(r *ghttp.Request) {
|
||||
// IP 白名单校验
|
||||
if !CheckCallbackIP(r.Context()) {
|
||||
r.Response.WriteJson(ImageCallbackResult{Code: 403, Msg: "IP not allowed"})
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
ctx = WithAdminUser(ctx)
|
||||
|
||||
callbackData := r.GetForm("callbackData", "").String()
|
||||
if callbackData == "" {
|
||||
r.Response.WriteJson(ImageCallbackResult{Code: 400, Msg: "callbackData不能为空"})
|
||||
return
|
||||
}
|
||||
|
||||
err := check.MaterialVerify.ProcessImageCallback(ctx, callbackData)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "处理图片检测回调失败: %v", err)
|
||||
r.Response.WriteJson(ImageCallbackResult{Code: 500, Msg: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
r.Response.WriteJson(ImageCallbackResult{Code: 0, Msg: "success"})
|
||||
}
|
||||
|
||||
// VideoCallbackResult 视频检测回调响应
|
||||
type VideoCallbackResult struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
// ReceiveVideoCallback 接收视频检测结果推送
|
||||
func (c *yidunController) ReceiveVideoCallback(r *ghttp.Request) {
|
||||
// IP 白名单校验
|
||||
if !CheckCallbackIP(r.Context()) {
|
||||
r.Response.WriteJson(VideoCallbackResult{Code: 403, Msg: "IP not allowed"})
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
ctx = WithAdminUser(ctx)
|
||||
|
||||
callbackData := r.GetForm("callbackData", "").String()
|
||||
if callbackData == "" {
|
||||
r.Response.WriteJson(VideoCallbackResult{Code: 400, Msg: "callbackData不能为空"})
|
||||
return
|
||||
}
|
||||
|
||||
err := check.MaterialVerify.ProcessVideoCallback(ctx, callbackData)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "处理视频检测回调失败: %v", err)
|
||||
r.Response.WriteJson(VideoCallbackResult{Code: 500, Msg: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
r.Response.WriteJson(VideoCallbackResult{Code: 0, Msg: "success"})
|
||||
}
|
||||
|
||||
// GetVideoResult 获取视频检测结果
|
||||
func (c *yidunController) GetVideoResult(r *ghttp.Request) {
|
||||
ctx := r.Context()
|
||||
ctx = WithAdminUser(ctx)
|
||||
|
||||
taskId := r.Get("taskId", "").String()
|
||||
if taskId == "" {
|
||||
r.Response.WriteJson(g.Map{"code": 400, "msg": "taskId不能为空"})
|
||||
return
|
||||
}
|
||||
|
||||
result, err := check.VideoDetection.GetVideoResult(ctx, taskId)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "查询视频检测结果失败: %v", err)
|
||||
r.Response.WriteJson(g.Map{"code": 500, "msg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
r.Response.WriteJson(result)
|
||||
}
|
||||
|
||||
// GetImageResult 获取图片检测结果
|
||||
func (c *yidunController) GetImageResult(r *ghttp.Request) {
|
||||
ctx := r.Context()
|
||||
ctx = WithAdminUser(ctx)
|
||||
|
||||
taskId := r.Get("taskId", "").String()
|
||||
if taskId == "" {
|
||||
r.Response.WriteJson(g.Map{"code": 400, "msg": "taskId不能为空"})
|
||||
return
|
||||
}
|
||||
|
||||
result, err := check.ImageDetection.GetImageResult(ctx, taskId)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "查询图片检测结果失败: %v", err)
|
||||
r.Response.WriteJson(g.Map{"code": 500, "msg": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
r.Response.WriteJson(result)
|
||||
}
|
||||
Reference in New Issue
Block a user