Skip to content

fix(115): 修复 115 驱动(旧驱动失效 + 子目录路径解析失败), fixes #128 - #152

Merged
PIKACHUIM merged 2 commits into
OpenListTeam:mainfrom
wumingzhinu:fix/115-driver-openapi
Oct 25, 2026
Merged

PIKACHUIM merged 2 commits into
OpenListTeam:mainfrom
wumingzhinu:fix/115-driver-openapi

Conversation

@wumingzhinu

Copy link
Copy Markdown
Contributor

Fixes #128

注:本仓库当前无法向 OpenListTeam/OpenList-Worker 直接推送,故从 fork
wumingzhinu/OpenList-Worker 提交,base 指向 OpenListTeam/OpenList-Worker:main。

问题

Issue #128 反馈:添加 115open 存储后提示 [115] API error (),
无法使用。该问题由两个 bug 引起。

Bug 1:115Open 被失效的旧驱动接管,恒报 [115] API error ()

仓库里存在两套 115 驱动:

目录 类 状态
src/backend/drivers/115/ Client115 已损坏(本次删除)
src/backend/drivers/115open/ Pan115Driver 正确,自 Go 版移植

src/backend/internal/op/storage.ts 的驱动工厂按 normDriver
(toLowerCase().replace(/[^a-z0-9]/g, ""))做 if-else 链式匹配。
DB 里的 115Open 归一化为 115open,被靠前的旧驱动分支截走:

} else if (
  normDriver === "115" ||
  normDriver === "115cloud" ||
  normDriver === "115open" ||     // ← 被这里匹配
  normDriver === "115netdisk"
) {
  driver = new Driver115(parseAddition(storageConfig))
  await driver.init?.()
}

而下方正确的 Pan115Driver 分支(含 token 刷新持久化回调)因列出
同样的 115open 而永远走不到,成为死代码。

旧驱动 Client115 有三处错误,叠加导致报错信息为空:

  1. URL 路径重复:util.ts:10 的 API_BASE 已含 /open,util.ts:100
    又请求 "/open/ufile/files",实际请求 /open/open/ufile/files。
    实测返回 {"state":false,"message":"Method POST does not exist","code":0},
    响应里没有 error / errno / errcode 字段。
  2. HTTP 方法错误:util.ts:38-70 用 POST + JSON body,
    115 开放平台要求 GET + query string。
  3. 错误码字段名错误:util.ts:65-67 的错误模板取
    data.error || data.errmsg || data.errno || data.errcode,
    一个都不存在,于是输出 [115] API error () —— 括号里空空如也。

Bug 2:子目录解析失败,报 code 20001 目录名称不能为空。

Bug 1 修好后,根目录可浏览,但进入任意子目录(或访问子目录下的文件、
新建/重命名/移动)仍失败。两个缺陷叠加,导致一次性路径解析失败
且兜底逻辑被跳过:

  1. resolveFolderId 拼接的 fullPath 多出一个斜杠(driver.ts:184):

    const fullPath = `/${rootId === "0" ? "" : rootId}${clean === "/" ? "" : clean}`

    root_id 默认 "0",于是 / + `` + /测试 = `//测试`。
    115 开放平台对 `folder/get_info` 的 `path` 严格校验:

    path 返回
    /测试 state:true,返回 file_id
    //测试 code 20001 目录名称不能为空。
    测试(缺前导斜杠) code 990002 参数错误。
  2. request() 的业务错误是裸 Error,不带 code(util.ts:231),
    只有 430004 分支才设置 err.code。resolveFolderId 的回退判断
    if (e?.code !== ERR_OBJECT_NOT_FOUND && e?.code !== 990002) throw e
    因 e.code === undefined 而直接 rethrow,逐层解析兜底永远走不到
    —— 而逐层解析(用 ufile/files 列父目录)本身是通的。

根目录恰好能浏览,是因为 clean === "/" 时提前 return 了。

改动

  1. 删除 src/backend/drivers/115/(driver.ts / types.ts / util.ts,
    共 489 行)。grep 确认无其他引用。
  2. src/backend/internal/op/storage.ts:删掉 Driver115 的 import 与整个
    旧分支,留注释说明 115 / 115Open 统一走 Pan115Driver。
    normDriver.startsWith("115") 分支已能覆盖
    115 / 115Open / 115cloud / 115netdisk 等所有变体。
  3. src/backend/drivers/115open/driver.ts:root_id 非 "0" 时才拼前缀,
    避免产生 //xxx;回退条件增加 20001。
  4. src/backend/drivers/115open/util.ts:所有业务错误(含 token 刷新后
    重试仍失败)统一把 code 挂到 Error 对象上,调用方才能按 code
    决定是否回退。

验证

  • npm run lint(tsc -p tsconfig.json --noEmit)通过。
  • 测试全过:drivers 189、server 206、regress 71、189 20、model 39、
    cache 24、pkg 50、store 13,合计 612 pass / 0 fail。
  • 用真实 115 token 对 Pan115Driver 做全链路验证:
    • init()(userInfo)→ user_id 103645653
    • list("/") → 测试/;list("/测试") → apk
    • get("/测试/xxx.apk") → 5874314 字节,成功取得下载链接
    • mkdir → updateFile(rename) → delFile → 三步均成功并清理
    • refreshToken() → 成功,刷新后 userInfo 仍返回同一 user_id
  • Cloudflare Worker 线上部署后:
    • 存储 status 由 [115] API error () 变为 work
    • POST /api/fs/list {"path":"/115/测试"} → code 200,返回 apk
    • POST /api/fs/get {"path":"/115/测试/...apk"} → code 200,raw_url 正常

注:原配置里的 access_token / refresh_token 已过期(40140126 /
40140120),换新 token 后 status 恢复 work。该问题与本次代码修复
相互独立。

nvdadmin added 2 commits October 25, 2026 09:57
## 问题

后台添加 115Open 驱动后,存储状态恒为 [115] API error (),浏览任何目录都失败。

## 根因

仓库里存在两套 115 驱动实现:

- src/backend/drivers/115/(类 Client115)—— 已损坏
- src/backend/drivers/115open/(类 Pan115Driver)—— 正确,自 Go 版移植

storage.ts 的驱动工厂按 normDriver(toLowerCase().replace(/[^a-z0-9]/g,""))
做 if-else 链式匹配。DB 里的 115Open 归一化为 115open,被靠前的旧驱动分支
截走;下方正确的 Pan115Driver 分支因列出同样的 115open 而永远走不到,
成为死代码。

旧驱动 Client115 有三处错误,叠加导致报错信息为空:

1. URL 路径重复:util.ts:10 的 API_BASE 已含 /open,util.ts:100 又请求
   "/open/ufile/files",实际请求 /open/open/ufile/files。实测返回
   {"state":false,"message":"Method POST does not exist","code":0},
   且响应里没有 error / errno / errcode 字段。
2. HTTP 方法错误:util.ts:38-70 的 request() 用 POST + JSON body,
   而 115 开放平台要求 GET + query string。
3. 错误码字段名错误:util.ts:65-67 的错误模板取
   data.error || data.errmsg || data.errno || data.errcode,
   上述字段一个都不存在,于是输出 [115] API error () —— 括号里空空如也,
   与用户看到的 status 一字不差。

## 改动

1. 删除 src/backend/drivers/115/(driver.ts / types.ts / util.ts,共 489 行)。
   grep 确认无其他引用。
2. storage.ts:删掉 Driver115 的 import 与整个旧分支,留注释说明
   115 / 115Open 统一走 Pan115Driver。normDriver.startsWith("115")
   分支已能覆盖 115 / 115Open / 115cloud / 115netdisk 等所有变体。

## 验证

- npm run lint(tsc --noEmit)通过。
- 测试全过:drivers 189、server 206、model 39、regress 71、cache 24、
  pkg 50、189 20、store 13,合计 626 pass / 0 fail。
- 用真实 115 token 对 Pan115Driver 做全链路验证:init、list、get、
  mkdir、rename、delFile、refreshToken 均成功。
- 线上部署后存储 status 由 [115] API error () 变为 work,/115 可正常浏览。

原配置里的 access_token / refresh_token 已过期(40140126 / 40140120),
换新 token 后 status 恢复 work。该问题与本次代码修复相互独立。
## 问题

115 存储挂载后根目录可浏览,但进入任意子目录(或访问子目录下的文件、
新建/重命名/移动)时报:

    115 网盘 API 错误(code 20001 目录名称不能为空。)

## 根因

两个缺陷叠加,导致一次性路径解析失败且兜底逻辑被跳过:

1. resolveFolderId 拼接的 fullPath 多出一个斜杠(driver.ts:184):

       const fullPath = `/${rootId === "0" ? "" : rootId}${clean === "/" ? "" : clean}`

   root_id 默认 "0",于是 `/` + `` + `/测试` = `//测试`。
   115 开放平台对 folder/get_info 的 path 参数严格校验:

   | path | 返回 |
   | --- | --- |
   | `/测试` | state:true,返回 file_id |
   | `//测试` | code 20001 目录名称不能为空。 |
   | `测试`(缺前导斜杠) | code 990002 参数错误。 |

2. request() 的业务错误是裸 Error,不带 code(util.ts:231),
   只有 430004 分支才设置 err.code。resolveFolderId 的回退判断
   `if (e?.code !== ERR_OBJECT_NOT_FOUND && e?.code !== 990002) throw e`
   因 e.code === undefined 而直接 rethrow,逐层解析兜底永远走不到
   —— 而逐层解析(用 ufile/files 列父目录)本身是通的。

根目录恰好能浏览,是因为 clean === "/" 时提前 return 了。

## 改动

1. driver.ts:root_id 非 "0" 时才拼前缀,避免产生 "//xxx":

       const rootPrefix = rootId === "0" ? "" : `/${rootId}`
       const fullPath = `${rootPrefix}${clean}`

2. driver.ts:回退条件增加 20001(空目录名),覆盖更多 path 相关失败。

3. util.ts:所有业务错误(含 token 刷新后重试仍失败)统一把 code
   挂到 Error 对象上,调用方才能按 code 决定是否回退。

## 验证

- 本地用真实 115 token 跑通 list("/")、list("/测试")、get(apk)
  (5874314 字节,成功取得下载链接)。
- npm run lint 通过。
- 测试全过:drivers 189、server 206、regress 71、189 20、
  model 39、cache 24、pkg 50、store 13,合计 612 pass / 0 fail。
- 线上部署后:
  - POST /api/fs/list {"path":"/115/测试"} → code 200,返回 apk
  - POST /api/fs/get {"path":"/115/测试/...apk"} → code 200,raw_url 正常
@PIKACHUIM
PIKACHUIM merged commit 485f4b0 into OpenListTeam:main Oct 25, 2026
1 check passed

@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.

方向没问题,Bug 1 / Bug 2 的定位和修法都对,// 消失、code 挂到 Error 上这两处我本地按边界值推演过,符合预期。

两点建议:

  1. startsWith("115")(586 行)在 115Share 分支(1051 行)之前,115Share 归一化成 115share 会先命中,被 Pan115Driver 构造(它没有 access_token)。这是 main 上就有的老问题,但本 PR 把 startsWith("115") 定位成「115 统一入口」,建议顺带排除 share,或把 share 分支挪到前面。
  2. root_id 非默认时 /{rootId} 不是 Go 的语义(Go 用 parentPath 拼接,parentPath 你在 init() 里算了却没用)。现在会多发一次注定失败的 folder/get_info,在 CF 45 次 subrequest 预算下不划算;而且 parentPath 没解析出来时不要退化传 clean,否则可能解析到账号根目录的同名目录。

另外这个修复挺适合补一个纯函数单测(默认 root / 非默认 root / 尾斜杠 / 空路径),仓库里 drivers/189/util.test.ts 就是现成模板。

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] 115open 添加后无法使用

4 participants