feat(账号): 添加 Sora 账号双表同步与创建
- 新增 sora_accounts 表与 accounts.extra GIN 索引\n- OpenAI OAuth 支持同时创建 Sora 账号并同步配置\n- Token 刷新同步关联 Sora 账号凭证与扩展表\n- 增加 Sora 账号连通性测试与前端开关文案
This commit is contained in:
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
@@ -82,16 +83,26 @@ func (r *ClaudeTokenRefresher) Refresh(ctx context.Context, account *Account) (m
|
||||
|
||||
// OpenAITokenRefresher 处理 OpenAI OAuth token刷新
|
||||
type OpenAITokenRefresher struct {
|
||||
openaiOAuthService *OpenAIOAuthService
|
||||
openaiOAuthService *OpenAIOAuthService
|
||||
accountRepo AccountRepository
|
||||
soraAccountRepo SoraAccountRepository // Sora 扩展表仓储,用于双表同步
|
||||
}
|
||||
|
||||
// NewOpenAITokenRefresher 创建 OpenAI token刷新器
|
||||
func NewOpenAITokenRefresher(openaiOAuthService *OpenAIOAuthService) *OpenAITokenRefresher {
|
||||
func NewOpenAITokenRefresher(openaiOAuthService *OpenAIOAuthService, accountRepo AccountRepository) *OpenAITokenRefresher {
|
||||
return &OpenAITokenRefresher{
|
||||
openaiOAuthService: openaiOAuthService,
|
||||
accountRepo: accountRepo,
|
||||
}
|
||||
}
|
||||
|
||||
// SetSoraAccountRepo 设置 Sora 账号扩展表仓储
|
||||
// 用于在 Token 刷新时同步更新 sora_accounts 表
|
||||
// 如果未设置,syncLinkedSoraAccounts 只会更新 accounts.credentials
|
||||
func (r *OpenAITokenRefresher) SetSoraAccountRepo(repo SoraAccountRepository) {
|
||||
r.soraAccountRepo = repo
|
||||
}
|
||||
|
||||
// CanRefresh 检查是否能处理此账号
|
||||
// 只处理 openai 平台的 oauth 类型账号
|
||||
func (r *OpenAITokenRefresher) CanRefresh(account *Account) bool {
|
||||
@@ -112,6 +123,7 @@ func (r *OpenAITokenRefresher) NeedsRefresh(account *Account, refreshWindow time
|
||||
|
||||
// Refresh 执行token刷新
|
||||
// 保留原有credentials中的所有字段,只更新token相关字段
|
||||
// 刷新成功后,异步同步关联的 Sora 账号
|
||||
func (r *OpenAITokenRefresher) Refresh(ctx context.Context, account *Account) (map[string]any, error) {
|
||||
tokenInfo, err := r.openaiOAuthService.RefreshAccountToken(ctx, account)
|
||||
if err != nil {
|
||||
@@ -128,5 +140,68 @@ func (r *OpenAITokenRefresher) Refresh(ctx context.Context, account *Account) (m
|
||||
}
|
||||
}
|
||||
|
||||
// 异步同步关联的 Sora 账号(不阻塞主流程)
|
||||
if r.accountRepo != nil {
|
||||
go r.syncLinkedSoraAccounts(context.Background(), account.ID, newCredentials)
|
||||
}
|
||||
|
||||
return newCredentials, nil
|
||||
}
|
||||
|
||||
// syncLinkedSoraAccounts 同步关联的 Sora 账号的 token(双表同步)
|
||||
// 该方法异步执行,失败只记录日志,不影响主流程
|
||||
//
|
||||
// 同步策略:
|
||||
// 1. 更新 accounts.credentials(主表)
|
||||
// 2. 更新 sora_accounts 扩展表(如果 soraAccountRepo 已设置)
|
||||
//
|
||||
// 超时控制:30 秒,防止数据库阻塞导致 goroutine 泄漏
|
||||
func (r *OpenAITokenRefresher) syncLinkedSoraAccounts(ctx context.Context, openaiAccountID int64, newCredentials map[string]any) {
|
||||
// 添加超时控制,防止 goroutine 泄漏
|
||||
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 1. 查找所有关联的 Sora 账号(限定 platform='sora')
|
||||
soraAccounts, err := r.accountRepo.FindByExtraField(ctx, "linked_openai_account_id", openaiAccountID)
|
||||
if err != nil {
|
||||
log.Printf("[TokenSync] 查找关联 Sora 账号失败: openai_account_id=%d err=%v", openaiAccountID, err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(soraAccounts) == 0 {
|
||||
// 没有关联的 Sora 账号,直接返回
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 同步更新每个 Sora 账号的双表数据
|
||||
for _, soraAccount := range soraAccounts {
|
||||
// 2.1 更新 accounts.credentials(主表)
|
||||
soraAccount.Credentials["access_token"] = newCredentials["access_token"]
|
||||
soraAccount.Credentials["refresh_token"] = newCredentials["refresh_token"]
|
||||
if expiresAt, ok := newCredentials["expires_at"]; ok {
|
||||
soraAccount.Credentials["expires_at"] = expiresAt
|
||||
}
|
||||
|
||||
if err := r.accountRepo.Update(ctx, &soraAccount); err != nil {
|
||||
log.Printf("[TokenSync] 更新 Sora accounts 表失败: sora_account_id=%d openai_account_id=%d err=%v",
|
||||
soraAccount.ID, openaiAccountID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// 2.2 更新 sora_accounts 扩展表(如果仓储已设置)
|
||||
if r.soraAccountRepo != nil {
|
||||
soraUpdates := map[string]any{
|
||||
"access_token": newCredentials["access_token"],
|
||||
"refresh_token": newCredentials["refresh_token"],
|
||||
}
|
||||
if err := r.soraAccountRepo.Upsert(ctx, soraAccount.ID, soraUpdates); err != nil {
|
||||
log.Printf("[TokenSync] 更新 sora_accounts 表失败: account_id=%d openai_account_id=%d err=%v",
|
||||
soraAccount.ID, openaiAccountID, err)
|
||||
// 继续处理其他账号,不中断
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("[TokenSync] 成功同步 Sora 账号 token: sora_account_id=%d openai_account_id=%d dual_table=%v",
|
||||
soraAccount.ID, openaiAccountID, r.soraAccountRepo != nil)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user