package service import ( "context" "encoding/json" "fmt" "log" "strings" "sync" "time" "carrot_bbs/internal/model" "gorm.io/gorm" ) // ==================== 内容审核服务接口和实现 ==================== // AuditServiceProvider 内容审核服务提供商接口 type AuditServiceProvider interface { // AuditText 审核文本 AuditText(ctx context.Context, text string, scene string) (*AuditResult, error) // AuditImage 审核图片 AuditImage(ctx context.Context, imageURL string) (*AuditResult, error) // GetName 获取提供商名称 GetName() string } // AuditResult 审核结果 type AuditResult struct { Pass bool `json:"pass"` // 是否通过 Risk string `json:"risk"` // 风险等级: low, medium, high Labels []string `json:"labels"` // 标签列表 Suggest string `json:"suggest"` // 建议: pass, review, block Detail string `json:"detail"` // 详细说明 Provider string `json:"provider"` // 服务提供商 } // AuditService 内容审核服务接口 type AuditService interface { // AuditText 审核文本 AuditText(ctx context.Context, text string, auditType string) (*AuditResult, error) // AuditImage 审核图片 AuditImage(ctx context.Context, imageURL string) (*AuditResult, error) // GetAuditResult 获取审核结果 GetAuditResult(ctx context.Context, auditID string) (*AuditResult, error) // SetProvider 设置审核服务提供商 SetProvider(provider AuditServiceProvider) // GetProvider 获取当前审核服务提供商 GetProvider() AuditServiceProvider } // auditServiceImpl 内容审核服务实现 type auditServiceImpl struct { db *gorm.DB provider AuditServiceProvider config *AuditConfig mu sync.RWMutex } // AuditConfig 内容审核服务配置 type AuditConfig struct { Enabled bool `mapstructure:"enabled" yaml:"enabled"` // 审核服务提供商: local, aliyun, tencent, baidu Provider string `mapstructure:"provider" yaml:"provider"` // 阿里云配置 AliyunAccessKey string `mapstructure:"aliyun_access_key" yaml:"aliyun_access_key"` AliyunSecretKey string `mapstructure:"aliyun_secret_key" yaml:"aliyun_secret_key"` AliyunRegion string `mapstructure:"aliyun_region" yaml:"aliyun_region"` // 腾讯云配置 TencentSecretID string `mapstructure:"tencent_secret_id" yaml:"tencent_secret_id"` TencentSecretKey string `mapstructure:"tencent_secret_key" yaml:"tencent_secret_key"` // 百度云配置 BaiduAPIKey string `mapstructure:"baidu_api_key" yaml:"baidu_api_key"` BaiduSecretKey string `mapstructure:"baidu_secret_key" yaml:"baidu_secret_key"` // 是否自动审核 AutoAudit bool `mapstructure:"auto_audit" yaml:"auto_audit"` // 审核超时时间(秒) Timeout int `mapstructure:"timeout" yaml:"timeout"` } // NewAuditService 创建内容审核服务 func NewAuditService(db *gorm.DB, config *AuditConfig) AuditService { s := &auditServiceImpl{ db: db, config: config, } // 根据配置初始化提供商 if config.Enabled { provider := s.initProvider(config.Provider) s.provider = provider } return s } // initProvider 根据配置初始化审核服务提供商 func (s *auditServiceImpl) initProvider(providerType string) AuditServiceProvider { switch strings.ToLower(providerType) { case "aliyun": return NewAliyunAuditProvider(s.config.AliyunAccessKey, s.config.AliyunSecretKey, s.config.AliyunRegion) case "tencent": return NewTencentAuditProvider(s.config.TencentSecretID, s.config.TencentSecretKey) case "baidu": return NewBaiduAuditProvider(s.config.BaiduAPIKey, s.config.BaiduSecretKey) case "local": fallthrough default: // 默认使用本地审核服务 return NewLocalAuditProvider() } } // AuditText 审核文本 func (s *auditServiceImpl) AuditText(ctx context.Context, text string, auditType string) (*AuditResult, error) { if !s.config.Enabled { // 如果审核服务未启用,直接返回通过 return &AuditResult{ Pass: true, Risk: "low", Suggest: "pass", Detail: "Audit service disabled", }, nil } if text == "" { return &AuditResult{ Pass: true, Risk: "low", Suggest: "pass", Detail: "Empty text", }, nil } var result *AuditResult var err error // 使用提供商审核 if s.provider != nil { result, err = s.provider.AuditText(ctx, text, auditType) } else { // 如果没有设置提供商,使用本地审核 localProvider := NewLocalAuditProvider() result, err = localProvider.AuditText(ctx, text, auditType) } if err != nil { log.Printf("Audit text error: %v", err) return &AuditResult{ Pass: false, Risk: "high", Suggest: "review", Detail: fmt.Sprintf("Audit error: %v", err), }, err } // 记录审核日志 go s.saveAuditLog(ctx, "text", "", text, auditType, result) return result, nil } // AuditImage 审核图片 func (s *auditServiceImpl) AuditImage(ctx context.Context, imageURL string) (*AuditResult, error) { if !s.config.Enabled { return &AuditResult{ Pass: true, Risk: "low", Suggest: "pass", Detail: "Audit service disabled", }, nil } if imageURL == "" { return &AuditResult{ Pass: true, Risk: "low", Suggest: "pass", Detail: "Empty image URL", }, nil } var result *AuditResult var err error // 使用提供商审核 if s.provider != nil { result, err = s.provider.AuditImage(ctx, imageURL) } else { // 如果没有设置提供商,使用本地审核 localProvider := NewLocalAuditProvider() result, err = localProvider.AuditImage(ctx, imageURL) } if err != nil { log.Printf("Audit image error: %v", err) return &AuditResult{ Pass: false, Risk: "high", Suggest: "review", Detail: fmt.Sprintf("Audit error: %v", err), }, err } // 记录审核日志 go s.saveAuditLog(ctx, "image", "", "", "image", result) return result, nil } // GetAuditResult 获取审核结果 func (s *auditServiceImpl) GetAuditResult(ctx context.Context, auditID string) (*AuditResult, error) { if s.db == nil || auditID == "" { return nil, fmt.Errorf("invalid audit ID") } var auditLog model.AuditLog if err := s.db.Where("id = ?", auditID).First(&auditLog).Error; err != nil { return nil, err } result := &AuditResult{ Pass: auditLog.Result == model.AuditResultPass, Risk: string(auditLog.RiskLevel), Suggest: auditLog.Suggestion, Detail: auditLog.Detail, } // 解析标签 if auditLog.Labels != "" { json.Unmarshal([]byte(auditLog.Labels), &result.Labels) } return result, nil } // SetProvider 设置审核服务提供商 func (s *auditServiceImpl) SetProvider(provider AuditServiceProvider) { s.mu.Lock() defer s.mu.Unlock() s.provider = provider } // GetProvider 获取当前审核服务提供商 func (s *auditServiceImpl) GetProvider() AuditServiceProvider { s.mu.RLock() defer s.mu.RUnlock() return s.provider } // saveAuditLog 保存审核日志 func (s *auditServiceImpl) saveAuditLog(ctx context.Context, contentType, content, imageURL, auditType string, result *AuditResult) { if s.db == nil { return } auditLog := model.AuditLog{ ContentType: contentType, Content: content, ContentURL: imageURL, AuditType: auditType, Labels: strings.Join(result.Labels, ","), Suggestion: result.Suggest, Detail: result.Detail, Source: model.AuditSourceAuto, Status: "completed", } if result.Pass { auditLog.Result = model.AuditResultPass } else if result.Suggest == "review" { auditLog.Result = model.AuditResultReview } else { auditLog.Result = model.AuditResultBlock } switch result.Risk { case "low": auditLog.RiskLevel = model.AuditRiskLevelLow case "medium": auditLog.RiskLevel = model.AuditRiskLevelMedium case "high": auditLog.RiskLevel = model.AuditRiskLevelHigh default: auditLog.RiskLevel = model.AuditRiskLevelLow } if err := s.db.Create(&auditLog).Error; err != nil { log.Printf("Failed to save audit log: %v", err) } } // ==================== 本地审核服务提供商 ==================== // localAuditProvider 本地审核服务提供商 type localAuditProvider struct { // 可以注入敏感词服务进行本地审核 sensitiveService SensitiveService } // NewLocalAuditProvider 创建本地审核服务提供商 func NewLocalAuditProvider() AuditServiceProvider { return &localAuditProvider{ sensitiveService: nil, } } // GetName 获取提供商名称 func (p *localAuditProvider) GetName() string { return "local" } // AuditText 审核文本 func (p *localAuditProvider) AuditText(ctx context.Context, text string, scene string) (*AuditResult, error) { // 本地审核逻辑 // 1. 敏感词检查 // 2. 规则匹配 // 3. 简单的关键词检测 result := &AuditResult{ Pass: true, Risk: "low", Suggest: "pass", Labels: []string{}, Provider: "local", } // 如果有敏感词服务,使用它进行检测 if p.sensitiveService != nil { hasSensitive, words := p.sensitiveService.Check(ctx, text) if hasSensitive { result.Pass = false result.Risk = "high" result.Suggest = "block" result.Detail = fmt.Sprintf("包含敏感词: %s", strings.Join(words, ",")) result.Labels = append(result.Labels, "sensitive") } } // 简单的关键词检测规则 // 实际项目中应该从数据库加载 suspiciousPatterns := []string{ "诈骗", "钓鱼", "木马", "病毒", } for _, pattern := range suspiciousPatterns { if strings.Contains(text, pattern) { result.Pass = false result.Risk = "high" result.Suggest = "block" result.Labels = append(result.Labels, "suspicious") if result.Detail == "" { result.Detail = fmt.Sprintf("包含可疑内容: %s", pattern) } else { result.Detail += fmt.Sprintf(", %s", pattern) } } } return result, nil } // AuditImage 审核图片 func (p *localAuditProvider) AuditImage(ctx context.Context, imageURL string) (*AuditResult, error) { // 本地图片审核逻辑 // 1. 图片URL合法性检查 // 2. 图片格式检查 // 3. 可以扩展接入本地图片识别服务 result := &AuditResult{ Pass: true, Risk: "low", Suggest: "pass", Labels: []string{}, Provider: "local", } // 检查URL是否为空 if imageURL == "" { result.Detail = "Empty image URL" return result, nil } // 检查是否为支持的图片URL格式 validPrefixes := []string{"http://", "https://", "s3://", "oss://", "cos://"} isValid := false for _, prefix := range validPrefixes { if strings.HasPrefix(strings.ToLower(imageURL), prefix) { isValid = true break } } if !isValid { result.Pass = false result.Risk = "medium" result.Suggest = "review" result.Detail = "Invalid image URL format" result.Labels = append(result.Labels, "invalid_url") } return result, nil } // SetSensitiveService 设置敏感词服务 func (p *localAuditProvider) SetSensitiveService(ss SensitiveService) { p.sensitiveService = ss } // ==================== 阿里云审核服务提供商 ==================== // aliyunAuditProvider 阿里云审核服务提供商 type aliyunAuditProvider struct { accessKey string secretKey string region string } // NewAliyunAuditProvider 创建阿里云审核服务提供商 func NewAliyunAuditProvider(accessKey, secretKey, region string) AuditServiceProvider { return &aliyunAuditProvider{ accessKey: accessKey, secretKey: secretKey, region: region, } } // GetName 获取提供商名称 func (p *aliyunAuditProvider) GetName() string { return "aliyun" } // AuditText 审核文本 func (p *aliyunAuditProvider) AuditText(ctx context.Context, text string, scene string) (*AuditResult, error) { // 阿里云内容安全API调用 // 实际项目中需要实现阿里云SDK调用 // 这里预留接口 result := &AuditResult{ Pass: true, Risk: "low", Suggest: "pass", Labels: []string{}, Provider: "aliyun", Detail: "Aliyun audit not implemented, using pass", } // TODO: 实现阿里云内容安全API调用 // 具体参考: https://help.aliyun.com/document_detail/28417.html return result, nil } // AuditImage 审核图片 func (p *aliyunAuditProvider) AuditImage(ctx context.Context, imageURL string) (*AuditResult, error) { result := &AuditResult{ Pass: true, Risk: "low", Suggest: "pass", Labels: []string{}, Provider: "aliyun", Detail: "Aliyun image audit not implemented, using pass", } // TODO: 实现阿里云图片审核API调用 return result, nil } // ==================== 腾讯云审核服务提供商 ==================== // tencentAuditProvider 腾讯云审核服务提供商 type tencentAuditProvider struct { secretID string secretKey string } // NewTencentAuditProvider 创建腾讯云审核服务提供商 func NewTencentAuditProvider(secretID, secretKey string) AuditServiceProvider { return &tencentAuditProvider{ secretID: secretID, secretKey: secretKey, } } // GetName 获取提供商名称 func (p *tencentAuditProvider) GetName() string { return "tencent" } // AuditText 审核文本 func (p *tencentAuditProvider) AuditText(ctx context.Context, text string, scene string) (*AuditResult, error) { result := &AuditResult{ Pass: true, Risk: "low", Suggest: "pass", Labels: []string{}, Provider: "tencent", Detail: "Tencent audit not implemented, using pass", } // TODO: 实现腾讯云内容审核API调用 // 具体参考: https://cloud.tencent.com/document/product/1124/64508 return result, nil } // AuditImage 审核图片 func (p *tencentAuditProvider) AuditImage(ctx context.Context, imageURL string) (*AuditResult, error) { result := &AuditResult{ Pass: true, Risk: "low", Suggest: "pass", Labels: []string{}, Provider: "tencent", Detail: "Tencent image audit not implemented, using pass", } // TODO: 实现腾讯云图片审核API调用 return result, nil } // ==================== 百度云审核服务提供商 ==================== // baiduAuditProvider 百度云审核服务提供商 type baiduAuditProvider struct { apiKey string secretKey string } // NewBaiduAuditProvider 创建百度云审核服务提供商 func NewBaiduAuditProvider(apiKey, secretKey string) AuditServiceProvider { return &baiduAuditProvider{ apiKey: apiKey, secretKey: secretKey, } } // GetName 获取提供商名称 func (p *baiduAuditProvider) GetName() string { return "baidu" } // AuditText 审核文本 func (p *baiduAuditProvider) AuditText(ctx context.Context, text string, scene string) (*AuditResult, error) { result := &AuditResult{ Pass: true, Risk: "low", Suggest: "pass", Labels: []string{}, Provider: "baidu", Detail: "Baidu audit not implemented, using pass", } // TODO: 实现百度云内容审核API调用 // 具体参考: https://cloud.baidu.com/doc/ANTISPAM/s/Jjw0r1iF6 return result, nil } // AuditImage 审核图片 func (p *baiduAuditProvider) AuditImage(ctx context.Context, imageURL string) (*AuditResult, error) { result := &AuditResult{ Pass: true, Risk: "low", Suggest: "pass", Labels: []string{}, Provider: "baidu", Detail: "Baidu image audit not implemented, using pass", } // TODO: 实现百度云图片审核API调用 return result, nil } // ==================== 审核结果回调处理 ==================== // AuditCallback 审核回调处理 type AuditCallback struct { service AuditService } // NewAuditCallback 创建审核回调处理 func NewAuditCallback(service AuditService) *AuditCallback { return &AuditCallback{ service: service, } } // HandleTextCallback 处理文本审核回调 func (c *AuditCallback) HandleTextCallback(ctx context.Context, auditID string, result *AuditResult) error { if c.service == nil || auditID == "" || result == nil { return fmt.Errorf("invalid parameters") } log.Printf("Processing text audit callback: auditID=%s, result=%+v", auditID, result) // 根据审核结果执行相应操作 // 例如: 更新帖子状态、发送通知等 return nil } // HandleImageCallback 处理图片审核回调 func (c *AuditCallback) HandleImageCallback(ctx context.Context, auditID string, result *AuditResult) error { if c.service == nil || auditID == "" || result == nil { return fmt.Errorf("invalid parameters") } log.Printf("Processing image audit callback: auditID=%s, result=%+v", auditID, result) // 根据审核结果执行相应操作 // 例如: 更新图片状态、删除违规图片等 return nil } // ==================== 辅助函数 ==================== // IsContentSafe 判断内容是否安全 func IsContentSafe(result *AuditResult) bool { if result == nil { return true } return result.Pass && result.Suggest != "block" } // NeedReview 判断内容是否需要人工复审 func NeedReview(result *AuditResult) bool { if result == nil { return false } return result.Suggest == "review" } // GetRiskLevel 获取风险等级 func GetRiskLevel(result *AuditResult) string { if result == nil { return "low" } return result.Risk } // FormatAuditResult 格式化审核结果为字符串 func FormatAuditResult(result *AuditResult) string { if result == nil { return "{}" } data, _ := json.Marshal(result) return string(data) } // ParseAuditResult 从字符串解析审核结果 func ParseAuditResult(data string) (*AuditResult, error) { if data == "" { return nil, fmt.Errorf("empty data") } var result AuditResult if err := json.Unmarshal([]byte(data), &result); err != nil { return nil, err } return &result, nil } // ==================== 审核日志查询 ==================== // GetAuditLogs 获取审核日志列表 func GetAuditLogs(db *gorm.DB, targetType string, targetID string, result string, page, pageSize int) ([]model.AuditLog, int64, error) { query := db.Model(&model.AuditLog{}) if targetType != "" { query = query.Where("target_type = ?", targetType) } if targetID != "" { query = query.Where("target_id = ?", targetID) } if result != "" { query = query.Where("result = ?", result) } var total int64 if err := query.Count(&total).Error; err != nil { return nil, 0, err } var logs []model.AuditLog offset := (page - 1) * pageSize if err := query.Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&logs).Error; err != nil { return nil, 0, err } return logs, total, nil } // ==================== 定时任务 ==================== // AuditScheduler 审核调度器 type AuditScheduler struct { db *gorm.DB service AuditService interval time.Duration stopCh chan bool } // NewAuditScheduler 创建审核调度器 func NewAuditScheduler(db *gorm.DB, service AuditService, interval time.Duration) *AuditScheduler { return &AuditScheduler{ db: db, service: service, interval: interval, stopCh: make(chan bool), } } // Start 启动调度器 func (s *AuditScheduler) Start() { go func() { ticker := time.NewTicker(s.interval) defer ticker.Stop() for { select { case <-ticker.C: s.processPendingAudits() case <-s.stopCh: return } } }() } // Stop 停止调度器 func (s *AuditScheduler) Stop() { s.stopCh <- true } // processPendingAudits 处理待审核内容 func (s *AuditScheduler) processPendingAudits() { // 查询待审核的内容 // 1. 查询审核状态为 pending 的记录 // 2. 调用审核服务 // 3. 更新审核状态 // 示例逻辑,实际需要根据业务需求实现 log.Println("Processing pending audits...") } // CleanupOldLogs 清理旧的审核日志 func CleanupOldLogs(db *gorm.DB, days int) error { // 清理指定天数之前的审核日志 cutoffTime := time.Now().AddDate(0, 0, -days) return db.Where("created_at < ? AND result = ?", cutoffTime, model.AuditResultPass).Delete(&model.AuditLog{}).Error }