Skip to content

Commit 115e3da

Browse files
committed
feat:slice upload
1 parent 6499374 commit 115e3da

File tree

21 files changed

+1106
-70
lines changed

21 files changed

+1106
-70
lines changed

drivers/123_open/driver.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,16 @@ package _123_open
33
import (
44
"context"
55
"fmt"
6+
"io"
67
"strconv"
8+
"strings"
79
"time"
810

911
"github.com/OpenListTeam/OpenList/v4/internal/driver"
1012
"github.com/OpenListTeam/OpenList/v4/internal/errs"
1113
"github.com/OpenListTeam/OpenList/v4/internal/model"
14+
"github.com/OpenListTeam/OpenList/v4/internal/model/reqres"
15+
"github.com/OpenListTeam/OpenList/v4/internal/model/tables"
1216
"github.com/OpenListTeam/OpenList/v4/internal/op"
1317
"github.com/OpenListTeam/OpenList/v4/internal/stream"
1418
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
@@ -27,6 +31,13 @@ func (d *Open123) GetAddition() driver.Additional {
2731
return &d.Addition
2832
}
2933

34+
func (d *Open123) GetUploadInfo() *model.UploadInfo {
35+
return &model.UploadInfo{
36+
SliceHashNeed: true,
37+
HashMd5Need: true,
38+
}
39+
}
40+
3041
func (d *Open123) Init(ctx context.Context) error {
3142
if d.UploadThread < 1 || d.UploadThread > 32 {
3243
d.UploadThread = 3
@@ -213,5 +224,56 @@ func (d *Open123) Put(ctx context.Context, dstDir model.Obj, file model.FileStre
213224
return nil, fmt.Errorf("upload complete timeout")
214225
}
215226

227+
// Preup 预上传
228+
func (d *Open123) Preup(c context.Context, srcobj model.Obj, req *reqres.PreupReq) (*model.PreupInfo, error) {
229+
pid, err := strconv.ParseUint(srcobj.GetID(), 10, 64)
230+
if err != nil {
231+
return nil, err
232+
}
233+
duplicate := 1
234+
if req.Overwrite {
235+
duplicate = 2
236+
}
237+
238+
ucr := &UploadCreateReq{
239+
ParentFileID: pid,
240+
Etag: req.Hash.Md5,
241+
FileName: req.Name,
242+
Size: int64(req.Size),
243+
Duplicate: duplicate,
244+
}
245+
246+
resp, err := d.uploadCreate(ucr)
247+
if err != nil {
248+
return nil, err
249+
}
250+
return &model.PreupInfo{
251+
PreupID: resp.PreuploadID,
252+
Server: resp.Servers[0],
253+
SliceSize: resp.SliceSize,
254+
Reuse: resp.Reuse,
255+
}, nil
256+
}
257+
258+
// UploadSlice 上传分片
259+
func (d *Open123) SliceUpload(c context.Context, req *tables.SliceUpload, sliceno uint, fd io.Reader) error {
260+
sh := strings.Split(req.SliceHash, ",")
261+
r := &UploadSliceReq{
262+
Name: req.Name,
263+
PreuploadID: req.PreupID,
264+
Server: req.Server,
265+
Slice: fd,
266+
SliceMD5: sh[sliceno],
267+
SliceNo: int(sliceno) + 1,
268+
}
269+
return d.uploadSlice(r)
270+
}
271+
272+
// UploadSliceComplete 分片上传完成
273+
func (d *Open123) UploadSliceComplete(c context.Context, su *tables.SliceUpload) error {
274+
275+
return d.sliceUpComplete(su.PreupID)
276+
}
277+
216278
var _ driver.Driver = (*Open123)(nil)
217279
var _ driver.PutResult = (*Open123)(nil)

drivers/123_open/types.go

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

33
import (
4+
"io"
45
"strconv"
56
"time"
67

@@ -165,18 +166,6 @@ type DirectLinkResp struct {
165166
} `json:"data"`
166167
}
167168

168-
// 创建文件V2返回
169-
type UploadCreateResp struct {
170-
BaseResp
171-
Data struct {
172-
FileID int64 `json:"fileID"`
173-
PreuploadID string `json:"preuploadID"`
174-
Reuse bool `json:"reuse"`
175-
SliceSize int64 `json:"sliceSize"`
176-
Servers []string `json:"servers"`
177-
} `json:"data"`
178-
}
179-
180169
// 上传完毕V2返回
181170
type UploadCompleteResp struct {
182171
BaseResp
@@ -185,3 +174,94 @@ type UploadCompleteResp struct {
185174
FileID int64 `json:"fileID"`
186175
} `json:"data"`
187176
}
177+
178+
// UploadCreateReq 预上传请求
179+
// parentFileID number 必填 父目录id,上传到根目录时填写 0
180+
// filename string 必填 文件名要小于255个字符且不能包含以下任何字符:"\/:*?|><。(注:不能重名)
181+
// containDir 为 true 时,传入路径+文件名,例如:/你好/123/测试文件.mp4
182+
// etag string 必填 文件md5
183+
// size number 必填 文件大小,单位为 byte 字节
184+
// duplicate number 非必填 当有相同文件名时,文件处理策略(1保留两者,新文件名将自动添加后缀,2覆盖原文件)
185+
// containDir bool 非必填 上传文件是否包含路径,默认false
186+
type UploadCreateReq struct {
187+
ParentFileID uint64 `json:"parentFileID"`
188+
FileName string `json:"filename"`
189+
Etag string `json:"etag"`
190+
Size int64 `json:"size"`
191+
Duplicate int `json:"duplicate"`
192+
ContainDir bool `json:"containDir"`
193+
}
194+
195+
type UploadCreateResp struct {
196+
BaseResp
197+
Data UploadCreateData `json:"data"`
198+
}
199+
200+
// UploadCreateData 预上传响应
201+
// fileID number 非必填 文件ID。当123云盘已有该文件,则会发生秒传。此时会将文件ID字段返回。唯一
202+
// preuploadID string 必填 预上传ID(如果 reuse 为 true 时,该字段不存在)
203+
// reuse boolean 必填 是否秒传,返回true时表示文件已上传成功
204+
// sliceSize number 必填 分片大小,必须按此大小生成文件分片再上传
205+
// servers array 必填 上传地址
206+
type UploadCreateData struct {
207+
FileID int64 `json:"fileID"`
208+
PreuploadID string `json:"preuploadID"`
209+
Reuse bool `json:"reuse"`
210+
SliceSize int64 `json:"sliceSize"`
211+
Servers []string `json:"servers"`
212+
}
213+
214+
// UploadSliceReq 分片上传请求
215+
// preuploadID string 必填 预上传ID
216+
// sliceNo number 必填 分片序号,从1开始自增
217+
// sliceMD5 string 必填 当前分片md5
218+
// slice file 必填 分片二进制流
219+
type UploadSliceReq struct {
220+
Name string `json:"name"`
221+
PreuploadID string `json:"preuploadID"`
222+
SliceNo int `json:"sliceNo"`
223+
SliceMD5 string `json:"sliceMD5"`
224+
Slice io.Reader `json:"slice"`
225+
Server string `json:"server"`
226+
}
227+
228+
type SliceUpCompleteResp struct {
229+
SingleUploadResp
230+
}
231+
232+
type GetUploadServerResp struct {
233+
BaseResp
234+
Data []string `json:"data"`
235+
}
236+
237+
// SingleUploadReq 单文件上传请求
238+
// parentFileID number 必填 父目录id,上传到根目录时填写 0
239+
// filename string 必填 文件名要小于255个字符且不能包含以下任何字符:"\/:*?|><。(注:不能重名)
240+
//
241+
// containDir 为 true 时,传入路径+文件名,例如:/你好/123/测试文件.mp4
242+
//
243+
// etag string 必填 文件md5
244+
// size number 必填 文件大小,单位为 byte 字节
245+
// file file 必填 文件二进制流
246+
// duplicate number 非必填 当有相同文件名时,文件处理策略(1保留两者,新文件名将自动添加后缀,2覆盖原文件)
247+
// containDir bool 非必填 上传文件是否包含路径,默认false
248+
type SingleUploadReq struct {
249+
ParentFileID int64 `json:"parentFileID"`
250+
FileName string `json:"filename"`
251+
Etag string `json:"etag"`
252+
Size int64 `json:"size"`
253+
File io.Reader `json:"file"`
254+
Duplicate int `json:"duplicate"`
255+
ContainDir bool `json:"containDir"`
256+
}
257+
258+
// SingleUploadResp 单文件上传响应
259+
type SingleUploadResp struct {
260+
BaseResp
261+
Data SingleUploadData `json:"data"`
262+
}
263+
264+
type SingleUploadData struct {
265+
FileID int64 `json:"fileID"`
266+
Completed bool `json:"completed"`
267+
}

drivers/123_open/upload.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"context"
66
"encoding/json"
7+
"errors"
78
"fmt"
89
"io"
910
"mime/multipart"
@@ -20,6 +21,7 @@ import (
2021
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
2122
"github.com/avast/retry-go"
2223
"github.com/go-resty/resty/v2"
24+
log "github.com/sirupsen/logrus"
2325
)
2426

2527
// 创建文件 V2
@@ -183,3 +185,63 @@ func (d *Open123) complete(preuploadID string) (*UploadCompleteResp, error) {
183185
}
184186
return &resp, nil
185187
}
188+
189+
func (d *Open123) uploadSlice(req *UploadSliceReq) error {
190+
_, err := d.Request(InitApiInfo(req.Server+"/upload/v2/file/slice", 0), http.MethodPost, func(rt *resty.Request) {
191+
rt.SetHeader("Content-Type", "multipart/form-data")
192+
rt.SetMultipartFormData(map[string]string{
193+
"preuploadID": req.PreuploadID,
194+
"sliceMD5": req.SliceMD5,
195+
"sliceNo": strconv.FormatInt(int64(req.SliceNo), 10),
196+
})
197+
rt.SetMultipartField("slice", req.Name, "multipart/form-data", req.Slice)
198+
}, nil)
199+
return err
200+
}
201+
202+
func (d *Open123) sliceUpComplete(uploadID string) error {
203+
r := &SliceUpCompleteResp{}
204+
205+
b, err := d.Request(UploadComplete, http.MethodPost, func(req *resty.Request) {
206+
req.SetBody(base.Json{
207+
"preuploadID": uploadID,
208+
})
209+
}, r)
210+
if err != nil {
211+
log.Error("123 open uploadComplete error", err)
212+
return err
213+
}
214+
log.Infof("upload complete,body: %s", string(b))
215+
if r.Data.Completed {
216+
return nil
217+
}
218+
219+
return errors.New("upload uncomplete")
220+
221+
}
222+
223+
func (d *Open123) getUploadServer() (string, error) {
224+
r := &GetUploadServerResp{}
225+
body, err := d.Request(UploadFileDomain, "GET", nil, r)
226+
if err != nil {
227+
log.Error("get upload server failed", string(body), r, err)
228+
return "", err
229+
}
230+
if len(r.Data) == 0 {
231+
return "", errors.New("upload server is empty")
232+
}
233+
234+
return r.Data[0], err
235+
}
236+
237+
func (d *Open123) uploadCreate(uc *UploadCreateReq) (*UploadCreateData, error) {
238+
r := &UploadCreateResp{}
239+
_, err := d.Request(UploadCreate, http.MethodPost, func(req *resty.Request) {
240+
req.SetBody(uc)
241+
}, r)
242+
if err != nil {
243+
log.Error("123 open uploadCreate error", err)
244+
}
245+
return &r.Data, err
246+
247+
}

drivers/123_open/util.go

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,19 @@ import (
2121
var ( //不同情况下获取的AccessTokenQPS限制不同 如下模块化易于拓展
2222
Api = "https://open-api.123pan.com"
2323

24-
AccessToken = InitApiInfo(Api+"/api/v1/access_token", 1)
25-
RefreshToken = InitApiInfo(Api+"/api/v1/oauth2/access_token", 1)
26-
UserInfo = InitApiInfo(Api+"/api/v1/user/info", 1)
27-
FileList = InitApiInfo(Api+"/api/v2/file/list", 3)
28-
DownloadInfo = InitApiInfo(Api+"/api/v1/file/download_info", 5)
29-
DirectLink = InitApiInfo(Api+"/api/v1/direct-link/url", 5)
30-
Mkdir = InitApiInfo(Api+"/upload/v1/file/mkdir", 2)
31-
Move = InitApiInfo(Api+"/api/v1/file/move", 1)
32-
Rename = InitApiInfo(Api+"/api/v1/file/name", 1)
33-
Trash = InitApiInfo(Api+"/api/v1/file/trash", 2)
34-
UploadCreate = InitApiInfo(Api+"/upload/v2/file/create", 2)
35-
UploadComplete = InitApiInfo(Api+"/upload/v2/file/upload_complete", 0)
24+
AccessToken = InitApiInfo(Api+"/api/v1/access_token", 1)
25+
RefreshToken = InitApiInfo(Api+"/api/v1/oauth2/access_token", 1)
26+
UserInfo = InitApiInfo(Api+"/api/v1/user/info", 1)
27+
FileList = InitApiInfo(Api+"/api/v2/file/list", 3)
28+
DownloadInfo = InitApiInfo(Api+"/api/v1/file/download_info", 5)
29+
DirectLink = InitApiInfo(Api+"/api/v1/direct-link/url", 5)
30+
Mkdir = InitApiInfo(Api+"/upload/v1/file/mkdir", 2)
31+
Move = InitApiInfo(Api+"/api/v1/file/move", 1)
32+
Rename = InitApiInfo(Api+"/api/v1/file/name", 1)
33+
Trash = InitApiInfo(Api+"/api/v1/file/trash", 2)
34+
UploadCreate = InitApiInfo(Api+"/upload/v2/file/create", 2)
35+
UploadComplete = InitApiInfo(Api+"/upload/v2/file/upload_complete", 0)
36+
UploadFileDomain = InitApiInfo(Api+"/upload/v2/file/domain", 0)
3637
)
3738

3839
func (d *Open123) Request(apiInfo *ApiInfo, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
@@ -78,7 +79,10 @@ func (d *Open123) Request(apiInfo *ApiInfo, method string, callback base.ReqCall
7879
} else if baseResp.Code == 429 {
7980
time.Sleep(500 * time.Millisecond)
8081
log.Warningf("API: %s, QPS: %d, 请求太频繁,对应API提示过多请减小QPS", apiInfo.url, apiInfo.qps)
82+
} else if baseResp.Code == 20103 { //code: 20103, error: 文件正在校验中,请间隔1秒后再试
83+
time.Sleep(2 * time.Second)
8184
} else {
85+
log.Errorf("API: %s, body:%s, code: %d, error: %s", apiInfo.url, res.Body(), baseResp.Code, baseResp.Message)
8286
return nil, errors.New(baseResp.Message)
8387
}
8488
}

0 commit comments

Comments
 (0)