optimize token refresh task

This commit is contained in:
tickstep
2025-08-06 15:34:36 +08:00
parent e8bda159fa
commit 9b276f2caf
5 changed files with 193 additions and 47 deletions
+3 -1
View File
@@ -24,6 +24,7 @@ import (
"github.com/tickstep/aliyunpan/internal/config"
"github.com/tickstep/aliyunpan/internal/file/downloader"
"github.com/tickstep/aliyunpan/internal/functions/pandownload"
"github.com/tickstep/aliyunpan/internal/functions/panlogin"
"github.com/tickstep/aliyunpan/internal/taskframework"
"github.com/tickstep/aliyunpan/internal/utils"
"github.com/tickstep/aliyunpan/library/requester/transfer"
@@ -628,7 +629,8 @@ func RunAlbumDownloadFile(albumNames []string, options *DownloadOptions) {
go func(flag *int32) {
for atomic.LoadInt32(flag) == 0 {
time.Sleep(time.Duration(1) * time.Minute)
if RefreshWebTokenInNeed(activeUser, config.Config.DeviceName) {
re := RefreshWebTokenInNeed(activeUser)
if re.Code == panlogin.SUCCESS {
logger.Verboseln("update access token for download task")
}
}
+125 -22
View File
@@ -23,6 +23,7 @@ import (
"github.com/tickstep/aliyunpan/internal/utils"
"github.com/tickstep/library-go/logger"
"github.com/urfave/cli"
"math"
"math/rand"
"net/url"
"path"
@@ -142,9 +143,9 @@ func NewWebLoginToken(accessToken string, expired int64) aliyunpan_web.WebLoginT
}
// RefreshWebTokenInNeed 刷新 webapi access token
func RefreshWebTokenInNeed(activeUser *config.PanUser, deviceName string) bool {
func RefreshWebTokenInNeed(activeUser *config.PanUser) *panlogin.CommonResultError {
if activeUser == nil {
return false
return panlogin.NewCommonResultError("activeUser is nil")
}
// refresh expired web token
@@ -153,7 +154,7 @@ func RefreshWebTokenInNeed(activeUser *config.PanUser, deviceName string) bool {
cz := time.FixedZone("CST", 8*3600) // 东8区
expiredTime := time.Unix(activeUser.WebapiToken.Expired, 0).In(cz)
now := time.Now()
if (expiredTime.Unix() - now.Unix()) <= (2 * 60) { // 有效期小于2min就刷新
if (expiredTime.Unix() - now.Unix()) <= (10 * 60) { // 有效期小于10min就刷新
pluginManger := plugins.NewPluginManager(config.GetPluginDir())
plugin, _ := pluginManger.GetPlugin()
params := &plugins.UserTokenRefreshFinishParams{
@@ -167,9 +168,12 @@ func RefreshWebTokenInNeed(activeUser *config.PanUser, deviceName string) bool {
// need update refresh token
logger.Verboseln("web access token expired, get new from server")
loginHelper := panlogin.NewLoginHelper(config.DefaultTokenServiceWebHost)
wt, e := loginHelper.GetWebapiNewToken(activeUser.TicketId, activeUser.UserId, activeUser.PanClient().WebapiPanClient().GetAccessToken())
if e != nil {
logger.Verboseln("get web token from server error: ", e)
wt, re := loginHelper.GetWebapiNewToken(activeUser.TicketId, activeUser.UserId, activeUser.PanClient().WebapiPanClient().GetAccessToken())
if re != nil {
logger.Verboseln("get web token from server error: ", re.Msg)
if re.Code != panlogin.SUCCESS {
return re
}
}
if wt != nil {
params.Result = "success"
@@ -191,42 +195,51 @@ func RefreshWebTokenInNeed(activeUser *config.PanUser, deviceName string) bool {
}
// create new signature
_, e := activeUser.PanClient().WebapiPanClient().CreateSession(nil)
if e != nil {
logger.Verboseln("call CreateSession error in RefreshWebTokenInNeed: " + e.Error())
_, e1 := activeUser.PanClient().WebapiPanClient().CreateSession(nil)
if e1 != nil {
logger.Verboseln("call CreateSession error in RefreshWebTokenInNeed: " + e1.Error())
}
return &panlogin.CommonResultError{
Code: 0,
Msg: "",
}
return true
} else {
// token refresh error
// if token has expired, callback plugin api for notify
if now.Unix() >= expiredTime.Unix() {
params.Result = "fail"
params.Message = e.Error()
params.Message = re.Msg
params.OldToken = activeUser.WebapiToken.AccessToken
if er1 := plugin.UserTokenRefreshFinishCallback(plugins.GetContext(activeUser), params); er1 != nil {
logger.Verbosef("UserTokenRefreshFinishCallback error: " + er1.Error())
}
}
}
} else {
return &panlogin.CommonResultError{
Code: 0,
Msg: "web token is validate, not need to refresh",
}
}
}
}
return false
return panlogin.NewCommonResultError("activeUser is invalidate")
}
// RefreshOpenTokenInNeed 刷新 openapi access token
func RefreshOpenTokenInNeed(activeUser *config.PanUser) bool {
func RefreshOpenTokenInNeed(activeUser *config.PanUser) *panlogin.CommonResultError {
if activeUser == nil {
return false
return panlogin.NewCommonResultError("activeUser is nil")
}
// OpenAPI的token只有2个小时有效期,需要在token过期之前进行刷新并把获取到的新token存储在本地
// refresh expired openapi token
if activeUser.PanClient().OpenapiPanClient() != nil {
if activeUser.OpenapiToken != nil && len(activeUser.OpenapiToken.AccessToken) > 0 {
cz := time.FixedZone("CST", 8*3600) // 东8区
expiredTime := time.Unix(activeUser.OpenapiToken.Expired, 0).In(cz)
now := time.Now().In(cz)
if (expiredTime.Unix() - now.Unix()) <= (2 * 60) { // 有效期小于2min就刷新
if (expiredTime.Unix() - now.Unix()) <= (10 * 60) { // 有效期小于10min就刷新
pluginManger := plugins.NewPluginManager(config.GetPluginDir())
plugin, _ := pluginManger.GetPlugin()
params := &plugins.UserTokenRefreshFinishParams{
@@ -240,11 +253,15 @@ func RefreshOpenTokenInNeed(activeUser *config.PanUser) bool {
// need update refresh token
logger.Verboseln("openapi access token expired, get new from server")
loginHelper := panlogin.NewLoginHelper(config.DefaultTokenServiceWebHost)
wt, e := loginHelper.GetOpenapiNewToken(activeUser.TicketId, activeUser.UserId, activeUser.PanClient().OpenapiPanClient().GetAccessToken())
if e != nil {
logger.Verboseln("get openapi token from server error: ", e)
wt, re := loginHelper.GetOpenapiNewToken(activeUser.TicketId, activeUser.UserId, activeUser.PanClient().OpenapiPanClient().GetAccessToken())
if re != nil {
logger.Verboseln("get openapi token from server error: ", re.Msg)
if re.Code != panlogin.SUCCESS {
return re
}
}
if wt != nil {
// 存储新token到本地
params.Result = "success"
params.OldToken = activeUser.OpenapiToken.AccessToken
params.NewToken = wt.AccessToken
@@ -261,23 +278,109 @@ func RefreshOpenTokenInNeed(activeUser *config.PanUser) bool {
logger.Verbosef("UserTokenRefreshFinishCallback error: " + er1.Error())
}
return true
return &panlogin.CommonResultError{
Code: 0,
Msg: "",
}
} else {
// token refresh error
// if token has expired, callback plugin api for notify
if now.Unix() >= expiredTime.Unix() {
params.Result = "fail"
params.Message = e.Error()
params.OldToken = activeUser.WebapiToken.AccessToken
params.Message = re.Msg
params.OldToken = activeUser.OpenapiToken.AccessToken
if er1 := plugin.UserTokenRefreshFinishCallback(plugins.GetContext(activeUser), params); er1 != nil {
logger.Verbosef("UserTokenRefreshFinishCallback error: " + er1.Error())
}
}
}
} else {
return &panlogin.CommonResultError{
Code: 0,
Msg: "openapi token is validate, not need to refresh",
}
}
}
}
return false
return panlogin.NewCommonResultError("activeUser is invalidate")
}
// AutomaticallyRefreshTokenTask 自动刷新Token的后台任务
func AutomaticallyRefreshTokenTask() {
go func() {
// 检测延迟时间(分钟)
delayMinuteTime := time.Duration(1) * time.Minute
//delayMinuteTime := time.Duration(10) * time.Second // for test
// 请求重试次数
attempt := 1
for {
// Token刷新进程,不管是CLI命令行模式,还是直接命令模式,本刷新任务都会执行
time.Sleep(delayMinuteTime) // 定时器,每1分钟调用一次
// 重新加载配置文件
ReloadConfigFunc(nil)
activeUser := config.Config.ActiveUser()
if activeUser != nil && activeUser.UserId != "" && activeUser.PanClient() != nil {
// 刷新Openapi端Token
errResult := RefreshOpenTokenInNeed(activeUser)
attempt += 1
if errResult != nil && errResult.Code == panlogin.SUCCESS {
// 刷新成功,重置定时器
attempt = 1
delayMinuteTime = time.Duration(1) * time.Minute
// 刷新Web端Token
RefreshWebTokenInNeed(activeUser)
// 保存新token到配置文件
SaveConfigFunc(nil)
} else {
// 刷新Token失败
logger.Verboseln("刷新Token失败:", errResult.Msg)
if errResult.Code == panlogin.ERROR_NEED_LOGIN_AGAIN {
// token已经失效,需要重新登录
logger.Verboseln("token已经失效,需要重新登录,退出自动刷新Token任务")
return
} else if errResult.Code == panlogin.ERROR {
// 访问错误
delayMinuteTime = ProgressiveBackoffAlg(attempt)
} else if errResult.Code == panlogin.ERROR_TOO_MANY_REQUESTS {
// 请求过于频繁
delayMinuteTime = ProgressiveBackoffAlg(attempt)
}
}
}
}
}()
}
// ProgressiveBackoffAlg 指数退降算法
func ProgressiveBackoffAlg(attempt int) time.Duration {
maxRetries := 120
baseDelay := float64(1)
maxDelay := float64(1440)
if attempt >= maxRetries {
return time.Duration(maxDelay) * time.Minute
}
// 计算当前延迟
var delay float64
if attempt < 100 {
// 前 100 次:线性增长 + 小指数基数(缓慢增加)
delay = baseDelay * (1 + float64(attempt)/5) // 调整分母控制增长速度
} else {
// 100 次后:标准指数退避
delay = baseDelay * math.Pow(1.5, float64(attempt-100))
if delay > maxDelay {
delay = maxDelay
}
}
// 添加随机抖动(最多 20% 的扰动)
jitterSec := rand.Float64() * delay * 0.2 // ±20% 随机扰动
delay += jitterSec
return time.Duration(delay * float64(time.Minute))
}
func isIncludeFile(pattern string, fileName string) bool {
+13
View File
@@ -0,0 +1,13 @@
package command
import (
"fmt"
"testing"
)
func TestProgressiveBackoffAlg(t *testing.T) {
for attempt := 1; attempt <= 150; attempt++ {
delayTime := ProgressiveBackoffAlg(attempt)
fmt.Printf("第%d次,延迟分钟: %.2f\n", attempt, delayTime.Minutes())
}
}
+6 -2
View File
@@ -161,7 +161,7 @@ doOpenLoginAct:
tryRefreshOpenToken = false
wt, e := loginHelper.GetOpenapiNewToken(ticketId, userId, openapiToken.AccessToken)
if e != nil {
logger.Verboseln("get openapi token from server error: ", e)
logger.Verboseln("get openapi token from server error: ", e.Msg)
return nil, apierror.NewFailedApiError("get new openapi token error, try login again")
}
if wt != nil {
@@ -169,7 +169,11 @@ doOpenLoginAct:
AccessToken: wt.AccessToken,
Expired: wt.Expired,
}
// 检查token有效期
}
// 回到token检测流程,必须确保token是有效
time.Sleep(time.Duration(1) * time.Second)
goto doOpenLoginAct
}
@@ -207,7 +211,7 @@ doWebLoginAct:
tryRefreshWebToken = false
wt, e := loginHelper.GetWebapiNewToken(ticketId, userId, webapiToken.AccessToken)
if e != nil {
logger.Verboseln("get web token from server error: ", e)
logger.Verboseln("get web token from server error: ", e.Msg)
}
if wt != nil {
webapiToken = &PanClientToken{
+46 -22
View File
@@ -21,8 +21,19 @@ type LoginHelper struct {
webHost string
}
// 通用接口错误
type respCommonResult struct {
const (
// SUCCESS 成功
SUCCESS = 0
// ERROR 失败
ERROR = 500
// ERROR_TOO_MANY_REQUESTS 请求太频繁,已被限流
ERROR_TOO_MANY_REQUESTS = 429
// ERROR_NEED_LOGIN_AGAIN Token已经过期,需要重新登录
ERROR_NEED_LOGIN_AGAIN = 100004
)
// CommonResultError 通用接口错误
type CommonResultError struct {
Code int `json:"code"`
Msg string `json:"msg"`
}
@@ -57,6 +68,13 @@ type CommonTokenEntity struct {
Webapi *LoginTokenResult `json:"webapi"`
}
func NewCommonResultError(msg string) *CommonResultError {
return &CommonResultError{
Code: 500,
Msg: msg,
}
}
func NewLoginHelper(webHost string) *LoginHelper {
return &LoginHelper{
webHost: webHost,
@@ -93,7 +111,7 @@ func (h *LoginHelper) GetQRCodeLoginUrl(keyStr string) (*QRCodeUrlResult, error)
}
// 通用接口错误
commonResult := &respCommonResult{}
commonResult := &CommonResultError{}
if err1 := json.Unmarshal(body, commonResult); err1 != nil {
logger.Verboseln("parse openapi result json error ", err1)
return nil, err1
@@ -154,7 +172,7 @@ func (h *LoginHelper) GetQRCodeLoginResult(tokenId string) (*QRCodeLoginResult,
}
// 通用接口错误
commonResult := &respCommonResult{}
commonResult := &CommonResultError{}
if err1 := json.Unmarshal(body, commonResult); err1 != nil {
logger.Verboseln("parse openapi result json error ", err1)
return nil, err1
@@ -204,7 +222,7 @@ func (h *LoginHelper) GetRefreshToken(tokenId string) (*QRCodeLoginResult, error
}
// 通用接口错误
commonResult := &respCommonResult{}
commonResult := &CommonResultError{}
if err1 := json.Unmarshal(body, commonResult); err1 != nil {
logger.Verboseln("parse openapi result json error ", err1)
return nil, err1
@@ -269,7 +287,7 @@ func (h *LoginHelper) ParseSecureRefreshToken(keyStr, secureRefreshToken string)
}
// GetWebapiNewToken 获取Webapi Token
func (h *LoginHelper) GetWebapiNewToken(ticketId, userId, oldAccessToken string) (*LoginTokenResult, error) {
func (h *LoginHelper) GetWebapiNewToken(ticketId, userId, oldAccessToken string) (*LoginTokenResult, *CommonResultError) {
fullUrl := strings.Builder{}
fmt.Fprintf(&fullUrl, "%s/auth/tickstep/aliyunpan/token/webapi/%s/refresh?userId=%s",
h.webHost, ticketId, userId)
@@ -287,26 +305,29 @@ func (h *LoginHelper) GetWebapiNewToken(ticketId, userId, oldAccessToken string)
body, err := client.Fetch("GET", fullUrl.String(), nil, header)
if err != nil {
logger.Verboseln("get web token result error ", err)
return nil, err
return nil, NewCommonResultError(err.Error())
}
// 通用接口错误
commonResult := &respCommonResult{}
commonResult := &CommonResultError{}
if err1 := json.Unmarshal(body, commonResult); err1 != nil {
logger.Verboseln("parse openapi result json error ", err1)
return nil, err1
return nil, NewCommonResultError(err1.Error())
}
if commonResult.Code != 0 {
return nil, fmt.Errorf(commonResult.Msg)
return nil, commonResult
}
errResp := &LoginHttpResult{}
if err1 := json.Unmarshal(body, errResp); err1 != nil {
logger.Verboseln("parse result json error ", err1)
return nil, err1
return nil, NewCommonResultError(err1.Error())
}
if errResp.Code != 0 {
return nil, fmt.Errorf(errResp.Msg)
return nil, &CommonResultError{
Code: errResp.Code,
Msg: errResp.Msg,
}
}
// parse result
@@ -314,13 +335,13 @@ func (h *LoginHelper) GetWebapiNewToken(ticketId, userId, oldAccessToken string)
r.Data = &LoginTokenResult{}
if err2 := json.Unmarshal(body, r); err2 != nil {
logger.Verboseln("parse web token result json error ", err2)
return nil, err2
return nil, NewCommonResultError(err2.Error())
}
return r.Data.(*LoginTokenResult), nil
}
// GetOpenapiNewToken 获取Openapi Token
func (h *LoginHelper) GetOpenapiNewToken(ticketId, userId, oldAccessToken string) (*LoginTokenResult, error) {
func (h *LoginHelper) GetOpenapiNewToken(ticketId, userId, oldAccessToken string) (*LoginTokenResult, *CommonResultError) {
fullUrl := strings.Builder{}
fmt.Fprintf(&fullUrl, "%s/auth/tickstep/aliyunpan/token/openapi/%s/refresh?userId=%s",
h.webHost, ticketId, userId)
@@ -338,27 +359,30 @@ func (h *LoginHelper) GetOpenapiNewToken(ticketId, userId, oldAccessToken string
body, err := client.Fetch("GET", fullUrl.String(), nil, header)
if err != nil {
logger.Verboseln("get openapi token result error ", err)
return nil, err
return nil, NewCommonResultError(err.Error())
}
// 通用接口错误
commonResult := &respCommonResult{}
commonResult := &CommonResultError{}
if err1 := json.Unmarshal(body, commonResult); err1 != nil {
logger.Verboseln("parse openapi result json error ", err1)
return nil, err1
return nil, NewCommonResultError(err1.Error())
}
if commonResult.Code != 0 {
return nil, fmt.Errorf(commonResult.Msg)
return nil, commonResult
}
// 解析结果
errResp := &LoginHttpResult{}
if err1 := json.Unmarshal(body, errResp); err1 != nil {
logger.Verboseln("parse result json error ", err1)
return nil, err1
return nil, NewCommonResultError(err1.Error())
}
if errResp.Code != 0 {
return nil, fmt.Errorf(errResp.Msg)
return nil, &CommonResultError{
Code: errResp.Code,
Msg: errResp.Msg,
}
}
// parse result
@@ -366,7 +390,7 @@ func (h *LoginHelper) GetOpenapiNewToken(ticketId, userId, oldAccessToken string
r.Data = &LoginTokenResult{}
if err2 := json.Unmarshal(body, r); err2 != nil {
logger.Verboseln("parse openapi token result json error ", err2)
return nil, err2
return nil, NewCommonResultError(err2.Error())
}
return r.Data.(*LoginTokenResult), nil
}
@@ -393,7 +417,7 @@ func (h *LoginHelper) GetLoginToken(ticketId string) (*CommonTokenEntity, error)
}
// 通用接口错误
commonResult := &respCommonResult{}
commonResult := &CommonResultError{}
if err1 := json.Unmarshal(body, commonResult); err1 != nil {
logger.Verboseln("parse openapi result json error ", err1)
return nil, err1