Repository: AlistGo/alist (language: go)

Issue:
复制文件到115网盘报错: upload failed
### Please make sure of the following things

- [X] I have read the [documentation](https://alist.nn.ci).
我已经阅读了[文档](https://alist.nn.ci)。

- [X] I'm sure there are no duplicate issues or discussions.
我确定没有重复的issue或讨论。

- [X] I'm sure it's due to `AList` and not something else(such as [Network](https://alist.nn.ci/faq/howto.html#tls-handshake-timeout-read-connection-reset-by-peer-dns-lookup-failed-connect-connection-refused-client-timeout-exceeded-while-awaiting-headers-no-such-host) ,`Dependencies` or `Operational`).
我确定是`AList`的问题，而不是其他原因（例如[网络](https://alist.nn.ci/zh/faq/howto.html#tls-handshake-timeout-read-connection-reset-by-peer-dns-lookup-failed-connect-connection-refused-client-timeout-exceeded-while-awaiting-headers-no-such-host)，`依赖`或`操作`）。

- [X] I'm sure this issue is not fixed in the latest version.
我确定这个问题在最新版本中没有被修复。


### AList Version / AList 版本

v3.38.0

### Driver used / 使用的存储驱动

115

### Describe the bug / 问题描述

- 版本

```bash
./alist version
Built At: 2024-10-11 17:03:33 +0000
Go Version: go1.22.4 linux/amd64
Author: Xhofe <i@nn.ci>
Commit ID: c3e43ff6
Version: v3.38.0
WebVersion: 3.38.0
```

- 问题


alist 所在服务器的本地文件(100M以上)复制到115网盘报错: upload failed
![Snipaste_2024-10-25_13-55-59](https://github.com/user-attachments/assets/322c3a87-c919-492c-b19e-a20a497a987a)

但通过网页上传文件(100M以上)到115网盘是成功的
![Snipaste_2024-10-25_13-56-45](https://github.com/user-attachments/assets/e6499594-2f11-44f4-b3bd-c5fab02a8b9e)




### Reproduction / 复现链接

详见描述

### Config / 配置

```json
{
  "force": false,
  "site_url": "",
  "cdn": "",
  "jwt_secret": "UrOws0KhRK6Q9MFY",
  "token_expires_in": 48,
  "database": {
    "type": "sqlite3",
    "host": "",
    "port": 0,
    "user": "",
    "password": "",
    "name": "",
    "db_file": "data/data.db",
    "table_prefix": "x_",
    "ssl_mode": "",
    "dsn": ""
  },
  "meilisearch": {
    "host": "http://localhost:7700",
    "api_key": "",
    "index_prefix": ""
  },
  "scheme": {
    "address": "0.0.0.0",
    "http_port": 5244,
    "https_port": -1,
    "force_https": false,
    "cert_file": "",
    "key_file": "",
    "unix_file": "",
    "unix_file_perm": ""
  },
  "temp_dir": "data/temp",
  "bleve_dir": "data/bleve",
  "dist_dir": "",
  "log": {
    "enable": true,
    "name": "data/log/log.log",
    "max_size": 50,
    "max_backups": 30,
    "max_age": 28,
    "compress": false
  },
  "delayed_start": 0,
  "max_connections": 0,
  "tls_insecure_skip_verify": true,
  "tasks": {
    "download": {
      "workers": 5,
      "max_retry": 1,
      "task_persistant": true
    },
    "transfer": {
      "workers": 5,
      "max_retry": 2,
      "task_persistant": true
    },
    "upload": {
      "workers": 5,
      "max_retry": 0,
      "task_persistant": false
    },
    "copy": {
      "workers": 5,
      "max_retry": 2,
      "task_persistant": true
    }
  },
  "cors": {
    "allow_origins": [
      "*"
    ],
    "allow_methods": [
      "*"
    ],
    "allow_headers": [
      "*"
    ]
  },
  "s3": {
    "enable": false,
    "port": 5246,
    "ssl": false
  }
}
```

### Logs / 日志

```txt
ERRO[2024-10-25 13:19:18] COPY /dav/local/live/biliup/upload/2024-10-25/星星星星星的工作室/星星星星星的工作室.2024-10-25 09_39_27.ing.flv upload failed
github.com/SheltonZhu/115driver/pkg/driver.init
	<autogenerated>:1
runtime.doInit1
	/home/runner/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.22.4.linux-amd64/src/runtime/proc.go:7176
runtime.doInit
	/home/runner/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.22.4.linux-amd64/src/runtime/proc.go:7143
runtime.main
	/home/runner/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.22.4.linux-amd64/src/runtime/proc.go:253
runtime.goexit
	/home/runner/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.22.4.linux-amd64/src/runtime/asm_amd64.s:1695
github.com/alist-org/alist/v3/internal/op.Put
	/home/runner/work/alist/alist/internal/op/fs.go:587
github.com/alist-org/alist/v3/internal/fs._copy
	/home/runner/work/alist/alist/internal/fs/copy.go:91
github.com/alist-org/alist/v3/internal/fs.Copy
	/home/runner/work/alist/alist/internal/fs/fs.go:73
github.com/alist-org/alist/v3/server/webdav.copyFiles
	/home/runner/work/alist/alist/server/webdav/file.go:59
github.com/alist-org/alist/v3/server/webdav.(*Handler).handleCopyMove
	/home/runner/work/alist/alist/server/webdav/webdav.go:477
github.com/alist-org/alist/v3/server/webdav.(*Handler).ServeHTTP
	/home/runner/work/alist/alist/server/webdav/webdav.go:70
github.com/alist-org/alist/v3/server.ServeWebDAV
	/home/runner/work/alist/alist/server/webdav.go:46
github.com/gin-gonic/gin.(*Context).Next
	/home/runner/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185
github.com/alist-org/alist/v3/server.WebDAVAuth
	/home/runner/work/alist/alist/server/webdav.go:113
github.com/gin-gonic/gin.(*Context).Next
	/home/runner/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185
github.com/alist-org/alist/v3/server/middlewares.StoragesLoaded
	/home/runner/work/alist/alist/server/middlewares/check.go:14
github.com/gin-gonic/gin.(*Context).Next
	/home/runner/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185
github.com/gin-gonic/gin.CustomRecoveryWithWriter.func1
	/home/runner/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/recovery.go:102
github.com/gin-gonic/gin.(*Context).Next
	/home/runner/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185
github.com/gin-gonic/gin.LoggerWithConfig.func1
	/home/runner/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/logger.go:249
github.com/gin-gonic/gin.(*Context).Next
	/home/runner/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185
github.com/gin-gonic/gin.(*Engine).handleHTTPRequest
	/home/runner/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/gin.go:633
github.com/gin-gonic/gin.(*Engine).ServeHTTP
	/home/runner/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/gin.go:589
net/http.serverHandler.ServeHTTP
	/home/runner/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.22.4.linux-amd64/src/net/http/server.go:3137
net/http.(*conn).serve
	/home/runner/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.22.4.linux-amd64/src/net/http/server.go:2039
runtime.goexit
	/home/runner/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.22.4.linux-amd64/src/runtime/asm_amd64.s:1695 
[GIN] 2024/10/25 - 13:19:18 | 500 | 39.043209803s |       127.0.0.1 | COPY     "/dav/local/live/biliup/upload/2024-10-25/星星星星星的工作室/星星星星星的工作室.2024-10-25 09_39_27.ing.flv"
```


Discussion / hints:
+1，上传5m的zip都不行
+1
准确地说，是秒传还可以用，但全新完整地上传是报错的，就是提示"Upload failed"
> +1 准确地说，是秒传还可以用，但全新完整地上传是报错的，就是提示“Upload failed”

是的，现在我网页上传一个未上传过的文件报: 
```txt
{"StatusCode":"400","ErrorCode":"400","ErrorMessage":"Throttling.User"}: unexpected error
```
+1
上传文件报错：{"StatusCode":"400","ErrorCode":"400","ErrorMessage":"Throttling.User"}: unexpected error
+1
+1
can't obtain oss token from here https://uplb.115.com/3.0/gettoken.php which gives `{"StatusCode":"400","ErrorCode":"400","ErrorMessage":"Throttling.User"}`
> > +1 准确地说，是秒传还可以用，但全新完整地上传是报错的，就是提示“Upload failed”
> 
> 是的，现在我网页上传一个未上传过的文件报:
> 
> ```
> {"StatusCode":"400","ErrorCode":"400","ErrorMessage":"Throttling.User"}: unexpected error
> ```

确实是这样，upload failed 和 400 Throttling.User 交替出现
+1
关注中
+1
是的, 最近突然出现的问题
同样的问题，期待修复，感谢
115调整接口了，期待大佬修复
期待大佬早日修复解决  非常感谢
+1，希望尽快修复
mark
相同问题,期待修复
+1 相同问题,期待修复
猜测是115再次收紧API以及上传限制
+1
等大佬修复
+1
频繁更新接口，官方开始打击三方了，项目风险很高
https://github.com/orzogc/fake115uploader/issues/74#issuecomment-2443455879

这个串有定位到问题。

@xhofe 

Fix patch:
diff --git a/drivers/115/meta.go b/drivers/115/meta.go
index 38c1742a741..d9526775229 100644
--- a/drivers/115/meta.go
+++ b/drivers/115/meta.go
@@ -9,7 +9,7 @@ type Addition struct {
 	Cookie       string  `json:"cookie" type:"text" help:"one of QR code token and cookie required"`
 	QRCodeToken  string  `json:"qrcode_token" type:"text" help:"one of QR code token and cookie required"`
 	QRCodeSource string  `json:"qrcode_source" type:"select" options:"web,android,ios,tv,alipaymini,wechatmini,qandroid" default:"linux" help:"select the QR code device, default linux"`
-	PageSize     int64   `json:"page_size" type:"number" default:"56" help:"list api per page size of 115 driver"`
+	PageSize     int64   `json:"page_size" type:"number" default:"1000" help:"list api per page size of 115 driver"`
 	LimitRate    float64 `json:"limit_rate" type:"number" default:"2" help:"limit all api request rate (1r/[limit_rate]s)"`
 	driver.RootID
 }
diff --git a/drivers/115/types.go b/drivers/115/types.go
index 830e347b44e..40b951d80ce 100644
--- a/drivers/115/types.go
+++ b/drivers/115/types.go
@@ -1,10 +1,11 @@
 package _115
 
 import (
+	"time"
+
 	"github.com/SheltonZhu/115driver/pkg/driver"
 	"github.com/alist-org/alist/v3/internal/model"
 	"github.com/alist-org/alist/v3/pkg/utils"
-	"time"
 )
 
 var _ model.Obj = (*FileObj)(nil)
@@ -20,3 +21,18 @@ func (f *FileObj) CreateTime() time.Time {
 func (f *FileObj) GetHash() utils.HashInfo {
 	return utils.NewHashInfo(utils.SHA1, f.Sha1)
 }
+
+type UploadResult struct {
+	driver.BasicResp
+	Data struct {
+		PickCode string `json:"pick_code"`
+		FileSize int    `json:"file_size"`
+		FileID   string `json:"file_id"`
+		ThumbURL string `json:"thumb_url"`
+		Sha1     string `json:"sha1"`
+		Aid      int    `json:"aid"`
+		FileName string `json:"file_name"`
+		Cid      string `json:"cid"`
+		IsVideo  int    `json:"is_video"`
+	} `json:"data"`
+}
diff --git a/drivers/115/util.go b/drivers/115/util.go
index 7d5889af9f9..381ef0bd185 100644
--- a/drivers/115/util.go
+++ b/drivers/115/util.go
@@ -10,7 +10,6 @@ import (
 	"io"
 	"net/http"
 	"net/url"
-	"path/filepath"
 	"strconv"
 	"strings"
 	"sync"
@@ -254,6 +253,7 @@ func (d *Pan115) UploadByMultipart(params *driver115.UploadOSSParams, fileSize i
 		ossClient *oss.Client
 		bucket    *oss.Bucket
 		ossToken  *driver115.UploadOSSTokenResp
+		bodyBytes []byte
 		err       error
 	)
 
@@ -268,12 +268,14 @@ func (d *Pan115) UploadByMultipart(params *driver115.UploadOSSParams, fileSize i
 			f(options)
 		}
 	}
+	// oss 启用Sequential必须按顺序上传
+	options.ThreadsNum = 1
 
 	if ossToken, err = d.client.GetOSSToken(); err != nil {
 		return err
 	}
 
-	if ossClient, err = oss.New(driver115.OSSEndpoint, ossToken.AccessKeyID, ossToken.AccessKeySecret); err != nil {
+	if ossClient, err = oss.New(driver115.OSSEndpoint, ossToken.AccessKeyID, ossToken.AccessKeySecret, oss.EnableMD5(true), oss.EnableCRC(true)); err != nil {
 		return err
 	}
 
@@ -294,6 +296,7 @@ func (d *Pan115) UploadByMultipart(params *driver115.UploadOSSParams, fileSize i
 	if imur, err = bucket.InitiateMultipartUpload(params.Object,
 		oss.SetHeader(driver115.OssSecurityTokenHeaderName, ossToken.SecurityToken),
 		oss.UserAgentHeader(driver115.OSSUserAgent),
+		oss.EnableSha1(), oss.Sequential(),
 	); err != nil {
 		return err
 	}
@@ -337,8 +340,7 @@ func (d *Pan115) UploadByMultipart(params *driver115.UploadOSSParams, fileSize i
 						continue
 					}
 
-					b := bytes.NewBuffer(buf)
-					if part, err = bucket.UploadPart(imur, b, chunk.Size, chunk.Number, driver115.OssOption(params, ossToken)...); err == nil {
+					if part, err = bucket.UploadPart(imur, bytes.NewBuffer(buf), chunk.Size, chunk.Number, driver115.OssOption(params, ossToken)...); err == nil {
 						break
 					}
 				}
@@ -373,14 +375,20 @@ LOOP:
 		}
 	}
 
-	// EOF错误是xml的Unmarshal导致的，响应其实是json格式，所以实际上上传是成功的
-	if _, err = bucket.CompleteMultipartUpload(imur, parts, driver115.OssOption(params, ossToken)...); err != nil && !errors.Is(err, io.EOF) {
-		// 当文件名含有 &< 这两个字符之一时响应的xml解析会出现错误，实际上上传是成功的
-		if filename := filepath.Base(stream.GetName()); !strings.ContainsAny(filename, "&<") {
-			return err
-		}
+	// 不知道啥原因，oss那边分片上传不计算sha1，导致115服务器校验错误
+	// params.Callback.Callback = strings.ReplaceAll(params.Callback.Callback, "${sha1}", params.SHA1)
+	if _, err := bucket.CompleteMultipartUpload(imur, parts, append(
+		driver115.OssOption(params, ossToken),
+		oss.CallbackResult(&bodyBytes),
+	)...); err != nil {
+		return err
 	}
-	return d.checkUploadStatus(dirID, params.SHA1)
+
+	var uploadResult UploadResult
+	if err = json.Unmarshal(bodyBytes, &uploadResult); err != nil {
+		return err
+	}
+	return uploadResult.Err(string(bodyBytes))
 }
 
 func chunksProducer(ch chan oss.FileChunk, chunks []oss.FileChunk) {
@@ -389,27 +397,6 @@ func chunksProducer(ch chan oss.FileChunk, chunks []oss.FileChunk) {
 	}
 }
 
-func (d *Pan115) checkUploadStatus(dirID, sha1 string) error {
-	// 验证上传是否成功
-	req := d.client.NewRequest().ForceContentType("application/json;charset=UTF-8")
-	opts := []driver115.GetFileOptions{
-		driver115.WithOrder(driver115.FileOrderByTime),
-		driver115.WithShowDirEnable(false),
-		driver115.WithAsc(false),
-		driver115.WithLimit(500),
-	}
-	fResp, err := driver115.GetFiles(req, dirID, opts...)
-	if err != nil {
-		return err
-	}
-	for _, fileInfo := range fResp.Files {
-		if fileInfo.Sha1 == sha1 {
-			return nil
-		}
-	}
-	return driver115.ErrUploadFailed
-}
-
 func SplitFile(fileSize int64) (chunks []oss.FileChunk, err error) {
 	for i := int64(1); i < 10; i++ {
 		if fileSize < i*utils.GB { // 文件大小小于iGB时分为i*1000片
diff --git a/drivers/115_share/meta.go b/drivers/115_share/meta.go
index 1d203b24c2b..3fcc7b92133 100644
--- a/drivers/115_share/meta.go
+++ b/drivers/115_share/meta.go
@@ -9,7 +9,7 @@ type Addition struct {
 	Cookie       string  `json:"cookie" type:"text" help:"one of QR code token and cookie required"`
 	QRCodeToken  string  `json:"qrcode_token" type:"text" help:"one of QR code token and cookie required"`
 	QRCodeSource string  `json:"qrcode_source" type:"select" options:"web,android,ios,tv,alipaymini,wechatmini,qandroid" default:"linux" help:"select the QR code device, default linux"`
-	PageSize     int64   `json:"page_size" type:"number" default:"20" help:"list api per page size of 115 driver"`
+	PageSize     int64   `json:"page_size" type:"number" default:"1000" help:"list api per page size of 115 driver"`
 	LimitRate    float64 `json:"limit_rate" type:"number" default:"2" help:"limit all api request rate (1r/[limit_rate]s)"`
 	ShareCode    string  `json:"share_code" type:"text" required:"true" help:"share code of 115 share link"`
 	ReceiveCode  string  `json:"receive_code" type:"text" required:"true" help:"receive code of 115 share link"`


Test patch: