2025-06-17 18:38:25 +08:00
|
|
|
|
package _123_open
|
|
|
|
|
|
|
|
|
|
import (
|
2025-08-06 15:27:13 +08:00
|
|
|
|
"bytes"
|
2025-06-17 18:38:25 +08:00
|
|
|
|
"context"
|
2025-08-06 15:27:13 +08:00
|
|
|
|
"encoding/json"
|
|
|
|
|
"fmt"
|
2025-08-05 21:42:54 +08:00
|
|
|
|
"io"
|
2025-08-06 15:27:13 +08:00
|
|
|
|
"mime/multipart"
|
2025-06-17 18:38:25 +08:00
|
|
|
|
"net/http"
|
2025-08-06 15:27:13 +08:00
|
|
|
|
"strconv"
|
2025-06-24 22:14:11 +08:00
|
|
|
|
"strings"
|
2025-06-17 18:38:25 +08:00
|
|
|
|
"time"
|
|
|
|
|
|
2025-07-01 09:54:50 +08:00
|
|
|
|
"github.com/OpenListTeam/OpenList/v4/drivers/base"
|
|
|
|
|
"github.com/OpenListTeam/OpenList/v4/internal/driver"
|
|
|
|
|
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
2025-08-05 21:42:54 +08:00
|
|
|
|
"github.com/OpenListTeam/OpenList/v4/internal/stream"
|
2025-07-01 09:54:50 +08:00
|
|
|
|
"github.com/OpenListTeam/OpenList/v4/pkg/errgroup"
|
|
|
|
|
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
2025-06-17 18:38:25 +08:00
|
|
|
|
"github.com/avast/retry-go"
|
|
|
|
|
"github.com/go-resty/resty/v2"
|
|
|
|
|
)
|
|
|
|
|
|
2025-08-06 15:27:13 +08:00
|
|
|
|
// 创建文件 V2
|
2025-06-17 18:38:25 +08:00
|
|
|
|
func (d *Open123) create(parentFileID int64, filename string, etag string, size int64, duplicate int, containDir bool) (*UploadCreateResp, error) {
|
|
|
|
|
var resp UploadCreateResp
|
|
|
|
|
_, err := d.Request(UploadCreate, http.MethodPost, func(req *resty.Request) {
|
|
|
|
|
req.SetBody(base.Json{
|
|
|
|
|
"parentFileId": parentFileID,
|
|
|
|
|
"filename": filename,
|
2025-06-24 22:14:11 +08:00
|
|
|
|
"etag": strings.ToLower(etag),
|
2025-06-17 18:38:25 +08:00
|
|
|
|
"size": size,
|
|
|
|
|
"duplicate": duplicate,
|
|
|
|
|
"containDir": containDir,
|
|
|
|
|
})
|
|
|
|
|
}, &resp)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return &resp, nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-06 15:27:13 +08:00
|
|
|
|
// 上传分片 V2
|
2025-06-17 18:38:25 +08:00
|
|
|
|
func (d *Open123) Upload(ctx context.Context, file model.FileStreamer, createResp *UploadCreateResp, up driver.UpdateProgress) error {
|
2025-08-06 15:27:13 +08:00
|
|
|
|
uploadDomain := createResp.Data.Servers[0]
|
2025-06-17 18:38:25 +08:00
|
|
|
|
size := file.GetSize()
|
|
|
|
|
chunkSize := createResp.Data.SliceSize
|
2025-08-11 23:41:22 +08:00
|
|
|
|
|
|
|
|
|
ss, err := stream.NewStreamSectionReader(file, int(chunkSize), &up)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-17 18:38:25 +08:00
|
|
|
|
uploadNums := (size + chunkSize - 1) / chunkSize
|
2025-08-05 21:42:54 +08:00
|
|
|
|
thread := min(int(uploadNums), d.UploadThread)
|
|
|
|
|
threadG, uploadCtx := errgroup.NewOrderedGroupWithContext(ctx, thread,
|
2025-06-17 18:38:25 +08:00
|
|
|
|
retry.Attempts(3),
|
|
|
|
|
retry.Delay(time.Second),
|
|
|
|
|
retry.DelayType(retry.BackOffDelay))
|
|
|
|
|
|
2025-08-06 15:27:13 +08:00
|
|
|
|
for partIndex := range uploadNums {
|
2025-06-17 18:38:25 +08:00
|
|
|
|
if utils.IsCanceled(uploadCtx) {
|
2025-08-05 21:42:54 +08:00
|
|
|
|
break
|
2025-06-17 18:38:25 +08:00
|
|
|
|
}
|
|
|
|
|
partIndex := partIndex
|
|
|
|
|
partNumber := partIndex + 1 // 分片号从1开始
|
|
|
|
|
offset := partIndex * chunkSize
|
|
|
|
|
size := min(chunkSize, size-offset)
|
2025-08-05 21:42:54 +08:00
|
|
|
|
var reader *stream.SectionReader
|
|
|
|
|
var rateLimitedRd io.Reader
|
2025-08-06 15:27:13 +08:00
|
|
|
|
sliceMD5 := ""
|
2025-08-17 14:25:23 +08:00
|
|
|
|
// 表单
|
|
|
|
|
b := bytes.NewBuffer(make([]byte, 0, 2048))
|
2025-08-05 21:42:54 +08:00
|
|
|
|
threadG.GoWithLifecycle(errgroup.Lifecycle{
|
|
|
|
|
Before: func(ctx context.Context) error {
|
|
|
|
|
if reader == nil {
|
|
|
|
|
var err error
|
2025-08-06 15:27:13 +08:00
|
|
|
|
// 每个分片一个reader
|
2025-08-05 21:42:54 +08:00
|
|
|
|
reader, err = ss.GetSectionReader(offset, size)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2025-08-06 15:27:13 +08:00
|
|
|
|
// 计算当前分片的MD5
|
|
|
|
|
sliceMD5, err = utils.HashReader(utils.MD5, reader)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2025-08-05 21:42:54 +08:00
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
},
|
|
|
|
|
Do: func(ctx context.Context) error {
|
2025-08-06 15:27:13 +08:00
|
|
|
|
// 重置分片reader位置,因为HashReader、上一次失败已经读取到分片EOF
|
2025-08-05 21:42:54 +08:00
|
|
|
|
reader.Seek(0, io.SeekStart)
|
2025-08-06 15:27:13 +08:00
|
|
|
|
|
2025-08-17 14:25:23 +08:00
|
|
|
|
b.Reset()
|
|
|
|
|
w := multipart.NewWriter(b)
|
2025-08-06 15:27:13 +08:00
|
|
|
|
// 添加表单字段
|
|
|
|
|
err = w.WriteField("preuploadID", createResp.Data.PreuploadID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
err = w.WriteField("sliceNo", strconv.FormatInt(partNumber, 10))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
err = w.WriteField("sliceMD5", sliceMD5)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
// 写入文件内容
|
2025-08-17 14:25:23 +08:00
|
|
|
|
_, err = w.CreateFormFile("slice", fmt.Sprintf("%s.part%d", file.GetName(), partNumber))
|
2025-08-06 15:27:13 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2025-08-17 14:25:23 +08:00
|
|
|
|
headSize := b.Len()
|
2025-08-06 15:27:13 +08:00
|
|
|
|
err = w.Close()
|
2025-08-05 21:42:54 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2025-08-17 14:25:23 +08:00
|
|
|
|
head := bytes.NewReader(b.Bytes()[:headSize])
|
|
|
|
|
tail := bytes.NewReader(b.Bytes()[headSize:])
|
|
|
|
|
rateLimitedRd = driver.NewLimitedUploadStream(ctx, io.MultiReader(head, reader, tail))
|
2025-08-06 15:27:13 +08:00
|
|
|
|
// 创建请求并设置header
|
2025-08-17 14:25:23 +08:00
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, uploadDomain+"/upload/v2/file/slice", rateLimitedRd)
|
2025-08-05 21:42:54 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2025-08-06 15:27:13 +08:00
|
|
|
|
|
|
|
|
|
// 设置请求头
|
|
|
|
|
req.Header.Add("Authorization", "Bearer "+d.AccessToken)
|
|
|
|
|
req.Header.Add("Content-Type", w.FormDataContentType())
|
|
|
|
|
req.Header.Add("Platform", "open_platform")
|
2025-06-17 18:38:25 +08:00
|
|
|
|
|
2025-08-05 21:42:54 +08:00
|
|
|
|
res, err := base.HttpClient.Do(req)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2025-08-06 15:27:13 +08:00
|
|
|
|
defer res.Body.Close()
|
|
|
|
|
if res.StatusCode != 200 {
|
|
|
|
|
return fmt.Errorf("slice %d upload failed, status code: %d", partNumber, res.StatusCode)
|
|
|
|
|
}
|
|
|
|
|
var resp BaseResp
|
|
|
|
|
respBody, err := io.ReadAll(res.Body)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
err = json.Unmarshal(respBody, &resp)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if resp.Code != 0 {
|
|
|
|
|
return fmt.Errorf("slice %d upload failed: %s", partNumber, resp.Message)
|
|
|
|
|
}
|
2025-06-17 18:38:25 +08:00
|
|
|
|
|
2025-08-05 21:42:54 +08:00
|
|
|
|
progress := 10.0 + 85.0*float64(threadG.Success())/float64(uploadNums)
|
|
|
|
|
up(progress)
|
|
|
|
|
return nil
|
|
|
|
|
},
|
|
|
|
|
After: func(err error) {
|
2025-08-11 23:41:22 +08:00
|
|
|
|
ss.FreeSectionReader(reader)
|
2025-08-05 21:42:54 +08:00
|
|
|
|
},
|
2025-06-17 18:38:25 +08:00
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := threadG.Wait(); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-06 15:27:13 +08:00
|
|
|
|
return nil
|
|
|
|
|
}
|
2025-06-17 18:38:25 +08:00
|
|
|
|
|
2025-08-06 15:27:13 +08:00
|
|
|
|
// 上传完毕
|
|
|
|
|
func (d *Open123) complete(preuploadID string) (*UploadCompleteResp, error) {
|
|
|
|
|
var resp UploadCompleteResp
|
|
|
|
|
_, err := d.Request(UploadComplete, http.MethodPost, func(req *resty.Request) {
|
|
|
|
|
req.SetBody(base.Json{
|
|
|
|
|
"preuploadID": preuploadID,
|
|
|
|
|
})
|
|
|
|
|
}, &resp)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
2025-06-17 18:38:25 +08:00
|
|
|
|
}
|
2025-08-06 15:27:13 +08:00
|
|
|
|
return &resp, nil
|
2025-06-17 18:38:25 +08:00
|
|
|
|
}
|