Files
2026-07-16 14:34:06 +08:00

134 lines
4.1 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}