Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions cmd/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,19 @@ import (
"path/filepath"
"strconv"

"github.com/OpenListTeam/OpenList/v4/internal/bootstrap"
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
log "github.com/sirupsen/logrus"
)

func Init() {
bootstrap.Init()
}

func Release() {
bootstrap.Release()
}

var pid = -1
var pidFile string

Expand Down
158 changes: 155 additions & 3 deletions drivers/123_open/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package _123_open

import (
"context"
"encoding/hex"
"fmt"
"strconv"
"time"
Expand Down Expand Up @@ -156,49 +157,196 @@ func (d *Open123) Remove(ctx context.Context, obj model.Obj) error {
}

func (d *Open123) Put(ctx context.Context, dstDir model.Obj, file model.FileStreamer, up driver.UpdateProgress) (model.Obj, error) {
// 1. 创建文件
utils.Log.Debugf("╔══════════════════════════════════════════════════════════╗")
utils.Log.Debugf("║ [123网盘Put] 开始上传文件")
utils.Log.Debugf("╠══════════════════════════════════════════════════════════╣")
utils.Log.Debugf("║ 文件名: %s", file.GetName())
utils.Log.Debugf("║ 文件大小: %d bytes", file.GetSize())
utils.Log.Debugf("║ 目标目录ID: %s", dstDir.GetID())

// 1. 准备参数
// parentFileID 父目录id,上传到根目录时填写 0
parentFileId, err := strconv.ParseInt(dstDir.GetID(), 10, 64)
if err != nil {
utils.Log.Errorf("║ ✗ 解析父目录ID失败: %v", err)
utils.Log.Debugf("╚══════════════════════════════════════════════════════════╝")
return nil, fmt.Errorf("parse parentFileID error: %v", err)
}
utils.Log.Debugf("║ 父目录ID(int64): %d", parentFileId)

utils.Log.Debugf("╠══════════════════════════════════════════════════════════╣")
utils.Log.Debugf("║ [步骤1] 获取文件MD5(etag)")

// etag 文件md5
etag := file.GetHash().GetHash(utils.MD5)
utils.Log.Debugf("║ 从FileStreamer获取的MD5: '%s'", etag)
utils.Log.Debugf("║ MD5长度: %d", len(etag))
utils.Log.Debugf("║ MD5.Width要求: %d", utils.MD5.Width)
utils.Log.Debugf("║ 长度检查: len(%d) < Width(%d) = %v", len(etag), utils.MD5.Width, len(etag) < utils.MD5.Width)

// 验证etag是否是有效的十六进制字符串
if etag != "" {
if _, err := hex.DecodeString(etag); err == nil {
utils.Log.Debugf("║ ✓ 当前MD5是有效的十六进制字符串")
} else {
utils.Log.Warnf("║ ✗ 当前MD5不是有效的十六进制: %v", err)
}
}

if len(etag) < utils.MD5.Width {
_, etag, err = stream.CacheFullAndHash(file, &up, utils.MD5)
utils.Log.Debugf("║ ⚠ MD5长度不足,需要流式计算真实MD5")
utils.Log.Debugf("║ 开始调用 stream.CacheFullAndHash...")
utils.Log.Debugf("║ 这将:")
utils.Log.Debugf("║ 1. 缓存整个文件到本地临时文件")
utils.Log.Debugf("║ 2. 同时计算文件的真实MD5")
utils.Log.Debugf("║ 3. 返回 hex.EncodeToString(md5Hash.Sum(nil))")

cachedFile, newEtag, err := stream.CacheFullAndHash(file, &up, utils.MD5)
if err != nil {
utils.Log.Errorf("║ ✗ 计算MD5失败: %v", err)
utils.Log.Debugf("╚══════════════════════════════════════════════════════════╝")
return nil, err
}

utils.Log.Debugf("║ ✓ 计算完成")
utils.Log.Debugf("║ 旧MD5: '%s'", etag)
utils.Log.Debugf("║ 新MD5: '%s'", newEtag)
utils.Log.Debugf("║ 缓存文件: %v (类型: %T)", cachedFile != nil, cachedFile)

// 验证新MD5格式
if decoded, err := hex.DecodeString(newEtag); err == nil {
utils.Log.Debugf("║ ✓ 新MD5是有效的十六进制字符串 (decoded length: %d)", len(decoded))
} else {
utils.Log.Errorf("║ ✗ 新MD5不是有效的十六进制: %v", err)
}

etag = newEtag
utils.Log.Debugf("║ 使用新计算的MD5: '%s'", etag)
} else {
utils.Log.Debugf("║ ✓ MD5长度符合要求,直接使用")
utils.Log.Debugf("║ 当前MD5: '%s'", etag)
utils.Log.Warnf("║")
utils.Log.Warnf("║ ⚠⚠⚠ 关键问题:此MD5来自源网盘!⚠⚠⚠")
utils.Log.Warnf("║")
utils.Log.Warnf("║ 如果源是百度网盘:")
utils.Log.Warnf("║ 百度返回: 加密MD5 (如 'b0ea37602n80afe104da316baece0b3c')")
utils.Log.Warnf("║ 经过解密: 标准MD5 (如 'ae2b620eb1c972072765c6d305f9740c')")
utils.Log.Warnf("║ 但是!解密后的MD5 ≠ 文件真实MD5")
utils.Log.Warnf("║ 这只是百度的内部文件标识符")
utils.Log.Warnf("║")
utils.Log.Warnf("║ 后果:")
utils.Log.Warnf("║ 使用错误MD5上传到123网盘")
utils.Log.Warnf("║ → 123服务器计算文件真实MD5")
utils.Log.Warnf("║ → 发现与提交的MD5不匹配")
utils.Log.Warnf("║ → complete失败: '服务端的哈希和之前提交的不同'")
utils.Log.Warnf("║")
utils.Log.Warnf("║ 解决方案: 强制重新计算文件真实MD5")
utils.Log.Warnf("║")

// 测试:强制重新计算真实MD5进行对比
utils.Log.Warnf("║ [测试] 强制计算文件真实MD5进行对比...")
savedEtag := etag
cachedFile, realEtag, err := stream.CacheFullAndHash(file, &up, utils.MD5)
if err != nil {
utils.Log.Errorf("║ ✗ 计算真实MD5失败: %v", err)
} else {
utils.Log.Warnf("║ 对比结果:")
utils.Log.Warnf("║ 源网盘提供的MD5: %s", savedEtag)
utils.Log.Warnf("║ 文件真实MD5: %s", realEtag)
utils.Log.Warnf("║ 是否一致: %v", savedEtag == realEtag)
if savedEtag != realEtag {
utils.Log.Errorf("║")
utils.Log.Errorf("║ ✗✗✗ 确认:源网盘MD5是错误的!✗✗✗")
utils.Log.Errorf("║")
utils.Log.Errorf("║ 这就是导致上传失败的根本原因!")
utils.Log.Errorf("║ 必须使用真实计算的MD5: %s", realEtag)
utils.Log.Errorf("║")
etag = realEtag // 使用真实MD5
} else {
utils.Log.Debugf("║ ✓ 源网盘MD5是正确的,可以直接使用")
etag = savedEtag
}
utils.Log.Debugf("║ 缓存文件: %v", cachedFile != nil)
}
}

utils.Log.Debugf("╠══════════════════════════════════════════════════════════╣")
utils.Log.Debugf("║ [步骤2] 调用create接口")
utils.Log.Debugf("║ 参数:")
utils.Log.Debugf("║ parentFileId: %d", parentFileId)
utils.Log.Debugf("║ filename: %s", file.GetName())
utils.Log.Debugf("║ etag: %s", etag)
utils.Log.Debugf("║ size: %d", file.GetSize())
utils.Log.Debugf("║ duplicate: 2")
utils.Log.Debugf("║ containDir: false")

createResp, err := d.create(parentFileId, file.GetName(), etag, file.GetSize(), 2, false)
if err != nil {
utils.Log.Errorf("║ ✗ create调用失败: %v", err)
utils.Log.Debugf("╚══════════════════════════════════════════════════════════╝")
return nil, err
}
utils.Log.Debugf("║ ✓ create调用成功")
utils.Log.Debugf("║ 响应: Reuse=%v, FileID=%d, PreuploadID=%s",
createResp.Data.Reuse, createResp.Data.FileID, createResp.Data.PreuploadID)

// 是否秒传
if createResp.Data.Reuse {
utils.Log.Debugf("╠══════════════════════════════════════════════════════════╣")
utils.Log.Debugf("║ [秒传] 文件已存在,可以秒传")
// 秒传成功才会返回正确的 FileID,否则为 0
if createResp.Data.FileID != 0 {
utils.Log.Debugf("║ ✓ 秒传成功!FileID: %d", createResp.Data.FileID)
utils.Log.Debugf("╚══════════════════════════════════════════════════════════╝")
return File{
FileName: file.GetName(),
Size: file.GetSize(),
FileId: createResp.Data.FileID,
Type: 2,
Etag: etag,
}, nil
} else {
utils.Log.Warnf("║ ⚠ Reuse=true但FileID=0,继续正常上传流程")
}
} else {
utils.Log.Debugf("║ 不能秒传,需要上传分片")
}

utils.Log.Debugf("╠══════════════════════════════════════════════════════════╣")
utils.Log.Debugf("║ [步骤3] 上传分片")
// 2. 上传分片
err = d.Upload(ctx, file, createResp, up)
if err != nil {
utils.Log.Errorf("║ ✗ 上传分片失败: %v", err)
utils.Log.Debugf("╚══════════════════════════════════════════════════════════╝")
return nil, err
}
utils.Log.Debugf("║ ✓ 所有分片上传完成")

utils.Log.Debugf("╠══════════════════════════════════════════════════════════╣")
utils.Log.Debugf("║ [步骤4] 等待complete确认 (最多60秒)")
utils.Log.Debugf("║ PreuploadID: %s", createResp.Data.PreuploadID)

// 3. 上传完毕
for range 60 {
for i := range 60 {
uploadCompleteResp, err := d.complete(createResp.Data.PreuploadID)

if i == 0 || i%5 == 0 {
utils.Log.Debugf("║ [轮询%d/60] 调用complete...", i+1)
}

if err != nil {
utils.Log.Warnf("║ [轮询%d/60] complete返回错误: %v", i+1, err)
} else {
utils.Log.Debugf("║ [轮询%d/60] 响应: Completed=%v, FileID=%d",
i+1, uploadCompleteResp.Data.Completed, uploadCompleteResp.Data.FileID)
}

// 返回错误代码未知,如:20103,文档也没有具体说
if err == nil && uploadCompleteResp.Data.Completed && uploadCompleteResp.Data.FileID != 0 {
utils.Log.Debugf("║ ✓ 上传完成确认成功!")
utils.Log.Debugf("║ 最终FileID: %d", uploadCompleteResp.Data.FileID)
utils.Log.Debugf("╚══════════════════════════════════════════════════════════╝")
up(100)
return File{
FileName: file.GetName(),
Expand All @@ -208,9 +356,13 @@ func (d *Open123) Put(ctx context.Context, dstDir model.Obj, file model.FileStre
Etag: etag,
}, nil
}

// 若接口返回的completed为 false 时,则需间隔1秒继续轮询此接口,获取上传最终结果。
time.Sleep(time.Second)
}

utils.Log.Errorf("║ ✗ complete超时!60秒后仍未确认完成")
utils.Log.Debugf("╚══════════════════════════════════════════════════════════╝")
return nil, fmt.Errorf("upload complete timeout")
}

Expand Down
48 changes: 47 additions & 1 deletion drivers/123_open/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,50 @@ import (

// 创建文件 V2
func (d *Open123) create(parentFileID int64, filename string, etag string, size int64, duplicate int, containDir bool) (*UploadCreateResp, error) {
utils.Log.Debugf(" ┌─ [create API] 调用开始 ─────────────────────────┐")
utils.Log.Debugf(" │ URL: %s", UploadCreate)

lowerEtag := strings.ToLower(etag)
utils.Log.Debugf(" │ 请求参数:")
utils.Log.Debugf(" │ parentFileId: %d", parentFileID)
utils.Log.Debugf(" │ filename: %s", filename)
utils.Log.Debugf(" │ etag(原始): %s", etag)
utils.Log.Debugf(" │ etag(小写): %s", lowerEtag)
utils.Log.Debugf(" │ size: %d", size)
utils.Log.Debugf(" │ duplicate: %d", duplicate)
utils.Log.Debugf(" │ containDir: %v", containDir)

var resp UploadCreateResp
_, err := d.Request(UploadCreate, http.MethodPost, func(req *resty.Request) {
req.SetBody(base.Json{
"parentFileId": parentFileID,
"filename": filename,
"etag": strings.ToLower(etag),
"etag": lowerEtag,
"size": size,
"duplicate": duplicate,
"containDir": containDir,
})
}, &resp)

if err != nil {
utils.Log.Errorf(" │ ✗ API调用失败: %v", err)
utils.Log.Debugf(" └───────────────────────────────────────────────┘")
return nil, err
}

utils.Log.Debugf(" │ ✓ API调用成功")
utils.Log.Debugf(" │ 响应:")
utils.Log.Debugf(" │ Code: %d", resp.Code)
utils.Log.Debugf(" │ Message: %s", resp.Message)
utils.Log.Debugf(" │ Data.Reuse: %v", resp.Data.Reuse)
utils.Log.Debugf(" │ Data.FileID: %d", resp.Data.FileID)
utils.Log.Debugf(" │ Data.PreuploadID: %s", resp.Data.PreuploadID)
utils.Log.Debugf(" │ Data.SliceSize: %d", resp.Data.SliceSize)
if len(resp.Data.Servers) > 0 {
utils.Log.Debugf(" │ Data.Servers[0]: %s", resp.Data.Servers[0])
}
utils.Log.Debugf(" └───────────────────────────────────────────────┘")

return &resp, nil
}

Expand Down Expand Up @@ -168,14 +198,30 @@ func (d *Open123) Upload(ctx context.Context, file model.FileStreamer, createRes

// 上传完毕
func (d *Open123) complete(preuploadID string) (*UploadCompleteResp, error) {
utils.Log.Debugf(" ┌─ [complete API] 调用 ────────────────────────┐")
utils.Log.Debugf(" │ URL: %s", UploadComplete)
utils.Log.Debugf(" │ 请求参数:")
utils.Log.Debugf(" │ preuploadID: %s", preuploadID)

var resp UploadCompleteResp
_, err := d.Request(UploadComplete, http.MethodPost, func(req *resty.Request) {
req.SetBody(base.Json{
"preuploadID": preuploadID,
})
}, &resp)

if err != nil {
utils.Log.Debugf(" │ ✗ API调用失败: %v", err)
utils.Log.Debugf(" └──────────────────────────────────────────────┘")
return nil, err
}

utils.Log.Debugf(" │ ✓ API响应:")
utils.Log.Debugf(" │ Code: %d", resp.Code)
utils.Log.Debugf(" │ Message: %s", resp.Message)
utils.Log.Debugf(" │ Data.Completed: %v", resp.Data.Completed)
utils.Log.Debugf(" │ Data.FileID: %d", resp.Data.FileID)
utils.Log.Debugf(" └──────────────────────────────────────────────┘")

return &resp, nil
}
Loading