Skip to content

Commit c15ae94

Browse files
authored
feat(189PC,189TV): add refreshToken and qrcode login (#1205)
### Key Changes - **189PC**: Add QR code login and refresh token support - **189TV**: Add session refresh mechanism and fix TempUuid persistence issue - **Both**: Implement session keep-alive with cron jobs (5min interval) ### Features - QR code authentication for 189PC as alternative to password login - Automatic token refresh to avoid frequent re-authentication - Session keep-alive to maintain long-term connections - Retry logic with max attempts to prevent infinite loops ### Fixes - Fixed 189TV TempUuid causing storage corruption on QR code reload - Enhanced error handling for token expiration scenarios
1 parent f1a5048 commit c15ae94

File tree

8 files changed

+381
-65
lines changed

8 files changed

+381
-65
lines changed

drivers/189_tv/driver.go

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package _189_tv
22

33
import (
4-
"container/ring"
54
"context"
65
"net/http"
76
"strconv"
@@ -12,18 +11,20 @@ import (
1211
"github.com/OpenListTeam/OpenList/v4/internal/driver"
1312
"github.com/OpenListTeam/OpenList/v4/internal/errs"
1413
"github.com/OpenListTeam/OpenList/v4/internal/model"
14+
"github.com/OpenListTeam/OpenList/v4/pkg/cron"
1515
"github.com/go-resty/resty/v2"
1616
)
1717

1818
type Cloud189TV struct {
1919
model.Storage
2020
Addition
21-
client *resty.Client
22-
tokenInfo *AppSessionResp
23-
uploadThread int
24-
familyTransferFolder *ring.Ring
25-
cleanFamilyTransferFile func()
26-
storageConfig driver.Config
21+
client *resty.Client
22+
tokenInfo *AppSessionResp
23+
uploadThread int
24+
storageConfig driver.Config
25+
26+
TempUuid string
27+
cron *cron.Cron // 新增 cron 字段
2728
}
2829

2930
func (y *Cloud189TV) Config() driver.Config {
@@ -79,10 +80,17 @@ func (y *Cloud189TV) Init(ctx context.Context) (err error) {
7980
}
8081
}
8182

83+
y.cron = cron.NewCron(time.Minute * 5)
84+
y.cron.Do(y.keepAlive)
85+
8286
return
8387
}
8488

8589
func (y *Cloud189TV) Drop(ctx context.Context) error {
90+
if y.cron != nil {
91+
y.cron.Stop()
92+
y.cron = nil
93+
}
8694
return nil
8795
}
8896

drivers/189_tv/meta.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
type Addition struct {
99
driver.RootID
1010
AccessToken string `json:"access_token"`
11-
TempUuid string
1211
OrderBy string `json:"order_by" type:"select" options:"filename,filesize,lastOpTime" default:"filename"`
1312
OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" default:"asc"`
1413
Type string `json:"type" type:"select" options:"personal,family" default:"personal"`

drivers/189_tv/utils.go

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ func (y *Cloud189TV) AppKeySignatureHeader(url, method string) map[string]string
6666
}
6767

6868
func (y *Cloud189TV) request(url, method string, callback base.ReqCallback, params map[string]string, resp interface{}, isFamily ...bool) ([]byte, error) {
69+
return y.requestWithRetry(url, method, callback, params, resp, 0, isFamily...)
70+
}
71+
72+
func (y *Cloud189TV) requestWithRetry(url, method string, callback base.ReqCallback, params map[string]string, resp interface{}, retryCount int, isFamily ...bool) ([]byte, error) {
6973
req := y.client.R().SetQueryParams(clientSuffix())
7074

7175
if params != nil {
@@ -91,7 +95,22 @@ func (y *Cloud189TV) request(url, method string, callback base.ReqCallback, para
9195

9296
if strings.Contains(res.String(), "userSessionBO is null") ||
9397
strings.Contains(res.String(), "InvalidSessionKey") {
94-
return nil, errors.New("session expired")
98+
// 限制重试次数,避免无限递归
99+
if retryCount >= 3 {
100+
y.Addition.AccessToken = ""
101+
op.MustSaveDriverStorage(y)
102+
return nil, errors.New("session expired after retry")
103+
}
104+
105+
// 尝试刷新会话
106+
if err := y.refreshSession(); err != nil {
107+
// 如果刷新失败,说明AccessToken也已过期,需要重新登录
108+
y.Addition.AccessToken = ""
109+
op.MustSaveDriverStorage(y)
110+
return nil, errors.New("session expired")
111+
}
112+
// 如果刷新成功,则重试原始请求(增加重试计数)
113+
return y.requestWithRetry(url, method, callback, params, resp, retryCount+1, isFamily...)
95114
}
96115

97116
// 处理错误
@@ -211,7 +230,7 @@ func (y *Cloud189TV) login() (err error) {
211230
var erron RespErr
212231
var tokenInfo AppSessionResp
213232
if y.Addition.AccessToken == "" {
214-
if y.Addition.TempUuid == "" {
233+
if y.TempUuid == "" {
215234
// 获取登录参数
216235
var uuidInfo UuidInfoResp
217236
req.SetResult(&uuidInfo).SetError(&erron)
@@ -230,7 +249,7 @@ func (y *Cloud189TV) login() (err error) {
230249
if uuidInfo.Uuid == "" {
231250
return errors.New("uuidInfo is empty")
232251
}
233-
y.Addition.TempUuid = uuidInfo.Uuid
252+
y.TempUuid = uuidInfo.Uuid
234253
op.MustSaveDriverStorage(y)
235254

236255
// 展示二维码
@@ -258,7 +277,7 @@ func (y *Cloud189TV) login() (err error) {
258277
// Signature
259278
req.SetHeaders(y.AppKeySignatureHeader(ApiUrl+"/family/manage/qrcodeLoginResult.action",
260279
http.MethodGet))
261-
req.SetQueryParam("uuid", y.Addition.TempUuid)
280+
req.SetQueryParam("uuid", y.TempUuid)
262281
_, err = req.Execute(http.MethodGet, ApiUrl+"/family/manage/qrcodeLoginResult.action")
263282
if err != nil {
264283
return
@@ -270,7 +289,6 @@ func (y *Cloud189TV) login() (err error) {
270289
return errors.New("E189AccessToken is empty")
271290
}
272291
y.Addition.AccessToken = accessTokenResp.E189AccessToken
273-
y.Addition.TempUuid = ""
274292
}
275293
}
276294
// 获取SessionKey 和 SessionSecret
@@ -294,6 +312,44 @@ func (y *Cloud189TV) login() (err error) {
294312
return
295313
}
296314

315+
// refreshSession 尝试使用现有的 AccessToken 刷新会话
316+
func (y *Cloud189TV) refreshSession() (err error) {
317+
var erron RespErr
318+
var tokenInfo AppSessionResp
319+
reqb := y.client.R().SetQueryParams(clientSuffix())
320+
reqb.SetResult(&tokenInfo).SetError(&erron)
321+
// Signature
322+
reqb.SetHeaders(y.AppKeySignatureHeader(ApiUrl+"/family/manage/loginFamilyMerge.action",
323+
http.MethodGet))
324+
reqb.SetQueryParam("e189AccessToken", y.Addition.AccessToken)
325+
_, err = reqb.Execute(http.MethodGet, ApiUrl+"/family/manage/loginFamilyMerge.action")
326+
if err != nil {
327+
return
328+
}
329+
330+
if erron.HasError() {
331+
return &erron
332+
}
333+
334+
y.tokenInfo = &tokenInfo
335+
return nil
336+
}
337+
338+
func (y *Cloud189TV) keepAlive() {
339+
_, err := y.get(ApiUrl+"/keepUserSession.action", func(r *resty.Request) {
340+
r.SetQueryParams(clientSuffix())
341+
}, nil)
342+
if err != nil {
343+
utils.Log.Warnf("189tv: Failed to keep user session alive: %v", err)
344+
// 如果keepAlive失败,尝试刷新session
345+
if refreshErr := y.refreshSession(); refreshErr != nil {
346+
utils.Log.Errorf("189tv: Failed to refresh session after keepAlive error: %v", refreshErr)
347+
}
348+
} else {
349+
utils.Log.Debugf("189tv: User session kept alive successfully.")
350+
}
351+
}
352+
297353
func (y *Cloud189TV) RapidUpload(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, isFamily bool, overwrite bool) (model.Obj, error) {
298354
fileMd5 := stream.GetHash().GetHash(utils.MD5)
299355
if len(fileMd5) < utils.MD5.Width {

drivers/189pc/driver.go

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/OpenListTeam/OpenList/v4/internal/driver"
1313
"github.com/OpenListTeam/OpenList/v4/internal/errs"
1414
"github.com/OpenListTeam/OpenList/v4/internal/model"
15+
"github.com/OpenListTeam/OpenList/v4/pkg/cron"
1516
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
1617
"github.com/go-resty/resty/v2"
1718
"github.com/google/uuid"
@@ -21,12 +22,12 @@ type Cloud189PC struct {
2122
model.Storage
2223
Addition
2324

24-
identity string
25-
2625
client *resty.Client
2726

28-
loginParam *LoginParam
29-
tokenInfo *AppSessionResp
27+
loginParam *LoginParam
28+
qrcodeParam *QRLoginParam
29+
30+
tokenInfo *AppSessionResp
3031

3132
uploadThread int
3233

@@ -35,6 +36,7 @@ type Cloud189PC struct {
3536

3637
storageConfig driver.Config
3738
ref *Cloud189PC
39+
cron *cron.Cron
3840
}
3941

4042
func (y *Cloud189PC) Config() driver.Config {
@@ -84,14 +86,22 @@ func (y *Cloud189PC) Init(ctx context.Context) (err error) {
8486
})
8587
}
8688

87-
// 避免重复登陆
88-
identity := utils.GetMD5EncodeStr(y.Username + y.Password)
89-
if !y.isLogin() || y.identity != identity {
90-
y.identity = identity
89+
// 先尝试用Token刷新,之后尝试登陆
90+
if y.Addition.RefreshToken != "" {
91+
y.tokenInfo = &AppSessionResp{RefreshToken: y.Addition.RefreshToken}
92+
if err = y.refreshToken(); err != nil {
93+
return
94+
}
95+
} else {
9196
if err = y.login(); err != nil {
9297
return
9398
}
9499
}
100+
101+
// 初始化并启动 cron 任务
102+
y.cron = cron.NewCron(time.Duration(time.Minute * 5))
103+
// 每5分钟执行一次 keepAlive
104+
y.cron.Do(y.keepAlive)
95105
}
96106

97107
// 处理家庭云ID
@@ -128,6 +138,10 @@ func (d *Cloud189PC) InitReference(storage driver.Driver) error {
128138

129139
func (y *Cloud189PC) Drop(ctx context.Context) error {
130140
y.ref = nil
141+
if y.cron != nil {
142+
y.cron.Stop()
143+
y.cron = nil
144+
}
131145
return nil
132146
}
133147

drivers/189pc/help.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,20 @@ func timestamp() int64 {
8080
return time.Now().UTC().UnixNano() / 1e6
8181
}
8282

83+
// formatDate formats a time.Time object into the "YYYY-MM-DDHH:mm:ssSSS" format.
84+
func formatDate(t time.Time) string {
85+
// The layout string "2006-01-0215:04:05.000" corresponds to:
86+
// 2006 -> Year (YYYY)
87+
// 01 -> Month (MM)
88+
// 02 -> Day (DD)
89+
// 15 -> Hour (HH)
90+
// 04 -> Minute (mm)
91+
// 05 -> Second (ss)
92+
// 000 -> Millisecond (SSS) with leading zeros
93+
// Note the lack of a separator between the date and hour, matching the desired output.
94+
return t.Format("2006-01-0215:04:05.000")
95+
}
96+
8397
func MustParseTime(str string) *time.Time {
8498
lastOpTime, _ := time.ParseInLocation("2006-01-02 15:04:05 -07", str+" +08", time.Local)
8599
return &lastOpTime

drivers/189pc/meta.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ import (
66
)
77

88
type Addition struct {
9-
Username string `json:"username" required:"true"`
10-
Password string `json:"password" required:"true"`
11-
VCode string `json:"validate_code"`
9+
LoginType string `json:"login_type" type:"select" options:"password,qrcode" default:"password" required:"true"`
10+
Username string `json:"username" required:"true"`
11+
Password string `json:"password" required:"true"`
12+
VCode string `json:"validate_code"`
13+
RefreshToken string `json:"refresh_token" help:"To switch accounts, please clear this field"`
1214
driver.RootID
1315
OrderBy string `json:"order_by" type:"select" options:"filename,filesize,lastOpTime" default:"filename"`
1416
OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" default:"asc"`

drivers/189pc/types.go

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -68,15 +68,7 @@ func (e *RespErr) Error() string {
6868
return ""
6969
}
7070

71-
// 登陆需要的参数
72-
type LoginParam struct {
73-
// 加密后的用户名和密码
74-
RsaUsername string
75-
RsaPassword string
76-
77-
// rsa密钥
78-
jRsaKey string
79-
71+
type BaseLoginParam struct {
8072
// 请求头参数
8173
Lt string
8274
ReqId string
@@ -88,6 +80,27 @@ type LoginParam struct {
8880
CaptchaToken string
8981
}
9082

83+
// QRLoginParam 用于暂存二维码登录过程中的参数
84+
type QRLoginParam struct {
85+
BaseLoginParam
86+
87+
UUID string `json:"uuid"`
88+
EncodeUUID string `json:"encodeuuid"`
89+
EncryUUID string `json:"encryuuid"`
90+
}
91+
92+
// 登陆需要的参数
93+
type LoginParam struct {
94+
// 加密后的用户名和密码
95+
RsaUsername string
96+
RsaPassword string
97+
98+
// rsa密钥
99+
jRsaKey string
100+
101+
BaseLoginParam
102+
}
103+
91104
// 登陆加密相关
92105
type EncryptConfResp struct {
93106
Result int `json:"result"`

0 commit comments

Comments
 (0)