Repository navigation
fix(139): 子目录列表静默回退根目录与文件路径套娃 - #126
wumingzhinu wants to merge 2 commits into
Conversation
resolveCatalogId 逐段查找目录时,任何一段没找到就 break 并静默返回 最近一个已解析的父目录 id,list() 拿着这个 id 继续列目录: - 路径末段是文件(/手机软件/阅读.apk)时,list() 返回父目录内容, 前端把文件路径渲染成目录,出现 /手机软件/阅读.apk/阅读.apk 套娃; - 首段就解析失败(列表瞬时缺失、缓存不一致等)时回退到根目录, 表现为访问子目录却看到根目录的全部文件夹(issue OpenListTeam#112 症状 1)。 修复: - resolveCatalogId 解析不到即抛 Path not found: <path>,不再静默 回退;平台返回明确错误,前端不再把文件/不存在路径当目录渲染。 - 目录/文件名比较改为 trim + Unicode NFC 归一化,避免同名目录因 归一化形态不同被误判为找不到。 - 个人云 /file/list 翻页游标优先读 data.pageInfo.nextPageCursor (社区实现 gaozhangmin/boxplayer 实测的响应形态),保留 data.nextPageCursor 兼容回退;游标不再变化时终止循环防死循环。 此前只读 data.nextPageCursor,超过 100 项的目录只返回第一页。 新增 3 组回归测试:子目录内容/文件路径报错/不存在目录报错、NFC 归一化匹配、pageInfo 翻页合并;139 驱动测试 6/6 通过。 Relates to OpenListTeam#112 Co-Authored-By: MiMo (OpenCode) <noreply@opencode.ai>
简要说明:错误原因 + 已修改未测试错误原因
顺带发现的独立 bug:个人云 已修改(commit
|
|
break这个应该不是根因,在#121有相同的实现,但没有解决问题 |
维护者反馈 OpenListTeam#121(break→throw)并未解决症状 1:按名查找目录成功时 任何 throw 都不会触发,问题出在查找成功之后——解析结果或请求目标 host 静默退化为根目录: - init() 在 personal_new 取不到 queryRoutePolicy 下发的 personal host 时硬失败(对齐 Go 版 PersonalCloudHost is empty → Init error), 不再静默回退默认域 yun.139.com——默认域的 /file/list 是 legacy 端点,忽略 parentFileId 恒返回根目录内容,且因按名解析仍成功而不 触发任何报错(issue OpenListTeam#112 症状 1、OpenListTeam#121 无效的原因)。 - 新增 buildRequestUrls:personal_new 拒绝把业务端点发到默认域; 候选 URL 按 host 直拼(现网 /hcy 形态)优先,404/route not found 时回退 orchestration 形态(裸 host 补 /orchestration),仅路由类 错误重试——对齐 gaozhangmin/boxplayer 的候选序列与重试语义。 - /file/list 目录项 id 并集回退 fileId||catalogId||catalogID;仍拿 不到 id 时 resolveCatalogId 抛错,杜绝 listFiles(undefined) 回退 parentFileId="/" 的静默列根目录路径。 - 错误信息附带请求 URL,非 JSON 响应显式报错,便于复现者直接定位。 新增 3 组回归测试(id 并集与空 id 报错、init 硬失败、URL 候选与 重试语义);139 驱动测试 9/9 通过,tsc --strict、prettier 通过。 未做端到端真实 API 验证(无139 账号凭据)。 Relates to OpenListTeam#112 Co-Authored-By: MiMo (OpenCode) <noreply@opencode.ai>
简要说明:认可证伪 + 重新定位的根因 + 已修改未测试@PIKACHUIM 的质疑成立,感谢指正。#121( 路径 A(主嫌疑):路由 host 静默回退
路径 B:目录项缺 id
排除项(均已逐项核对):新旧 API 分支——报告者抓包时间戳 已修改(commit
|
|
验证了,并未修复问题,和#121一样 |
Summary / 摘要
修复 #112 报告的移动云盘(139 个人云)两类异常列表行为。
根因
维护者在评审中指出 #121(
break→throw,与本 PR 首个 commit 同实现)并未解决问题——该判断成立:throw只覆盖「按名称找不到目录」的场景,而 #112 报告者的现象是按名查找成功、不报任何错、却拿到根目录内容。重新对照 Go 版驱动、boxplayer、Cnotech/cloud139 三方实现与报告者抓包后,症状 1 存在两条同样绕过throw的静默回退路径:init()捕获queryRoutePolicy的任何错误后仅告警并继续使用默认域https://yun.139.com;Go 版驱动在此处是硬失败(PersonalCloudHost is empty→Init error)。新版个人云的/file/list只存在于路由下发的 host(如xxx/hcy,见 OpenList 文档「hcy/file/list」、Cnotech/cloud139 配置personal_cloud_host = ".../hcy");打到默认域的是 legacy 端点,忽略parentFileId恒返回根目录——目录仍按名解析成功,任何throw都不触发。driverCache以${id}_${modified}为键缓存驱动实例,错误 host 在 isolate 生命周期内粘滞、随冷启动抖动,与维护者「似乎和缓存有点关系」的观察吻合。/file/list映射只读catalogID: i.fileId;boxplayer 对同一字段做了四重回退fileId || catalogId || catalogID || contentID(不同账号/版本的响应形态差异)。目录项缺fileId时catalogID为undefined,listFiles(undefined)落到parentFileId = undefined || root_folder_id || "/"——同样静默列根目录,且按名查找照样成功、throw不触发。佐证:报告者抓包中错误响应的时间戳格式
2026-10-04T19:47:40.056+08:00正是 Go 版getPersonalTime(2006-01-02T15:04:05.999-07:00,新版/file/list的updatedAt)解析格式,而旧版getDisk是20060102150405紧凑格式——证明执行的是 personal_new 分支;新旧 API 的请求体、字段名(fileId/type/updatedAt)与 Go 版逐项一致,差异只剩 host 处理与 id 回退这两处。套娃症状(
/手机软件/阅读.apk/阅读.apk)的成因仍是break静默回退到父目录 id(路径末段是文件时list()返回父级内容),首版分析在该症状上成立。修复
resolveCatalogId解析不到即throw new Error("Path not found: <path>"),不再静默回退(修套娃);目录项仍拿不到 id 时抛[139] folder "<名>" has no catalog id in listing(堵路径 2)。/file/list目录/文件 id 并集回退fileId || catalogId || catalogID(对齐 boxplayer),目录项缺fileId时不再退化为undefined。init()在 personal_new 拿不到路由下发的 personal host 时硬失败(对齐 Go 版),错误携带原始原因;旧版个人云走/orchestration硬编码路径不依赖路由,仅告警(堵路径 1)。buildRequestUrls:personal_new 拒绝把业务端点发到默认域(防御长生命周期 isolate 的陈旧实例);候选 URL 按「host 直拼(现网/hcy形态)→ orchestration 形态(裸 host 补/orchestration、/hcy去前缀回退)」排列,仅 404 /route not found才回退,校验/权限错误原样抛出——对齐 gaozhangmin/boxplayercloud139PersonalUrl(s)的实测序列与重试语义。/file/list翻页游标优先读data.pageInfo.nextPageCursor、保留data.nextPageCursor兼容回退,游标不变时终止循环防死循环;此前超过 100 项的目录只返回第一页(顺带修复;cloud139 读 top-level、boxplayer 读 pageInfo,两形态均覆盖)。trim+ Unicode NFC 归一化(get()/remove()同步)。变更文件
src/backend/drivers/139/driver.ts:resolveCatalogId显式报错(找不到 / 缺 id)+nameEquals归一化比较src/backend/drivers/139/util.ts:路由硬失败、候选 URL 与回退语义、id 并集映射、游标读取与死循环防护、错误带 URLsrc/backend/drivers/139/types.ts:PersonalFileItem补catalogId/catalogID、fileId改为可选;PersonalListResp.data.pageInfo类型src/backend/drivers/139/driver.test.ts:新增 6 组回归测试诚实说明(验证边界)
已验证:139 驱动单测 9/9 通过、
tsc --strict聚焦检查通过、prettier --check通过。未验证:无 139 账号凭据,未做端到端真实 API 测试;两条根因路径均以「机制分析 + 单测锁定」方式修复——修复后「静默展示根目录内容」这一 bug class 被消除:要么列出子目录真实内容,要么返回带 请求 URL 的明确错误。
若报告者/维护者部署后仍复现,请携带错误信息(现已包含请求 URL 与状态码)回报,可直接定位是 host 还是上游数据问题。
This PR has breaking changes.
/ 此 PR 包含破坏性变更。
This PR changes public API, config, storage format, or migration behavior.
/ 此 PR 修改了公开 API、配置、存储格式或迁移行为。
(行为变化:personal_new 路由查询失败由「静默用错 host 列出根目录」变为初始化显式报错;不存在/缺 id 路径由静默回退变为报错——均属 bug 修复。)
This PR requires corresponding changes in related repositories.
/ 此 PR 需要关联仓库同步修改。
Related repository PRs / 关联仓库 PR :
resolveCatalogId不存在——由核心按 id 解析路径;其Init对空PersonalCloudHost本就是硬失败,本 PR 对齐该语义)Related Issues / 关联 Issue
Relates to #112
Testing / 测试
tsc --strict聚焦类型检查(src/backend/drivers/139/*.ts)通过tsx --test src/backend/drivers/139/driver.test.ts— 9/9 通过:- 既有 3 组(签名计算、实例化/列表/链接、token 刷新)无回归;
- 回归组 1:子目录返回子目录内容(症状 1)、文件路径
list报Path not found(套娃)、不存在目录报错;- 回归组 2:NFD 形态目录名 + NFC 路径仍能匹配;
- 回归组 3:
pageInfo.nextPageCursor两页合并、回传游标断言;- 回归组 4:目录项仅带
catalogId时并集回退可解析子目录;完全缺 id 时报has no catalog id;- 回归组 5:personal_new 路由失败/空列表 →
init报queryRoutePolicy failed;旧版个人云仅告警不失败;路由正常时 host 生效;- 回归组 6:候选 URL 序列(
/hcy双候选、裸 host 补/orchestration、已带路径单候选)、404 回退第二候选、500 不重试且错误带 URL。prettier --check:4 个改动文件全部通过(prettier --write顺手修正了util.ts与测试文件上游各一处既有违规行)Checklist / 检查清单
/ 我已阅读 CONTRIBUTING。
/ 我确认此贡献符合仓库许可证、贡献规范和行为准则。
prettierwhere applicable./ 我已按适用情况使用 prettier 格式化变更代码。
/ 我已在适用情况下请求相关维护者审查。
AI Disclosure / AI 使用声明
/ 此 PR 包含 AI 辅助内容。
Tools used / 使用工具:
Usage scope / 使用范围:
Code generation / 代码生成
Tests / 测试
Documentation / 文档
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-Byattribution./ 我已确保所有 AI 辅助提交都包含
Co-Authored-By归属信息。I can reproduce all AI-assisted content included in this PR without any AI tools.
/ 我可以在没有任何 AI 工具的情况下重现此 PR 中的所有 AI 辅助内容。(请提交人确认后勾选)