From 8d3115d61359fff5a9a8cb7584881e41103dd589 Mon Sep 17 00:00:00 2001 From: "jiaji.H" Date: Wed, 15 Oct 2025 14:52:37 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E5=A2=9E=E5=8A=A0=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/launch.json | 34 +++++ .vscode/settings.json | 22 +++ README.md | 174 ++++++++++++++++++++++- cmd/main.go | 257 ++++++++++++++++++++++++++++++++++ conf/alibabacloud.env | 12 ++ conf/config.go | 81 +++++++++++ examples.md | 137 ++++++++++++++++++ go.mod | 17 +++ go.sum | 89 ++++++++++++ internal/api/image_scanner.go | 182 ++++++++++++++++++++++++ internal/api/text_scanner.go | 166 ++++++++++++++++++++++ internal/api/video_scanner.go | 200 ++++++++++++++++++++++++++ internal/model/image.go | 31 ++++ internal/model/text.go | 36 +++++ internal/model/video.go | 57 ++++++++ run.bat | 44 ++++++ run.sh | 41 ++++++ 17 files changed, 1578 insertions(+), 2 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 cmd/main.go create mode 100644 conf/alibabacloud.env create mode 100644 conf/config.go create mode 100644 examples.md create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/api/image_scanner.go create mode 100644 internal/api/text_scanner.go create mode 100644 internal/api/video_scanner.go create mode 100644 internal/model/image.go create mode 100644 internal/model/text.go create mode 100644 internal/model/video.go create mode 100644 run.bat create mode 100644 run.sh diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..83ccebb --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,34 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Package", + "type": "go", + "request": "launch", + "mode": "auto", + "envFile": "${workspaceFolder}/conf/alibabacloud.env", + "env": { + "GOPATH":"C:\\Users\\lenovo\\go", + "GOOS":"windows" + }, + "program": "${workspaceFolder}/cmd", + "args":[] + }, + { + "name": "Run main.go", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceFolder}/cmd", + "cwd": "${workspaceFolder}", + "envFile": "${workspaceFolder}/conf/alibabacloud.env", + "env": { + "DEBUG": "true" + }, + "dlvFlags": ["--check-go-version=false"] + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..4fe3b60 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,22 @@ +{ + "workbench.colorCustomizations": { + "activityBar.activeBackground": "#fa1b49", + "activityBar.background": "#fa1b49", + "activityBar.foreground": "#e7e7e7", + "activityBar.inactiveForeground": "#e7e7e799", + "activityBarBadge.background": "#155e02", + "activityBarBadge.foreground": "#e7e7e7", + "commandCenter.border": "#e7e7e799", + "sash.hoverBorder": "#fa1b49", + "statusBar.background": "#dd0531", + "statusBar.foreground": "#e7e7e7", + "statusBarItem.hoverBackground": "#fa1b49", + "statusBarItem.remoteBackground": "#dd0531", + "statusBarItem.remoteForeground": "#e7e7e7", + "titleBar.activeBackground": "#dd0531", + "titleBar.activeForeground": "#e7e7e7", + "titleBar.inactiveBackground": "#dd053199", + "titleBar.inactiveForeground": "#e7e7e799" + }, + "peacock.color": "#dd0531" +} \ No newline at end of file diff --git a/README.md b/README.md index cd29d5d..27d7049 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,173 @@ -# contentSecurity +# 阿里云内容安全2.0 Demo -阿里云内容安全2.0服务demo,支持对视频、图片和文字进行内容安全审核。 \ No newline at end of file +这是一个使用Go语言编写的阿里云内容安全2.0服务演示程序,支持对视频、图片和文字进行内容安全审核。 + +## 功能特性 + +- 🖼️ **图片内容安全审核** + - 支持通过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 new file mode 100644 index 0000000..60bf20f --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,257 @@ +package main + +import ( + "bufio" + "contentSecurityDemo/conf" + "contentSecurityDemo/internal/api" + "fmt" + "os" + "strings" + "time" +) + +func main() { + fmt.Println("=== 阿里云内容安全2.0 Demo ===") + fmt.Println("支持视频、图片、文字内容安全审核") + fmt.Println() + + // 加载配置 + fmt.Println("正在加载配置...") + config, err := conf.LoadConfigFromFile("conf/alibabacloud.env") + if err != nil { + fmt.Printf("❌ 加载配置失败: %v\n", err) + return + } + + // 显示配置信息(隐藏敏感信息) + fmt.Printf("✅ 配置加载成功\n") + 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]) + } + fmt.Println() + + // 检查必要的配置 + 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 + } + + // 显示菜单 + for { + showMenu() + choice := getUserInput("请选择操作 (1-4): ") + + switch choice { + case "1": + handleImageScan(config) + case "2": + handleTextScan(config) + case "3": + handleVideoScan(config) + case "4": + fmt.Println("感谢使用!") + return + default: + fmt.Println("❌ 无效选择,请重新输入") + } + + fmt.Println() + } +} + +func showMenu() { + fmt.Println("请选择要测试的功能:") + fmt.Println("1. 图片内容安全审核") + fmt.Println("2. 文本内容安全审核") + fmt.Println("3. 视频内容安全审核") + fmt.Println("4. 退出") + fmt.Println() +} + +func handleImageScan(config *conf.Config) { + fmt.Println("\n=== 图片内容安全审核 ===") + + scanner, err := api.NewImageScanner(config) + if err != nil { + fmt.Printf("❌ 创建图片扫描器失败: %v\n", err) + return + } + + fmt.Println("请选择图片输入方式:") + fmt.Println("1. 通过URL") + fmt.Println("2. 通过本地文件") + + choice := getUserInput("请选择 (1-2): ") + + switch choice { + case "1": + url := getUserInput("请输入图片URL: ") + if url == "" { + fmt.Println("❌ URL不能为空") + return + } + + fmt.Println("正在扫描图片...") + result, err := scanner.ScanImageByURL(url, "image_"+time.Now().Format("20060102150405")) + if err != nil { + fmt.Printf("❌ 扫描失败: %v\n", err) + return + } + scanner.PrintResult(result) + + case "2": + filePath := getUserInput("请输入图片文件路径: ") + if filePath == "" { + fmt.Println("❌ 文件路径不能为空") + return + } + + fmt.Println("正在扫描图片...") + result, err := scanner.ScanImageByFile(filePath, "image_"+time.Now().Format("20060102150405")) + if err != nil { + fmt.Printf("❌ 扫描失败: %v\n", err) + return + } + scanner.PrintResult(result) + + default: + fmt.Println("❌ 无效选择") + } +} + +func handleTextScan(config *conf.Config) { + fmt.Println("\n=== 文本内容安全审核 ===") + + scanner, err := api.NewTextScanner(config) + if err != nil { + fmt.Printf("❌ 创建文本扫描器失败: %v\n", err) + return + } + + fmt.Println("请选择文本输入方式:") + fmt.Println("1. 单条文本") + fmt.Println("2. 批量文本") + + choice := getUserInput("请选择 (1-2): ") + + switch choice { + case "1": + text := getUserInput("请输入要审核的文本: ") + if text == "" { + fmt.Println("❌ 文本不能为空") + return + } + + fmt.Println("正在扫描文本...") + result, err := scanner.ScanText(text, "text_"+time.Now().Format("20060102150405")) + if err != nil { + fmt.Printf("❌ 扫描失败: %v\n", err) + return + } + scanner.PrintResult(result) + + case "2": + fmt.Println("请输入多条文本,每行一条,输入空行结束:") + var texts []string + var dataIDs []string + + for i := 1; ; i++ { + text := getUserInput(fmt.Sprintf("文本%d: ", i)) + if text == "" { + break + } + texts = append(texts, text) + dataIDs = append(dataIDs, fmt.Sprintf("text_%d_%s", i, time.Now().Format("20060102150405"))) + } + + if len(texts) == 0 { + fmt.Println("❌ 没有输入任何文本") + return + } + + fmt.Printf("正在批量扫描 %d 条文本...\n", len(texts)) + result, err := scanner.ScanTextBatch(texts, dataIDs) + if err != nil { + fmt.Printf("❌ 扫描失败: %v\n", err) + return + } + scanner.PrintResult(result) + + default: + fmt.Println("❌ 无效选择") + } +} + +func handleVideoScan(config *conf.Config) { + fmt.Println("\n=== 视频内容安全审核 ===") + + scanner, err := api.NewVideoScanner(config) + if err != nil { + fmt.Printf("❌ 创建视频扫描器失败: %v\n", err) + return + } + + fmt.Println("请选择视频审核方式:") + fmt.Println("1. 异步扫描(提交任务后立即返回)") + fmt.Println("2. 同步扫描(等待结果返回)") + + choice := getUserInput("请选择 (1-2): ") + + videoURL := getUserInput("请输入视频URL: ") + if videoURL == "" { + fmt.Println("❌ 视频URL不能为空") + return + } + + dataID := "video_" + time.Now().Format("20060102150405") + + switch choice { + case "1": + fmt.Println("正在提交视频扫描任务...") + result, err := scanner.ScanVideoAsync(videoURL, dataID) + if err != nil { + fmt.Printf("❌ 提交任务失败: %v\n", err) + return + } + scanner.PrintScanResult(result) + + if len(result.Data) > 0 && result.Data[0].TaskID != "" { + fmt.Printf("\n💡 提示: 使用任务ID %s 可以稍后查询结果\n", result.Data[0].TaskID) + } + + case "2": + fmt.Println("正在扫描视频(这可能需要几分钟)...") + result, err := scanner.ScanVideoAndWait(videoURL, dataID, 10*time.Minute) + if err != nil { + fmt.Printf("❌ 扫描失败: %v\n", err) + return + } + scanner.PrintResult(result) + + default: + fmt.Println("❌ 无效选择") + } +} + +func getUserInput(prompt string) string { + fmt.Print(prompt) + reader := bufio.NewReader(os.Stdin) + input, _ := reader.ReadString('\n') + return strings.TrimSpace(input) +} diff --git a/conf/alibabacloud.env b/conf/alibabacloud.env new file mode 100644 index 0000000..8d99df4 --- /dev/null +++ b/conf/alibabacloud.env @@ -0,0 +1,12 @@ +#=========== 阿里云内容安全配置 =========== +# 阿里云AccessKey ID +ALIBABA_CLOUD_ACCESS_KEY_ID=your_access_key_id_here + +# 阿里云AccessKey Secret +ALIBABA_CLOUD_ACCESS_KEY_SECRET=your_access_key_secret_here + +# 阿里云区域(可选,默认为cn-shanghai) +ALIBABA_CLOUD_REGION=cn-shanghai + +# 阿里云端点(可选,默认为green.cn-shanghai.aliyuncs.com) +ALIBABA_CLOUD_ENDPOINT=green.cn-shanghai.aliyuncs.com \ No newline at end of file diff --git a/conf/config.go b/conf/config.go new file mode 100644 index 0000000..33965c3 --- /dev/null +++ b/conf/config.go @@ -0,0 +1,81 @@ +package conf + +import ( + "fmt" + "os" + "strconv" + + "github.com/joho/godotenv" +) + +// Config 配置结构体 +type Config struct { + AccessKeyID string + AccessKeySecret string + Region string + Endpoint string +} + +// LoadConfig 加载配置 +func LoadConfig() (*Config, error) { + // 尝试加载多个可能的配置文件 + configFiles := []string{ + ".env", // 根目录的.env文件 + "conf/alibabacloud.env", // 阿里云配置文件 + "alibabacloud.env", // 根目录的阿里云配置文件 + } + + // 按顺序尝试加载配置文件 + for _, configFile := range configFiles { + if err := godotenv.Load(configFile); err == nil { + fmt.Printf("成功加载配置文件: %s\n", configFile) + break + } + } + + 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"), + } + + return config, nil +} + +// LoadConfigFromFile 从指定文件加载配置 +func LoadConfigFromFile(configFile string) (*Config, error) { + // 加载指定的配置文件 + if err := godotenv.Load(configFile); err != nil { + return nil, fmt.Errorf("加载配置文件失败 %s: %v", configFile, err) + } + + 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"), + } + + return config, nil +} + +// getEnv 获取环境变量,如果不存在则返回默认值 +func getEnv(key, defaultValue string) string { + if value := os.Getenv(key); value != "" { + return value + } + 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 + } + } + return defaultValue +} diff --git a/examples.md b/examples.md new file mode 100644 index 0000000..baa86c6 --- /dev/null +++ b/examples.md @@ -0,0 +1,137 @@ +# 使用示例 + +## 1. 图片审核示例 + +### 通过URL审核图片 +``` +请输入图片URL: https://example.com/test-image.jpg +``` + +### 通过本地文件审核图片 +``` +请输入图片文件路径: C:\Users\Desktop\test-image.png +``` + +## 2. 文本审核示例 + +### 单条文本审核 +``` +请输入要审核的文本: 这是一段测试文本,包含一些敏感词汇 +``` + +### 批量文本审核 +``` +文本1: 第一条测试文本 +文本2: 第二条测试文本 +文本3: 第三条测试文本 +文本4: [空行结束输入] +``` + +## 3. 视频审核示例 + +### 异步视频审核 +``` +请输入视频URL: https://example.com/test-video.mp4 +``` + +### 同步视频审核 +``` +请输入视频URL: https://example.com/test-video.mp4 +``` + +## 4. 审核结果示例 + +### 图片审核结果 +``` +=== 图片内容安全审核结果 === +状态码: 200 +消息: OK + +数据ID: image_20231201120000 +处理状态: 200 - OK +✅ 未检测到违规内容 +``` + +### 文本审核结果 +``` +=== 文本内容安全审核结果 === +状态码: 200 +消息: OK + +数据ID: text_20231201120000 +处理状态: 200 - OK +场景: antispam +标签: spam +建议: block +置信度: 0.95 +违规详情: + 内容: 敏感词汇 + 位置: [10, 15] +--- +``` + +### 视频审核结果 +``` +=== 视频内容安全审核结果 === +状态码: 200 +消息: OK + +数据ID: video_20231201120000 +任务ID: task_123456789 +处理状态: 200 - OK +审核状态: success +场景: porn +标签: porn +建议: block +置信度: 0.88 +违规帧详情: + 时间偏移: 30秒 + 帧URL: https://example.com/frame.jpg + 场景: porn, 标签: porn, 建议: block, 置信度: 0.88 +--- +``` + +## 5. 环境变量配置示例 + +### Windows (PowerShell) +```powershell +$env:ALIBABA_CLOUD_ACCESS_KEY_ID="your_access_key_id" +$env:ALIBABA_CLOUD_ACCESS_KEY_SECRET="your_access_key_secret" +``` + +### Windows (CMD) +```cmd +set ALIBABA_CLOUD_ACCESS_KEY_ID=your_access_key_id +set ALIBABA_CLOUD_ACCESS_KEY_SECRET=your_access_key_secret +``` + +### Linux/macOS +```bash +export ALIBABA_CLOUD_ACCESS_KEY_ID="your_access_key_id" +export ALIBABA_CLOUD_ACCESS_KEY_SECRET="your_access_key_secret" +``` + +### .env文件 +```env +ALIBABA_CLOUD_ACCESS_KEY_ID=your_access_key_id +ALIBABA_CLOUD_ACCESS_KEY_SECRET=your_access_key_secret +ALIBABA_CLOUD_REGION=cn-shanghai +ALIBABA_CLOUD_ENDPOINT=green.cn-shanghai.aliyuncs.com +``` + +## 6. 常见问题 + +### Q: 如何获取阿里云AccessKey? +A: 登录阿里云控制台,进入"访问控制" -> "用户" -> "创建AccessKey" + +### Q: 支持哪些图片格式? +A: 支持JPG、JPEG、PNG、BMP、GIF、WEBP格式,文件大小不超过9MB + +### Q: 视频审核需要多长时间? +A: 视频审核时间取决于视频长度,通常需要几分钟到十几分钟 + +### Q: 如何查看审核历史? +A: 可以通过任务ID查询历史审核结果 + +### Q: 审核费用如何计算? +A: 按调用次数计费,具体价格请查看阿里云官方定价页面 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..a8b350d --- /dev/null +++ b/go.mod @@ -0,0 +1,17 @@ +module contentSecurityDemo + +go 1.23 + +require ( + github.com/aliyun/alibaba-cloud-sdk-go v1.62.706 + github.com/joho/godotenv v1.5.1 +) + +require ( + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect + gopkg.in/ini.v1 v1.67.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..2784d53 --- /dev/null +++ b/go.sum @@ -0,0 +1,89 @@ +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/aliyun/alibaba-cloud-sdk-go v1.62.706 h1:5qi9iWE+e6XoobqYHdg6rQj1o+ygAWhM4AzyvHgk4fA= +github.com/aliyun/alibaba-cloud-sdk-go v1.62.706/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A= +github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= +github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= +github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/internal/api/image_scanner.go b/internal/api/image_scanner.go new file mode 100644 index 0000000..4b33cff --- /dev/null +++ b/internal/api/image_scanner.go @@ -0,0 +1,182 @@ +package api + +import ( + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "contentSecurityDemo/conf" + "contentSecurityDemo/internal/model" + + "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" + "github.com/aliyun/alibaba-cloud-sdk-go/services/green" +) + +// ImageScanner 图片内容安全扫描器 +type ImageScanner struct { + Client *green.Client + Config *conf.Config +} + +// NewImageScanner 创建图片扫描器 +func NewImageScanner(config *conf.Config) (*ImageScanner, error) { + client, err := green.NewClientWithAccessKey(config.Region, config.AccessKeyID, config.AccessKeySecret) + if err != nil { + return nil, fmt.Errorf("创建客户端失败: %v", err) + } + + return &ImageScanner{ + Client: client, + Config: config, + }, nil +} + +// ScanImageByURL 通过URL扫描图片 +func (s *ImageScanner) ScanImageByURL(imageURL string, dataID string) (*model.ImageScanResponse, error) { + request := requests.NewCommonRequest() + request.Method = "POST" + request.Scheme = "https" + request.Domain = s.Config.Endpoint + request.Version = "2018-05-09" + request.ApiName = "ImageSyncScan" + request.QueryParams["RegionId"] = s.Config.Region + + scanRequest := model.ImageScanRequest{ + Tasks: []model.ImageTask{ + { + DataID: dataID, + URL: imageURL, + }, + }, + Scenes: []string{"porn", "terrorism", "ad", "live", "logo"}, + } + + requestBody, err := json.Marshal(scanRequest) + if err != nil { + return nil, fmt.Errorf("序列化请求失败: %v", err) + } + + request.Content = requestBody + + response, err := s.Client.ProcessCommonRequest(request) + if err != nil { + return nil, fmt.Errorf("请求失败: %v", err) + } + + var scanResponse model.ImageScanResponse + if err := json.Unmarshal(response.GetHttpContentBytes(), &scanResponse); err != nil { + return nil, fmt.Errorf("解析响应失败: %v", err) + } + + return &scanResponse, nil +} + +// ScanImageByFile 通过文件扫描图片 +func (s *ImageScanner) ScanImageByFile(filePath string, dataID string) (*model.ImageScanResponse, error) { + // 读取文件 + file, err := os.Open(filePath) + if err != nil { + return nil, fmt.Errorf("打开文件失败: %v", err) + } + defer file.Close() + + // 检查文件大小(限制为9MB) + fileInfo, err := file.Stat() + if err != nil { + return nil, fmt.Errorf("获取文件信息失败: %v", err) + } + + if fileInfo.Size() > 9*1024*1024 { + return nil, fmt.Errorf("文件大小超过9MB限制") + } + + // 读取文件内容并转换为base64 + fileContent, err := io.ReadAll(file) + if err != nil { + return nil, fmt.Errorf("读取文件失败: %v", err) + } + + // 检查文件类型 + ext := strings.ToLower(filepath.Ext(filePath)) + supportedExts := []string{".jpg", ".jpeg", ".png", ".bmp", ".gif", ".webp"} + isSupported := false + for _, supportedExt := range supportedExts { + if ext == supportedExt { + isSupported = true + break + } + } + + if !isSupported { + return nil, fmt.Errorf("不支持的文件格式: %s", ext) + } + + // 将文件内容转换为base64 + base64Content := fmt.Sprintf("data:image/%s;base64,%s", ext[1:], + strings.ReplaceAll(string(fileContent), "\n", "")) + + request := requests.NewCommonRequest() + request.Method = "POST" + request.Scheme = "https" + request.Domain = s.Config.Endpoint + request.Version = "2018-05-09" + request.ApiName = "ImageSyncScan" + request.QueryParams["RegionId"] = s.Config.Region + + scanRequest := model.ImageScanRequest{ + Tasks: []model.ImageTask{ + { + DataID: dataID, + Content: base64Content, + }, + }, + Scenes: []string{"porn", "terrorism", "ad", "live", "logo"}, + } + + requestBody, err := json.Marshal(scanRequest) + if err != nil { + return nil, fmt.Errorf("序列化请求失败: %v", err) + } + + request.Content = requestBody + + response, err := s.Client.ProcessCommonRequest(request) + if err != nil { + return nil, fmt.Errorf("请求失败: %v", err) + } + + var scanResponse model.ImageScanResponse + if err := json.Unmarshal(response.GetHttpContentBytes(), &scanResponse); err != nil { + return nil, fmt.Errorf("解析响应失败: %v", err) + } + + return &scanResponse, nil +} + +// PrintResult 打印扫描结果 +func (s *ImageScanner) PrintResult(response *model.ImageScanResponse) { + fmt.Println("=== 图片内容安全审核结果 ===") + fmt.Printf("状态码: %d\n", response.Code) + fmt.Printf("消息: %s\n", response.Message) + + for _, data := range response.Data { + fmt.Printf("\n数据ID: %s\n", data.DataID) + fmt.Printf("处理状态: %d - %s\n", data.Code, data.Message) + + if len(data.Results) == 0 { + fmt.Println("✅ 未检测到违规内容") + continue + } + + for _, result := range data.Results { + fmt.Printf("场景: %s\n", result.Scene) + fmt.Printf("标签: %s\n", result.Label) + fmt.Printf("建议: %s\n", result.Suggestion) + fmt.Printf("置信度: %.2f\n", result.Rate) + fmt.Println("---") + } + } +} diff --git a/internal/api/text_scanner.go b/internal/api/text_scanner.go new file mode 100644 index 0000000..f4f0cc5 --- /dev/null +++ b/internal/api/text_scanner.go @@ -0,0 +1,166 @@ +package api + +import ( + "encoding/json" + "fmt" + + "contentSecurityDemo/conf" + "contentSecurityDemo/internal/model" + + "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" + "github.com/aliyun/alibaba-cloud-sdk-go/services/green" +) + +// TextScanner 文本内容安全扫描器 +type TextScanner struct { + Client *green.Client + Config *conf.Config +} + +// NewTextScanner 创建文本扫描器 +func NewTextScanner(config *conf.Config) (*TextScanner, error) { + client, err := green.NewClientWithAccessKey(config.Region, config.AccessKeyID, config.AccessKeySecret) + if err != nil { + return nil, fmt.Errorf("创建客户端失败: %v", err) + } + + return &TextScanner{ + Client: client, + Config: config, + }, nil +} + +// ScanText 扫描文本内容 +func (s *TextScanner) ScanText(content string, dataID string) (*model.TextScanResponse, error) { + request := requests.NewCommonRequest() + request.Method = "POST" + request.Scheme = "https" + request.Domain = s.Config.Endpoint + request.Version = "2018-05-09" + request.ApiName = "TextScan" + request.QueryParams["RegionId"] = s.Config.Region + + scanRequest := model.TextScanRequest{ + Tasks: []model.TextTask{ + { + DataID: dataID, + Content: content, + }, + }, + Scenes: []string{"antispam"}, + } + + requestBody, err := json.Marshal(scanRequest) + if err != nil { + return nil, fmt.Errorf("序列化请求失败: %v", err) + } + + request.Content = requestBody + + response, err := s.Client.ProcessCommonRequest(request) + if err != nil { + return nil, fmt.Errorf("请求失败: %v", err) + } + + var scanResponse model.TextScanResponse + if err := json.Unmarshal(response.GetHttpContentBytes(), &scanResponse); err != nil { + return nil, fmt.Errorf("解析响应失败: %v", err) + } + + return &scanResponse, nil +} + +// ScanTextBatch 批量扫描文本内容 +func (s *TextScanner) ScanTextBatch(texts []string, dataIDs []string) (*model.TextScanResponse, error) { + if len(texts) != len(dataIDs) { + return nil, fmt.Errorf("文本数量和ID数量不匹配") + } + + request := requests.NewCommonRequest() + request.Method = "POST" + request.Scheme = "https" + request.Domain = s.Config.Endpoint + request.Version = "2018-05-09" + request.ApiName = "TextScan" + request.QueryParams["RegionId"] = s.Config.Region + + var tasks []model.TextTask + for i, text := range texts { + tasks = append(tasks, model.TextTask{ + DataID: dataIDs[i], + Content: text, + }) + } + + scanRequest := model.TextScanRequest{ + Tasks: tasks, + Scenes: []string{"antispam"}, + } + + requestBody, err := json.Marshal(scanRequest) + if err != nil { + return nil, fmt.Errorf("序列化请求失败: %v", err) + } + + request.Content = requestBody + + response, err := s.Client.ProcessCommonRequest(request) + if err != nil { + return nil, fmt.Errorf("请求失败: %v", err) + } + + var scanResponse model.TextScanResponse + if err := json.Unmarshal(response.GetHttpContentBytes(), &scanResponse); err != nil { + return nil, fmt.Errorf("解析响应失败: %v", err) + } + + return &scanResponse, nil +} + +// PrintResult 打印扫描结果 +func (s *TextScanner) PrintResult(response *model.TextScanResponse) { + fmt.Println("=== 文本内容安全审核结果 ===") + fmt.Printf("状态码: %d\n", response.Code) + fmt.Printf("消息: %s\n", response.Message) + + for _, data := range response.Data { + fmt.Printf("\n数据ID: %s\n", data.DataID) + fmt.Printf("处理状态: %d - %s\n", data.Code, data.Message) + + if len(data.Results) == 0 { + fmt.Println("✅ 未检测到违规内容") + continue + } + + for _, result := range data.Results { + fmt.Printf("场景: %s\n", result.Scene) + fmt.Printf("标签: %s\n", result.Label) + 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) + fmt.Printf(" 位置: %v\n", detail.Context.Pos) + } + } + fmt.Println("---") + } + } +} + +// GetSuggestionText 获取建议文本 +func GetSuggestionText(suggestion string) string { + switch suggestion { + case "pass": + return "通过" + case "review": + return "需要人工审核" + case "block": + return "拒绝" + default: + return suggestion + } +} diff --git a/internal/api/video_scanner.go b/internal/api/video_scanner.go new file mode 100644 index 0000000..a05ab0c --- /dev/null +++ b/internal/api/video_scanner.go @@ -0,0 +1,200 @@ +package api + +import ( + "encoding/json" + "fmt" + "time" + + "contentSecurityDemo/conf" + "contentSecurityDemo/internal/model" + + "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" + "github.com/aliyun/alibaba-cloud-sdk-go/services/green" +) + +// VideoScanner 视频内容安全扫描器 +type VideoScanner struct { + Client *green.Client + Config *conf.Config +} + +// NewVideoScanner 创建视频扫描器 +func NewVideoScanner(config *conf.Config) (*VideoScanner, error) { + client, err := green.NewClientWithAccessKey(config.Region, config.AccessKeyID, config.AccessKeySecret) + if err != nil { + return nil, fmt.Errorf("创建客户端失败: %v", err) + } + + return &VideoScanner{ + Client: client, + Config: config, + }, nil +} + +// ScanVideoAsync 异步扫描视频 +func (s *VideoScanner) ScanVideoAsync(videoURL string, dataID string) (*model.VideoScanResponse, error) { + request := requests.NewCommonRequest() + request.Method = "POST" + request.Scheme = "https" + request.Domain = s.Config.Endpoint + request.Version = "2018-05-09" + request.ApiName = "VideoAsyncScan" + request.QueryParams["RegionId"] = s.Config.Region + + scanRequest := model.VideoScanRequest{ + Tasks: []model.VideoTask{ + { + DataID: dataID, + URL: videoURL, + Interval: 1, // 每秒截取一帧 + MaxFrames: 100, // 最多截取100帧 + }, + }, + Scenes: []string{"porn", "terrorism", "ad", "live"}, + } + + requestBody, err := json.Marshal(scanRequest) + if err != nil { + return nil, fmt.Errorf("序列化请求失败: %v", err) + } + + request.Content = requestBody + + response, err := s.Client.ProcessCommonRequest(request) + if err != nil { + return nil, fmt.Errorf("请求失败: %v", err) + } + + var scanResponse model.VideoScanResponse + if err := json.Unmarshal(response.GetHttpContentBytes(), &scanResponse); err != nil { + return nil, fmt.Errorf("解析响应失败: %v", err) + } + + return &scanResponse, nil +} + +// GetVideoResult 获取视频审核结果 +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.ApiName = "VideoAsyncScanResults" + request.QueryParams["RegionId"] = s.Config.Region + + requestBody := fmt.Sprintf(`{"taskId": "%s"}`, taskID) + request.Content = []byte(requestBody) + + response, err := s.Client.ProcessCommonRequest(request) + if err != nil { + return nil, fmt.Errorf("请求失败: %v", err) + } + + var resultResponse model.VideoResultResponse + if err := json.Unmarshal(response.GetHttpContentBytes(), &resultResponse); err != nil { + return nil, fmt.Errorf("解析响应失败: %v", err) + } + + return &resultResponse, nil +} + +// ScanVideoAndWait 扫描视频并等待结果 +func (s *VideoScanner) ScanVideoAndWait(videoURL string, dataID string, maxWaitTime time.Duration) (*model.VideoResultResponse, error) { + // 提交扫描任务 + scanResponse, err := s.ScanVideoAsync(videoURL, dataID) + if err != nil { + return nil, fmt.Errorf("提交扫描任务失败: %v", err) + } + + if len(scanResponse.Data) == 0 { + return nil, fmt.Errorf("未获取到任务ID") + } + + taskID := scanResponse.Data[0].TaskID + fmt.Printf("视频扫描任务已提交,任务ID: %s\n", taskID) + + // 轮询获取结果 + startTime := time.Now() + for time.Since(startTime) < maxWaitTime { + result, err := s.GetVideoResult(taskID) + if err != nil { + return nil, fmt.Errorf("获取结果失败: %v", err) + } + + if len(result.Data) > 0 { + status := result.Data[0].Status + fmt.Printf("当前状态: %s\n", status) + + switch status { + case "success": + return result, nil + case "failure": + return nil, fmt.Errorf("视频审核失败: %s", result.Data[0].Message) + case "running", "pending": + // 继续等待 + time.Sleep(5 * time.Second) + default: + return nil, fmt.Errorf("未知状态: %s", status) + } + } else { + time.Sleep(5 * time.Second) + } + } + + return nil, fmt.Errorf("等待超时,请稍后手动查询结果,任务ID: %s", taskID) +} + +// PrintScanResult 打印扫描提交结果 +func (s *VideoScanner) PrintScanResult(response *model.VideoScanResponse) { + fmt.Println("=== 视频扫描任务提交结果 ===") + fmt.Printf("状态码: %d\n", response.Code) + fmt.Printf("消息: %s\n", response.Message) + + for _, data := range response.Data { + fmt.Printf("数据ID: %s\n", data.DataID) + fmt.Printf("任务ID: %s\n", data.TaskID) + fmt.Printf("处理状态: %d - %s\n", data.Code, data.Message) + fmt.Printf("视频URL: %s\n", data.URL) + } +} + +// PrintResult 打印审核结果 +func (s *VideoScanner) PrintResult(response *model.VideoResultResponse) { + fmt.Println("=== 视频内容安全审核结果 ===") + fmt.Printf("状态码: %d\n", response.Code) + fmt.Printf("消息: %s\n", response.Message) + + for _, data := range response.Data { + fmt.Printf("\n数据ID: %s\n", data.DataID) + fmt.Printf("任务ID: %s\n", data.TaskID) + fmt.Printf("处理状态: %d - %s\n", data.Code, data.Message) + fmt.Printf("审核状态: %s\n", data.Status) + + if len(data.Results) == 0 { + fmt.Println("✅ 未检测到违规内容") + continue + } + + for _, result := range data.Results { + fmt.Printf("场景: %s\n", result.Scene) + fmt.Printf("标签: %s\n", result.Label) + fmt.Printf("建议: %s\n", result.Suggestion) + fmt.Printf("置信度: %.2f\n", result.Rate) + + // 打印帧级别的结果 + if len(result.Frames) > 0 { + fmt.Println("违规帧详情:") + for _, frame := range result.Frames { + fmt.Printf(" 时间偏移: %d秒\n", frame.Offset) + fmt.Printf(" 帧URL: %s\n", frame.URL) + for _, frameResult := range frame.Results { + fmt.Printf(" 场景: %s, 标签: %s, 建议: %s, 置信度: %.2f\n", + frameResult.Scene, frameResult.Label, frameResult.Suggestion, frameResult.Rate) + } + } + } + fmt.Println("---") + } + } +} diff --git a/internal/model/image.go b/internal/model/image.go new file mode 100644 index 0000000..09c01f4 --- /dev/null +++ b/internal/model/image.go @@ -0,0 +1,31 @@ +package model + +// ImageScanRequest 图片审核请求结构 +type ImageScanRequest struct { + Tasks []ImageTask `json:"tasks"` + Scenes []string `json:"scenes"` +} + +// ImageTask 图片任务结构 +type ImageTask struct { + DataID string `json:"dataId"` + URL string `json:"url,omitempty"` + Content string `json:"content,omitempty"` // base64编码的图片内容 +} + +// ImageScanResponse 图片审核响应结构 +type ImageScanResponse struct { + Code int `json:"code"` + Message string `json:"message"` + Data []struct { + Code int `json:"code"` + Message string `json:"message"` + DataID string `json:"dataId"` + Results []struct { + Scene string `json:"scene"` + Label string `json:"label"` + Suggestion string `json:"suggestion"` + Rate float64 `json:"rate"` + } `json:"results"` + } `json:"data"` +} diff --git a/internal/model/text.go b/internal/model/text.go new file mode 100644 index 0000000..c839011 --- /dev/null +++ b/internal/model/text.go @@ -0,0 +1,36 @@ +package model + +// TextScanRequest 文本审核请求结构 +type TextScanRequest struct { + Tasks []TextTask `json:"tasks"` + Scenes []string `json:"scenes"` +} + +// TextTask 文本任务结构 +type TextTask struct { + DataID string `json:"dataId"` + Content string `json:"content"` +} + +// TextScanResponse 文本审核响应结构 +type TextScanResponse struct { + Code int `json:"code"` + Message string `json:"message"` + Data []struct { + Code int `json:"code"` + Message string `json:"message"` + DataID string `json:"dataId"` + Results []struct { + Scene string `json:"scene"` + 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"` + } `json:"results"` + } `json:"data"` +} diff --git a/internal/model/video.go b/internal/model/video.go new file mode 100644 index 0000000..0d64dd8 --- /dev/null +++ b/internal/model/video.go @@ -0,0 +1,57 @@ +package model + +// VideoScanRequest 视频审核请求结构 +type VideoScanRequest struct { + Tasks []VideoTask `json:"tasks"` + Scenes []string `json:"scenes"` +} + +// VideoTask 视频任务结构 +type VideoTask struct { + DataID string `json:"dataId"` + URL string `json:"url"` + Interval int `json:"interval,omitempty"` // 截帧间隔,单位秒 + MaxFrames int `json:"maxFrames,omitempty"` // 最大截帧数 +} + +// VideoScanResponse 视频审核响应结构 +type VideoScanResponse struct { + Code int `json:"code"` + Message string `json:"message"` + Data []struct { + Code int `json:"code"` + Message string `json:"message"` + DataID string `json:"dataId"` + TaskID string `json:"taskId"` + URL string `json:"url"` + } `json:"data"` +} + +// VideoResultResponse 视频审核结果响应结构 +type VideoResultResponse struct { + Code int `json:"code"` + Message string `json:"message"` + Data []struct { + Code int `json:"code"` + Message string `json:"message"` + DataID string `json:"dataId"` + TaskID string `json:"taskId"` + Status string `json:"status"` + Results []struct { + Scene string `json:"scene"` + Label string `json:"label"` + Suggestion string `json:"suggestion"` + Rate float64 `json:"rate"` + Frames []struct { + Offset int `json:"offset"` + URL string `json:"url"` + Results []struct { + Scene string `json:"scene"` + Label string `json:"label"` + Suggestion string `json:"suggestion"` + Rate float64 `json:"rate"` + } `json:"results"` + } `json:"frames"` + } `json:"results"` + } `json:"data"` +} diff --git a/run.bat b/run.bat new file mode 100644 index 0000000..b755d8d --- /dev/null +++ b/run.bat @@ -0,0 +1,44 @@ +@echo off +echo === 阿里云内容安全2.0 Demo === +echo. + +REM 检查Go是否安装 +go version >nul 2>&1 +if errorlevel 1 ( + echo ❌ 错误: 未找到Go,请先安装Go 1.21或更高版本 + pause + exit /b 1 +) + +echo ✅ Go环境检查通过 +echo. + +REM 下载依赖 +echo 正在下载依赖... +go mod tidy +if errorlevel 1 ( + echo ❌ 依赖下载失败 + pause + exit /b 1 +) + +echo ✅ 依赖下载完成 +echo. + +REM 检查环境变量 +if "%ALIBABA_CLOUD_ACCESS_KEY_ID%"=="" ( + echo ⚠️ 警告: 未设置 ALIBABA_CLOUD_ACCESS_KEY_ID 环境变量 + echo 请设置环境变量或创建.env文件 + echo. +) + +if "%ALIBABA_CLOUD_ACCESS_KEY_SECRET%"=="" ( + echo ⚠️ 警告: 未设置 ALIBABA_CLOUD_ACCESS_KEY_SECRET 环境变量 + echo 请设置环境变量或创建.env文件 + echo. +) + +REM 运行程序 +echo 启动程序... +go run cmd/main.go +pause diff --git a/run.sh b/run.sh new file mode 100644 index 0000000..87b0d26 --- /dev/null +++ b/run.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +echo "=== 阿里云内容安全2.0 Demo ===" +echo + +# 检查Go是否安装 +if ! command -v go &> /dev/null; then + echo "❌ 错误: 未找到Go,请先安装Go 1.21或更高版本" + exit 1 +fi + +echo "✅ Go环境检查通过" +echo + +# 下载依赖 +echo "正在下载依赖..." +go mod tidy +if [ $? -ne 0 ]; then + echo "❌ 依赖下载失败" + exit 1 +fi + +echo "✅ 依赖下载完成" +echo + +# 检查环境变量 +if [ -z "$ALIBABA_CLOUD_ACCESS_KEY_ID" ]; then + echo "⚠️ 警告: 未设置 ALIBABA_CLOUD_ACCESS_KEY_ID 环境变量" + echo "请设置环境变量或创建.env文件" + echo +fi + +if [ -z "$ALIBABA_CLOUD_ACCESS_KEY_SECRET" ]; then + echo "⚠️ 警告: 未设置 ALIBABA_CLOUD_ACCESS_KEY_SECRET 环境变量" + echo "请设置环境变量或创建.env文件" + echo +fi + +# 运行程序 +echo "启动程序..." +go run cmd/main.go