From 6ff8743c720d808178075a735a08a3a431f6beaa Mon Sep 17 00:00:00 2001 From: liulong <18539103286> Date: Wed, 3 Jun 2026 23:22:22 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E4=BF=AE=E5=A4=8D=E5=90=88=E5=B9=B6?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/.env.example | 2 +- .../gateway/controller/asset_controller.go | 65 ++++++++++--------- .../assetService/service/mint_service.go | 2 +- frontend/utils/api.js | 5 +- 4 files changed, 39 insertions(+), 35 deletions(-) diff --git a/backend/.env.example b/backend/.env.example index eb7c14b..f4e45fa 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -6,7 +6,7 @@ SERVER_PORT=8080 # ==================== JWT Configuration ==================== # JWT密钥 - 生产环境请修改为安全的随机字符串 -JWT_SECRET=topfans-secret-key-please-change-in-production +JWT_SECRET= # ==================== Dubbo Service URLs ==================== # 各微服务的Dubbo连接地址(直连模式) diff --git a/backend/gateway/controller/asset_controller.go b/backend/gateway/controller/asset_controller.go index 281258a..581980a 100644 --- a/backend/gateway/controller/asset_controller.go +++ b/backend/gateway/controller/asset_controller.go @@ -1408,23 +1408,15 @@ func (ctrl *AssetController) buildOSSPrefix(uploadType string, userID, starID in return fmt.Sprintf("%s%d/%d/", baseDir, userID, starID) } -// generatePresignedURL 使用STS临时凭证生成预签名URL +// generatePresignedURL 生成预签名URL(优先 STS,失败降级到永久 AK/SK 直连) func (ctrl *AssetController) generatePresignedURL( ossConfig config.OSSConfig, filePath string, expiresInSeconds int64, ) (string, error) { - // 1. 获取STS临时凭证 - // 注意:STS 的 DurationSeconds 最小 15 分钟(900秒),最大 1 小时(3600秒) - // 但预签名 URL 的过期时间可以更长,由 OSS SDK 的 SignURL 方法控制 - // 所以我们需要限制 STS token 的过期时间,但预签名 URL 可以使用更长的过期时间 - stsExpiration := expiresInSeconds - if stsExpiration > 3600 { - stsExpiration = 3600 // STS 最大支持 1 小时 - } - if stsExpiration < 900 { - stsExpiration = 900 // STS 最小支持 15 分钟 - } + // 尝试 STS AssumeRole,失败则降级到永久 AccessKey 直连 + var accessKeyID, accessKeySecret, securityToken string + useSTS := false credConfig := new(credentials.Config). SetType("ram_role_arn"). @@ -1433,34 +1425,47 @@ func (ctrl *AssetController) generatePresignedURL( SetRoleArn(ossConfig.RoleArn). SetRoleSessionName("topfans-download-session"). SetPolicy(""). - SetRoleSessionExpiration(int(stsExpiration)) + SetRoleSessionExpiration(3600) - provider, err := credentials.NewCredential(credConfig) - if err != nil { - return "", fmt.Errorf("创建凭证提供器失败: %w", err) + if provider, err := credentials.NewCredential(credConfig); err == nil { + if cred, err := provider.GetCredential(); err == nil { + accessKeyID = *cred.AccessKeyId + accessKeySecret = *cred.AccessKeySecret + securityToken = *cred.SecurityToken + useSTS = true + } else { + logger.Logger.Warn("STS AssumeRole failed for presigned URL, falling back to direct AK/SK", zap.Error(err)) + } + } else { + logger.Logger.Warn("STS credential provider failed for presigned URL, falling back to direct AK/SK", zap.Error(err)) } - cred, err := provider.GetCredential() - if err != nil { - return "", fmt.Errorf("获取临时凭证失败: %w", err) + if !useSTS { + accessKeyID = ossConfig.AccessKeyID + accessKeySecret = ossConfig.AccessKeySecret } - // 2. 创建OSS客户端(使用临时凭证) + // 创建OSS客户端 endpoint := fmt.Sprintf("https://oss-%s.aliyuncs.com", ossConfig.Region) - client, err := oss.New(endpoint, *cred.AccessKeyId, *cred.AccessKeySecret, - oss.SecurityToken(*cred.SecurityToken)) + var client *oss.Client + var err error + if useSTS && securityToken != "" { + client, err = oss.New(endpoint, accessKeyID, accessKeySecret, oss.SecurityToken(securityToken)) + } else { + client, err = oss.New(endpoint, accessKeyID, accessKeySecret) + } if err != nil { return "", fmt.Errorf("创建OSS客户端失败: %w", err) } - // 3. 获取Bucket + // 获取Bucket bucket, err := client.Bucket(ossConfig.BucketName) if err != nil { return "", fmt.Errorf("获取Bucket失败: %w", err) } - // 4. 生成预签名URL + // 生成预签名URL signedURL, err := bucket.SignURL(filePath, oss.HTTPGet, expiresInSeconds) if err != nil { logger.Logger.Error("OSS SignURL failed", @@ -1471,7 +1476,7 @@ func (ctrl *AssetController) generatePresignedURL( return "", fmt.Errorf("生成预签名URL失败: %w", err) } - // 5. 修复 path 的 URL 编码:OSS SDK 的 buildURL 用 QueryEscape 把 / 编成 %2F, + // 修复 path 的 URL 编码:OSS SDK 的 buildURL 用 QueryEscape 把 / 编成 %2F, // 导致 OSS 按字面 key "asset%2F18%2F88%2Fxxx" 查找失败(403)。只把 path 段(? 之前)的 %2F 改回 /。 if idx := strings.Index(signedURL, "?"); idx >= 0 { signedURL = strings.ReplaceAll(signedURL[:idx], "%2F", "/") + signedURL[idx:] @@ -1479,9 +1484,9 @@ func (ctrl *AssetController) generatePresignedURL( signedURL = strings.ReplaceAll(signedURL, "%2F", "/") } - // 6. 若 SDK 未把 STS 的 security-token 加入 URL,则手动追加(使用 STS 临时凭证时,预签名 URL 必须带此参数,否则 403) - if !strings.Contains(signedURL, "security-token") && cred.SecurityToken != nil && *cred.SecurityToken != "" { - signedURL = signedURL + "&security-token=" + url.QueryEscape(*cred.SecurityToken) + // 若 SDK 未把 STS 的 security-token 加入 URL,则手动追加(使用 STS 临时凭证时,预签名 URL 必须带此参数,否则 403) + if useSTS && securityToken != "" && !strings.Contains(signedURL, "security-token") { + signedURL = signedURL + "&security-token=" + url.QueryEscape(securityToken) } // 检查生成的预签名 URL 是否包含 security-token 参数 @@ -1493,8 +1498,8 @@ func (ctrl *AssetController) generatePresignedURL( } tokenPreview := "" - if cred.SecurityToken != nil && *cred.SecurityToken != "" { - token := *cred.SecurityToken + if securityToken != "" { + token := securityToken if len(token) > 50 { tokenPreview = token[:50] + "..." } else { diff --git a/backend/services/assetService/service/mint_service.go b/backend/services/assetService/service/mint_service.go index 9b33ecb..7e93172 100644 --- a/backend/services/assetService/service/mint_service.go +++ b/backend/services/assetService/service/mint_service.go @@ -967,7 +967,7 @@ func syncAssetsIDSequence(tx *gorm.DB) error { return tx.Exec(` SELECT setval( pg_get_serial_sequence('assets', 'id'), - COALESCE((SELECT MAX(id) FROM assets), 0) + GREATEST(COALESCE((SELECT MAX(id) FROM assets), 1), 1) ) `).Error } diff --git a/frontend/utils/api.js b/frontend/utils/api.js index 31773bf..1580c05 100644 --- a/frontend/utils/api.js +++ b/frontend/utils/api.js @@ -2,9 +2,8 @@ // 自动检测后端环境:探测开发服务器是否可用,能连通则用开发地址,否则用生产地址 // 团队开发机(Gateway + 团队数据库;注册/登录/余额/铸造) -const DEV_BASE = 'http://192.168.110.60:8080' -// 本机 Gateway(所有接口走本地) -// const DEV_BASE = 'http://localhost:8081' +// const DEV_BASE = 'http://192.168.110.60:8080' +const DEV_BASE = 'http://localhost:8081' const SEGMENT_BASE = 'http://localhost:8081' const LASER_BASE = 'http://localhost:8081' // 镭射 AI 生成 + compositor 走本地 Gateway const PROD_BASE = 'http://101.132.250.62:8080' // 生产环境