2022-09-08 07:00:57 +00:00
|
|
|
package _189pc
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"crypto/md5"
|
|
|
|
"encoding/base64"
|
|
|
|
"encoding/hex"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"math"
|
|
|
|
"net/http"
|
|
|
|
"net/http/cookiejar"
|
|
|
|
"net/url"
|
|
|
|
"os"
|
|
|
|
"regexp"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/alist-org/alist/v3/drivers/base"
|
|
|
|
"github.com/alist-org/alist/v3/internal/conf"
|
|
|
|
"github.com/alist-org/alist/v3/internal/driver"
|
|
|
|
"github.com/alist-org/alist/v3/internal/model"
|
|
|
|
"github.com/alist-org/alist/v3/internal/op"
|
|
|
|
"github.com/alist-org/alist/v3/internal/setting"
|
|
|
|
"github.com/alist-org/alist/v3/pkg/utils"
|
|
|
|
"github.com/go-resty/resty/v2"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
jsoniter "github.com/json-iterator/go"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
ACCOUNT_TYPE = "02"
|
|
|
|
APP_ID = "8025431004"
|
|
|
|
CLIENT_TYPE = "10020"
|
|
|
|
VERSION = "6.2"
|
|
|
|
|
|
|
|
WEB_URL = "https://cloud.189.cn"
|
|
|
|
AUTH_URL = "https://open.e.189.cn"
|
|
|
|
API_URL = "https://api.cloud.189.cn"
|
|
|
|
UPLOAD_URL = "https://upload.cloud.189.cn"
|
|
|
|
|
|
|
|
RETURN_URL = "https://m.cloud.189.cn/zhuanti/2020/loginErrorPc/index.html"
|
|
|
|
|
|
|
|
PC = "TELEPC"
|
|
|
|
MAC = "TELEMAC"
|
|
|
|
|
|
|
|
CHANNEL_ID = "web_cloud.189.cn"
|
|
|
|
)
|
|
|
|
|
|
|
|
func (y *Yun189PC) request(url, method string, callback base.ReqCallback, params Params, resp interface{}) ([]byte, error) {
|
|
|
|
dateOfGmt := getHttpDateStr()
|
|
|
|
sessionKey := y.tokenInfo.SessionKey
|
|
|
|
sessionSecret := y.tokenInfo.SessionSecret
|
|
|
|
if y.isFamily() {
|
|
|
|
sessionKey = y.tokenInfo.FamilySessionKey
|
|
|
|
sessionSecret = y.tokenInfo.FamilySessionSecret
|
|
|
|
}
|
|
|
|
|
|
|
|
req := y.client.R().SetQueryParams(clientSuffix()).SetHeaders(map[string]string{
|
|
|
|
"Date": dateOfGmt,
|
|
|
|
"SessionKey": sessionKey,
|
|
|
|
"X-Request-ID": uuid.NewString(),
|
|
|
|
})
|
|
|
|
|
|
|
|
// 设置params
|
|
|
|
var paramsData string
|
|
|
|
if params != nil {
|
|
|
|
paramsData = AesECBEncrypt(params.Encode(), sessionSecret[:16])
|
|
|
|
req.SetQueryParam("params", paramsData)
|
|
|
|
}
|
|
|
|
req.SetHeader("Signature", signatureOfHmac(sessionSecret, sessionKey, method, url, dateOfGmt, paramsData))
|
|
|
|
|
|
|
|
if callback != nil {
|
|
|
|
callback(req)
|
|
|
|
}
|
|
|
|
if resp != nil {
|
|
|
|
req.SetResult(resp)
|
|
|
|
}
|
|
|
|
res, err := req.Execute(method, url)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
var erron RespErr
|
|
|
|
utils.Json.Unmarshal(res.Body(), &erron)
|
|
|
|
|
|
|
|
if erron.ResCode != "" {
|
|
|
|
return nil, fmt.Errorf("res_code: %s ,res_msg: %s", erron.ResCode, erron.ResMessage)
|
|
|
|
}
|
|
|
|
if erron.Code != "" && erron.Code != "SUCCESS" {
|
|
|
|
if erron.Msg != "" {
|
|
|
|
return nil, fmt.Errorf("code: %s ,msg: %s", erron.Code, erron.Msg)
|
|
|
|
}
|
|
|
|
if erron.Message != "" {
|
|
|
|
return nil, fmt.Errorf("code: %s ,msg: %s", erron.Code, erron.Message)
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf(res.String())
|
|
|
|
}
|
|
|
|
switch erron.ErrorCode {
|
|
|
|
case "":
|
|
|
|
break
|
|
|
|
case "InvalidSessionKey":
|
|
|
|
if err = y.refreshSession(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return y.request(url, method, callback, params, resp)
|
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("err_code: %s ,err_msg: %s", erron.ErrorCode, erron.ErrorMsg)
|
|
|
|
}
|
|
|
|
|
|
|
|
if strings.Contains(res.String(), "userSessionBO is null") {
|
|
|
|
if err = y.refreshSession(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return y.request(url, method, callback, params, resp)
|
|
|
|
}
|
|
|
|
|
|
|
|
resCode := utils.Json.Get(res.Body(), "res_code").ToInt64()
|
|
|
|
message := utils.Json.Get(res.Body(), "res_message").ToString()
|
|
|
|
switch resCode {
|
|
|
|
case 0:
|
|
|
|
return res.Body(), nil
|
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("res_code: %d ,res_msg: %s", resCode, message)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (y *Yun189PC) get(url string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
|
|
|
|
return y.request(url, http.MethodGet, callback, nil, resp)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (y *Yun189PC) post(url string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
|
|
|
|
return y.request(url, http.MethodPost, callback, nil, resp)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (y *Yun189PC) getFiles(ctx context.Context, fileId string) ([]model.Obj, error) {
|
|
|
|
fullUrl := API_URL
|
|
|
|
if y.isFamily() {
|
|
|
|
fullUrl += "/family/file"
|
|
|
|
}
|
|
|
|
fullUrl += "/listFiles.action"
|
|
|
|
|
|
|
|
res := make([]model.Obj, 0, 130)
|
2022-09-11 05:18:29 +00:00
|
|
|
for pageNum := 1; ; pageNum++ {
|
2022-09-08 07:00:57 +00:00
|
|
|
var resp Cloud189FilesResp
|
|
|
|
_, err := y.get(fullUrl, func(r *resty.Request) {
|
|
|
|
r.SetContext(ctx)
|
|
|
|
r.SetQueryParams(map[string]string{
|
|
|
|
"folderId": fileId,
|
|
|
|
"fileType": "0",
|
|
|
|
"mediaAttr": "0",
|
|
|
|
"iconOption": "5",
|
|
|
|
"pageNum": fmt.Sprint(pageNum),
|
|
|
|
"pageSize": "130",
|
|
|
|
})
|
|
|
|
if y.isFamily() {
|
|
|
|
r.SetQueryParams(map[string]string{
|
|
|
|
"familyId": y.FamilyID,
|
|
|
|
"orderBy": toFamilyOrderBy(y.OrderBy),
|
|
|
|
"descending": toDesc(y.OrderDirection),
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
r.SetQueryParams(map[string]string{
|
|
|
|
"recursive": "0",
|
|
|
|
"orderBy": y.OrderBy,
|
|
|
|
"descending": toDesc(y.OrderDirection),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}, &resp)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// 获取完毕跳出
|
|
|
|
if resp.FileListAO.Count == 0 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := 0; i < len(resp.FileListAO.FolderList); i++ {
|
|
|
|
res = append(res, &resp.FileListAO.FolderList[i])
|
|
|
|
}
|
|
|
|
for i := 0; i < len(resp.FileListAO.FileList); i++ {
|
|
|
|
res = append(res, &resp.FileListAO.FileList[i])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return res, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (y *Yun189PC) login() (err error) {
|
|
|
|
// 初始化登陆所需参数
|
2022-10-16 16:54:39 +00:00
|
|
|
if y.loginParam == nil || !y.NonuseOrc {
|
2022-09-08 07:00:57 +00:00
|
|
|
if err = y.initLoginParam(); err != nil {
|
|
|
|
// 验证码也通过错误返回
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
// 销毁验证码
|
|
|
|
y.VCode = ""
|
|
|
|
// 销毁登陆参数
|
|
|
|
y.loginParam = nil
|
|
|
|
// 遇到错误,重新加载登陆参数
|
2022-10-16 16:54:39 +00:00
|
|
|
if err != nil && y.NonuseOrc {
|
2022-09-08 07:00:57 +00:00
|
|
|
if err1 := y.initLoginParam(); err1 != nil {
|
|
|
|
err = fmt.Errorf("err1: %s \nerr2: %s", err, err1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
param := y.loginParam
|
|
|
|
var loginresp LoginResp
|
|
|
|
_, err = y.client.R().
|
|
|
|
ForceContentType("application/json;charset=UTF-8").SetResult(&loginresp).
|
|
|
|
SetHeaders(map[string]string{
|
|
|
|
"REQID": param.ReqId,
|
|
|
|
"lt": param.Lt,
|
|
|
|
}).
|
|
|
|
SetFormData(map[string]string{
|
|
|
|
"appKey": APP_ID,
|
|
|
|
"accountType": ACCOUNT_TYPE,
|
|
|
|
"userName": param.RsaUsername,
|
|
|
|
"password": param.RsaPassword,
|
|
|
|
"validateCode": y.VCode,
|
|
|
|
"captchaToken": param.CaptchaToken,
|
|
|
|
"returnUrl": RETURN_URL,
|
|
|
|
"mailSuffix": "@189.cn",
|
|
|
|
"dynamicCheck": "FALSE",
|
|
|
|
"clientType": CLIENT_TYPE,
|
|
|
|
"cb_SaveName": "1",
|
|
|
|
"isOauth2": "false",
|
|
|
|
"state": "",
|
|
|
|
"paramId": param.ParamId,
|
|
|
|
}).
|
|
|
|
Post(AUTH_URL + "/api/logbox/oauth2/loginSubmit.do")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if loginresp.ToUrl == "" {
|
|
|
|
return fmt.Errorf("login failed,No toUrl obtained, msg: %s", loginresp.Msg)
|
|
|
|
}
|
|
|
|
|
|
|
|
// 获取Session
|
|
|
|
var erron RespErr
|
|
|
|
var tokenInfo AppSessionResp
|
|
|
|
_, err = y.client.R().
|
|
|
|
SetResult(&tokenInfo).SetError(&erron).
|
|
|
|
SetQueryParams(clientSuffix()).
|
|
|
|
SetQueryParam("redirectURL", url.QueryEscape(loginresp.ToUrl)).
|
|
|
|
Post(API_URL + "/getSessionForPC.action")
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if erron.ResCode != "" {
|
|
|
|
err = fmt.Errorf(erron.ResMessage)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if tokenInfo.ResCode != 0 {
|
|
|
|
err = fmt.Errorf(tokenInfo.ResMessage)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
y.tokenInfo = &tokenInfo
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 初始化登陆需要的参数
|
|
|
|
* 如果遇到验证码返回错误
|
|
|
|
*/
|
|
|
|
func (y *Yun189PC) initLoginParam() error {
|
|
|
|
// 清除cookie
|
|
|
|
jar, _ := cookiejar.New(nil)
|
|
|
|
y.client.SetCookieJar(jar)
|
|
|
|
|
|
|
|
res, err := y.client.R().
|
|
|
|
SetQueryParams(map[string]string{
|
|
|
|
"appId": APP_ID,
|
|
|
|
"clientType": CLIENT_TYPE,
|
|
|
|
"returnURL": RETURN_URL,
|
|
|
|
"timeStamp": fmt.Sprint(timestamp()),
|
|
|
|
}).
|
|
|
|
Get(WEB_URL + "/api/portal/unifyLoginForPC.action")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
param := LoginParam{
|
|
|
|
CaptchaToken: regexp.MustCompile(`'captchaToken' value='(.+?)'`).FindStringSubmatch(res.String())[1],
|
|
|
|
Lt: regexp.MustCompile(`lt = "(.+?)"`).FindStringSubmatch(res.String())[1],
|
|
|
|
ParamId: regexp.MustCompile(`paramId = "(.+?)"`).FindStringSubmatch(res.String())[1],
|
|
|
|
ReqId: regexp.MustCompile(`reqId = "(.+?)"`).FindStringSubmatch(res.String())[1],
|
|
|
|
// jRsaKey: regexp.MustCompile(`"j_rsaKey" value="(.+?)"`).FindStringSubmatch(res.String())[1],
|
|
|
|
}
|
|
|
|
|
|
|
|
// 获取rsa公钥
|
|
|
|
var encryptConf EncryptConfResp
|
|
|
|
_, err = y.client.R().
|
|
|
|
ForceContentType("application/json;charset=UTF-8").SetResult(&encryptConf).
|
|
|
|
SetFormData(map[string]string{"appId": APP_ID}).
|
|
|
|
Post(AUTH_URL + "/api/logbox/config/encryptConf.do")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
param.jRsaKey = fmt.Sprintf("-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----", encryptConf.Data.PubKey)
|
|
|
|
param.RsaUsername = encryptConf.Data.Pre + RsaEncrypt(param.jRsaKey, y.Username)
|
|
|
|
param.RsaPassword = encryptConf.Data.Pre + RsaEncrypt(param.jRsaKey, y.Password)
|
2022-10-16 16:54:39 +00:00
|
|
|
y.loginParam = ¶m
|
2022-09-08 07:00:57 +00:00
|
|
|
|
2022-10-16 16:54:39 +00:00
|
|
|
imgRes, err := y.client.R().
|
|
|
|
SetQueryParams(map[string]string{
|
|
|
|
"token": param.CaptchaToken,
|
|
|
|
"REQID": param.ReqId,
|
|
|
|
"rnd": fmt.Sprint(timestamp()),
|
2022-09-08 07:00:57 +00:00
|
|
|
}).
|
2022-10-16 16:54:39 +00:00
|
|
|
Get(AUTH_URL + "/api/logbox/oauth2/picCaptcha.do")
|
2022-09-08 07:00:57 +00:00
|
|
|
if err != nil {
|
2022-10-16 16:54:39 +00:00
|
|
|
return fmt.Errorf("failed to obtain verification code")
|
2022-09-08 07:00:57 +00:00
|
|
|
}
|
2022-10-17 14:39:51 +00:00
|
|
|
if imgRes.Size() > 20 {
|
2022-10-16 16:54:39 +00:00
|
|
|
if setting.GetStr(conf.OcrApi) != "" && !y.NonuseOrc {
|
|
|
|
vRes, err := base.RestyClient.R().
|
|
|
|
SetMultipartField("image", "validateCode.png", "image/png", bytes.NewReader(imgRes.Body())).
|
|
|
|
Post(setting.GetStr(conf.OcrApi))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if jsoniter.Get(vRes.Body(), "status").ToInt() == 200 {
|
|
|
|
y.VCode = jsoniter.Get(vRes.Body(), "result").ToString()
|
|
|
|
return nil
|
|
|
|
}
|
2022-09-08 07:00:57 +00:00
|
|
|
}
|
|
|
|
|
2022-10-16 16:54:39 +00:00
|
|
|
// 返回验证码图片给前端
|
|
|
|
return fmt.Errorf(`need img validate code: <img src="data:image/png;base64,%s"/>`, base64.StdEncoding.EncodeToString(imgRes.Body()))
|
2022-09-08 07:00:57 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// 刷新会话
|
|
|
|
func (y *Yun189PC) refreshSession() (err error) {
|
|
|
|
var erron RespErr
|
|
|
|
var userSessionResp UserSessionResp
|
|
|
|
_, err = y.client.R().
|
|
|
|
SetResult(&userSessionResp).SetError(&erron).
|
|
|
|
SetQueryParams(clientSuffix()).
|
|
|
|
SetQueryParams(map[string]string{
|
|
|
|
"appId": APP_ID,
|
|
|
|
"accessToken": y.tokenInfo.AccessToken,
|
|
|
|
}).
|
|
|
|
SetHeader("X-Request-ID", uuid.NewString()).
|
|
|
|
Get(API_URL + "/getSessionForPC.action")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// 错误影响正常访问,下线该储存
|
|
|
|
defer func() {
|
|
|
|
if err != nil {
|
|
|
|
y.GetStorage().SetStatus(fmt.Sprintf("%+v", err.Error()))
|
|
|
|
op.MustSaveDriverStorage(y)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
switch erron.ResCode {
|
|
|
|
case "":
|
|
|
|
break
|
|
|
|
case "UserInvalidOpenToken":
|
|
|
|
if err = y.login(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
err = fmt.Errorf("res_code: %s ,res_msg: %s", erron.ResCode, erron.ResMessage)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
switch userSessionResp.ResCode {
|
|
|
|
case 0:
|
|
|
|
y.tokenInfo.UserSessionResp = userSessionResp
|
|
|
|
default:
|
|
|
|
err = fmt.Errorf("code: %d , msg: %s", userSessionResp.ResCode, userSessionResp.ResMessage)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// 普通上传
|
|
|
|
func (y *Yun189PC) CommonUpload(ctx context.Context, dstDir model.Obj, file model.FileStreamer, up driver.UpdateProgress) (err error) {
|
|
|
|
const DEFAULT int64 = 10485760
|
|
|
|
var count = int64(math.Ceil(float64(file.GetSize()) / float64(DEFAULT)))
|
|
|
|
|
2022-10-17 14:39:51 +00:00
|
|
|
requestID := uuid.NewString()
|
2022-09-08 07:00:57 +00:00
|
|
|
params := Params{
|
|
|
|
"parentFolderId": dstDir.GetID(),
|
|
|
|
"fileName": url.QueryEscape(file.GetName()),
|
|
|
|
"fileSize": fmt.Sprint(file.GetSize()),
|
|
|
|
"sliceSize": fmt.Sprint(DEFAULT),
|
|
|
|
"lazyCheck": "1",
|
|
|
|
}
|
|
|
|
|
|
|
|
fullUrl := UPLOAD_URL
|
|
|
|
if y.isFamily() {
|
|
|
|
params.Set("familyId", y.FamilyID)
|
|
|
|
fullUrl += "/family"
|
|
|
|
} else {
|
|
|
|
//params.Set("extend", `{"opScene":"1","relativepath":"","rootfolderid":""}`)
|
|
|
|
fullUrl += "/person"
|
|
|
|
}
|
|
|
|
|
|
|
|
// 初始化上传
|
|
|
|
var initMultiUpload InitMultiUploadResp
|
|
|
|
_, err = y.request(fullUrl+"/initMultiUpload", http.MethodGet, func(req *resty.Request) {
|
|
|
|
req.SetContext(ctx)
|
2022-10-17 14:39:51 +00:00
|
|
|
req.SetHeader("X-Request-ID", requestID)
|
2022-09-08 07:00:57 +00:00
|
|
|
}, params, &initMultiUpload)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
fileMd5 := md5.New()
|
|
|
|
silceMd5 := md5.New()
|
|
|
|
silceMd5Hexs := make([]string, 0, count)
|
|
|
|
byteData := bytes.NewBuffer(make([]byte, DEFAULT))
|
|
|
|
for i := int64(1); i <= count; i++ {
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
return ctx.Err()
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
|
|
|
|
// 读取块
|
|
|
|
byteData.Reset()
|
|
|
|
silceMd5.Reset()
|
|
|
|
_, err := io.CopyN(io.MultiWriter(fileMd5, silceMd5, byteData), file, DEFAULT)
|
|
|
|
if err != io.EOF && err != io.ErrUnexpectedEOF && err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// 计算块md5并进行hex和base64编码
|
|
|
|
md5Bytes := silceMd5.Sum(nil)
|
|
|
|
silceMd5Hexs = append(silceMd5Hexs, strings.ToUpper(hex.EncodeToString(md5Bytes)))
|
|
|
|
silceMd5Base64 := base64.StdEncoding.EncodeToString(md5Bytes)
|
|
|
|
|
|
|
|
// 获取上传链接
|
|
|
|
var uploadUrl UploadUrlsResp
|
|
|
|
_, err = y.request(fullUrl+"/getMultiUploadUrls", http.MethodGet,
|
|
|
|
func(req *resty.Request) {
|
|
|
|
req.SetContext(ctx)
|
2022-10-17 14:39:51 +00:00
|
|
|
req.SetHeader("X-Request-ID", requestID)
|
2022-09-08 07:00:57 +00:00
|
|
|
}, Params{
|
|
|
|
"partInfo": fmt.Sprintf("%d-%s", i, silceMd5Base64),
|
|
|
|
"uploadFileId": initMultiUpload.Data.UploadFileID,
|
|
|
|
}, &uploadUrl)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// 开始上传
|
|
|
|
uploadData := uploadUrl.UploadUrls[fmt.Sprint("partNumber_", i)]
|
|
|
|
res, err := y.putClient.R().
|
|
|
|
SetContext(ctx).
|
|
|
|
SetQueryParams(clientSuffix()).
|
|
|
|
SetHeaders(ParseHttpHeader(uploadData.RequestHeader)).
|
|
|
|
SetBody(byteData).
|
|
|
|
Put(uploadData.RequestURL)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if res.StatusCode() != http.StatusOK {
|
|
|
|
return fmt.Errorf("updload fail,msg: %s", res.String())
|
|
|
|
}
|
|
|
|
up(int(i * 100 / count))
|
|
|
|
}
|
|
|
|
|
|
|
|
fileMd5Hex := strings.ToUpper(hex.EncodeToString(fileMd5.Sum(nil)))
|
|
|
|
sliceMd5Hex := fileMd5Hex
|
|
|
|
if file.GetSize() > DEFAULT {
|
|
|
|
sliceMd5Hex = strings.ToUpper(utils.GetMD5Encode(strings.Join(silceMd5Hexs, "\n")))
|
|
|
|
}
|
|
|
|
|
|
|
|
// 提交上传
|
|
|
|
_, err = y.request(fullUrl+"/commitMultiUploadFile", http.MethodGet,
|
|
|
|
func(req *resty.Request) {
|
|
|
|
req.SetContext(ctx)
|
2022-10-17 14:39:51 +00:00
|
|
|
req.SetHeader("X-Request-ID", requestID)
|
2022-09-08 07:00:57 +00:00
|
|
|
}, Params{
|
|
|
|
"uploadFileId": initMultiUpload.Data.UploadFileID,
|
|
|
|
"fileMd5": fileMd5Hex,
|
|
|
|
"sliceMd5": sliceMd5Hex,
|
|
|
|
"lazyCheck": "1",
|
|
|
|
"isLog": "0",
|
|
|
|
"opertype": "3",
|
|
|
|
}, nil)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// 快传
|
|
|
|
func (y *Yun189PC) FastUpload(ctx context.Context, dstDir model.Obj, file model.FileStreamer, up driver.UpdateProgress) (err error) {
|
|
|
|
// 需要获取完整文件md5,必须支持 io.Seek
|
2022-09-10 06:11:06 +00:00
|
|
|
tempFile, err := utils.CreateTempFile(file.GetReadCloser())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2022-09-08 07:00:57 +00:00
|
|
|
}
|
2022-09-10 06:11:06 +00:00
|
|
|
defer func() {
|
|
|
|
_ = tempFile.Close()
|
|
|
|
_ = os.Remove(tempFile.Name())
|
|
|
|
}()
|
2022-09-08 07:00:57 +00:00
|
|
|
|
|
|
|
const DEFAULT int64 = 10485760
|
|
|
|
count := int(math.Ceil(float64(file.GetSize()) / float64(DEFAULT)))
|
|
|
|
|
|
|
|
// 优先计算所需信息
|
|
|
|
fileMd5 := md5.New()
|
|
|
|
silceMd5 := md5.New()
|
|
|
|
silceMd5Hexs := make([]string, 0, count)
|
|
|
|
silceMd5Base64s := make([]string, 0, count)
|
|
|
|
for i := 1; i <= count; i++ {
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
return ctx.Err()
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
|
|
|
|
silceMd5.Reset()
|
2022-09-12 14:45:30 +00:00
|
|
|
if _, err := io.CopyN(io.MultiWriter(fileMd5, silceMd5), tempFile, DEFAULT); err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
|
2022-09-08 07:00:57 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
md5Byte := silceMd5.Sum(nil)
|
|
|
|
silceMd5Hexs = append(silceMd5Hexs, strings.ToUpper(hex.EncodeToString(md5Byte)))
|
|
|
|
silceMd5Base64s = append(silceMd5Base64s, fmt.Sprint(i, "-", base64.StdEncoding.EncodeToString(md5Byte)))
|
|
|
|
}
|
2022-09-12 09:10:02 +00:00
|
|
|
if _, err = tempFile.Seek(0, io.SeekStart); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-09-08 07:00:57 +00:00
|
|
|
|
|
|
|
fileMd5Hex := strings.ToUpper(hex.EncodeToString(fileMd5.Sum(nil)))
|
|
|
|
sliceMd5Hex := fileMd5Hex
|
|
|
|
if file.GetSize() > DEFAULT {
|
|
|
|
sliceMd5Hex = strings.ToUpper(utils.GetMD5Encode(strings.Join(silceMd5Hexs, "\n")))
|
|
|
|
}
|
|
|
|
|
2022-10-17 14:39:51 +00:00
|
|
|
requestID := uuid.NewString()
|
2022-09-08 07:00:57 +00:00
|
|
|
// 检测是否支持快传
|
|
|
|
params := Params{
|
|
|
|
"parentFolderId": dstDir.GetID(),
|
|
|
|
"fileName": url.QueryEscape(file.GetName()),
|
|
|
|
"fileSize": fmt.Sprint(file.GetSize()),
|
|
|
|
"fileMd5": fileMd5Hex,
|
|
|
|
"sliceSize": fmt.Sprint(DEFAULT),
|
|
|
|
"sliceMd5": sliceMd5Hex,
|
|
|
|
}
|
|
|
|
|
|
|
|
fullUrl := UPLOAD_URL
|
|
|
|
if y.isFamily() {
|
|
|
|
params.Set("familyId", y.FamilyID)
|
|
|
|
fullUrl += "/family"
|
|
|
|
} else {
|
|
|
|
//params.Set("extend", `{"opScene":"1","relativepath":"","rootfolderid":""}`)
|
|
|
|
fullUrl += "/person"
|
|
|
|
}
|
|
|
|
|
|
|
|
var uploadInfo InitMultiUploadResp
|
|
|
|
_, err = y.request(fullUrl+"/initMultiUpload", http.MethodGet, func(req *resty.Request) {
|
|
|
|
req.SetContext(ctx)
|
2022-10-17 14:39:51 +00:00
|
|
|
req.SetHeader("X-Request-ID", requestID)
|
2022-09-08 07:00:57 +00:00
|
|
|
}, params, &uploadInfo)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// 网盘中不存在该文件,开始上传
|
|
|
|
if uploadInfo.Data.FileDataExists != 1 {
|
|
|
|
var uploadUrls UploadUrlsResp
|
|
|
|
_, err = y.request(fullUrl+"/getMultiUploadUrls", http.MethodGet,
|
|
|
|
func(req *resty.Request) {
|
|
|
|
req.SetContext(ctx)
|
2022-10-17 14:39:51 +00:00
|
|
|
req.SetHeader("X-Request-ID", requestID)
|
2022-09-08 07:00:57 +00:00
|
|
|
}, Params{
|
|
|
|
"uploadFileId": uploadInfo.Data.UploadFileID,
|
|
|
|
"partInfo": strings.Join(silceMd5Base64s, ","),
|
|
|
|
}, &uploadUrls)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := 1; i <= count; i++ {
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
return ctx.Err()
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
|
|
|
|
uploadData := uploadUrls.UploadUrls[fmt.Sprint("partNumber_", i)]
|
|
|
|
res, err := y.putClient.R().
|
|
|
|
SetContext(ctx).
|
|
|
|
SetQueryParams(clientSuffix()).
|
|
|
|
SetHeaders(ParseHttpHeader(uploadData.RequestHeader)).
|
2022-09-12 14:45:30 +00:00
|
|
|
SetBody(io.LimitReader(tempFile, DEFAULT)).
|
2022-09-08 07:00:57 +00:00
|
|
|
Put(uploadData.RequestURL)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if res.StatusCode() != http.StatusOK {
|
|
|
|
return fmt.Errorf("updload fail,msg: %s", res.String())
|
|
|
|
}
|
|
|
|
up(int(i * 100 / count))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 提交
|
|
|
|
_, err = y.request(fullUrl+"/commitMultiUploadFile", http.MethodGet,
|
|
|
|
func(req *resty.Request) {
|
|
|
|
req.SetContext(ctx)
|
2022-10-17 14:39:51 +00:00
|
|
|
req.SetHeader("X-Request-ID", requestID)
|
2022-09-08 07:00:57 +00:00
|
|
|
}, Params{
|
|
|
|
"uploadFileId": uploadInfo.Data.UploadFileID,
|
|
|
|
"isLog": "0",
|
|
|
|
"opertype": "3",
|
|
|
|
}, nil)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (y *Yun189PC) isFamily() bool {
|
|
|
|
return y.Type == "family"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (y *Yun189PC) isLogin() bool {
|
|
|
|
if y.tokenInfo == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
_, err := y.get(API_URL+"/getUserInfo.action", nil, nil)
|
|
|
|
return err == nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// 获取家庭云所有用户信息
|
|
|
|
func (y *Yun189PC) getFamilyInfoList() ([]FamilyInfoResp, error) {
|
|
|
|
var resp FamilyInfoListResp
|
|
|
|
_, err := y.get(API_URL+"/family/manage/getFamilyList.action", nil, &resp)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return resp.FamilyInfoResp, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// 抽取家庭云ID
|
|
|
|
func (y *Yun189PC) getFamilyID() (string, error) {
|
|
|
|
infos, err := y.getFamilyInfoList()
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
if len(infos) == 0 {
|
|
|
|
return "", fmt.Errorf("cannot get automatically,please input family_id")
|
|
|
|
}
|
|
|
|
for _, info := range infos {
|
|
|
|
if strings.Contains(y.tokenInfo.LoginName, info.RemarkName) {
|
|
|
|
return fmt.Sprint(info.FamilyID), nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return fmt.Sprint(infos[0].FamilyID), nil
|
|
|
|
}
|