diff --git a/.codex/config.toml b/.codex/config.toml deleted file mode 100644 index ed6994e..0000000 --- a/.codex/config.toml +++ /dev/null @@ -1,6 +0,0 @@ -[mcp_servers.code-review-graph] -command = "uvx" -args = [ - "code-review-graph", - "serve", -] diff --git a/.codex/hooks.json b/.codex/hooks.json deleted file mode 100644 index 1bd167c..0000000 --- a/.codex/hooks.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "hooks": { - "PostToolUse": [ - { - "matcher": "Edit|Write|Bash", - "hooks": [ - { - "type": "command", - "command": "git rev-parse --git-dir >/dev/null 2>&1 && code-review-graph update --skip-flows --repo \"/Users/liulujian/Documents/code/TopFansByGithub\" || true", - "timeout": 30 - } - ] - } - ], - "SessionStart": [ - { - "matcher": "", - "hooks": [ - { - "type": "command", - "command": "git rev-parse --git-dir >/dev/null 2>&1 && code-review-graph status --repo \"/Users/liulujian/Documents/code/TopFansByGithub\" || echo 'Not a git repo, skipping'", - "timeout": 10 - } - ] - } - ] - } -} diff --git a/.trae/skills/karpathy-guidelines/SKILL.md b/.trae/skills/karpathy-guidelines/SKILL.md deleted file mode 100644 index 456afa3..0000000 --- a/.trae/skills/karpathy-guidelines/SKILL.md +++ /dev/null @@ -1,70 +0,0 @@ ---- -name: "karpathy-guidelines" -description: "Karpathy 启发的编码行为指南,包含四大原则:编码前思考、简洁优先、精准修改、目标驱动执行。适用于所有编码任务,自动引导模型减少常见 LLM 编码错误。" ---- - -# Karpathy 编码行为指南 - -源自 [Andrej Karpathy 的观察](https://x.com/karpathy/status/2015883857489522876) 关于 LLM 编码陷阱的总结。 - -**权衡说明:** 这些指南倾向于谨慎而非速度。对于琐碎任务(简单拼写错误修复、显而易见的一行修改),请自行判断。 - -## 1. 编码前思考 - -**不要假设。不要隐藏困惑。呈现权衡。** - -在实现之前: -- 明确陈述你的假设。如果不确定,询问。 -- 如果存在多种解释,呈现它们 —— 不要默默选择。 -- 如果存在更简单的方法,说出来。适时提出异议。 -- 如果有不清楚的地方,停下来。指出困惑之处。询问。 - -## 2. 简洁优先 - -**用最少的代码解决问题。不要过度推测。** - -- 不添加要求之外的功能。 -- 不为一次性代码创建抽象。 -- 不添加未要求的"灵活性"或"可配置性"。 -- 不为不可能发生的场景做错误处理。 -- 如果你写了 200 行代码,而 50 行就能搞定,重写它。 - -**自问:** "资深工程师会觉得这过于复杂吗?" 如果是,简化。 - -## 3. 精准修改 - -**只碰必须碰的。只清理自己造成的混乱。** - -编辑现有代码时: -- 不要"改进"相邻的代码、注释或格式。 -- 不要重构没坏的东西。 -- 匹配现有风格,即使你更倾向于不同的写法。 -- 如果注意到无关的死代码,提一下 —— 不要删除它。 - -当你的改动产生孤儿代码时: -- 删除因你的改动而变得无用的导入/变量/函数。 -- 不要删除预先存在的死代码,除非被要求。 - -**检验标准:** 每一行修改都应该能直接追溯到用户的请求。 - -## 4. 目标驱动执行 - -**定义成功标准。循环验证直到达成。** - -将指令式任务转化为可验证的目标: -- "添加验证" → "为无效输入编写测试,然后让它们通过" -- "修复 bug" → "编写重现 bug 的测试,然后让它通过" -- "重构 X" → "确保重构前后测试都能通过" - -对于多步骤任务,说明一个简短的计划: -``` -1. [步骤] → 验证: [检查] -2. [步骤] → 验证: [检查] -3. [步骤] → 验证: [检查] -``` - -强有力的成功标准让你能够独立循环执行。弱标准("让它工作")需要不断澄清。 - ---- - -**这些指南在起作用的标志:** diff 中不必要的改动更少、因过度复杂而导致的重写更少、澄清问题在实现之前提出而不是在犯错之后。 diff --git a/backend/gateway/config/config.go b/backend/gateway/config/config.go index b0c8260..0c16287 100644 --- a/backend/gateway/config/config.go +++ b/backend/gateway/config/config.go @@ -8,17 +8,18 @@ import ( // Config 网关配置 type Config struct { - Server ServerConfig - Dubbo DubboConfig - JWT JWTConfig - OSS OSSConfig - Segment SegmentConfig - Dify DifyConfig - Minimax MinimaxConfig + Server ServerConfig + Dubbo DubboConfig + JWT JWTConfig + OSS OSSConfig + Segment SegmentConfig + Dify DifyConfig + Minimax MinimaxConfig LaserCompositor LaserCompositorConfig - Redis RedisConfig - DB DBConfig - Root string + Redis RedisConfig + DB DBConfig + WebSocket WebSocketConfig + Root string } // MinimaxConfig MiniMax 图像生成配置 @@ -36,13 +37,6 @@ type LaserCompositorConfig struct { type DifyConfig struct { APIBase string APIKey string - Server ServerConfig - Dubbo DubboConfig - JWT JWTConfig - OSS OSSConfig - Redis RedisConfig - WebSocket WebSocketConfig - Root string } // RedisConfig Redis 配置 @@ -175,6 +169,15 @@ func Load() *Config { Password: getEnv("REDIS_PASSWORD", ""), DB: getEnvInt("REDIS_DB", 0), }, + DB: DBConfig{ + Host: getEnv("DB_HOST", "localhost"), + Port: getEnvInt("DB_PORT", 5432), + User: getEnv("DB_USER", "postgres"), + Password: getEnv("DB_PASSWORD", ""), + DBName: getEnv("DB_NAME", "top-fans"), + SSLMode: getEnv("DB_SSLMODE", "disable"), + TimeZone: getEnv("DB_TIMEZONE", "Asia/Shanghai"), + }, WebSocket: WebSocketConfig{ AIChatPath: getEnv("WS_AI_CHAT_PATH", "/ws/ai-chat"), }, diff --git a/backend/gateway/controller/asset_controller.go b/backend/gateway/controller/asset_controller.go index 3c6e43a..154eeb7 100644 --- a/backend/gateway/controller/asset_controller.go +++ b/backend/gateway/controller/asset_controller.go @@ -1041,21 +1041,15 @@ func (ctrl *AssetController) generateOSSPolicyTokenWithKey( ossConfig config.OSSConfig, uploadKey string, ) (map[string]interface{}, error) { - + utcTime := time.Now().UTC() date := utcTime.Format("20060102") expiration := utcTime.Add(time.Duration(ossConfig.TokenExpireTime) * time.Second) - baseDir := ossConfig.GetUploadDir(uploadType) - uploadDir := baseDir - if userID != nil && starID != nil { - uploadDir = fmt.Sprintf("%s%d/%d/", baseDir, userID, starID) - } - + // 尝试 STS AssumeRole,失败则降级到永久 AccessKey 直连 var accessKeyID, accessKeySecret, securityToken string useSTS := false - // 尝试 STS AssumeRole,失败则降级到永久 AccessKey 直连 // 1. 创建 STS 凭证提供器 credConfig := new(credentials.Config). SetType("ram_role_arn"). @@ -1078,33 +1072,24 @@ func (ctrl *AssetController) generateOSSPolicyTokenWithKey( } else { logger.Logger.Warn("STS credential provider failed, falling back to direct AK/SK", zap.Error(err)) } - if !useSTS { accessKeyID = ossConfig.AccessKeyID accessKeySecret = ossConfig.AccessKeySecret } - // 2. 获取临时凭证 - cred, err := provider.GetCredential() - if err != nil { - return nil, fmt.Errorf("获取临时凭证失败: %w", err) - } - // 3. 构建 Policy - utcTime := time.Now().UTC() - date := utcTime.Format("20060102") - expiration := utcTime.Add(time.Duration(ossConfig.TokenExpireTime) * time.Second) - - // 构建 Policy 条件 + // 2. 构建 Policy 条件 conditions := []interface{}{ map[string]string{"bucket": ossConfig.BucketName}, // 限制 key 必须等于指定完整 key(更严:前端只能写到该路径) []interface{}{"eq", "$key", uploadKey}, map[string]string{"x-oss-signature-version": "OSS4-HMAC-SHA256"}, map[string]string{"x-oss-credential": fmt.Sprintf("%s/%s/%s/%s/aliyun_v4_request", - *cred.AccessKeyId, date, ossConfig.Region, "oss")}, + accessKeyID, date, ossConfig.Region, "oss")}, map[string]string{"x-oss-date": utcTime.Format("20060102T150405Z")}, - map[string]string{"x-oss-security-token": *cred.SecurityToken}, + } + if useSTS && securityToken != "" { + conditions = append(conditions, map[string]string{"x-oss-security-token": securityToken}) } policyMap := map[string]interface{}{ @@ -1112,33 +1097,36 @@ func (ctrl *AssetController) generateOSSPolicyTokenWithKey( "conditions": conditions, } - // 4. 生成 Policy 的 Base64 编码 + // 3. 生成 Policy 的 Base64 编码 policyJSON, err := json.Marshal(policyMap) if err != nil { return nil, fmt.Errorf("序列化 policy 失败: %w", err) } policyBase64 := base64.StdEncoding.EncodeToString(policyJSON) - // 5. 计算签名 - signingKey := buildSigningKey(*cred.AccessKeySecret, date, ossConfig.Region, "oss") + // 4. 计算签名 + signingKey := buildSigningKey(accessKeySecret, date, ossConfig.Region, "oss") signature := calculateSignature(signingKey, policyBase64) - // 6. 构建返回数据 + // 5. 构建返回数据 host := fmt.Sprintf("https://%s.oss-%s.aliyuncs.com", ossConfig.BucketName, ossConfig.Region) - return map[string]interface{}{ + result := map[string]interface{}{ "policy": policyBase64, - "security_token": *cred.SecurityToken, "x_oss_signature_version": "OSS4-HMAC-SHA256", "x_oss_credential": fmt.Sprintf("%s/%s/%s/%s/aliyun_v4_request", - *cred.AccessKeyId, date, ossConfig.Region, "oss"), + accessKeyID, date, ossConfig.Region, "oss"), "x_oss_date": utcTime.Format("20060102T150405Z"), "signature": signature, "host": host, "dir": dirFromKey(uploadKey), "key": uploadKey, "expire_time": expiration.Unix(), - }, nil + } + if useSTS && securityToken != "" { + result["security_token"] = securityToken + } + return result, nil } // generateOSSPolicyTokenWithDir 按显式目录生成 OSS 上传策略和签名 @@ -1157,15 +1145,26 @@ func (ctrl *AssetController) generateOSSPolicyTokenWithDir( SetPolicy(""). SetRoleSessionExpiration(ossConfig.TokenExpireTime) - provider, err := credentials.NewCredential(credConfig) - if err != nil { - return nil, fmt.Errorf("创建凭证提供器失败: %w", err) + // 尝试 STS AssumeRole,失败则降级到永久 AccessKey 直连 + var accessKeyID, accessKeySecret, securityToken string + useSTS := false + + 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 WithDir, falling back to direct AK/SK", zap.Error(err)) + } + } else { + logger.Logger.Warn("STS credential provider failed for WithDir, falling back to direct AK/SK", zap.Error(err)) } - // 2. 获取临时凭证 - cred, err := provider.GetCredential() - if err != nil { - return nil, fmt.Errorf("获取临时凭证失败: %w", err) + if !useSTS { + accessKeyID = ossConfig.AccessKeyID + accessKeySecret = ossConfig.AccessKeySecret } // 3. 构建 Policy