134 lines
4.1 KiB
Go
134 lines
4.1 KiB
Go
package sync
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"net/http"
|
||
"net/url"
|
||
"sync"
|
||
"time"
|
||
|
||
consts "dataengine/consts/public"
|
||
"dataengine/utils"
|
||
|
||
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
|
||
"github.com/gogf/gf/v2/frame/g"
|
||
"github.com/gogf/gf/v2/os/gtime"
|
||
"github.com/sirupsen/logrus"
|
||
)
|
||
|
||
// refreshTokenMu 防止并发刷新 token(多个接口同时检测到过期时串行化)
|
||
var refreshTokenMu sync.Mutex
|
||
|
||
// tencentTokenResponse 腾讯广告 OAuth 刷新 token 响应
|
||
type tencentTokenResponse struct {
|
||
Code int `json:"code"`
|
||
Message string `json:"message"`
|
||
Data *struct {
|
||
AccessToken string `json:"access_token"`
|
||
RefreshToken string `json:"refresh_token"`
|
||
ExpiresIn int `json:"access_token_expires_in"`
|
||
RefreshExpiresIn int `json:"refresh_token_expires_in"`
|
||
} `json:"data"`
|
||
}
|
||
|
||
// RefreshTencentToken 刷新腾讯广告 OAuth2 token
|
||
// 由 ApiClient 在检测到 401/token 过期时自动调用
|
||
func RefreshTencentToken(ctx context.Context, platform *PlatformConfig) error {
|
||
refreshTokenMu.Lock()
|
||
defer refreshTokenMu.Unlock()
|
||
|
||
if platform.AuthConfig == nil {
|
||
return fmt.Errorf("平台 [%s] 未配置 auth_config", platform.PlatformCode)
|
||
}
|
||
|
||
clientID := platform.ClientId
|
||
clientSecret := platform.ClientSecret
|
||
refreshToken, _ := platform.AuthConfig["refresh_token"].(string)
|
||
|
||
if clientID == "" || clientSecret == "" || refreshToken == "" {
|
||
return fmt.Errorf("平台 [%s] OAuth2 配置不完整: client_id / client_secret / refresh_token 缺失",
|
||
platform.PlatformCode)
|
||
}
|
||
|
||
logrus.Infof("正在刷新腾讯广告 token [platform=%s]", platform.PlatformCode)
|
||
|
||
// 调用腾讯 OAuth 刷新端点(使用短超时防止阻塞)
|
||
refreshCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||
defer cancel()
|
||
|
||
// 腾讯 OAuth refresh_token 接口使用 GET 方法,参数在查询字符串中
|
||
refreshURL := fmt.Sprintf("https://api.e.qq.com/oauth/refresh_token?client_id=%s&client_secret=%s&refresh_token=%s",
|
||
url.QueryEscape(clientID), url.QueryEscape(clientSecret), url.QueryEscape(refreshToken))
|
||
req, err := http.NewRequestWithContext(refreshCtx, "GET", refreshURL, nil)
|
||
if err != nil {
|
||
return fmt.Errorf("创建刷新请求失败: %w", err)
|
||
}
|
||
|
||
resp, err := http.DefaultClient.Do(req)
|
||
if err != nil {
|
||
return fmt.Errorf("请求刷新 token 失败: %w", err)
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
body, err := io.ReadAll(resp.Body)
|
||
if err != nil {
|
||
return fmt.Errorf("读取刷新响应失败: %w", err)
|
||
}
|
||
|
||
var tokenResp tencentTokenResponse
|
||
if err := json.Unmarshal(body, &tokenResp); err != nil {
|
||
return fmt.Errorf("解析刷新响应失败: %w", err)
|
||
}
|
||
|
||
if tokenResp.Code != 0 {
|
||
return fmt.Errorf("腾讯 OAuth 刷新失败: code=%d, msg=%s", tokenResp.Code, tokenResp.Message)
|
||
}
|
||
|
||
if tokenResp.Data == nil {
|
||
return fmt.Errorf("腾讯 OAuth 刷新响应缺少 data 字段")
|
||
}
|
||
|
||
newAccessToken := tokenResp.Data.AccessToken
|
||
newRefreshToken := tokenResp.Data.RefreshToken
|
||
|
||
if newAccessToken == "" {
|
||
return fmt.Errorf("腾讯 OAuth 刷新返回的 access_token 为空")
|
||
}
|
||
|
||
// --- 更新内存中的配置 ---
|
||
// token 字段(存明文 token)
|
||
platform.Token = newAccessToken
|
||
// AccessToken 字段(运行时使用的 token)
|
||
platform.AccessToken = newAccessToken
|
||
// auth_config 中的 refresh_token
|
||
platform.AuthConfig["refresh_token"] = newRefreshToken
|
||
|
||
// --- 更新数据库 ---
|
||
tenantID := utils.GetCurrentTenantId(ctx)
|
||
if tenantID == 0 {
|
||
tenantID = 1 // 保底默认租户
|
||
}
|
||
|
||
// 使用 gfdb 直接更新 token 和 auth_config(避免 DAO 层的 OmitEmpty 吞掉空值)
|
||
_, err = gfdb.DB(ctx).Model(ctx, consts.DatasourcePlatformTable).
|
||
Data(g.Map{
|
||
"token": newAccessToken,
|
||
"auth_config": platform.AuthConfig,
|
||
"updated_at": gtime.Now(),
|
||
}).
|
||
Where("platform_code", platform.PlatformCode).
|
||
Where("tenant_id", tenantID).
|
||
Update()
|
||
if err != nil {
|
||
return fmt.Errorf("更新数据库 token 失败: %w", err)
|
||
}
|
||
|
||
logrus.Infof("腾讯广告 Token 刷新成功 [platform=%s] (access_token有效期:%ds, refresh_token有效期:%ds)",
|
||
platform.PlatformCode, tokenResp.Data.ExpiresIn, tokenResp.Data.RefreshExpiresIn)
|
||
|
||
return nil
|
||
}
|