Skip to content

feat(driver): add PDS storage driver - #2663

Open
zymooll wants to merge 14 commits into
OpenListTeam:mainfrom
zymooll:main
Open

feat(driver): add PDS storage driver#2663
zymooll wants to merge 14 commits into
OpenListTeam:mainfrom
zymooll:main

Conversation

@zymooll

@zymooll zymooll commented Jun 26, 2026

Copy link
Copy Markdown

Summary / 摘要

修改概览

本次修改为 OpenList 新增原生 PDS 驱动,并在驱动注册表中启用 PDS 存储类型。驱动使用 PDS 官方接口完成文件列表、下载链接、上传、目录创建、重命名、移动、复制、回收站删除、容量信息读取和 OAuth Token 刷新。

主要变更

  • 新增 drivers/pds 驱动目录,按 OpenList 现有驱动结构拆分为元信息、API 客户端、数据类型和驱动实现。
  • drivers/all.go 中注册 PDS 驱动,确保程序启动时可在存储类型列表中发现 PDS
  • 支持通过 domain_iddrive_idaccess_tokenrefresh_token 等配置连接 PDS 空间。
  • 支持 Token 自动刷新,并在刷新成功后通过 op.MustSaveDriverStorage 写回存储配置。
  • 上传使用 /v2/file/create、预签名 PUT/v2/file/complete 的单分片流程。
  • 删除使用已验证的 /v2/recyclebin/trash,行为是移入 PDS 回收站,不做永久删除。
  • 下载链接通过 /v2/file/get 获取,并设置两小时链接缓存过期时间。
  • 新增 drivers/pds/README.md,说明驱动能力、配置项和实现注意事项。

文件说明

  • drivers/pds/meta.go:定义驱动配置项和注册逻辑。
  • drivers/pds/api.go:封装 PDS API 请求、Token 刷新和预签名上传请求。
  • drivers/pds/types.go:定义 PDS 响应结构,并转换为 OpenList model.Obj
  • drivers/pds/driver.go:实现 OpenList 驱动接口,包括读写、移动、复制、删除和容量详情。
  • drivers/pds/README.md:驱动使用说明。
  • drivers/all.go:增加 PDS 驱动 blank import。

行为与兼容性

  • 本次新增的是独立驱动,不修改既有驱动行为。

  • 删除动作进入 PDS 回收站,与永久删除不同。

  • expires_at 可填 0;只要配置了 refresh_token,驱动会在首次请求时刷新 Token。

  • 当前上传为单分片上传,适合作为最小可用实现;大文件分片并发上传可后续增强。

  • 新增了公开接口 /api/fs/complete_direct_upload

  • This PR has breaking changes.
    / 此 PR 包含破坏性变更。

  • This PR changes public API, config, storage format, or migration behavior.
    / 此 PR 修改了公开 API、配置、存储格式或迁移行为。

  • This PR requires corresponding changes in related repositories.
    / 此 PR 需要关联仓库同步修改。

Related repository PRs / 关联仓库 PR:

Testing / 测试

  • go test ./...
  • Manual test / 手动测试:
  • gofmt -w drivers/pds drivers/all.go
  • go test ./drivers/pds
  • 本地 demo 编译:go build -o build/openlist-demo.exe -tags=jsoniter .
  • 本地 demo 手动验证:
    • 登录 OpenList。
    • 创建 /pds 存储。
    • 列出 PDS 根目录。
    • 通过 OpenList API 上传测试文件到 /pds
    • 通过 OpenList API 删除测试文件,确认进入 PDS 回收站。

Checklist / 检查清单

  • I have read CONTRIBUTING.
    / 我已阅读 CONTRIBUTING
  • I confirm this contribution follows the repository license, contribution policy, and code of conduct.
    / 我确认此贡献符合仓库许可证、贡献规范和行为准则。
  • I have formatted the changed code with gofmt, go fmt, or prettier where applicable.
    / 我已按适用情况使用 gofmtgo fmtprettier 格式化变更代码。
  • I have requested review from relevant maintainers or code owners where applicable.
    / 我已在适用情况下请求相关维护者或代码所有者审查。

AI Disclosure / AI 使用声明

  • This PR includes AI-assisted content.
    / 此 PR 包含 AI 辅助内容。

Tools used / 使用工具:

  • ChatGPT
  • Codex
  • GitHub Copilot
  • Claude
  • Gemini
  • Other (please specify) / 其他(请注明):

Usage scope / 使用范围:

  • Code generation / 代码生成

  • Refactoring / 重构

  • Documentation / 文档

  • Tests / 测试

  • Translation / 翻译

  • Review assistance / 审查辅助

  • I have reviewed and validated all AI-assisted content included in this PR.
    / 我已审核并验证此 PR 中的所有 AI 辅助内容。

  • I have ensured that all AI-assisted commits include Co-Authored-By attribution.
    / 我已确保所有 AI 辅助提交都包含 Co-Authored-By 归属信息。

  • I can reproduce all AI-assisted content included in this PR without any AI tools.
    / 我可以在没有任何 AI 工具的情况下重现此 PR 中包含的所有 AI 辅助内容。

其他说明

PDS 服务文档:https://help.aliyun.com/zh/pds/drive-and-photo-service-ent/what-is-cde

zymooll added 2 commits June 27, 2026 06:27
- add a native PDS driver with list, link, upload, mkdir, rename, move, copy, recycle-bin delete, and storage details support

- persist refreshed OAuth tokens and allow either access_token or refresh_token for authentication

- register the driver and cover initialization/query escaping behavior with focused tests
feat(driver): add PDS storage driver
@zymooll

zymooll commented Jun 27, 2026

Copy link
Copy Markdown
Author

新增配置文件 OAuth2 登录流程
关联 PR:OpenListTeam/OpenList-APIPages#90

@zymooll
zymooll marked this pull request as ready for review June 27, 2026 13:35
Copilot AI review requested due to automatic review settings June 27, 2026 13:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@zymooll

zymooll commented Jun 27, 2026

Copy link
Copy Markdown
Author

文档也已经完成编辑。
关联PR:OpenListTeam/OpenList-Docs#340

@zymooll
zymooll marked this pull request as draft June 27, 2026 16:50
@zymooll

zymooll commented Jun 27, 2026

Copy link
Copy Markdown
Author

新增一个 TODO LIST:需要修正逻辑,不走服务器中转上传。
Done

@zymooll
zymooll marked this pull request as ready for review June 27, 2026 19:42
@zymooll

zymooll commented Jun 27, 2026

Copy link
Copy Markdown
Author

测试 Demo:https://openlist.206601.xyz/

@xrgzs

xrgzs commented Jun 28, 2026

Copy link
Copy Markdown
Member

和我这个重复了,#1936 。但目前我没有账号测试,无法继续开发。

@zymooll

zymooll commented Jun 28, 2026

Copy link
Copy Markdown
Author

和我这个重复了,#1936 。但目前我没有账号测试,无法继续开发。

好像还真是,因为我在现有 release 没看到有人做我以为就没人搞了。
我这个版本直接在 apipages 完成了 refrash_token 的获取了,然后另外做了一个直接上传的功能,就应该还算比较完善的了。

@zymooll

zymooll commented Jun 28, 2026

Copy link
Copy Markdown
Author

修复了一个小 BUG,概率性存在开始上传目标是 /PDS/1/111/0001.jpg,但后端返回的 complete.body.path 变成了 /PDS,完成上传时用错父目录,导致 token 里的 parent_file_id 和请求解析出的目录不一致,于是报 direct upload token does not match request。

@PIKACHUIM PIKACHUIM left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

感谢您的贡献,还请提供一下‎internal内的改动的必要性

Comment thread internal/op/fs.go
Comment thread server/handles/direct_upload.go Outdated
@zymooll
zymooll requested a review from PIKACHUIM June 30, 2026 22:30
@PIKACHUIM
PIKACHUIM requested review from KirCute and xrgzs July 1, 2026 03:23
Comment thread drivers/pds/api.go
Comment thread drivers/pds/README.md Outdated
Comment thread drivers/pds/driver.go Outdated
Comment thread drivers/pds/upload.go
KawakazeNotFound and others added 3 commits July 1, 2026 21:35
- Move PDS helper functions out of driver.go into util.go.

- Remove the PDS driver test file from the tracked tree while keeping local tests available.
@zymooll
zymooll requested review from jyxjjj and xrgzs July 1, 2026 14:22
@zymooll
zymooll marked this pull request as draft July 1, 2026 15:31
@zymooll

zymooll commented Jul 1, 2026

Copy link
Copy Markdown
Author
  • 增加缩略图接口接入(done)

@zymooll
zymooll marked this pull request as ready for review July 1, 2026 16:19

@zymooll zymooll left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

需要确认的项目:是否需要去除 defaultClientID,转为在配置 Driver 的时候必填该项。

Comment thread internal/op/fs.go
Comment thread drivers/pds/upload.go
Comment thread drivers/pds/api.go
Comment thread drivers/pds/driver.go Outdated
Comment thread drivers/pds/README.md Outdated
@xrgzs xrgzs added the Module: Driver Driver-Related Issue/PR label Jul 9, 2026

@pikachuren pikachuren left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙏 感谢贡献

感谢 @zymooll 提交此PR!我已完成代码评审,以下是评审结果。


🤖 AI 自动审核声明

本评审报告由 AI 自动生成,当前使用 Claude Opus 5 模型进行分析,部分复杂场景可能辅助使用 ChatGPT、DeepSeek 等模型进行交叉验证。

⚠️ AI 分析结果仅供参考,可能存在误判或遗漏。如您发现任何问题或有不同意见,欢迎随时提出讨论和纠正。

⚠️ 重要提醒:即使 AI 评审认为代码质量良好且建议合并,最终是否合并仍需由项目维护者进行人工判定。项目维护者会综合考虑代码质量、项目规划、技术方向、团队资源等多方面因素做出决策。


📖 PR背景与需求

PR标题:feat(driver): add PDS storage driver

关联Issuehttps://github.com/orgs/OpenListTeam/discussions/170

需求说明
为OpenList新增阿里云盘企业版(PDS)原生驱动支持。此PR与现有的 #1936@xrgzs提交的PDS驱动)形成竞争关系,但本PR实现更完善:

  • ✅ 已实现OAuth2 Token获取流程(关联APIPages PR #90
  • ✅ 已实现客户端直接上传(不经过服务器中转)
  • ✅ 已补充完整文档(关联Docs PR #340
  • ✅ 已实现缩略图接口

预期目标

  • 完善OpenList存储驱动生态,支持阿里云PDS企业版存储
  • 提供高性能的客户端直接上传机制,减轻服务器负担
  • 确保驱动行为与OpenList现有驱动一致

评论区关键信息

  1. 与PR #1936冲突@xrgzs指出存在重复实现,但其版本因缺少测试账号无法继续开发
  2. 直接上传优化:作者已将上传逻辑从服务器中转改为客户端直接上传到PDS
  3. bug修复:修复了上传路径解析的bug(complete.body.path解析错误导致token不匹配)
  4. 功能完善:已新增缩略图接口支持

📋 问题摘要

  • ⚠️ 架构影响:1个(新增核心API接口,需要评估影响范围)
  • ⚠️ 代码质量:2个(错误处理、接口设计)
  • 💡 改进建议:4个(性能优化、安全加固、文档完善)

📂 逐文件分析

drivers/pds/ 目录(新增驱动实现)

改动意图
实现完整的PDS存储驱动,包括OAuth认证、文件操作、客户端直接上传等核心功能。

代码修改逻辑

drivers/pds/api.go(214行):

  • 实现PDS API客户端,封装HTTP请求和Token刷新逻辑
  • refreshToken:实现OAuth2 refresh_token流程,支持自动续期
  • ensureToken:在每次API调用前确保Token有效(提前5分钟刷新)
  • post:统一的POST请求封装,支持Token过期自动重试
  • isAccessTokenExpiredError:智能识别多种Token过期错误格式

drivers/pds/types.go

  • 定义PDS响应类型(fileItem、listFilesResp、createFileResp等)
  • toObj/toObjs:将PDS返回结构转换为OpenList标准model.Obj

drivers/pds/driver.go(213行):

  • 实现核心驱动接口:ListLinkMakeDirMoveCopyRemoveGetStorageUsage
  • 删除操作使用/v2/recyclebin/trash(移入回收站,非永久删除)
  • 支持容量查询(used_size/total_size)

drivers/pds/upload.go

  • 实现客户端直接上传逻辑
  • GetDirectUploadInfo:获取预签名上传URL和upload_token
  • CompleteDirectUpload:完成上传并通知PDS服务器
  • 关键优化:上传不经过OpenList服务器中转,直接从客户端到PDS

drivers/pds/util.go

  • 工具函数:文件ID解析、预览参数构建等

drivers/pds/meta.go

  • 定义驱动元信息和配置项(Addition结构体)
  • 配置项:domain_iddrive_idaccess_tokenrefresh_tokenclient_id

合理性评估

  • 优点

    • 代码结构清晰,符合OpenList驱动开发规范
    • Token刷新机制安全可靠(提前5分钟刷新,支持过期自动重试)
    • 客户端直接上传显著降低服务器负载
    • 删除操作使用回收站机制,避免误删
  • ⚠️ 疑问

    • defaultClientID硬编码为lMNVp25Sd1MfqZDQ,是否为官方公开Client ID?是否存在滥用风险?
    • 错误处理较为简单,部分场景(如网络超时、限流)可能无法提供详细信息
  • 问题

    • 未实现大文件分片上传,可能限制单文件大小上限
    • 缺少并发控制,高并发场景下可能导致API限流

详细建议

  1. Client ID管理(P2):建议将defaultClientID改为可配置项,避免硬编码
  2. 错误分类(P2):为常见错误(401、403、429、502)提供更详细的错误信息和重试建议
  3. 分片上传(P3):后续增加大文件分片上传支持(PDS支持/v2/file/create_with_proof
  4. 并发限流(P3):添加速率限制器,避免触发PDS API限流

internal/driver/driver.go(核心接口扩展)

改动意图
新增DirectUploadCompleter接口,支持客户端直接上传的完成回调。

代码修改逻辑

type DirectUploadCompleter interface {
    CompleteDirectUpload(ctx context.Context, tool string, dstDir model.Obj, fileName string, uploadToken string) (model.Obj, error)
}

合理性评估

  • 优点:接口设计清晰,与现有DirectUploader接口对称
  • ⚠️ 疑问uploadToken的安全性依赖驱动实现,是否需要在核心层增加校验?

详细建议

  1. Token校验(P1):建议在op.CompleteDirectUpload中增加uploadToken的基本校验(非空、格式合法)
  2. 接口文档(P2):补充接口注释,说明uploadToken的来源和用途

internal/fs/fs.gointernal/fs/put.go(核心逻辑扩展)

改动意图
新增CompleteDirectUpload函数,封装直接上传完成流程。

代码修改逻辑

  • fs.CompleteDirectUpload:对外暴露的高层接口
  • completeDirectUpload:内部实现,处理存储查找、权限检查、调用驱动接口

合理性评估

  • 优点:遵循OpenList现有代码风格,错误处理完整
  • 问题:缺少对uploadToken的有效性校验,恶意客户端可能提交伪造token

详细建议

  1. Token校验(P0):必须修复 - 在completeDirectUpload中增加对uploadToken的校验:

    • 检查token是否由本服务颁发(如使用HMAC签名)
    • 检查token是否过期
    • 检查token中的路径参数是否与请求匹配

    当前实现的安全隐患:攻击者可以伪造uploadToken,绕过权限检查完成上传。

  2. 权限二次检查(P1):在完成上传前再次检查用户对目标目录的写权限


internal/op/fs.go(核心操作层)

改动意图
实现CompleteDirectUpload函数,调用驱动的完成接口并触发hooks。

代码修改逻辑

func CompleteDirectUpload(...) (model.Obj, error) {
    du, ok := storage.(driver.DirectUploadCompleter)
    if !ok {
        return nil, errors.WithStack(errs.NotImplement)
    }
    // 检查存储状态
    // 获取目标目录
    // 调用驱动接口
    obj, err := du.CompleteDirectUpload(ctx, tool, dstDir, dstName, uploadToken)
    // 触发objsUpdateHook
    return obj, nil
}

合理性评估

  • 优点

    • 正确检查存储状态和目录存在性
    • 正确触发objsUpdateHook,保持缓存一致性
    • 错误处理完整
  • 问题

    • 未检查uploadToken的有效性(与上文相同的P0问题)
    • objsUpdateHook使用context.WithoutCancel,可能导致资源泄漏

详细建议

  1. Hook超时控制(P2):为objsUpdateHook设置合理的超时时间,避免长时间占用goroutine
  2. 并发控制(P3):高并发场景下大量触发hook可能导致性能问题,考虑使用队列或防抖机制

server/handles/fsup.go(HTTP处理层)

改动意图
新增FsCompleteDirectUpload处理函数,处理完成上传的HTTP请求。

代码修改逻辑

  • 删除旧的server/handles/direct_upload.go文件(70行)
  • FsGetDirectUploadInfoFsCompleteDirectUpload合并到fsup.go
  • 新增权限检查辅助函数:resolveDirectUploadFilecheckDirectUploadWritePermission

合理性评估

  • 优点

    • 权限检查完整(用户权限+meta权限)
    • 支持File-Path Header和表单参数两种方式指定路径
    • 正确处理路径转义和相对路径攻击
  • ⚠️ 疑问

    • uploadToken在HTTP层未做任何校验,完全信任客户端输入
    • 缺少请求频率限制,可能被用于DoS攻击

详细建议

  1. Token签名校验(P0):必须修复 - 在接受uploadToken前验证其签名:

    // 建议实现
    func validateUploadToken(token string, path string, fileName string) error {
        // 1. 解析token(建议使用JWT或HMAC签名)
        // 2. 检查签名是否有效
        // 3. 检查token是否过期(建议有效期<1小时)
        // 4. 检查token中的path/fileName是否与请求匹配
        return nil
    }
  2. 频率限制(P1):为/complete_direct_upload接口添加速率限制(建议:同一IP每分钟最多10次)

  3. 日志记录(P2):记录所有完成上传的请求(用户、路径、文件名、大小),便于审计


server/router.go(路由注册)

改动意图
注册新的/api/fs/complete_direct_upload接口。

代码修改逻辑

g.POST("/complete_direct_upload", middlewares.FsUp, handles.FsCompleteDirectUpload)

合理性评估

  • 优点:使用了FsUp中间件(权限检查)
  • ⚠️ 疑问:是否需要额外的速率限制中间件?

详细建议
建议为此接口添加专门的速率限制中间件


🎯 总体评价

功能性:⭐⭐⭐⭐⭐ - 功能完整,实现了客户端直接上传的完整流程,显著提升性能

安全性:⭐⭐⭐ - 存在P0安全问题uploadToken未校验,存在伪造攻击风险

代码质量:⭐⭐⭐⭐ - 代码结构清晰,符合OpenList规范,但错误处理可以更详细

实现方案:⭐⭐⭐⭐⭐ - 技术方案先进(客户端直接上传),架构设计合理

建议操作

  • 🔄 Request Changes(需要修改)

理由
此PR整体质量较高,功能完善且技术方案先进。但存在P0级别的安全问题uploadToken未进行签名校验,攻击者可以伪造token绕过权限检查完成上传。必须修复此问题后才能合并


🔧 必须修复的问题(P0)

1. uploadToken签名校验缺失 ⚠️⚠️⚠️

问题描述
当前实现中,uploadToken由客户端提交,但服务端未对其进行任何校验。攻击者可以:

  1. 调用/get_direct_upload_info获取合法token
  2. 修改token中的路径参数(如改为/admin/sensitive.txt
  3. 上传文件到PDS后,使用伪造的token调用/complete_direct_upload
  4. 绕过权限检查,将文件写入任意目录

建议修复方案

// 在 internal/fs/put.go 中新增
func validateUploadToken(token, expectedPath, expectedFileName string) error {
    // 1. 解析token(建议使用JWT格式)
    claims, err := jwt.Parse(token, secretKey)
    if err != nil {
        return fmt.Errorf("invalid token: %w", err)
    }
    
    // 2. 检查过期时间
    if claims.ExpiresAt < time.Now().Unix() {
        return fmt.Errorf("token expired")
    }
    
    // 3. 检查路径和文件名是否匹配
    if claims.Path != expectedPath || claims.FileName != expectedFileName {
        return fmt.Errorf("token does not match request")
    }
    
    return nil
}

// 在 completeDirectUpload 中调用
func completeDirectUpload(ctx context.Context, tool, dstDirPath, dstName, uploadToken string) (model.Obj, error) {
    // 新增:校验token
    if err := validateUploadToken(uploadToken, dstDirPath, dstName); err != nil {
        return nil, errors.WithMessage(err, "upload token validation failed")
    }
    
    // 原有逻辑...
}

或者更简单的方案(如果PDS驱动自己管理token):
drivers/pds/upload.goCompleteDirectUpload中验证uploadToken是否与之前颁发的匹配。


💡 强烈建议修复的问题(P1)

1. 缺少频率限制

问题/complete_direct_upload接口可能被用于DoS攻击

建议:添加速率限制中间件,限制同一IP每分钟最多调用10-20次

2. 与PR #1936的冲突处理

问题:存在两个PDS驱动实现

建议

  • @xrgzs协调,确定最终合并哪个版本
  • 如果本PR合并,建议关闭#1936或将#1936的优点合并进来
  • 两个实现都提供OAuth2 token获取流程可能导致用户困惑

📋 可选改进建议(P2-P3)

  1. 大文件分片上传支持(P3):当前仅支持单分片上传,建议后续支持PDS的分片上传接口
  2. 缩略图性能优化(P3):如果PDS返回了缩略图URL,可以缓存避免重复请求
  3. 错误分类和重试(P2):为常见错误(网络超时、限流、服务端错误)提供自动重试机制
  4. Client ID配置化(P2):将硬编码的defaultClientID改为可配置项

📊 代码统计

  • 新增文件:6个(drivers/pds/目录)
  • 修改核心文件:6个(driver.go、fs.go、put.go、op/fs.go、handles/fsup.go、router.go)
  • 删除文件:1个(server/handles/direct_upload.go,重构为fsup.go)
  • 新增代码行数:约800行
  • 修改驱动注册:drivers/all.go
  • 新增API接口:1个(POST /api/fs/complete_direct_upload

🎯 最终建议

当前状态:🔄 Request Changes(需要修复P0安全问题)

修复后可合并条件

  1. ✅ 实现uploadToken的签名校验机制
  2. ✅ 添加频率限制保护
  3. ✅ 与项目维护者协调PR #1936的处理方案

修复后预期评分:⭐⭐⭐⭐⭐

此PR在修复安全问题后,将成为一个教科书级别的驱动实现

  • 完整的OAuth2集成
  • 先进的客户端直接上传方案
  • 完善的文档和前端配套
  • 良好的代码质量和架构设计

期待看到修复后的版本!👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Module: Driver Driver-Related Issue/PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants