@@ -24,6 +24,7 @@ import (
2424 "github.com/OpenListTeam/OpenList/v4/drivers/base"
2525 "github.com/OpenListTeam/OpenList/v4/internal/driver"
2626 "github.com/OpenListTeam/OpenList/v4/internal/model"
27+ "github.com/OpenListTeam/OpenList/v4/internal/stream"
2728 "github.com/OpenListTeam/OpenList/v4/pkg/errgroup"
2829 "github.com/OpenListTeam/OpenList/v4/pkg/utils"
2930 "github.com/avast/retry-go"
@@ -519,57 +520,84 @@ func (d *Doubao) UploadByMultipart(ctx context.Context, config *UploadConfig, fi
519520 totalParts := (fileSize + chunkSize - 1 ) / chunkSize
520521 // 创建分片信息组
521522 parts := make ([]UploadPart , totalParts )
522- // 缓存文件
523- tempFile , err := file .CacheFullInTempFile ()
523+
524+ // 用 stream.NewStreamSectionReader 替代缓存临时文件
525+ ss , err := stream .NewStreamSectionReader (file , int (chunkSize ))
524526 if err != nil {
525- return nil , fmt .Errorf ("failed to cache file : %w" , err )
527+ return nil , fmt .Errorf ("failed to create section reader : %w" , err )
526528 }
527529 up (10.0 ) // 更新进度
528530 // 设置并行上传
529- threadG , uploadCtx := errgroup .NewGroupWithContext (ctx , d .uploadThread ,
530- retry .Attempts (1 ),
531+ thread := min (int (totalParts ), d .uploadThread )
532+ threadG , uploadCtx := errgroup .NewGroupWithContext (ctx , thread ,
533+ retry .Attempts (3 ),
531534 retry .Delay (time .Second ),
532535 retry .DelayType (retry .BackOffDelay ))
533536
534537 var partsMutex sync.Mutex
535538 // 并行上传所有分片
536- for partIndex := int64 ( 0 ); partIndex < totalParts ; partIndex ++ {
539+ for partIndex := range totalParts {
537540 if utils .IsCanceled (uploadCtx ) {
538541 break
539542 }
540- partIndex := partIndex
541543 partNumber := partIndex + 1 // 分片编号从1开始
542544
543- threadG .Go (func (ctx context.Context ) error {
544- // 计算此分片的大小和偏移
545- offset := partIndex * chunkSize
546- size := chunkSize
547- if partIndex == totalParts - 1 {
548- size = fileSize - offset
549- }
545+ // 计算此分片的大小和偏移
546+ offset := partIndex * chunkSize
547+ size := chunkSize
548+ if partIndex == totalParts - 1 {
549+ size = fileSize - offset
550+ }
550551
551- limitedReader := driver .NewLimitedUploadStream (ctx , io .NewSectionReader (tempFile , offset , size ))
552- // 读取数据到内存
553- data , err := io .ReadAll (limitedReader )
554- if err != nil {
555- return fmt .Errorf ("failed to read part %d: %w" , partNumber , err )
552+ var reader * stream.SectionReader
553+ var rateLimitedRd io.Reader
554+ threadG .GoWithResult (func (ctx context.Context ) error {
555+ if reader == nil {
556+ var err error
557+ reader , err = ss .GetSectionReader (offset , size )
558+ if err != nil {
559+ return err
560+ }
561+ rateLimitedRd = driver .NewLimitedUploadStream (ctx , reader )
556562 }
563+ reader .Seek (0 , io .SeekStart )
557564 // 计算CRC32
558- crc32Value := calculateCRC32 (data )
565+ crc32Value := calculateCRC32 (reader )
559566 // 使用_retryOperation上传分片
560- var uploadPart UploadPart
561- if err = d ._retryOperation (fmt .Sprintf ("Upload part %d" , partNumber ), func () error {
562- var err error
563- uploadPart , err = d .uploadPart (config , uploadUrl , uploadID , partNumber , data , crc32Value )
567+ reader .Seek (0 , io .SeekStart )
568+ req , err := http .NewRequestWithContext (ctx , http .MethodPost , fmt .Sprintf ("%s?uploadid=%s&part_number=%d&phase=transfer" , uploadUrl , uploadID , partNumber ), rateLimitedRd )
569+ if err != nil {
570+ return err
571+ }
572+ req .Header = map [string ][]string {
573+ "Referer" : {BaseURL + "/" },
574+ "Origin" : {BaseURL },
575+ "User-Agent" : {UserAgent },
576+ "X-Storage-U" : {d .UserId },
577+ "Authorization" : {storeInfo .Auth },
578+ "Content-Type" : {"application/octet-stream" },
579+ "Content-Crc32" : {crc32Value },
580+ "Content-Length" : {fmt .Sprintf ("%d" , size )},
581+ "Content-Disposition" : {fmt .Sprintf ("attachment; filename=%s" , url .QueryEscape (storeInfo .StoreURI ))},
582+ }
583+ res , err := base .HttpClient .Do (req )
584+ if err != nil {
564585 return err
565- }); err != nil {
566- return fmt .Errorf ("part %d upload failed: %w" , partNumber , err )
586+ }
587+ defer res .Body .Close ()
588+ bytes , _ := io .ReadAll (res .Body )
589+ uploadResp := UploadResp {}
590+ utils .Json .Unmarshal (bytes , & uploadResp )
591+ if uploadResp .Code != 2000 {
592+ return fmt .Errorf ("upload part failed: %s" , uploadResp .Message )
593+ } else if uploadResp .Data .Crc32 != crc32Value {
594+ return fmt .Errorf ("upload part failed: crc32 mismatch, expected %s, got %s" , crc32Value , uploadResp .Data .Crc32 )
567595 }
568596 // 记录成功上传的分片
569597 partsMutex .Lock ()
570598 parts [partIndex ] = UploadPart {
571599 PartNumber : strconv .FormatInt (partNumber , 10 ),
572- Etag : uploadPart .Etag ,
600+ Etag : uploadResp . Data .Etag ,
573601 Crc32 : crc32Value ,
574602 }
575603 partsMutex .Unlock ()
@@ -578,6 +606,8 @@ func (d *Doubao) UploadByMultipart(ctx context.Context, config *UploadConfig, fi
578606 up (math .Min (progress , 95.0 ))
579607
580608 return nil
609+ }, func (err error ) {
610+ ss .RecycleSectionReader (reader )
581611 })
582612 }
583613
@@ -785,9 +815,9 @@ func (d *Doubao) commitMultipartUpload(uploadConfig *UploadConfig) error {
785815}
786816
787817// 计算CRC32
788- func calculateCRC32 (data [] byte ) string {
818+ func calculateCRC32 (rs io. Reader ) string {
789819 hash := crc32 .NewIEEE ()
790- hash . Write ( data )
820+ utils . CopyWithBuffer ( hash , rs )
791821 return hex .EncodeToString (hash .Sum (nil ))
792822}
793823
0 commit comments