// set-oss-cors/main.go // 给 OSS bucket 设置 CORS 规则,允许浏览器跨域读取预签名 URL。 // 背景: // - 前端 useLaserSegment.downloadToLocal 通过 fetch 直连 OSS 拉取 cutout/png // - OSS bucket 未配 CORS 时,浏览器在 CORS 预检后中断响应, // 表现为 net::ERR_CONTENT_LENGTH_MISMATCH 200 (OK) 或 TypeError: Failed to fetch // // 用法: // // go run scripts/set-oss-cors/main.go // // (会自动从 backend/.env / docker/.env.prod 读取 OSS_REGION / OSS_BUCKET_NAME / // OSS_ACCESS_KEY_ID / OSS_ACCESS_KEY_SECRET) // // 注意:aliyun-oss-go-sdk v3.0.2 的 CORS 方法挂在 *Client 上,不是 *Bucket。 package main import ( "fmt" "os" "github.com/aliyun/aliyun-oss-go-sdk/oss" "github.com/joho/godotenv" "github.com/topfans/backend/gateway/config" ) func main() { // 多路径 fallback:本地 .env / docker/.env.prod / docker/.env / .env for _, p := range []string{ "backend/.env", "../.env", ".env", "docker/.env.prod", "docker/.env", "../docker/.env.prod", "../docker/.env", } { if _, err := os.Stat(p); err == nil { _ = godotenv.Load(p) } } cfg := config.Load() fmt.Println("=== SetBucketCORS ===") fmt.Printf("Bucket: %s Region: %s\n", cfg.OSS.BucketName, cfg.OSS.Region) fmt.Printf("AK: %s\n", mask(cfg.OSS.AccessKeyID)) endpoint := fmt.Sprintf("https://oss-%s.aliyuncs.com", cfg.OSS.Region) client, err := oss.New(endpoint, cfg.OSS.AccessKeyID, cfg.OSS.AccessKeySecret) if err != nil { fmt.Printf("FAIL 创建 OSS 客户端: %v\n", err) os.Exit(1) } // 全域名 * 放行 (用户确认: top-fans-test 为测试桶, 预签名 URL 仍需签名才能访问) // // ⚠️ 重要: 阿里云 OSS 的 AllowedMethod 不支持 OPTIONS。 // 浏览器 CORS 预检的 OPTIONS 请求由 OSS 隐式处理(无需显式列出)。 // 加上 OPTIONS 会触发 400 InvalidArgument: "CORS Rule AllowedMethod Format Error"。 // // Method 集合: // GET - H5 fetch 拉 cutout / variant PNG (浏览器读图) // HEAD - 上传后取文件 size // POST - H5 PostObject 直传(uni-app H5 在浏览器跑时必须走 POST) rule := oss.CORSRule{ AllowedOrigin: []string{"*"}, AllowedMethod: []string{"GET", "HEAD", "POST"}, AllowedHeader: []string{"*"}, ExposeHeader: []string{"ETag", "Content-Length", "Content-Type", "x-oss-request-id"}, MaxAgeSeconds: 3600, } // SDK v3.0.2: CORS 方法在 *Client 上 if err := client.SetBucketCORS(cfg.OSS.BucketName, []oss.CORSRule{rule}); err != nil { fmt.Printf("FAIL SetBucketCORS: %v\n", err) os.Exit(1) } // 立即读回验证 got, gErr := client.GetBucketCORS(cfg.OSS.BucketName) if gErr != nil { fmt.Printf("WARN GetBucketCORS 回读失败: %v\n", gErr) } else { fmt.Printf("OK 当前 bucket CORS 规则数: %d\n", len(got.CORSRules)) for i, r := range got.CORSRules { fmt.Printf(" [%d] Origin=%v Method=%v Header=%v MaxAge=%d\n", i, r.AllowedOrigin, r.AllowedMethod, r.AllowedHeader, r.MaxAgeSeconds) } } fmt.Println("✅ CORS 配置已生效(可能有几秒传播延迟)") } func mask(s string) string { if len(s) <= 8 { return "***" } return s[:4] + "****" + s[len(s)-4:] }