Go语言自动化管理OpenAI访问令牌:opaitokens库实战指南
1. 项目概述与核心价值
最近在折腾一些需要调用OpenAI API的Go语言项目时,遇到了一个挺实际的问题:如何稳定、高效地获取和管理OpenAI的访问令牌。无论是个人开发还是团队协作,直接使用账号密码或者硬编码的API Key都存在安全风险和管理上的不便。尤其是在需要处理多账号、自动刷新令牌,或者对接一些第三方代理服务时,手动操作更是繁琐。于是,我花时间研究并封装了一个Go库,也就是今天要聊的fireinrain/opaitokens。这个库的核心目标很明确,就是帮你把获取、刷新OpenAI访问令牌这一系列“脏活累活”给自动化、标准化了。
简单来说,opaitokens是一个Go语言库,它封装了与OpenAI官方认证流程以及第三方服务(如ai.fakeopen.com)交互的细节。无论你是想用官方账号密码登录获取Access Token和Refresh Token,还是想利用共享令牌、池化令牌来管理多个账号资源,甚至是处理启用了MFA的账号,这个库都提供了相应的函数。对于需要在国内网络环境下开发的同行,它也考虑到了代理设置的问题。接下来,我会结合自己实际集成和使用这个库的经验,从设计思路、核心功能拆解、具体实现步骤到避坑指南,完整地分享一遍,希望能帮你省下不少摸索的时间。
2. 库的设计思路与架构解析
2.1 为什么需要这样一个库?
在深入代码之前,我们先聊聊为什么我会觉得有必要造这个轮子。OpenAI的API访问,核心凭证就是Access Token。传统方式下,开发者要么在代码里写死一个API Key(sk-开头),要么手动登录OpenAI官网,从开发者工具里复制出临时的Session Token或Access Token。这两种方式都有明显短板:硬编码的API Key一旦泄露,后果严重;手动获取的Token有过期时间,需要频繁人工干预,完全无法用于自动化流程。
更复杂的场景在于,有些项目会使用第三方中转服务(比如ai.fakeopen.com),这些服务可能使用共享令牌或池化令牌来聚合多个账号的额度或能力。手动管理这些令牌的生成、刷新和轮换,工作量巨大且容易出错。opaitokens库的诞生,就是为了将这些分散、手动的操作,统一成一套简洁的Go语言接口,让开发者能像调用一个普通函数一样,轻松获取到所需的令牌,从而更专注于业务逻辑的开发。
2.2 核心架构与模块划分
这个库的架构设计遵循了“单一职责”和“清晰分层”的原则。从顶层看,它主要提供了两大核心功能模块:
官方账号认证模块:对应
OpaiTokens结构体。这个模块模拟了用户通过邮箱和密码(以及MFA)登录OpenAI官网的完整流程,最终为你提取出官方的Access Token和更重要的Refresh Token。有了Refresh Token,你就可以在Access Token过期后,无需再次输入密码,直接刷新获取新的Access Token,这是实现长期自动化的关键。第三方服务交互模块:对应
FakeOpenTokens结构体。这个模块专门用于与ai.fakeopen.com这类服务进行交互。它提供了生成共享令牌、池化令牌、混合令牌以及用Refresh Token换取共享令牌等一系列高级功能。这对于需要利用多个OpenAI账号资源,或者使用第三方代理服务的场景非常有用。
这两个模块相对独立,你可以根据项目需求选择使用其中一个,或者组合使用。例如,你可以先用官方模块获取一批账号的Refresh Token并存储起来,然后在定时任务中,使用第三方模块的FetchSharedTokenWithRefreshToken方法来定期更新共享令牌,确保服务持续可用。
2.3 关键依赖与网络考虑
这个库底层依赖于HTTP客户端与OpenAI的认证服务器或第三方API进行通信。因此,网络连通性是一个基础前提。对于无法直接访问OpenAI服务的网络环境,库的使用说明里明确提到了需要通过设置HTTP_PROXY和HTTPS_PROXY环境变量来配置代理。这是一个非常务实的处理方式,将网络层的复杂性交给环境配置,保持了库本身的核心逻辑的纯粹性。
在代码层面,库应该使用了Go标准库的net/http或者类似resty这样的HTTP客户端库来发起请求,并解析返回的JSON数据。它需要处理登录时的CSRF Token、会话Cookie、以及应对OpenAI登录页面可能发生的改版。这部分逻辑通常封装在库的内部,对外暴露的只是简单的账号密码参数,这正是封装的价值所在。
3. 环境准备与基础配置
3.1 安装与引入库
首先,你需要将这个库添加到你的Go项目中。使用Go Modules管理依赖是最佳实践。
go get github.com/fireinrain/opaitokens在你的Go代码文件中,引入这个包:
import ( "fmt" "github.com/fireinrain/opaitokens" )注意:由于这个库涉及与外部API的交互,且可能处于活跃开发阶段,建议在使用前查看其GitHub仓库的Release版本或最新Commit,以确认API的稳定性。生产环境建议锁定某个特定的版本号。
3.2 网络代理配置详解
正如项目文档开头就强调的,如果你的开发或运行环境无法直接访问OpenAI,配置代理是第一步。这里详细解释一下环境变量的设置方法,因为这是后续所有功能能正常工作的基础。
Unix/Linux/macOS (Bash/Zsh Shell)在终端中执行以下命令,这些设置仅在当前终端会话中有效:
export http_proxy=http://你的代理服务器地址:端口 export https_proxy=http://你的代理服务器地址:端口如果你的代理服务器需要认证,格式通常是http://用户名:密码@代理地址:端口。
Windows (Command Prompt)
set http_proxy=http://你的代理服务器地址:端口 set https_proxy=http://你的代理服务器地址:端口Windows (PowerShell)
$env:http_proxy="http://你的代理服务器地址:端口" $env:https_proxy="http://你的代理服务器地址:端口"永久配置(可选)为了省去每次打开终端都要设置的麻烦,你可以将上述export或set命令添加到你的shell配置文件中(如~/.bashrc,~/.zshrc, 或系统环境变量中)。
实操心得:我强烈建议在编写自动化脚本或部署服务时,将代理配置作为程序启动逻辑的一部分,或者在Dockerfile中通过
ENV指令设置,而不是依赖宿主机不确定的环境。例如,可以在Go程序初始化时,通过os.Setenv来动态设置,但这需要你的程序有权限且知晓代理配置。更通用的做法是在容器或系统服务配置层面解决网络问题。
3.3 获取必要的账号凭证
使用这个库,你需要准备以下一种或多种凭证:
- OpenAI官方账号:邮箱和密码。这是最基础的凭证。
- MFA验证码:如果你的账号开启了二次验证(如Google Authenticator),则需要提供当前的一次性密码。
- Refresh Token:这是通过官方认证流程后获得的一个长期有效的令牌(通常有效期较长),用于刷新Access Token。一旦获得,可以安全存储,替代密码进行后续的令牌更新。
- Session Token:从浏览器会话中提取的临时令牌。
- 第三方服务信息:如果你使用
ai.fakeopen.com,可能需要在其平台注册并了解其具体的API端点和使用规则。
4. 核心功能使用指南与代码解析
接下来,我们逐一看一下库提供的各个功能如何使用,并解读背后的逻辑。
4.1 使用官方账号获取令牌
这是最直接的用法,模拟用户登录。
基础用法(无MFA)
package main import ( "fmt" "github.com/fireinrain/opaitokens" ) func main() { email := "your_email@example.com" password := "your_password" // 第三个参数 `useFakeOpenProxy` 设为 false,表示直接连接OpenAI官方 tokens := opaitokens.NewOpaiTokens(email, password, false) // 获取Token信息,这里包含了Access Token和Refresh Token等 tokenResult, err := tokens.FetchToken() if err != nil { panic(fmt.Sprintf("获取令牌失败: %v", err)) } fmt.Printf("Token结果: %+v\n", tokenResult) // 提取并使用Access Token accessToken := tokenResult.OpenaiToken.AccessToken fmt.Printf("获取到的Access Token: %s\n", accessToken) // 现在你可以用这个accessToken去调用OpenAI API了 // 使用Refresh Token刷新获取新的Access Token refreshedResult, err := tokens.RefreshToken() if err != nil { panic(fmt.Sprintf("刷新令牌失败: %v", err)) } newAccessToken := refreshedResult.RefreshedToken.AccessToken fmt.Printf("刷新后的Access Token: %s\n", newAccessToken) }代码解读:
NewOpaiTokens: 创建一个令牌获取器实例。第三个参数useFakeOpenProxy在这里为false,意味着库会直接向auth0.openai.com等官方端点发起登录请求。如果为true,则会使用库内可能内置的或指定的第三方代理来中转这个登录请求,这适用于某些特殊的网络配置。FetchToken: 执行完整的登录流程。内部会模拟浏览器行为,可能包括获取登录页、提交表单、处理重定向等,最终从响应中解析出令牌信息。返回的结构体通常包含原始的Access Token、Refresh Token、过期时间等。RefreshToken: 利用当前实例内保存的Refresh Token(在FetchToken成功后获得),向OpenAI的令牌刷新端点发起请求,获取一组全新的Access Token。这个过程不需要再次输入密码。
启用MFA的账号如果你的账号开启了二次验证,需要使用NewOpaiTokensWithMFA函数,并在登录时提供当前的MFA代码(通常是Google Authenticator等APP上显示的6位数字)。
email := "your_email@example.com" password := "your_password" mfaCode := "123456" // 从验证器APP获取的动态码 tokens := opaitokens.NewOpaiTokensWithMFA(email, password, mfaCode, false) // 后续的 FetchToken 和 RefreshToken 调用与上述相同注意事项:MFA代码有效期通常只有30秒。你需要确保在代码生成后尽快调用
FetchToken。自动化流程中处理MFA比较棘手,通常需要将账号的“备份代码”保存下来,或者在无头浏览器环境中配合自动化工具处理。这个库提供的MFA接口,更适用于半自动或手动触发刷新的场景。
4.2 与ai.fakeopen.com交互:共享令牌与池化令牌
ai.fakeopen.com是一个提供OpenAI API中转和增强服务的平台。opaitokens库封装了与其交互的几种常见模式。
获取共享令牌共享令牌(Shared Token)允许你将一个OpenAI账号的访问权限,以一个特定名称(unique_name)共享出来,生成一个FK或PK令牌供他人或其它服务使用。
tokens := opaitokens.FakeOpenTokens{} account := opaitokens.OpenaiAccount{ Email: "your_email@gmail.com", Password: "your_password", MFA: "", // 如果不需要MFA则留空 } sharedToken, err := tokens.FetchSharedToken(account, "my_unique_app_name") if err != nil { fmt.Printf("获取共享令牌失败: %v\n", err) return } fmt.Printf("共享令牌: %s\n", sharedToken.TokenKey)逻辑解析:这个函数内部很可能先调用类似官方登录的流程(或使用你提供的Refresh Token),为指定的账号获取一个有效的OpenAI Access Token。然后,它将这个Access Token连同你指定的唯一名称my_unique_app_name,提交给ai.fakeopen.com的特定API。该服务会返回一个它自己生成的令牌(FK或PK),这个令牌才是你最终用来访问其中转服务的凭证。
获取池化令牌池化令牌(Pooled Token)更加强大,它可以将多个OpenAI账号的资源“池化”在一起,生成一个聚合令牌。使用这个聚合令牌调用API时,服务可能会在背后的多个账号间进行负载均衡或故障转移。
var accounts []opaitokens.OpenaiAccount account1 := opaitokens.OpenaiAccount{Email: "account1@xx.com", Password: "pass1"} account2 := opaitokens.OpenaiAccount{Email: "account2@xx.com", Password: "pass2", MFA: ""} // 假设第二个账号无MFA accounts = append(accounts, account1, account2) tokens := opaitokens.FakeOpenTokens{} pooledToken, err := tokens.FetchPooledToken(accounts) if err != nil { fmt.Printf("获取池化令牌失败: %v\n", err) return } fmt.Printf("池化令牌: %s\n", pooledToken)重要提示:使用池化令牌需要你将多个账号的明文密码交给这个库,由它去依次登录并收集令牌。请务必确保你信任这个库的代码,并且只在安全的环境下运行。从安全角度考虑,更推荐先通过官方方式获取各个账号的Refresh Token并安全存储,然后使用下文提到的基于Refresh Token的方法。
刷新共享令牌以维持池化令牌ai.fakeopen.com的共享令牌和池化令牌可能有过期机制。为了让你创建的池化令牌长期有效,你需要定期刷新构成它的那些共享令牌。
var accounts []opaitokens.OpenaiAccount account := opaitokens.OpenaiAccount{Email: "account1@xx.com", Password: "pass1"} accounts = append(accounts, account) tokens := opaitokens.FakeOpenTokens{} renewResult, err := tokens.RenewSharedToken(accounts) if err != nil { fmt.Printf("刷新共享令牌失败: %v\n", err) return } fmt.Printf("刷新结果: %v\n", renewResult)这个RenewSharedToken函数会遍历你提供的账号列表,为每个账号重新获取(或刷新)其在ai.fakeopen.com上的共享令牌。通常建议将其设置为一个定时任务(例如每10天运行一次),以确保池化令牌不会因底层共享令牌失效而失效。
4.3 高级与混合用法
使用RefreshToken获取共享令牌这是更安全、更推荐用于自动化流程的方式。你只需要预先获得账号的Refresh Token,无需保存密码。
openaiAccountEmail := "your_email@gmail.com" openaiRefreshToken := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." // 你的Refresh Token uniqueName := "my_backend_service" tokens := opaitokens.FakeOpenTokens{} sharedToken, err := tokens.FetchSharedTokenWithRefreshToken(openaiAccountEmail, openaiRefreshToken, uniqueName) if err != nil { fmt.Printf("使用RefreshToken获取共享令牌失败: %v\n", err) return } fmt.Printf("共享令牌: %v\n", sharedToken)这种方法的安全性更高,因为Refresh Token的泄露风险相对于密码要低(它只能用于刷新令牌,不能直接登录),并且可以随时在OpenAI官网上撤销。
获取混合池化令牌这个功能允许你创建同时包含官方账号和直接API Key(sk-开头)的池化令牌。
var accounts []opaitokens.OpenaiAccount account := opaitokens.OpenaiAccount{Email: "account1@xx.com", Password: "pass1"} accounts = append(accounts, account) var skKeys []string skKeys = append(skKeys, "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx") // 你的OpenAI API Key tokens := opaitokens.FakeOpenTokens{} mixedToken, err := tokens.FetchMixedPooledToken(accounts, skKeys) if err != nil { fmt.Printf("获取混合池化令牌失败: %v\n", err) return } fmt.Printf("混合池化令牌: %s\n", mixedToken)这对于那些既有付费账号(通过密码/Refresh Token管理),又有一些直接购买的API Key的场景非常有用,可以统一管理所有资源。
通过Session Token获取Access Token如果你通过其他方式(例如浏览器插件)获取了一个Session Token,这个库也提供了直接转换的函数。
tokens := &opaitokens.FakeOpenTokens{} sessionToken := "your_session_token_here" accessTokenInfo, err := tokens.FetchAccessTokenBySessionToken(sessionToken) if err != nil { println("获取失败: ", err) return } fmt.Println("获取到的Access Token:", accessTokenInfo.AccessToken)Session Token是浏览器登录后保存在Cookie中的,有效期较短。这个方法适合快速测试或临时获取令牌,不适合长期自动化。
5. 生产环境实践与安全建议
将opaitokens用于生产环境,需要考虑安全性、可靠性和可维护性。
5.1 凭证的安全存储
绝对不要将邮箱、密码、Refresh Token或API Key硬编码在源代码中,尤其是提交到公开的版本库。
环境变量:这是最简单的方式。使用
os.Getenv在程序中读取。export OPENAI_EMAIL="your_email" export OPENAI_PASSWORD="your_password" export OPENAI_REFRESH_TOKEN="your_refresh_token"email := os.Getenv("OPENAI_EMAIL") refreshToken := os.Getenv("OPENAI_REFRESH_TOKEN")密钥管理服务:对于云上应用,使用AWS Secrets Manager、Azure Key Vault、Google Secret Manager或HashiCorp Vault等服务是更专业的选择。程序启动时从这些服务拉取凭证。
配置文件:如果必须使用配置文件,确保其权限严格受限(如
chmod 600 config.yaml),并且不被纳入版本控制。可以使用加密配置文件。
5.2 实现自动化的令牌刷新与轮换
自动化是使用这个库的核心价值。通常你需要一个后台守护进程或定时任务(如Cron Job, Systemd Timer, K8s CronJob)。
示例:一个简单的刷新服务
package main import ( "context" "fmt" "log" "os" "time" "github.com/fireinrain/opaitokens" ) // 假设我们有一个需要定期刷新的共享令牌配置 type TokenConfig struct { AccountEmail string RefreshToken string UniqueName string } func refreshTask(ctx context.Context, config TokenConfig) { ticker := time.NewTicker(7 * 24 * time.Hour) // 每7天运行一次,早于常见的14天失效期 defer ticker.Stop() for { select { case <-ctx.Done(): return case <-ticker.C: log.Println("开始执行令牌刷新任务...") tokens := opaitokens.FakeOpenTokens{} // 使用Refresh Token获取新的共享令牌 newToken, err := tokens.FetchSharedTokenWithRefreshToken( config.AccountEmail, config.RefreshToken, config.UniqueName, ) if err != nil { log.Printf("刷新令牌失败 (账号: %s): %v\n", config.AccountEmail, err) // 这里可以加入告警逻辑,如发送邮件、Slack消息等 continue } log.Printf("账号 %s 的共享令牌刷新成功。新令牌(前10位): %s...\n", config.AccountEmail, newToken.TokenKey[:min(10, len(newToken.TokenKey))]) // 重要:将新令牌更新到你的应用配置或数据库中,供业务服务使用 // updateTokenInDatabase(config.UniqueName, newToken.TokenKey) } } } func min(a, b int) int { if a < b { return a } return b } func main() { ctx := context.Background() config := TokenConfig{ AccountEmail: os.Getenv("OPENAI_ACCOUNT_EMAIL"), RefreshToken: os.Getenv("OPENAI_REFRESH_TOKEN"), UniqueName: "production_chat_service", } // 启动时先执行一次 go refreshTask(ctx, config) // 保持主程序运行 select {} }5.3 错误处理与重试机制
网络请求和第三方服务都可能不稳定,完善的错误处理至关重要。
- 分类处理错误:区分是网络错误、认证错误(如密码错误)、令牌失效错误还是服务端错误。
- 实现指数退避重试:对于暂时的网络故障,应该重试。
func fetchTokenWithRetry(tokens *opaitokens.OpaiTokens, maxRetries int) (*opaitokens.TokenResult, error) { var lastErr error for i := 0; i < maxRetries; i++ { result, err := tokens.FetchToken() if err == nil { return result, nil } lastErr = err // 判断是否为可重试错误(如网络超时) if isRetryableError(err) { sleepTime := time.Duration(math.Pow(2, float64(i))) * time.Second log.Printf("获取令牌失败,第%d次重试,等待%v后重试。错误: %v\n", i+1, sleepTime, err) time.Sleep(sleepTime) continue } else { // 如果是认证失败等不可重试错误,直接返回 return nil, err } } return nil, fmt.Errorf("在 %d 次重试后仍失败: %v", maxRetries, lastErr) } - 熔断与降级:如果某个账号或服务持续失败,可以考虑将其从令牌池中暂时移除(熔断),并记录日志告警。在令牌全部失效时,业务层应有降级策略(如返回友好错误提示)。
5.4 监控与日志
为令牌管理服务添加详细的日志记录,方便问题排查。
- 记录每次令牌获取/刷新的时间、使用的账号、是否成功、获取到的令牌前缀(切勿记录完整令牌!)。
- 监控令牌的过期时间,在到期前提前触发刷新。
- 使用Prometheus、OpenTelemetry等工具暴露指标,如
token_refresh_success_total,token_refresh_failure_total,token_expiry_seconds等。
6. 常见问题排查与实战技巧
在实际集成和使用过程中,你可能会遇到以下问题。这里我总结了一些排查思路和技巧。
6.1 认证失败相关问题
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
FetchToken返回密码错误或认证失败 | 1. 账号密码确实错误。 2. OpenAI登录页面改版,库的解析逻辑失效。 3. 账号被锁定或需要验证。 | 1. 手动在浏览器登录OpenAI确认密码。 2. 检查库的GitHub Issues页面,看是否有类似问题。 3. 检查邮箱是否有OpenAI发来的验证邮件。 |
| MFA验证失败 | 1. MFA代码已过期(超过30秒)。 2. 提供的MFA代码错误。 3. 账号的MFA方式不是TOTP(时间型)。 | 1. 确保生成代码后立即调用函数。 2. 核对验证器APP中的账号是否正确。 3. 目前库可能只支持标准的TOTP MFA。 |
| 获取Refresh Token失败 | 1. 登录流程成功,但解析响应时未找到Refresh Token字段。 2. OpenAI的API响应格式发生变化。 | 1. 打开调试日志(如果库支持),查看完整的HTTP响应。 2. 关注库的更新,可能需要升级版本。 |
技巧:对于重要的生产账号,建议先在测试环境或通过一个简单的Go脚本,单独测试
FetchToken功能,确保基础认证流程畅通,再集成到复杂系统中。
6.2 网络与代理问题
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 连接超时或网络错误 | 1. 代理未设置或设置错误。 2. 代理服务器本身不可用。 3. 目标服务(OpenAI或ai.fakeopen.com)被防火墙阻断。 | 1. 在终端执行curl -v https://api.openai.com测试代理是否生效。2. 尝试更换其他可用的代理服务器。 3. 确认代理规则是否正确,是否对目标域名放行。 |
| SSL证书验证错误 | 1. 代理服务器使用了自签名证书进行中间人解密。 2. 系统根证书库不完整。 | 1. 如果信任代理服务器,可以在HTTP客户端中设置InsecureSkipVerify: true(仅限测试环境)。2. 将代理服务器的CA证书添加到系统的信任链中。 |
6.3 第三方服务(ai.fakeopen.com)相关问题
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
FetchSharedToken返回无效响应或错误 | 1. ai.fakeopen.com 服务不可用或API变更。 2. 提供的账号密码无法登录OpenAI。 3. 该服务对免费账号或新注册用户有限制。 | 1. 访问 ai.fakeopen.com 网站或其文档,确认服务状态。 2. 直接用账号密码测试官方登录。 3. 查看该服务的用户协议或社区讨论,了解使用限制。 |
| 共享令牌或池化令牌很快失效 | 1. 底层OpenAI账号的会话或Access Token过期。 2. ai.fakeopen.com 自身的令牌过期策略。 3. 账号被OpenAI风控,限制了API访问。 | 1. 确保定期(如每5-7天)运行RenewSharedToken。2. 查阅ai.fakeopen.com的文档,确认其令牌有效期。 3. 避免高频、异常地使用同一个账号获取令牌,模拟人类操作间隔。 |
FetchPooledToken返回错误,提示账号无效 | 1. 账号列表中的某个或多个账号认证失败。 2. 池化令牌功能可能需要特定的服务套餐或权限。 | 1. 逐个账号测试FetchSharedToken,找出有问题的账号。2. 确认你在ai.fakeopen.com上是否有权创建池化令牌。 |
6.4 性能与并发考量
- 速率限制:无论是向OpenAI官方发起登录请求,还是调用ai.fakeopen.com的API,都要注意对方的速率限制。短时间内大量请求可能导致IP或账号被临时封禁。在批量处理多个账号时,请在请求间加入随机延迟(例如
time.Sleep(time.Duration(rand.Intn(3)+2) * time.Second))。 - 连接池:如果你需要高频次地调用库中的函数(例如为每个用户请求刷新令牌),确保你的HTTP客户端使用了连接池,以避免频繁建立TCP连接的开销。检查库内部是否使用了全局的
http.Client或者允许你传入自定义的Client。 - 令牌缓存:Access Token通常有1小时左右的有效期。在你的业务代码中,不应该每次调用API前都去获取新令牌。应该将获取到的令牌在内存中缓存起来(例如使用
sync.Map或go-cache库),并在接近过期时再触发刷新。
6.5 一个完整的集成示例片段
下面是一个更贴近真实场景的示例,展示了如何安全地使用环境变量、实现简单的令牌缓存和刷新。
package myai import ( "context" "fmt" "log" "os" "sync" "time" "github.com/fireinrain/opaitokens" ) type TokenManager struct { fakeOpenClient opaitokens.FakeOpenTokens sharedTokenCache map[string]cachedToken cacheMutex sync.RWMutex refreshToken string // 从安全存储中读取 accountEmail string uniqueName string } type cachedToken struct { tokenKey string expiryTime time.Time } func NewTokenManager() (*TokenManager, error) { email := os.Getenv("OPENAI_ACCOUNT_EMAIL") refToken := os.Getenv("OPENAI_REFRESH_TOKEN") if email == "" || refToken == "" { return nil, fmt.Errorf("缺少必要的环境变量: OPENAI_ACCOUNT_EMAIL 或 OPENAI_REFRESH_TOKEN") } return &TokenManager{ fakeOpenClient: opaitokens.FakeOpenTokens{}, sharedTokenCache: make(map[string]cachedToken), refreshToken: refToken, accountEmail: email, uniqueName: "my_service_token", }, nil } func (tm *TokenManager) GetValidSharedToken(ctx context.Context) (string, error) { tm.cacheMutex.RLock() cached, exists := tm.sharedTokenCache[tm.uniqueName] tm.cacheMutex.RUnlock() // 如果缓存存在且未过期(假设我们设定缓存1小时),则直接返回 if exists && time.Now().Before(cached.expiryTime) { log.Println("返回缓存的共享令牌") return cached.tokenKey, nil } // 缓存无效,获取新令牌 log.Println("缓存失效,获取新的共享令牌...") newToken, err := tm.fakeOpenClient.FetchSharedTokenWithRefreshToken( tm.accountEmail, tm.refreshToken, tm.uniqueName, ) if err != nil { return "", fmt.Errorf("获取共享令牌失败: %w", err) } // 更新缓存,设置过期时间为1小时后 newCached := cachedToken{ tokenKey: newToken.TokenKey, expiryTime: time.Now().Add(1 * time.Hour), } tm.cacheMutex.Lock() tm.sharedTokenCache[tm.uniqueName] = newCached tm.cacheMutex.Unlock() log.Println("共享令牌更新成功") return newToken.TokenKey, nil } // 业务函数,使用令牌调用AI服务 func (tm *TokenManager) CallAIService(ctx context.Context, prompt string) (string, error) { token, err := tm.GetValidSharedToken(ctx) if err != nil { return "", err } // 这里使用获取到的token(假设是ai.fakeopen.com的FK令牌)来构造请求 // 例如: httpClient.Post("https://ai.fakeopen.com/v1/chat/completions", ...) // 请求头中带上 Authorization: Bearer {token} // ... 实际调用逻辑 ... return "AI Response Placeholder", nil }这个TokenManager提供了一个简单的封装,它内部缓存了共享令牌,避免了不必要的重复获取,同时也为未来的扩展(如多账号管理、更复杂的刷新策略)留下了空间。在实际项目中,你可以将其作为基础设施层的一个组件,为所有需要调用AI服务的业务模块提供可靠的令牌。
