From 62b860726d3e27654d99ef7eae86c4a83a2a73d5 Mon Sep 17 00:00:00 2001 From: "jiaji.H" Date: Wed, 15 Oct 2025 17:01:31 +0800 Subject: [PATCH] =?UTF-8?q?Updata:=E9=80=82=E9=85=8D=E8=B0=83=E7=94=A82.0?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 171 +--------------------------------- cmd/main.go | 160 +++++++++++++++++++++++++------ conf/alibabacloud.env | 18 +++- conf/config.go | 92 ++++++++++++++---- go.mod | 4 + internal/api/image_scanner.go | 75 ++++++++++++--- internal/api/text_scanner.go | 64 ++++++++++--- internal/api/video_scanner.go | 77 ++++++++++++--- internal/model/image.go | 20 +++- internal/model/text.go | 26 ++++-- internal/model/video.go | 26 +++++- 11 files changed, 454 insertions(+), 279 deletions(-) diff --git a/README.md b/README.md index 27d7049..70e9a35 100644 --- a/README.md +++ b/README.md @@ -1,173 +1,4 @@ # 阿里云内容安全2.0 Demo -这是一个使用Go语言编写的阿里云内容安全2.0服务演示程序,支持对视频、图片和文字进行内容安全审核。 +阿里云内容安全2.0服务demo,支持对视频、图片和文字进行内容安全审核。 -## 功能特性 - -- 🖼️ **图片内容安全审核** - - 支持通过URL或本地文件进行图片审核 - - 支持多种图片格式:JPG、PNG、BMP、GIF、WEBP - - 检测场景:色情、暴恐、广告、直播、Logo等 - -- 📝 **文本内容安全审核** - - 支持单条文本和批量文本审核 - - 检测场景:反垃圾信息 - - 提供详细的违规内容定位信息 - -- 🎥 **视频内容安全审核** - - 支持异步和同步视频审核 - - 自动截帧分析 - - 提供帧级别的违规内容定位 - -## 环境要求 - -- Go 1.21 或更高版本 -- 阿里云账号并开通内容安全2.0服务 -- 有效的阿里云AccessKey - -## 安装和配置 - -### 1. 克隆项目 - -```bash -git clone -cd content-security-demo -``` - -### 2. 安装依赖 - -```bash -go mod tidy -``` - -### 3. 配置阿里云AccessKey - -有两种方式配置AccessKey: - -#### 方式一:使用环境变量 - -```bash -export ALIBABA_CLOUD_ACCESS_KEY_ID="your_access_key_id" -export ALIBABA_CLOUD_ACCESS_KEY_SECRET="your_access_key_secret" -``` - -#### 方式二:使用.env文件 - -1. 复制配置文件: -```bash -cp env.example .env -``` - -2. 编辑`.env`文件,填入您的AccessKey信息: -```env -ALIBABA_CLOUD_ACCESS_KEY_ID=your_access_key_id_here -ALIBABA_CLOUD_ACCESS_KEY_SECRET=your_access_key_secret_here -ALIBABA_CLOUD_REGION=cn-shanghai -ALIBABA_CLOUD_ENDPOINT=green.cn-shanghai.aliyuncs.com -``` - -### 4. 运行程序 - -```bash -go run . -``` - -## 使用说明 - -运行程序后,会显示交互式菜单: - -``` -=== 阿里云内容安全2.0 Demo === -支持视频、图片、文字内容安全审核 - -请选择要测试的功能: -1. 图片内容安全审核 -2. 文本内容安全审核 -3. 视频内容安全审核 -4. 退出 -``` - -### 图片审核 - -- **通过URL审核**:输入图片的公开URL地址 -- **通过文件审核**:输入本地图片文件的完整路径 - -支持的图片格式:JPG、JPEG、PNG、BMP、GIF、WEBP -文件大小限制:最大9MB - -### 文本审核 - -- **单条文本**:输入一段文本进行审核 -- **批量文本**:输入多行文本,每行一条,空行结束 - -### 视频审核 - -- **异步扫描**:提交任务后立即返回任务ID,可稍后查询结果 -- **同步扫描**:等待审核完成并返回结果(可能需要几分钟) - -## 审核结果说明 - -### 建议类型 -- `pass`:通过 -- `review`:需要人工审核 -- `block`:拒绝 - -### 检测场景 -- `porn`:色情内容 -- `terrorism`:暴恐内容 -- `ad`:广告内容 -- `live`:直播内容 -- `logo`:Logo识别 -- `antispam`:反垃圾信息 - -## 项目结构 - -``` -. -├── main.go # 主程序入口 -├── config.go # 配置管理 -├── image_scanner.go # 图片审核功能 -├── text_scanner.go # 文本审核功能 -├── video_scanner.go # 视频审核功能 -├── go.mod # Go模块文件 -├── env.example # 环境变量示例 -└── README.md # 项目说明 -``` - -## 注意事项 - -1. **费用说明**:阿里云内容安全服务按调用次数计费,请查看[官方定价](https://www.alibabacloud.com/help/zh/content-moderation/latest/billing-overview) - -2. **权限配置**:建议为RAM用户创建AccessKey,并授予`AliyunYundunGreenWebFullAccess`权限 - -3. **文件限制**: - - 图片:最大9MB,建议分辨率大于256×256像素 - - 视频:支持多种格式,建议使用异步审核 - -4. **网络要求**:需要能够访问阿里云服务端点 - -## 错误处理 - -程序包含完善的错误处理机制: -- 配置验证 -- 网络请求错误处理 -- 文件格式和大小验证 -- API响应解析错误处理 - -## 扩展功能 - -可以根据需要扩展以下功能: -- 添加更多检测场景 -- 实现结果缓存机制 -- 添加批量处理功能 -- 集成到Web服务中 - -## 技术支持 - -如有问题,请参考: -- [阿里云内容安全官方文档](https://www.alibabacloud.com/help/zh/content-moderation/) -- [Go SDK文档](https://github.com/aliyun/alibaba-cloud-sdk-go) - -## 许可证 - -MIT License diff --git a/cmd/main.go b/cmd/main.go index 60bf20f..9f2ddc0 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -25,31 +25,69 @@ func main() { // 显示配置信息(隐藏敏感信息) fmt.Printf("✅ 配置加载成功\n") + fmt.Printf(" 登录模式: %s\n", config.LoginMode) fmt.Printf(" 区域: %s\n", config.Region) fmt.Printf(" 端点: %s\n", config.Endpoint) - if config.AccessKeyID != "" { - fmt.Printf(" AccessKey ID: %s...\n", config.AccessKeyID[:8]) - } - if config.AccessKeySecret != "" { - fmt.Printf(" AccessKey Secret: %s...\n", config.AccessKeySecret[:8]) + + if config.LoginMode == "sts" { + fmt.Printf(" RAM AccessKey ID: %s...\n", config.RAMAccessKeyID[:8]) + fmt.Printf(" RAM角色ARN: %s\n", config.RAMRoleArn) + } else { + if config.AccessKeyID != "" { + fmt.Printf(" AccessKey ID: %s...\n", config.AccessKeyID[:8]) + } + if config.AccessKeySecret != "" { + fmt.Printf(" AccessKey Secret: %s...\n", config.AccessKeySecret[:8]) + } } fmt.Println() // 检查必要的配置 - if config.AccessKeyID == "" || config.AccessKeySecret == "" { - fmt.Println("❌ 缺少必要的配置信息:") - if config.AccessKeyID == "" { - fmt.Println(" - ALIBABA_CLOUD_ACCESS_KEY_ID 未设置") + if config.LoginMode == "sts" { + // STS模式检查 + if config.RAMAccessKeyID == "" || config.RAMAccessKeySecret == "" || config.RAMRoleArn == "" { + fmt.Println("❌ STS模式缺少必要的配置信息:") + if config.RAMAccessKeyID == "" { + fmt.Println(" - RAM_ACCESS_KEY_ID 未设置") + } + if config.RAMAccessKeySecret == "" { + fmt.Println(" - RAM_ACCESS_KEY_SECRET 未设置") + } + if config.RAMRoleArn == "" { + fmt.Println(" - RAM_ROLE_ARN 未设置") + } + fmt.Println() + fmt.Println("请检查以下配置文件:") + fmt.Println(" - .env") + fmt.Println(" - conf/alibabacloud.env") + fmt.Println(" - 环境变量") + return } - if config.AccessKeySecret == "" { - fmt.Println(" - ALIBABA_CLOUD_ACCESS_KEY_SECRET 未设置") + + // 获取STS临时凭证 + fmt.Println("正在获取STS临时凭证...") + if err := config.GetSTSToken(); err != nil { + fmt.Printf("❌ 获取STS临时凭证失败: %v\n", err) + return + } + fmt.Println("✅ STS临时凭证获取成功") + } else { + // 直接模式检查 + if config.AccessKeyID == "" || config.AccessKeySecret == "" { + fmt.Println("❌ 直接模式缺少必要的配置信息:") + if config.AccessKeyID == "" { + fmt.Println(" - ALIBABA_CLOUD_ACCESS_KEY_ID 未设置") + } + if config.AccessKeySecret == "" { + fmt.Println(" - ALIBABA_CLOUD_ACCESS_KEY_SECRET 未设置") + } + fmt.Println() + fmt.Println("请检查以下配置文件:") + fmt.Println(" - .env") + fmt.Println(" - conf/alibabacloud.env") + fmt.Println(" - 环境变量") + return } - fmt.Println() - fmt.Println("请检查以下配置文件:") - fmt.Println(" - .env") - fmt.Println(" - conf/alibabacloud.env") - fmt.Println(" - 环境变量") - return } // 显示菜单 @@ -85,7 +123,7 @@ func showMenu() { } func handleImageScan(config *conf.Config) { - fmt.Println("\n=== 图片内容安全审核 ===") + fmt.Println("\n=== 图片内容安全审核(2.0版本)===") scanner, err := api.NewImageScanner(config) if err != nil { @@ -93,7 +131,31 @@ func handleImageScan(config *conf.Config) { return } - fmt.Println("请选择图片输入方式:") + // 选择服务类型 + fmt.Println("请选择图片审核服务类型:") + fmt.Println("1. 通用基线检测(baselineCheck_global)") + fmt.Println("2. 大小模型融合图片审核服务(postImageCheckByVL_global)") + fmt.Println("3. AI生成图片鉴别(aigcDetector_global)") + + serviceChoice := getUserInput("请选择服务类型 (1-3): ") + var serviceType api.ImageServiceType + + switch serviceChoice { + case "1": + serviceType = api.BaselineCheckGlobal + fmt.Println("✅ 已选择:通用基线检测") + case "2": + serviceType = api.PostImageCheckByVLGlobal + fmt.Println("✅ 已选择:大小模型融合图片审核服务") + case "3": + serviceType = api.AigcDetectorGlobal + fmt.Println("✅ 已选择:AI生成图片鉴别") + default: + fmt.Println("❌ 无效选择") + return + } + + fmt.Println("\n请选择图片输入方式:") fmt.Println("1. 通过URL") fmt.Println("2. 通过本地文件") @@ -108,7 +170,7 @@ func handleImageScan(config *conf.Config) { } fmt.Println("正在扫描图片...") - result, err := scanner.ScanImageByURL(url, "image_"+time.Now().Format("20060102150405")) + result, err := scanner.ScanImageByURL(url, "image_"+time.Now().Format("20060102150405"), serviceType) if err != nil { fmt.Printf("❌ 扫描失败: %v\n", err) return @@ -123,7 +185,7 @@ func handleImageScan(config *conf.Config) { } fmt.Println("正在扫描图片...") - result, err := scanner.ScanImageByFile(filePath, "image_"+time.Now().Format("20060102150405")) + result, err := scanner.ScanImageByFile(filePath, "image_"+time.Now().Format("20060102150405"), serviceType) if err != nil { fmt.Printf("❌ 扫描失败: %v\n", err) return @@ -136,7 +198,7 @@ func handleImageScan(config *conf.Config) { } func handleTextScan(config *conf.Config) { - fmt.Println("\n=== 文本内容安全审核 ===") + fmt.Println("\n=== 文本内容安全审核(2.0版本)===") scanner, err := api.NewTextScanner(config) if err != nil { @@ -144,7 +206,27 @@ func handleTextScan(config *conf.Config) { return } - fmt.Println("请选择文本输入方式:") + // 选择服务类型 + fmt.Println("请选择文本审核服务类型:") + fmt.Println("1. 通用基线检测(baselineCheck_global)") + fmt.Println("2. 大小模型融合文本审核服务(postTextCheckByVL_global)") + + serviceChoice := getUserInput("请选择服务类型 (1-2): ") + var serviceType api.TextServiceType + + switch serviceChoice { + case "1": + serviceType = api.TextBaselineCheckGlobal + fmt.Println("✅ 已选择:通用基线检测") + case "2": + serviceType = api.TextPostCheckByVLGlobal + fmt.Println("✅ 已选择:大小模型融合文本审核服务") + default: + fmt.Println("❌ 无效选择") + return + } + + fmt.Println("\n请选择文本输入方式:") fmt.Println("1. 单条文本") fmt.Println("2. 批量文本") @@ -159,7 +241,7 @@ func handleTextScan(config *conf.Config) { } fmt.Println("正在扫描文本...") - result, err := scanner.ScanText(text, "text_"+time.Now().Format("20060102150405")) + result, err := scanner.ScanText(text, "text_"+time.Now().Format("20060102150405"), serviceType) if err != nil { fmt.Printf("❌ 扫描失败: %v\n", err) return @@ -186,7 +268,7 @@ func handleTextScan(config *conf.Config) { } fmt.Printf("正在批量扫描 %d 条文本...\n", len(texts)) - result, err := scanner.ScanTextBatch(texts, dataIDs) + result, err := scanner.ScanTextBatch(texts, dataIDs, serviceType) if err != nil { fmt.Printf("❌ 扫描失败: %v\n", err) return @@ -199,7 +281,7 @@ func handleTextScan(config *conf.Config) { } func handleVideoScan(config *conf.Config) { - fmt.Println("\n=== 视频内容安全审核 ===") + fmt.Println("\n=== 视频内容安全审核(2.0版本)===") scanner, err := api.NewVideoScanner(config) if err != nil { @@ -207,7 +289,27 @@ func handleVideoScan(config *conf.Config) { return } - fmt.Println("请选择视频审核方式:") + // 选择服务类型 + fmt.Println("请选择视频审核服务类型:") + fmt.Println("1. 通用基线检测(baselineCheck_global)") + fmt.Println("2. 大小模型融合视频审核服务(postVideoCheckByVL_global)") + + serviceChoice := getUserInput("请选择服务类型 (1-2): ") + var serviceType api.VideoServiceType + + switch serviceChoice { + case "1": + serviceType = api.VideoBaselineCheckGlobal + fmt.Println("✅ 已选择:通用基线检测") + case "2": + serviceType = api.VideoPostCheckByVLGlobal + fmt.Println("✅ 已选择:大小模型融合视频审核服务") + default: + fmt.Println("❌ 无效选择") + return + } + + fmt.Println("\n请选择视频审核方式:") fmt.Println("1. 异步扫描(提交任务后立即返回)") fmt.Println("2. 同步扫描(等待结果返回)") @@ -224,7 +326,7 @@ func handleVideoScan(config *conf.Config) { switch choice { case "1": fmt.Println("正在提交视频扫描任务...") - result, err := scanner.ScanVideoAsync(videoURL, dataID) + result, err := scanner.ScanVideoAsync(videoURL, dataID, serviceType) if err != nil { fmt.Printf("❌ 提交任务失败: %v\n", err) return @@ -237,7 +339,7 @@ func handleVideoScan(config *conf.Config) { case "2": fmt.Println("正在扫描视频(这可能需要几分钟)...") - result, err := scanner.ScanVideoAndWait(videoURL, dataID, 10*time.Minute) + result, err := scanner.ScanVideoAndWait(videoURL, dataID, serviceType, 10*time.Minute) if err != nil { fmt.Printf("❌ 扫描失败: %v\n", err) return diff --git a/conf/alibabacloud.env b/conf/alibabacloud.env index 8d99df4..a2e3443 100644 --- a/conf/alibabacloud.env +++ b/conf/alibabacloud.env @@ -1,9 +1,23 @@ #=========== 阿里云内容安全配置 =========== +# 登录模式:direct(直接使用AccessKey)或 sts(使用STS临时凭证) +LOGIN_MODE=sts + +# 直接登录模式配置 # 阿里云AccessKey ID -ALIBABA_CLOUD_ACCESS_KEY_ID=your_access_key_id_here +ALIBABA_CLOUD_ACCESS_KEY_ID=LTAI5tNBzbeEbG1yCitvHsMb # 阿里云AccessKey Secret -ALIBABA_CLOUD_ACCESS_KEY_SECRET=your_access_key_secret_here +ALIBABA_CLOUD_ACCESS_KEY_SECRET=G1xAUB8G6WDVo0SLr6DJaJjNWIlpmO + +# STS登录模式配置 +# RAM用户AccessKey ID(用于获取STS临时凭证) +RAM_ACCESS_KEY_ID=LTAI5tNBzbeEbG1yCitvHsMb + +# RAM用户AccessKey Secret +RAM_ACCESS_KEY_SECRET=G1xAUB8G6WDVo0SLr6DJaJjNWIlpmO + +# 要扮演的RAM角色ARN +RAM_ROLE_ARN=acs:ram::5828544250383902:role/content-secret # 阿里云区域(可选,默认为cn-shanghai) ALIBABA_CLOUD_REGION=cn-shanghai diff --git a/conf/config.go b/conf/config.go index 33965c3..260f174 100644 --- a/conf/config.go +++ b/conf/config.go @@ -3,17 +3,33 @@ package conf import ( "fmt" "os" - "strconv" + "github.com/aliyun/alibaba-cloud-sdk-go/services/sts" "github.com/joho/godotenv" ) // Config 配置结构体 type Config struct { + // 登录模式 + LoginMode string + + // 直接登录模式配置 AccessKeyID string AccessKeySecret string - Region string - Endpoint string + + // STS登录模式配置 + RAMAccessKeyID string + RAMAccessKeySecret string + RAMRoleArn string + + // 通用配置 + Region string + Endpoint string + + // STS临时凭证(运行时获取) + TempAccessKeyID string + TempAccessKeySecret string + SecurityToken string } // LoadConfig 加载配置 @@ -34,10 +50,14 @@ func LoadConfig() (*Config, error) { } config := &Config{ - AccessKeyID: getEnv("ALIBABA_CLOUD_ACCESS_KEY_ID", ""), - AccessKeySecret: getEnv("ALIBABA_CLOUD_ACCESS_KEY_SECRET", ""), - Region: getEnv("ALIBABA_CLOUD_REGION", "cn-shanghai"), - Endpoint: getEnv("ALIBABA_CLOUD_ENDPOINT", "green.cn-shanghai.aliyuncs.com"), + LoginMode: getEnv("LOGIN_MODE", "direct"), + AccessKeyID: getEnv("ALIBABA_CLOUD_ACCESS_KEY_ID", ""), + AccessKeySecret: getEnv("ALIBABA_CLOUD_ACCESS_KEY_SECRET", ""), + RAMAccessKeyID: getEnv("RAM_ACCESS_KEY_ID", ""), + RAMAccessKeySecret: getEnv("RAM_ACCESS_KEY_SECRET", ""), + RAMRoleArn: getEnv("RAM_ROLE_ARN", ""), + Region: getEnv("ALIBABA_CLOUD_REGION", "cn-shanghai"), + Endpoint: getEnv("ALIBABA_CLOUD_ENDPOINT", "green-v2.cn-shanghai.aliyuncs.com"), // 2.0版本端点 } return config, nil @@ -53,10 +73,14 @@ func LoadConfigFromFile(configFile string) (*Config, error) { fmt.Printf("成功加载配置文件: %s\n", configFile) config := &Config{ - AccessKeyID: getEnv("ALIBABA_CLOUD_ACCESS_KEY_ID", ""), - AccessKeySecret: getEnv("ALIBABA_CLOUD_ACCESS_KEY_SECRET", ""), - Region: getEnv("ALIBABA_CLOUD_REGION", "cn-shanghai"), - Endpoint: getEnv("ALIBABA_CLOUD_ENDPOINT", "green.cn-shanghai.aliyuncs.com"), + LoginMode: getEnv("LOGIN_MODE", "direct"), + AccessKeyID: getEnv("ALIBABA_CLOUD_ACCESS_KEY_ID", ""), + AccessKeySecret: getEnv("ALIBABA_CLOUD_ACCESS_KEY_SECRET", ""), + RAMAccessKeyID: getEnv("RAM_ACCESS_KEY_ID", ""), + RAMAccessKeySecret: getEnv("RAM_ACCESS_KEY_SECRET", ""), + RAMRoleArn: getEnv("RAM_ROLE_ARN", ""), + Region: getEnv("ALIBABA_CLOUD_REGION", "cn-shanghai"), + Endpoint: getEnv("ALIBABA_CLOUD_ENDPOINT", "green-v2.cn-shanghai.aliyuncs.com"), // 2.0版本端点 } return config, nil @@ -70,12 +94,44 @@ func getEnv(key, defaultValue string) string { return defaultValue } -// getEnvAsInt 获取环境变量并转换为整数 -func getEnvAsInt(key string, defaultValue int) int { - if value := os.Getenv(key); value != "" { - if intValue, err := strconv.Atoi(value); err == nil { - return intValue - } +// GetSTSToken 获取STS临时凭证 +func (c *Config) GetSTSToken() error { + if c.LoginMode != "sts" { + return nil // 非STS模式,不需要获取临时凭证 } - return defaultValue + + // 创建STS客户端 + stsClient, err := sts.NewClientWithAccessKey(c.Region, c.RAMAccessKeyID, c.RAMAccessKeySecret) + if err != nil { + return fmt.Errorf("创建STS客户端失败: %w", err) + } + + // 构造AssumeRole请求 + request := sts.CreateAssumeRoleRequest() + request.Scheme = "https" + request.RoleArn = c.RAMRoleArn + request.RoleSessionName = "content-security-session" // 会话名称可自定义 + + // 调用接口获取凭证 + response, err := stsClient.AssumeRole(request) + if err != nil { + return fmt.Errorf("获取临时凭证失败: %w", err) + } + + // 保存临时凭证 + c.TempAccessKeyID = response.Credentials.AccessKeyId + c.TempAccessKeySecret = response.Credentials.AccessKeySecret + c.SecurityToken = response.Credentials.SecurityToken + + return nil +} + +// GetEffectiveCredentials 获取有效的访问凭证 +func (c *Config) GetEffectiveCredentials() (accessKeyID, accessKeySecret, securityToken string) { + if c.LoginMode == "sts" && c.TempAccessKeyID != "" { + // 使用STS临时凭证 + return c.TempAccessKeyID, c.TempAccessKeySecret, c.SecurityToken + } + // 使用直接凭证 + return c.AccessKeyID, c.AccessKeySecret, "" } diff --git a/go.mod b/go.mod index a8b350d..8b86dc1 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,10 @@ module contentSecurityDemo go 1.23 require ( + github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.4 + github.com/alibabacloud-go/green-20220302/v2 v2.0.0 + github.com/alibabacloud-go/tea v1.2.1 + github.com/alibabacloud-go/tea-utils/v2 v2.0.1 github.com/aliyun/alibaba-cloud-sdk-go v1.62.706 github.com/joho/godotenv v1.5.1 ) diff --git a/internal/api/image_scanner.go b/internal/api/image_scanner.go index 4b33cff..060cd14 100644 --- a/internal/api/image_scanner.go +++ b/internal/api/image_scanner.go @@ -15,15 +15,40 @@ import ( "github.com/aliyun/alibaba-cloud-sdk-go/services/green" ) -// ImageScanner 图片内容安全扫描器 +// ImageScanner 图片内容安全扫描器2.0 type ImageScanner struct { Client *green.Client Config *conf.Config } +// ImageServiceType 图片审核服务类型 +type ImageServiceType string + +const ( + // BaselineCheckGlobal 通用基线检测 + BaselineCheckGlobal ImageServiceType = "baselineCheck_global" + // PostImageCheckByVLGlobal 大小模型融合图片审核服务 + PostImageCheckByVLGlobal ImageServiceType = "postImageCheckByVL_global" + // AigcDetectorGlobal AI生成图片鉴别 + AigcDetectorGlobal ImageServiceType = "aigcDetector_global" +) + // NewImageScanner 创建图片扫描器 func NewImageScanner(config *conf.Config) (*ImageScanner, error) { - client, err := green.NewClientWithAccessKey(config.Region, config.AccessKeyID, config.AccessKeySecret) + // 获取有效的访问凭证 + accessKeyID, accessKeySecret, securityToken := config.GetEffectiveCredentials() + + var client *green.Client + var err error + + if securityToken != "" { + // 使用STS临时凭证 + client, err = green.NewClientWithStsToken(config.Region, accessKeyID, accessKeySecret, securityToken) + } else { + // 使用直接凭证 + client, err = green.NewClientWithAccessKey(config.Region, accessKeyID, accessKeySecret) + } + if err != nil { return nil, fmt.Errorf("创建客户端失败: %v", err) } @@ -34,13 +59,13 @@ func NewImageScanner(config *conf.Config) (*ImageScanner, error) { }, nil } -// ScanImageByURL 通过URL扫描图片 -func (s *ImageScanner) ScanImageByURL(imageURL string, dataID string) (*model.ImageScanResponse, error) { +// ScanImageByURL 通过URL扫描图片(2.0版本) +func (s *ImageScanner) ScanImageByURL(imageURL string, dataID string, serviceType ImageServiceType) (*model.ImageScanResponse, error) { request := requests.NewCommonRequest() request.Method = "POST" request.Scheme = "https" request.Domain = s.Config.Endpoint - request.Version = "2018-05-09" + request.Version = "2022-03-02" // 内容安全2.0图片审核API版本 request.ApiName = "ImageSyncScan" request.QueryParams["RegionId"] = s.Config.Region @@ -51,7 +76,7 @@ func (s *ImageScanner) ScanImageByURL(imageURL string, dataID string) (*model.Im URL: imageURL, }, }, - Scenes: []string{"porn", "terrorism", "ad", "live", "logo"}, + Services: []string{string(serviceType)}, // 使用服务类型而不是场景 } requestBody, err := json.Marshal(scanRequest) @@ -74,8 +99,8 @@ func (s *ImageScanner) ScanImageByURL(imageURL string, dataID string) (*model.Im return &scanResponse, nil } -// ScanImageByFile 通过文件扫描图片 -func (s *ImageScanner) ScanImageByFile(filePath string, dataID string) (*model.ImageScanResponse, error) { +// ScanImageByFile 通过文件扫描图片(2.0版本) +func (s *ImageScanner) ScanImageByFile(filePath string, dataID string, serviceType ImageServiceType) (*model.ImageScanResponse, error) { // 读取文件 file, err := os.Open(filePath) if err != nil { @@ -99,9 +124,9 @@ func (s *ImageScanner) ScanImageByFile(filePath string, dataID string) (*model.I return nil, fmt.Errorf("读取文件失败: %v", err) } - // 检查文件类型 + // 检查文件类型 - 2.0版本支持更多格式 ext := strings.ToLower(filepath.Ext(filePath)) - supportedExts := []string{".jpg", ".jpeg", ".png", ".bmp", ".gif", ".webp"} + supportedExts := []string{".jpg", ".jpeg", ".png", ".bmp", ".gif", ".webp", ".tiff", ".svg", ".ico", ".heic"} isSupported := false for _, supportedExt := range supportedExts { if ext == supportedExt { @@ -122,7 +147,7 @@ func (s *ImageScanner) ScanImageByFile(filePath string, dataID string) (*model.I request.Method = "POST" request.Scheme = "https" request.Domain = s.Config.Endpoint - request.Version = "2018-05-09" + request.Version = "2022-03-02" // 内容安全2.0图片审核API版本 request.ApiName = "ImageSyncScan" request.QueryParams["RegionId"] = s.Config.Region @@ -133,7 +158,7 @@ func (s *ImageScanner) ScanImageByFile(filePath string, dataID string) (*model.I Content: base64Content, }, }, - Scenes: []string{"porn", "terrorism", "ad", "live", "logo"}, + Services: []string{string(serviceType)}, // 使用服务类型而不是场景 } requestBody, err := json.Marshal(scanRequest) @@ -156,9 +181,9 @@ func (s *ImageScanner) ScanImageByFile(filePath string, dataID string) (*model.I return &scanResponse, nil } -// PrintResult 打印扫描结果 +// PrintResult 打印扫描结果(2.0版本) func (s *ImageScanner) PrintResult(response *model.ImageScanResponse) { - fmt.Println("=== 图片内容安全审核结果 ===") + fmt.Println("=== 图片内容安全审核结果(2.0版本)===") fmt.Printf("状态码: %d\n", response.Code) fmt.Printf("消息: %s\n", response.Message) @@ -174,8 +199,30 @@ func (s *ImageScanner) PrintResult(response *model.ImageScanResponse) { for _, result := range data.Results { fmt.Printf("场景: %s\n", result.Scene) fmt.Printf("标签: %s\n", result.Label) + if result.SubLabel != "" { + fmt.Printf("子标签: %s\n", result.SubLabel) + } fmt.Printf("建议: %s\n", result.Suggestion) fmt.Printf("置信度: %.2f\n", result.Rate) + + // 打印详细信息 + if len(result.Details) > 0 { + fmt.Println("详细信息:") + for _, detail := range result.Details { + fmt.Printf(" 内容: %s\n", detail.Context.Context) + if len(detail.Context.Pos) > 0 { + fmt.Printf(" 位置: %v\n", detail.Context.Pos) + } + } + } + + // 打印扩展信息 + if len(result.Extras) > 0 { + fmt.Println("扩展信息:") + for key, value := range result.Extras { + fmt.Printf(" %s: %v\n", key, value) + } + } fmt.Println("---") } } diff --git a/internal/api/text_scanner.go b/internal/api/text_scanner.go index f4f0cc5..86ff5c4 100644 --- a/internal/api/text_scanner.go +++ b/internal/api/text_scanner.go @@ -11,15 +11,38 @@ import ( "github.com/aliyun/alibaba-cloud-sdk-go/services/green" ) -// TextScanner 文本内容安全扫描器 +// TextScanner 文本内容安全扫描器2.0 type TextScanner struct { Client *green.Client Config *conf.Config } +// TextServiceType 文本审核服务类型 +type TextServiceType string + +const ( + // TextBaselineCheckGlobal 文本通用基线检测 + TextBaselineCheckGlobal TextServiceType = "baselineCheck_global" + // TextPostCheckByVLGlobal 文本大小模型融合审核服务 + TextPostCheckByVLGlobal TextServiceType = "postTextCheckByVL_global" +) + // NewTextScanner 创建文本扫描器 func NewTextScanner(config *conf.Config) (*TextScanner, error) { - client, err := green.NewClientWithAccessKey(config.Region, config.AccessKeyID, config.AccessKeySecret) + // 获取有效的访问凭证 + accessKeyID, accessKeySecret, securityToken := config.GetEffectiveCredentials() + + var client *green.Client + var err error + + if securityToken != "" { + // 使用STS临时凭证 + client, err = green.NewClientWithStsToken(config.Region, accessKeyID, accessKeySecret, securityToken) + } else { + // 使用直接凭证 + client, err = green.NewClientWithAccessKey(config.Region, accessKeyID, accessKeySecret) + } + if err != nil { return nil, fmt.Errorf("创建客户端失败: %v", err) } @@ -30,13 +53,13 @@ func NewTextScanner(config *conf.Config) (*TextScanner, error) { }, nil } -// ScanText 扫描文本内容 -func (s *TextScanner) ScanText(content string, dataID string) (*model.TextScanResponse, error) { +// ScanText 扫描文本内容(2.0版本) +func (s *TextScanner) ScanText(content string, dataID string, serviceType TextServiceType) (*model.TextScanResponse, error) { request := requests.NewCommonRequest() request.Method = "POST" request.Scheme = "https" request.Domain = s.Config.Endpoint - request.Version = "2018-05-09" + request.Version = "2022-03-02" // 内容安全2.0文本审核API版本 request.ApiName = "TextScan" request.QueryParams["RegionId"] = s.Config.Region @@ -47,7 +70,7 @@ func (s *TextScanner) ScanText(content string, dataID string) (*model.TextScanRe Content: content, }, }, - Scenes: []string{"antispam"}, + Services: []string{string(serviceType)}, // 使用服务类型而不是场景 } requestBody, err := json.Marshal(scanRequest) @@ -70,8 +93,8 @@ func (s *TextScanner) ScanText(content string, dataID string) (*model.TextScanRe return &scanResponse, nil } -// ScanTextBatch 批量扫描文本内容 -func (s *TextScanner) ScanTextBatch(texts []string, dataIDs []string) (*model.TextScanResponse, error) { +// ScanTextBatch 批量扫描文本内容(2.0版本) +func (s *TextScanner) ScanTextBatch(texts []string, dataIDs []string, serviceType TextServiceType) (*model.TextScanResponse, error) { if len(texts) != len(dataIDs) { return nil, fmt.Errorf("文本数量和ID数量不匹配") } @@ -80,7 +103,7 @@ func (s *TextScanner) ScanTextBatch(texts []string, dataIDs []string) (*model.Te request.Method = "POST" request.Scheme = "https" request.Domain = s.Config.Endpoint - request.Version = "2018-05-09" + request.Version = "2022-03-02" // 内容安全2.0文本审核API版本 request.ApiName = "TextScan" request.QueryParams["RegionId"] = s.Config.Region @@ -93,8 +116,8 @@ func (s *TextScanner) ScanTextBatch(texts []string, dataIDs []string) (*model.Te } scanRequest := model.TextScanRequest{ - Tasks: tasks, - Scenes: []string{"antispam"}, + Tasks: tasks, + Services: []string{string(serviceType)}, // 使用服务类型而不是场景 } requestBody, err := json.Marshal(scanRequest) @@ -117,9 +140,9 @@ func (s *TextScanner) ScanTextBatch(texts []string, dataIDs []string) (*model.Te return &scanResponse, nil } -// PrintResult 打印扫描结果 +// PrintResult 打印扫描结果(2.0版本) func (s *TextScanner) PrintResult(response *model.TextScanResponse) { - fmt.Println("=== 文本内容安全审核结果 ===") + fmt.Println("=== 文本内容安全审核结果(2.0版本)===") fmt.Printf("状态码: %d\n", response.Code) fmt.Printf("消息: %s\n", response.Message) @@ -135,6 +158,9 @@ func (s *TextScanner) PrintResult(response *model.TextScanResponse) { for _, result := range data.Results { fmt.Printf("场景: %s\n", result.Scene) fmt.Printf("标签: %s\n", result.Label) + if result.SubLabel != "" { + fmt.Printf("子标签: %s\n", result.SubLabel) + } fmt.Printf("建议: %s\n", result.Suggestion) fmt.Printf("置信度: %.2f\n", result.Rate) @@ -143,7 +169,17 @@ func (s *TextScanner) PrintResult(response *model.TextScanResponse) { fmt.Println("违规详情:") for _, detail := range result.Details { fmt.Printf(" 内容: %s\n", detail.Context.Context) - fmt.Printf(" 位置: %v\n", detail.Context.Pos) + if len(detail.Context.Pos) > 0 { + fmt.Printf(" 位置: %v\n", detail.Context.Pos) + } + } + } + + // 打印扩展信息 + if len(result.Extras) > 0 { + fmt.Println("扩展信息:") + for key, value := range result.Extras { + fmt.Printf(" %s: %v\n", key, value) } } fmt.Println("---") diff --git a/internal/api/video_scanner.go b/internal/api/video_scanner.go index a05ab0c..385535b 100644 --- a/internal/api/video_scanner.go +++ b/internal/api/video_scanner.go @@ -12,15 +12,38 @@ import ( "github.com/aliyun/alibaba-cloud-sdk-go/services/green" ) -// VideoScanner 视频内容安全扫描器 +// VideoScanner 视频内容安全扫描器2.0 type VideoScanner struct { Client *green.Client Config *conf.Config } +// VideoServiceType 视频审核服务类型 +type VideoServiceType string + +const ( + // VideoBaselineCheckGlobal 视频通用基线检测 + VideoBaselineCheckGlobal VideoServiceType = "baselineCheck_global" + // VideoPostCheckByVLGlobal 视频大小模型融合审核服务 + VideoPostCheckByVLGlobal VideoServiceType = "postVideoCheckByVL_global" +) + // NewVideoScanner 创建视频扫描器 func NewVideoScanner(config *conf.Config) (*VideoScanner, error) { - client, err := green.NewClientWithAccessKey(config.Region, config.AccessKeyID, config.AccessKeySecret) + // 获取有效的访问凭证 + accessKeyID, accessKeySecret, securityToken := config.GetEffectiveCredentials() + + var client *green.Client + var err error + + if securityToken != "" { + // 使用STS临时凭证 + client, err = green.NewClientWithStsToken(config.Region, accessKeyID, accessKeySecret, securityToken) + } else { + // 使用直接凭证 + client, err = green.NewClientWithAccessKey(config.Region, accessKeyID, accessKeySecret) + } + if err != nil { return nil, fmt.Errorf("创建客户端失败: %v", err) } @@ -31,13 +54,13 @@ func NewVideoScanner(config *conf.Config) (*VideoScanner, error) { }, nil } -// ScanVideoAsync 异步扫描视频 -func (s *VideoScanner) ScanVideoAsync(videoURL string, dataID string) (*model.VideoScanResponse, error) { +// ScanVideoAsync 异步扫描视频(2.0版本) +func (s *VideoScanner) ScanVideoAsync(videoURL string, dataID string, serviceType VideoServiceType) (*model.VideoScanResponse, error) { request := requests.NewCommonRequest() request.Method = "POST" request.Scheme = "https" request.Domain = s.Config.Endpoint - request.Version = "2018-05-09" + request.Version = "2022-03-02" // 内容安全2.0视频审核API版本 request.ApiName = "VideoAsyncScan" request.QueryParams["RegionId"] = s.Config.Region @@ -50,7 +73,7 @@ func (s *VideoScanner) ScanVideoAsync(videoURL string, dataID string) (*model.Vi MaxFrames: 100, // 最多截取100帧 }, }, - Scenes: []string{"porn", "terrorism", "ad", "live"}, + Services: []string{string(serviceType)}, // 使用服务类型而不是场景 } requestBody, err := json.Marshal(scanRequest) @@ -73,13 +96,13 @@ func (s *VideoScanner) ScanVideoAsync(videoURL string, dataID string) (*model.Vi return &scanResponse, nil } -// GetVideoResult 获取视频审核结果 +// GetVideoResult 获取视频审核结果(2.0版本) func (s *VideoScanner) GetVideoResult(taskID string) (*model.VideoResultResponse, error) { request := requests.NewCommonRequest() request.Method = "POST" request.Scheme = "https" request.Domain = s.Config.Endpoint - request.Version = "2018-05-09" + request.Version = "2022-03-02" // 内容安全2.0视频审核API版本 request.ApiName = "VideoAsyncScanResults" request.QueryParams["RegionId"] = s.Config.Region @@ -99,10 +122,10 @@ func (s *VideoScanner) GetVideoResult(taskID string) (*model.VideoResultResponse return &resultResponse, nil } -// ScanVideoAndWait 扫描视频并等待结果 -func (s *VideoScanner) ScanVideoAndWait(videoURL string, dataID string, maxWaitTime time.Duration) (*model.VideoResultResponse, error) { +// ScanVideoAndWait 扫描视频并等待结果(2.0版本) +func (s *VideoScanner) ScanVideoAndWait(videoURL string, dataID string, serviceType VideoServiceType, maxWaitTime time.Duration) (*model.VideoResultResponse, error) { // 提交扫描任务 - scanResponse, err := s.ScanVideoAsync(videoURL, dataID) + scanResponse, err := s.ScanVideoAsync(videoURL, dataID, serviceType) if err != nil { return nil, fmt.Errorf("提交扫描任务失败: %v", err) } @@ -159,9 +182,9 @@ func (s *VideoScanner) PrintScanResult(response *model.VideoScanResponse) { } } -// PrintResult 打印审核结果 +// PrintResult 打印审核结果(2.0版本) func (s *VideoScanner) PrintResult(response *model.VideoResultResponse) { - fmt.Println("=== 视频内容安全审核结果 ===") + fmt.Println("=== 视频内容安全审核结果(2.0版本)===") fmt.Printf("状态码: %d\n", response.Code) fmt.Printf("消息: %s\n", response.Message) @@ -179,9 +202,31 @@ func (s *VideoScanner) PrintResult(response *model.VideoResultResponse) { for _, result := range data.Results { fmt.Printf("场景: %s\n", result.Scene) fmt.Printf("标签: %s\n", result.Label) + if result.SubLabel != "" { + fmt.Printf("子标签: %s\n", result.SubLabel) + } fmt.Printf("建议: %s\n", result.Suggestion) fmt.Printf("置信度: %.2f\n", result.Rate) + // 打印详细信息 + if len(result.Details) > 0 { + fmt.Println("详细信息:") + for _, detail := range result.Details { + fmt.Printf(" 内容: %s\n", detail.Context.Context) + if len(detail.Context.Pos) > 0 { + fmt.Printf(" 位置: %v\n", detail.Context.Pos) + } + } + } + + // 打印扩展信息 + if len(result.Extras) > 0 { + fmt.Println("扩展信息:") + for key, value := range result.Extras { + fmt.Printf(" %s: %v\n", key, value) + } + } + // 打印帧级别的结果 if len(result.Frames) > 0 { fmt.Println("违规帧详情:") @@ -191,6 +236,12 @@ func (s *VideoScanner) PrintResult(response *model.VideoResultResponse) { for _, frameResult := range frame.Results { fmt.Printf(" 场景: %s, 标签: %s, 建议: %s, 置信度: %.2f\n", frameResult.Scene, frameResult.Label, frameResult.Suggestion, frameResult.Rate) + if frameResult.SubLabel != "" { + fmt.Printf(" 子标签: %s\n", frameResult.SubLabel) + } + if len(frameResult.Details) > 0 { + fmt.Printf(" 详细信息: %v\n", frameResult.Details) + } } } } diff --git a/internal/model/image.go b/internal/model/image.go index 09c01f4..532a5b9 100644 --- a/internal/model/image.go +++ b/internal/model/image.go @@ -1,9 +1,9 @@ package model -// ImageScanRequest 图片审核请求结构 +// ImageScanRequest 图片审核请求结构(2.0版本) type ImageScanRequest struct { - Tasks []ImageTask `json:"tasks"` - Scenes []string `json:"scenes"` + Tasks []ImageTask `json:"tasks"` + Services []string `json:"services"` // 2.0版本使用services而不是scenes } // ImageTask 图片任务结构 @@ -13,7 +13,7 @@ type ImageTask struct { Content string `json:"content,omitempty"` // base64编码的图片内容 } -// ImageScanResponse 图片审核响应结构 +// ImageScanResponse 图片审核响应结构(2.0版本) type ImageScanResponse struct { Code int `json:"code"` Message string `json:"message"` @@ -26,6 +26,18 @@ type ImageScanResponse struct { Label string `json:"label"` Suggestion string `json:"suggestion"` Rate float64 `json:"rate"` + // 2.0版本新增字段 + SubLabel string `json:"subLabel,omitempty"` // 子标签 + Details []ImageDetail `json:"details,omitempty"` // 详细信息 + Extras map[string]interface{} `json:"extras,omitempty"` // 扩展信息 } `json:"results"` } `json:"data"` } + +// ImageDetail 图片审核详细信息 +type ImageDetail struct { + Context struct { + Context string `json:"context"` + Pos []int `json:"pos"` + } `json:"context"` +} diff --git a/internal/model/text.go b/internal/model/text.go index c839011..74aee7a 100644 --- a/internal/model/text.go +++ b/internal/model/text.go @@ -1,9 +1,9 @@ package model -// TextScanRequest 文本审核请求结构 +// TextScanRequest 文本审核请求结构(2.0版本) type TextScanRequest struct { - Tasks []TextTask `json:"tasks"` - Scenes []string `json:"scenes"` + Tasks []TextTask `json:"tasks"` + Services []string `json:"services"` // 2.0版本使用services而不是scenes } // TextTask 文本任务结构 @@ -12,7 +12,7 @@ type TextTask struct { Content string `json:"content"` } -// TextScanResponse 文本审核响应结构 +// TextScanResponse 文本审核响应结构(2.0版本) type TextScanResponse struct { Code int `json:"code"` Message string `json:"message"` @@ -25,12 +25,18 @@ type TextScanResponse struct { Label string `json:"label"` Suggestion string `json:"suggestion"` Rate float64 `json:"rate"` - Details []struct { - Context struct { - Context string `json:"context"` - Pos []int `json:"pos"` - } `json:"context"` - } `json:"details"` + // 2.0版本新增字段 + SubLabel string `json:"subLabel,omitempty"` // 子标签 + Details []TextDetail `json:"details,omitempty"` // 详细信息 + Extras map[string]interface{} `json:"extras,omitempty"` // 扩展信息 } `json:"results"` } `json:"data"` } + +// TextDetail 文本审核详细信息 +type TextDetail struct { + Context struct { + Context string `json:"context"` + Pos []int `json:"pos"` + } `json:"context"` +} diff --git a/internal/model/video.go b/internal/model/video.go index 0d64dd8..6b761a4 100644 --- a/internal/model/video.go +++ b/internal/model/video.go @@ -1,9 +1,9 @@ package model -// VideoScanRequest 视频审核请求结构 +// VideoScanRequest 视频审核请求结构(2.0版本) type VideoScanRequest struct { - Tasks []VideoTask `json:"tasks"` - Scenes []string `json:"scenes"` + Tasks []VideoTask `json:"tasks"` + Services []string `json:"services"` // 2.0版本使用services而不是scenes } // VideoTask 视频任务结构 @@ -27,7 +27,7 @@ type VideoScanResponse struct { } `json:"data"` } -// VideoResultResponse 视频审核结果响应结构 +// VideoResultResponse 视频审核结果响应结构(2.0版本) type VideoResultResponse struct { Code int `json:"code"` Message string `json:"message"` @@ -42,7 +42,11 @@ type VideoResultResponse struct { Label string `json:"label"` Suggestion string `json:"suggestion"` Rate float64 `json:"rate"` - Frames []struct { + // 2.0版本新增字段 + SubLabel string `json:"subLabel,omitempty"` // 子标签 + Details []VideoDetail `json:"details,omitempty"` // 详细信息 + Extras map[string]interface{} `json:"extras,omitempty"` // 扩展信息 + Frames []struct { Offset int `json:"offset"` URL string `json:"url"` Results []struct { @@ -50,8 +54,20 @@ type VideoResultResponse struct { Label string `json:"label"` Suggestion string `json:"suggestion"` Rate float64 `json:"rate"` + // 2.0版本新增字段 + SubLabel string `json:"subLabel,omitempty"` + Details []VideoDetail `json:"details,omitempty"` + Extras map[string]interface{} `json:"extras,omitempty"` } `json:"results"` } `json:"frames"` } `json:"results"` } `json:"data"` } + +// VideoDetail 视频审核详细信息 +type VideoDetail struct { + Context struct { + Context string `json:"context"` + Pos []int `json:"pos"` + } `json:"context"` +}