| package service
|
|
|
| import (
|
| "bytes"
|
| "encoding/json"
|
| "fmt"
|
| "io"
|
| "log"
|
| "net/http"
|
| "strings"
|
| "time"
|
|
|
| "zencoder-2api/internal/model"
|
| "zencoder-2api/internal/database"
|
| "zencoder-2api/internal/service/provider"
|
| )
|
|
|
|
|
| type RefreshTokenRequest struct {
|
| GrantType string `json:"grant_type"`
|
| RefreshToken string `json:"refresh_token"`
|
| }
|
|
|
|
|
| type RefreshTokenResponse struct {
|
| TokenType string `json:"token_type"`
|
| AccessToken string `json:"access_token"`
|
| IDToken string `json:"id_token"`
|
| RefreshToken string `json:"refresh_token"`
|
| ExpiresIn int `json:"expires_in"`
|
| Federated map[string]interface{} `json:"federated"`
|
|
|
|
|
| UserID string `json:"-"`
|
| Email string `json:"-"`
|
| }
|
|
|
|
|
| type AccountLockoutError struct {
|
| StatusCode int
|
| Body string
|
| AccountID string
|
| }
|
|
|
| func (e *AccountLockoutError) Error() string {
|
| return fmt.Sprintf("account %s is locked out: status %d, body: %s", e.AccountID, e.StatusCode, e.Body)
|
| }
|
|
|
|
|
| func isAccountLockoutError(statusCode int, body string) bool {
|
| if statusCode == 400 {
|
|
|
| return strings.Contains(body, "User is locked out") ||
|
| strings.Contains(body, "user is locked out") ||
|
| strings.Contains(body, "locked out")
|
| }
|
| return false
|
| }
|
|
|
|
|
| func markAccountAsBanned(account *model.Account, reason string) error {
|
| updates := map[string]interface{}{
|
| "status": "banned",
|
| "is_active": false,
|
| "is_cooling": false,
|
| "ban_reason": reason,
|
| "updated_at": time.Now(),
|
| }
|
|
|
| if err := database.GetDB().Model(&model.Account{}).
|
| Where("id = ?", account.ID).
|
| Updates(updates).Error; err != nil {
|
| return fmt.Errorf("failed to update account status: %w", err)
|
| }
|
|
|
| log.Printf("[账号管理] 账号 %s (ID:%d) 已标记为封禁状态: %s", account.ClientID, account.ID, reason)
|
| return nil
|
| }
|
|
|
|
|
| func isRefreshTokenInvalidError(statusCode int, body string) bool {
|
| if statusCode == 401 {
|
| return strings.Contains(body, "Refresh token is not valid") ||
|
| strings.Contains(body, "refresh token is not valid") ||
|
| strings.Contains(body, "invalid refresh token") ||
|
| strings.Contains(body, "refresh_token is invalid")
|
| }
|
| return false
|
| }
|
|
|
|
|
| func markTokenRecordAsBanned(record *model.TokenRecord, reason string) error {
|
| updates := map[string]interface{}{
|
| "status": "banned",
|
| "is_active": false,
|
| "ban_reason": reason,
|
| "updated_at": time.Now(),
|
| }
|
|
|
| if err := database.GetDB().Model(&model.TokenRecord{}).
|
| Where("id = ?", record.ID).
|
| Updates(updates).Error; err != nil {
|
| return fmt.Errorf("failed to update token record status: %w", err)
|
| }
|
|
|
| log.Printf("[Token管理] Token记录 #%d 已标记为封禁状态: %s", record.ID, reason)
|
| return nil
|
| }
|
|
|
|
|
| func markTokenRecordAsExpired(record *model.TokenRecord, reason string) error {
|
| updates := map[string]interface{}{
|
| "status": "expired",
|
| "is_active": false,
|
| "ban_reason": reason,
|
| "updated_at": time.Now(),
|
| }
|
|
|
| if err := database.GetDB().Model(&model.TokenRecord{}).
|
| Where("id = ?", record.ID).
|
| Updates(updates).Error; err != nil {
|
| return fmt.Errorf("failed to update token record status: %w", err)
|
| }
|
|
|
| log.Printf("[Token管理] Token记录 #%d 已标记为过期状态: %s", record.ID, reason)
|
| return nil
|
| }
|
|
|
|
|
| func disableTokenRecordsByEmail(email string, reason string) error {
|
| updates := map[string]interface{}{
|
| "status": "banned",
|
| "is_active": false,
|
| "ban_reason": reason,
|
| "updated_at": time.Now(),
|
| }
|
|
|
| result := database.GetDB().Model(&model.TokenRecord{}).
|
| Where("email = ? AND status = ?", email, "active").
|
| Updates(updates)
|
|
|
| if result.Error != nil {
|
| return fmt.Errorf("failed to disable token records: %w", result.Error)
|
| }
|
|
|
| if result.RowsAffected > 0 {
|
| log.Printf("[Token管理] 已禁用邮箱 %s 相关的 %d 条token记录: %s", email, result.RowsAffected, reason)
|
| }
|
|
|
| return nil
|
| }
|
|
|
|
|
| func RefreshAccessToken(refreshToken string, proxy string) (*RefreshTokenResponse, error) {
|
| url := "https://auth.zencoder.ai/api/frontegg/oauth/token"
|
|
|
|
|
| if IsDebugMode() {
|
| log.Printf("[DEBUG] [RefreshToken] >>> 开始刷新Token")
|
| log.Printf("[DEBUG] [RefreshToken] 请求URL: %s", url)
|
| if len(refreshToken) > 20 {
|
| log.Printf("[DEBUG] [RefreshToken] RefreshToken: %s...", refreshToken[:20])
|
| } else {
|
| log.Printf("[DEBUG] [RefreshToken] RefreshToken: %s", refreshToken)
|
| }
|
| }
|
|
|
| reqBody := RefreshTokenRequest{
|
| GrantType: "refresh_token",
|
| RefreshToken: refreshToken,
|
| }
|
|
|
| jsonData, err := json.Marshal(reqBody)
|
| if err != nil {
|
| return nil, fmt.Errorf("failed to marshal request: %w", err)
|
| }
|
|
|
| if IsDebugMode() {
|
| log.Printf("[DEBUG] [RefreshToken] 请求Body: %s", string(jsonData))
|
| }
|
|
|
| req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
| if err != nil {
|
| return nil, fmt.Errorf("failed to create request: %w", err)
|
| }
|
|
|
|
|
| req.Header.Set("Accept", "*/*")
|
| req.Header.Set("Accept-Encoding", "gzip, deflate, br")
|
| req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7,ja;q=0.6")
|
| req.Header.Set("Cache-Control", "no-cache")
|
| req.Header.Set("Content-Type", "application/json")
|
| req.Header.Set("Origin", "https://auth.zencoder.ai")
|
| req.Header.Set("Pragma", "no-cache")
|
| req.Header.Set("Priority", "u=1, i")
|
| req.Header.Set("Sec-Ch-Ua", `"Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24"`)
|
| req.Header.Set("Sec-Ch-Ua-Mobile", "?0")
|
| req.Header.Set("Sec-Ch-Ua-Platform", `"Windows"`)
|
| req.Header.Set("Sec-Fetch-Dest", "empty")
|
| req.Header.Set("Sec-Fetch-Mode", "cors")
|
| req.Header.Set("Sec-Fetch-Site", "same-origin")
|
| req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36")
|
| req.Header.Set("X-Frontegg-Framework", "react@18.2.0")
|
| req.Header.Set("X-Frontegg-Sdk", "@frontegg/react@7.12.14")
|
|
|
|
|
| client := provider.NewHTTPClient(proxy, 30*time.Second)
|
|
|
| if IsDebugMode() {
|
| log.Printf("[DEBUG] [RefreshToken] → 发送请求...")
|
| }
|
|
|
| resp, err := client.Do(req)
|
| if err != nil {
|
| if IsDebugMode() {
|
| log.Printf("[DEBUG] [RefreshToken] ✗ 请求失败: %v", err)
|
| }
|
| return nil, fmt.Errorf("failed to send request: %w", err)
|
| }
|
| defer resp.Body.Close()
|
|
|
| if IsDebugMode() {
|
| log.Printf("[DEBUG] [RefreshToken] ← 收到响应: status=%d", resp.StatusCode)
|
|
|
| log.Printf("[DEBUG] [RefreshToken] 响应头:")
|
| for k, v := range resp.Header {
|
| log.Printf("[DEBUG] [RefreshToken] %s: %v", k, v)
|
| }
|
| }
|
|
|
| body, err := io.ReadAll(resp.Body)
|
| if err != nil {
|
| return nil, fmt.Errorf("failed to read response: %w", err)
|
| }
|
|
|
| if IsDebugMode() {
|
| log.Printf("[DEBUG] [RefreshToken] 响应Body: %s", string(body))
|
| }
|
|
|
| if resp.StatusCode != http.StatusOK {
|
| if IsDebugMode() {
|
| log.Printf("[DEBUG] [RefreshToken] ✗ API错误: %d - %s", resp.StatusCode, string(body))
|
| }
|
|
|
|
|
| if isAccountLockoutError(resp.StatusCode, string(body)) {
|
| return nil, &AccountLockoutError{
|
| StatusCode: resp.StatusCode,
|
| Body: string(body),
|
| }
|
| }
|
|
|
|
|
| if isRefreshTokenInvalidError(resp.StatusCode, string(body)) {
|
| return nil, fmt.Errorf("refresh token expired or invalid: status %d, body: %s", resp.StatusCode, string(body))
|
| }
|
|
|
| return nil, fmt.Errorf("failed to refresh token: status %d, body: %s", resp.StatusCode, string(body))
|
| }
|
|
|
| var tokenResp RefreshTokenResponse
|
| if err := json.Unmarshal(body, &tokenResp); err != nil {
|
| return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
| }
|
|
|
|
|
| if tokenResp.UserID == "" && tokenResp.AccessToken != "" {
|
| if payload, err := ParseJWT(tokenResp.AccessToken); err == nil {
|
|
|
| if payload.Email != "" {
|
| tokenResp.UserID = payload.Email
|
| tokenResp.Email = payload.Email
|
| } else if payload.Subject != "" {
|
| tokenResp.UserID = payload.Subject
|
| }
|
|
|
| if IsDebugMode() {
|
| log.Printf("[DEBUG] [RefreshToken] 从JWT解析UserID: %s", tokenResp.UserID)
|
| log.Printf("[DEBUG] [RefreshToken] JWT Payload - Email: %s, Subject: %s",
|
| payload.Email, payload.Subject)
|
| }
|
| } else {
|
| if IsDebugMode() {
|
| log.Printf("[DEBUG] [RefreshToken] 解析JWT失败: %v", err)
|
| }
|
| }
|
| }
|
|
|
| if IsDebugMode() {
|
| accessTokenPreview := tokenResp.AccessToken
|
| if len(accessTokenPreview) > 20 {
|
| accessTokenPreview = accessTokenPreview[:20]
|
| }
|
| log.Printf("[DEBUG] [RefreshToken] <<< 刷新成功: UserID=%s, AccessToken=%s..., ExpiresIn=%d",
|
| tokenResp.UserID,
|
| accessTokenPreview,
|
| tokenResp.ExpiresIn)
|
| }
|
|
|
| return &tokenResp, nil
|
| }
|
|
|
|
|
| func min(a, b int) int {
|
| if a < b {
|
| return a
|
| }
|
| return b
|
| }
|
|
|
|
|
| func UpdateAccountToken(account *model.Account) error {
|
| if account.RefreshToken == "" {
|
| return fmt.Errorf("account %s has no refresh token", account.ClientID)
|
| }
|
|
|
|
|
| tokenResp, err := RefreshAccessToken(account.RefreshToken, account.Proxy)
|
| if err != nil {
|
|
|
| if lockoutErr, ok := err.(*AccountLockoutError); ok {
|
|
|
| if markErr := markAccountAsBanned(account, "用户被锁定: "+lockoutErr.Body); markErr != nil {
|
| log.Printf("[账号管理] 标记账号封禁状态失败: %v", markErr)
|
| }
|
| }
|
| return fmt.Errorf("failed to refresh token for account %s: %w", account.ClientID, err)
|
| }
|
|
|
|
|
| expiry := time.Now().Add(time.Duration(tokenResp.ExpiresIn) * time.Second)
|
|
|
|
|
| updates := map[string]interface{}{
|
| "access_token": tokenResp.AccessToken,
|
| "refresh_token": tokenResp.RefreshToken,
|
| "token_expiry": expiry,
|
| "updated_at": time.Now(),
|
| }
|
|
|
| if err := database.DB.Model(&model.Account{}).
|
| Where("id = ?", account.ID).
|
| Updates(updates).Error; err != nil {
|
| return fmt.Errorf("failed to update account token: %w", err)
|
| }
|
|
|
|
|
| account.AccessToken = tokenResp.AccessToken
|
| account.RefreshToken = tokenResp.RefreshToken
|
| account.TokenExpiry = expiry
|
|
|
| debugLogf("✅ Refreshed token for account %s, expires at %s", account.ClientID, expiry.Format(time.RFC3339))
|
|
|
| return nil
|
| }
|
|
|
|
|
| func UpdateTokenRecordToken(record *model.TokenRecord) error {
|
| if record.RefreshToken == "" {
|
| return fmt.Errorf("token record %d has no refresh token", record.ID)
|
| }
|
|
|
|
|
| tokenResp, err := RefreshAccessToken(record.RefreshToken, "")
|
| if err != nil {
|
|
|
| if lockoutErr, ok := err.(*AccountLockoutError); ok {
|
|
|
| if markErr := markTokenRecordAsBanned(record, "账号被锁定: "+lockoutErr.Body); markErr != nil {
|
| log.Printf("[Token管理] 标记token记录封禁状态失败: %v", markErr)
|
| }
|
|
|
| if record.Email != "" {
|
| if disableErr := disableTokenRecordsByEmail(record.Email, "关联账号被锁定"); disableErr != nil {
|
| log.Printf("[Token管理] 禁用相关token记录失败: %v", disableErr)
|
| }
|
| }
|
| return fmt.Errorf("token record %d account locked out: %w", record.ID, err)
|
| }
|
|
|
|
|
| if strings.Contains(err.Error(), "refresh token expired or invalid") {
|
|
|
| if markErr := markTokenRecordAsExpired(record, "Refresh token过期或无效"); markErr != nil {
|
| log.Printf("[Token管理] 标记token记录过期状态失败: %v", markErr)
|
| }
|
| return fmt.Errorf("token record %d refresh token expired: %w", record.ID, err)
|
| }
|
|
|
| return fmt.Errorf("failed to refresh token for record %d: %w", record.ID, err)
|
| }
|
|
|
|
|
| expiry := time.Now().Add(time.Duration(tokenResp.ExpiresIn) * time.Second)
|
|
|
|
|
| updates := map[string]interface{}{
|
| "token": tokenResp.AccessToken,
|
| "refresh_token": tokenResp.RefreshToken,
|
| "token_expiry": expiry,
|
| "status": "active",
|
| "updated_at": time.Now(),
|
| }
|
|
|
| if err := database.DB.Model(&model.TokenRecord{}).
|
| Where("id = ?", record.ID).
|
| Updates(updates).Error; err != nil {
|
| return fmt.Errorf("failed to update token record: %w", err)
|
| }
|
|
|
|
|
| record.Token = tokenResp.AccessToken
|
| record.RefreshToken = tokenResp.RefreshToken
|
| record.TokenExpiry = expiry
|
| record.Status = "active"
|
|
|
| debugLogf("✅ Refreshed token for record %d, expires at %s", record.ID, expiry.Format(time.RFC3339))
|
|
|
| return nil
|
| }
|
|
|
|
|
| func CheckAndRefreshToken(account *model.Account) error {
|
|
|
| if account.RefreshToken == "" {
|
| return nil
|
| }
|
|
|
|
|
| if time.Until(account.TokenExpiry) < time.Hour {
|
| debugLogf("⚠️ Token for account %s expires in %v, refreshing...",
|
| account.ClientID, time.Until(account.TokenExpiry))
|
| return UpdateAccountToken(account)
|
| }
|
|
|
| return nil
|
| }
|
|
|
|
|
| func CheckAndRefreshTokenRecord(record *model.TokenRecord) error {
|
|
|
| if record.RefreshToken == "" {
|
| return nil
|
| }
|
|
|
|
|
| if time.Until(record.TokenExpiry) < time.Hour {
|
| debugLogf("⚠️ Token for record %d expires in %v, refreshing...",
|
| record.ID, time.Until(record.TokenExpiry))
|
| return UpdateTokenRecordToken(record)
|
| }
|
|
|
| return nil
|
| }
|
|
|
|
|
| func StartTokenRefreshScheduler() {
|
| go func() {
|
|
|
| refreshExpiredTokens()
|
|
|
|
|
| ticker := time.NewTicker(1 * time.Minute)
|
| defer ticker.Stop()
|
|
|
| for range ticker.C {
|
| refreshExpiredTokens()
|
| }
|
| }()
|
|
|
| log.Printf("🔄 Token refresh scheduler started - checking every minute")
|
| }
|
|
|
|
|
| func refreshExpiredTokens() {
|
| now := time.Now()
|
| threshold := now.Add(time.Hour)
|
|
|
|
|
| var accounts []model.Account
|
| if err := database.DB.Where("token_expiry < ?", threshold).
|
| Where("status != ?", "banned").
|
| Find(&accounts).Error; err == nil {
|
|
|
| for _, account := range accounts {
|
|
|
| if account.ClientSecret == "refresh-token-login" {
|
|
|
| if account.RefreshToken != "" {
|
| if err := UpdateAccountToken(&account); err != nil {
|
| log.Printf("[Token刷新] ❌ refresh-token账号 %s 刷新失败: %v", account.ClientID, err)
|
| }
|
| }
|
| } else {
|
|
|
| if account.ClientID != "" && account.ClientSecret != "" {
|
| if err := refreshAccountToken(&account); err != nil {
|
| log.Printf("[Token刷新] ❌ 账号 %s OAuth刷新失败: %v", account.ClientID, err)
|
| }
|
| }
|
| }
|
| }
|
| }
|
|
|
|
|
| var records []model.TokenRecord
|
| if err := database.DB.Where("refresh_token != '' AND token_expiry < ?", threshold).
|
| Where("status != ?", "banned").
|
| Find(&records).Error; err == nil {
|
|
|
| for _, record := range records {
|
| if err := UpdateTokenRecordToken(&record); err != nil {
|
| log.Printf("[Token刷新] ❌ 生成token #%d 刷新失败: %v", record.ID, err)
|
| }
|
| }
|
| }
|
| }
|
|
|
|
|
| func debugLogf(format string, args ...interface{}) {
|
| if IsDebugMode() {
|
| log.Printf("[DEBUG] "+format, args...)
|
| }
|
| }
|
|
|
|
|
| func RefreshTokenAndAccounts(tokenRecordID uint) error {
|
|
|
| var record model.TokenRecord
|
| if err := database.GetDB().First(&record, tokenRecordID).Error; err != nil {
|
| return fmt.Errorf("获取token记录失败: %w", err)
|
| }
|
|
|
| if record.RefreshToken == "" {
|
| return fmt.Errorf("token记录没有refresh_token")
|
| }
|
|
|
|
|
| log.Printf("[Token刷新] 开始刷新token记录 #%d", tokenRecordID)
|
|
|
|
|
| tokenResp, err := RefreshAccessToken(record.RefreshToken, "")
|
| if err != nil {
|
|
|
| if lockoutErr, ok := err.(*AccountLockoutError); ok {
|
|
|
| if markErr := markTokenRecordAsBanned(&record, "账号被锁定: "+lockoutErr.Body); markErr != nil {
|
| log.Printf("[Token管理] 标记token记录封禁状态失败: %v", markErr)
|
| }
|
|
|
| if record.Email != "" {
|
| if disableErr := disableTokenRecordsByEmail(record.Email, "关联账号被锁定"); disableErr != nil {
|
| log.Printf("[Token管理] 禁用相关token记录失败: %v", disableErr)
|
| }
|
| }
|
| }
|
|
|
|
|
| if strings.Contains(err.Error(), "refresh token expired or invalid") {
|
|
|
| if markErr := markTokenRecordAsExpired(&record, "Refresh token过期或无效"); markErr != nil {
|
| log.Printf("[Token管理] 标记token记录过期状态失败: %v", markErr)
|
| }
|
| }
|
|
|
| return fmt.Errorf("刷新token失败: %w", err)
|
| }
|
|
|
|
|
| expiry := time.Now().Add(time.Duration(tokenResp.ExpiresIn) * time.Second)
|
|
|
|
|
| updates := map[string]interface{}{
|
| "token": tokenResp.AccessToken,
|
| "refresh_token": tokenResp.RefreshToken,
|
| "token_expiry": expiry,
|
| "status": "active",
|
| "ban_reason": "",
|
| "updated_at": time.Now(),
|
| }
|
|
|
| if err := database.GetDB().Model(&model.TokenRecord{}).
|
| Where("id = ?", tokenRecordID).
|
| Updates(updates).Error; err != nil {
|
| return fmt.Errorf("更新token记录失败: %w", err)
|
| }
|
|
|
|
|
| email := ""
|
| if payload, err := ParseJWT(tokenResp.AccessToken); err == nil {
|
| email = payload.Email
|
| log.Printf("[Token刷新] 解析到邮箱: %s", email)
|
| } else {
|
| log.Printf("[Token刷新] 无法解析JWT获取邮箱: %v", err)
|
| return nil
|
| }
|
|
|
| if email == "" {
|
| log.Printf("[Token刷新] 邮箱为空,跳过账号刷新")
|
| return nil
|
| }
|
|
|
|
|
| go refreshAccountsByEmail(email)
|
|
|
| return nil
|
| }
|
|
|
|
|
| func refreshAccountsByEmail(email string) {
|
| log.Printf("[账号刷新] 开始刷新邮箱 %s 的所有账号", email)
|
|
|
|
|
| var accounts []model.Account
|
| if err := database.GetDB().Where("email = ?", email).Find(&accounts).Error; err != nil {
|
| log.Printf("[账号刷新] 查询邮箱 %s 的账号失败: %v", email, err)
|
| return
|
| }
|
|
|
| if len(accounts) == 0 {
|
| log.Printf("[账号刷新] 没有找到邮箱 %s 的账号", email)
|
| return
|
| }
|
|
|
| log.Printf("[账号刷新] 找到 %d 个账号需要刷新", len(accounts))
|
|
|
|
|
| successCount := 0
|
| failCount := 0
|
|
|
| for _, account := range accounts {
|
|
|
| if account.ClientID == "" || account.ClientSecret == "" {
|
| log.Printf("[账号刷新] 账号 ID:%d 缺少client_id或client_secret,跳过", account.ID)
|
| continue
|
| }
|
|
|
| log.Printf("[账号刷新] 正在刷新账号 ID:%d (ClientID: %s)", account.ID, account.ClientID)
|
|
|
|
|
| if err := refreshAccountToken(&account); err != nil {
|
| log.Printf("[账号刷新] 账号 ID:%d 刷新失败: %v", account.ID, err)
|
| failCount++
|
| } else {
|
| log.Printf("[账号刷新] 账号 ID:%d 刷新成功", account.ID)
|
| successCount++
|
| }
|
|
|
|
|
| time.Sleep(100 * time.Millisecond)
|
| }
|
|
|
| log.Printf("[账号刷新] 邮箱 %s 的账号刷新完成 - 成功: %d, 失败: %d",
|
| email, successCount, failCount)
|
| }
|
|
|
|
|
| func RefreshAccountToken(account *model.Account) error {
|
| return refreshAccountToken(account)
|
| }
|
|
|
|
|
| func refreshAccountToken(account *model.Account) error {
|
|
|
| url := "https://fe.zencoder.ai/oauth/token"
|
|
|
| reqBody := map[string]string{
|
| "grant_type": "client_credentials",
|
| "client_id": account.ClientID,
|
| "client_secret": account.ClientSecret,
|
| }
|
|
|
| jsonData, err := json.Marshal(reqBody)
|
| if err != nil {
|
| return fmt.Errorf("序列化请求失败: %w", err)
|
| }
|
|
|
| req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
| if err != nil {
|
| return fmt.Errorf("创建请求失败: %w", err)
|
| }
|
|
|
|
|
| req.Header.Set("Content-Type", "application/json")
|
| req.Header.Set("Accept", "application/json")
|
| req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
|
|
|
|
|
| client := provider.NewHTTPClient(account.Proxy, 30*time.Second)
|
|
|
| resp, err := client.Do(req)
|
| if err != nil {
|
| return fmt.Errorf("发送请求失败: %w", err)
|
| }
|
| defer resp.Body.Close()
|
|
|
| body, err := io.ReadAll(resp.Body)
|
| if err != nil {
|
| return fmt.Errorf("读取响应失败: %w", err)
|
| }
|
|
|
| if resp.StatusCode != http.StatusOK {
|
|
|
| if isAccountLockoutError(resp.StatusCode, string(body)) {
|
|
|
| if markErr := markAccountAsBanned(account, "OAuth认证失败-用户被锁定: "+string(body)); markErr != nil {
|
| log.Printf("[账号管理] 标记账号封禁状态失败: %v", markErr)
|
| }
|
| return &AccountLockoutError{
|
| StatusCode: resp.StatusCode,
|
| Body: string(body),
|
| AccountID: account.ClientID,
|
| }
|
| }
|
| return fmt.Errorf("API返回错误: %d - %s", resp.StatusCode, string(body))
|
| }
|
|
|
|
|
| var tokenResp struct {
|
| AccessToken string `json:"access_token"`
|
| TokenType string `json:"token_type"`
|
| ExpiresIn int `json:"expires_in"`
|
| RefreshToken string `json:"refresh_token"`
|
| }
|
|
|
| if err := json.Unmarshal(body, &tokenResp); err != nil {
|
| return fmt.Errorf("解析响应失败: %w", err)
|
| }
|
|
|
|
|
| expiry := time.Now().Add(time.Duration(tokenResp.ExpiresIn) * time.Second)
|
|
|
|
|
| planType := account.PlanType
|
| dailyUsed := account.DailyUsed
|
| totalUsed := account.TotalUsed
|
|
|
| if payload, err := ParseJWT(tokenResp.AccessToken); err == nil {
|
|
|
| if payload.CustomClaims.Plan != "" {
|
| planType = model.PlanType(payload.CustomClaims.Plan)
|
| }
|
|
|
| if account.Email != "" && payload.Email != account.Email {
|
| log.Printf("[账号刷新] 警告: 账号 ID:%d 邮箱不匹配 (期望: %s, 实际: %s)",
|
| account.ID, account.Email, payload.Email)
|
| }
|
| }
|
|
|
|
|
| updates := map[string]interface{}{
|
| "access_token": tokenResp.AccessToken,
|
| "refresh_token": tokenResp.RefreshToken,
|
| "token_expiry": expiry,
|
| "plan_type": planType,
|
| "daily_used": dailyUsed,
|
| "total_used": totalUsed,
|
| "updated_at": time.Now(),
|
| }
|
|
|
| return database.GetDB().Model(&model.Account{}).
|
| Where("id = ?", account.ID).
|
| Updates(updates).Error
|
| } |