Repository navigation
feat(cache): add file-tree and download-link caches (DB by default) - #75
Open
Wudarensheng wants to merge 4 commits into
Open
Wudarensheng wants to merge 4 commits into
Wudarensheng wants to merge 4 commits into
Conversation
This comment has been minimized.
This comment has been minimized.
Member
|
这个改动比较大,由于有些网盘的直链TTL非常短 |
Implement the two-level cache from the OpenList.ts reference: - File-tree cache (`ft`): directory listings are served from cache, keyed by storage + virtual path. Empty directories are cached too, so repeated browsing no longer hits the remote drive every time. - Download-link cache (`ln`): the raw_url returned by the driver is reused instead of being re-signed/re-exchanged on every download, which is usually the most expensive and most rate-limited step. Caching is DB-only by default. `CACHE_DRIVER=db` (the default) reuses the backend resolved by `getStorageBackend()`, i.e. the same one as `DB_DRIVER`, so zero configuration means "cache in the database". Dedicated KV / Blob / cfkv / do / memory backends must be opted into explicitly via `CACHE_DRIVER` (e.g. `db,kv`, `kv`, `blob`) and are never auto-detected or auto-substituted, matching the existing DB_DRIVER rule that an explicitly configured driver never falls back. Safety boundary: only driver-layer results are cached (raw FileItem lists and raw links). Permissions, meta passwords, hide rules and signatures are still computed per request in server/fs.ts and server/raw.ts, so a cache hit cannot leak privileges. Any cache error degrades to a no-op and never breaks the request. Invalidation: writes (mkdir/rename/remove/move/copy/put) invalidate the affected path plus its parent directory for the file tree, and the path itself for links. Storage create/update/enable/disable/delete clears that storage's cache entirely. New environment variables (all optional): CACHE_ENABLED, CACHE_DRIVER, CACHE_FILE_TREE, CACHE_DOWNLOAD_LINK, CACHE_TTL, CACHE_LINK_TTL, CACHE_EXCLUDE_DRIVERS, CACHE_PREFIX. New admin endpoints: GET /cache/status, POST /cache/clear, POST /storage/refresh, POST /storage/refresh_one.
The local assistant keeps its project memory under .workbuddy-ai/, which must never be committed. Ignore it next to the existing .codebuddy entry.
- encodeCachePathSegment 改为单射编码:转义前缀 x 本身转义为 x78, 消除 /a/b 与 /ax2fb 键碰撞(评审实测复现的跨目录串列表/直链问题); 超长路径折叠标记用 xg + 双 FNV-1a,xg 不可能出现在普通编码结果中, 折叠键与普通键不会互相碰撞;补参数化「任意不同路径键必不相等」回归 - 补缓存失效:/fs/upload/complete、/fs/multipart/complete、/fs/other、 /fs/get_direct_upload_info(含 other 回退分支)写入/签发直传 URL 后 调用 invalidatePaths,避免「上传成功但刷新看不到」 - CACHE_PREFIX 字符集校验:仅允许 [A-Za-z0-9_](KV 键约束),非法回退 默认值并告警,防止与业务键空间 / 整表 DELETE 冲突 - CACHE_DOWNLOAD_LINK 默认改为 false(维护者建议):部分网盘直链 TTL 极短,缓存复用易把失效直链发给用户;CACHE_LINK_TTL 保持独立选项, 同步 .env.example / .dev.vars.example / wrangler.jsonc / README
Wudarensheng
force-pushed
the
feat/cache
branch
from
October 2, 2026 09:08
fecc76a to
218669d
Compare
pikachuren
reviewed
Oct 9, 2026
pikachuren
left a comment
Collaborator
There was a problem hiding this comment.
🙏 感谢 @Wudarensheng 提交!
🤖 AI 自动审核声明:本评审报告由 AI 自动生成,当前使用 GLM 模型进行分析。
🔄 增量评审:上次评审 → 当前 head(218669d)
新增提交 fix(cache): 缓存键单射编码、补上传失效、前缀校验、链接缓存默认关闭,逐条回查如下。
新增改动的问题(按 P0/P1/P2 分级):
- 💡 [P2] 超长路径折叠键(编码 >180 时取前 100 字符 +
xg+ 双 FNV-1a)在两个超长路径之间依赖 64 位哈希区分,理论上仍可碰撞——概率层面可接受,建议在注释中保留此已知限制(当前注释已写明,确认即可)。 - 💡 [P2]
storageIdFromCacheKey按第一个_切分 storage id,而encodeKeyPart会原样保留_;若 storage id 本身含_会切错。实际 id 为自增数字,当前无触发路径,属防御性提醒。
本轮补充实测:用真实依赖运行 internal/cache/cache.test.ts 24/24 通过;并把上一轮评审中 5/5 碰撞的路径对(/a/b ↔ /ax2fb 等)在新 head 代码下用真实 buildCacheKey 重跑,0/5 碰撞——键单射修复经真实执行验证成立。
旧问题解决情况:
- ✅ 缓存键碰撞(P0,实测复现)→ 已修复。
isCachePlain不再原样保留小写x(x→x78),CACHE_PLAIN_RE同步排除x,/a/b与/ax2fb不再同键;长键折叠用xg标记(普通编码中x后必跟两位十六进制,xg不可能出现),短键与长键不会互撞。原评审建议的参数化断言(任意两路径键不相等)建议补上。 - ✅
/fs/upload/complete、/fs/multipart/complete、/fs/other未失效缓存 → 已修复(fs.ts:1041、1625、1128、1368 直传 URL 预失效)。 - ✅
CACHE_PREFIX未校验字符集 → 已修复,parseCachePrefix只允许[A-Za-z0-9_](≤64 字符),非法回退默认值并告警。 - ✅ 链接缓存 TTL 过短风险(评论区维护者建议)→
CACHE_DOWNLOAD_LINK默认改为false,并文档注明「确认网盘直链有效期后再显式开启」。 - ❌ 测试仍集中在 store 层假驱动,
resolveDrivers的 db 解析、op 层命中/失效集成无用例 → 未补。
🎯 结论:✅ Approve — P0 碰撞修复到位、失效边界补齐,集成测试可后续补充。
Member
|
代码没有明显问题但是不敢合,主要是怕影响已有稳定性 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
feat(cache): add file-tree and download-link caches (DB by default)
Summary / 摘要
Browsing a directory and starting a download both hit the remote drive on every
single request. The download-link exchange (
driver.get()→ presigned / raw URL)is usually the most expensive and most rate-limited step of the two. The upstream
OpenList.tsreference solves this with a two-level cache (file tree + downloadlinks); this PR ports that design to this repository.
浏览目录和开始下载这两个动作,此前每次请求都会打到远端存储;其中「换链」
(
driver.get()→ 预签名 / 直链)通常是最贵、最容易被网盘限流的一步。本 PR 参考上游
OpenList.ts的实现,把它的两级缓存(文件树 + 下载链接)移植过来。User-visible behavior / 用户可感知的变化
per-storage
cache_expirationandcustom_cache_policies). Empty directoriesare cached too, so repeated browsing stops hitting the remote drive.
/ 目录列表改为走缓存(默认 30 分钟,跟随存储级
cache_expiration与custom_cache_policies);空目录同样会被缓存。5 min), so repeated downloads / previews skip the link exchange.
/ 驱动换来的直链会在短时间内复用(默认 5 分钟),重复下载 / 预览不再重复换链。
mkdir/rename/remove/move/copy/put) invalidate theaffected path plus its parent directory for the file tree, and the path
itself for links. / 写操作后自动失效:文件树连带父目录,链接只失效自身。
entirely. / 存储的新增 / 修改 / 启停 / 删除会清空该存储的全部缓存。
GET /cache/status,POST /cache/clear,POST /storage/refresh,POST /storage/refresh_one./ 新增 4 个管理接口(含对齐参考实现的 refresh / refresh_one)。
GET /env_checknow also reports the resolved cache config and the actualbackends in use. /
/env_check额外回显生效的缓存配置与实际后端。Implementation / 重要实现变化
src/backend/internal/cache/—config.ts(env switches),store.ts(backend resolution + key encoding + envelope get/set/list/clear),filetree.ts,link.ts,index.ts(barrel + invalidation orchestration),plus
cache.test.ts(22 cases).CACHE_DRIVER=db(the default)resolves to the same backend as
DB_DRIVERviagetStorageBackend(), sozero configuration means "cache in the database".
kv/blob/cfkv/do/memorybackends must be opted intoexplicitly and are never auto-detected or auto-substituted — consistent with
the existing rule that an explicitly configured driver never falls back. An
unavailable backend is skipped with a one-time warning; if none are available
the cache silently degrades to a no-op.
put/get/delete/list(deliberately not viasaveDb(), which would trigger whole-configserialization, write guards and field encryption), under an isolated key prefix
<CACHE_PREFIX>_<kind>_<storageId>_<encodedPath>(kind=ft/ln), sobusiness-data keys are never touched. Paths longer than the EdgeOne KV key limit
are folded with a double FNV-1a hash.
FileItem[]andraw links). Permissions, meta passwords, hide rules and signatures are still
computed per request in
server/fs.ts/server/raw.ts, so a cache hit cannotleak privileges across users. Any cache error degrades to a no-op and never
fails the request.
Config / 配置 (all optional — defaults preserve "DB-only"):
CACHE_ENABLEDtrueCACHE_DRIVERdbdb/kv/blob/cfkv/do/memory/none.CACHE_FILE_TREEtrueCACHE_DOWNLOAD_LINKtrueCACHE_TTL00= follow per-storagecache_expiration(30).CACHE_LINK_TTL5CACHE_EXCLUDE_DRIVERSvirtual,alias,url_tree,strm,chunkCACHE_PREFIXopenlist_cacheCompatibility / 兼容性
Purely additive: no existing endpoint, config key, storage format or
migration path is modified.
Caching is enabled by default. Set
CACHE_ENABLED=false(orCACHE_DRIVER=none) to restore the previous behavior exactly; a single storagecan opt out with
cache_expiration=0or acustom_cache_policiesrule./ 缓存默认开启;
CACHE_ENABLED=false(或CACHE_DRIVER=none)可完全恢复原行为,单个存储也可用
cache_expiration=0退出。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:
CACHE_*variables and the cache admin endpointsTesting / 测试
Platform: Windows 10/11, Node.js 22.22.2,
tsx --test.go test ./...— N/A: this repository is the TypeScript / Cloudflare Workersport; no Go sources are changed.
npx tsc -p tsconfig.json --noEmit— 0 errors in every filechanged by this PR. (Repo-wide, 10 errors remain, all pre-existing and
unrelated:
pkg/validators.tscannot resolvezodin this sandbox, andinternal/model/db_cipher.test.tshas two pre-existing typing errors.)npm run test:cache— 22/22 passnpm run test:model— 39/39 passnpm run test:store— 12/12 passnpm run test:drivers— 111/111 passnpm run test:189— 20/20 passnpm run test:server— 125/129 passThe 4
test:serverfailures are pre-existing and unrelated to this PR. This wasverified by a control run: with this PR's four touched source files reverted to
HEAD, the suite produces the identical result (129 tests / 125 pass / 4 fail, thesame four cases):
default_credentials.test.ts(3) — admin bootstrap / password-reset semantics.Its import closure (
db,auth,user,password,middlewares,op/sshkey)does not contain any file this PR touches.
seed.test.ts(1) — "CAS codec matches casmeta base64 JSON field names": thecodec emits
cloud/slice_md5s/slice_sizebeyond the field set the testasserts.
Manual test / 手动测试: not performed — and here is why. This environment has
no credentials for a live remote storage (网盘 / object storage), so the cache
could not be exercised end-to-end against a real drive. Coverage is unit-level,
against the in-memory driver. Suggested manual check before merge:
1. Attach a real storage, list a directory twice — the second listing should be
served from cache (no remote request).
2. Download a file twice — the second download should reuse the cached link.
3. Rename / delete a file — both caches for that path and its parent should be
invalidated, and the next listing should reflect the change.
4.
GET /api/admin/cache/statusshould report non-zerofile_tree/download_linkentry counts.Checklist / 检查清单
/ 我已阅读 CONTRIBUTING.
/ 我确认此贡献符合仓库许可证、贡献规范和行为准则。
gofmt,go fmt, orprettierwhere applicable./ 我已按适用情况使用
gofmt、go fmt或prettier格式化变更代码。/ 我已在适用情况下请求相关维护者或代码所有者审查。
AI Disclosure / AI 使用声明
/ 此 PR 包含 AI 辅助内容。
Tools used / 使用工具:
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-Byattribution./ 我已确保所有 AI 辅助提交都包含
Co-Authored-By归属信息。I can reproduce all AI-assisted content included in this PR without any AI tools.
/ 我可以在没有任何 AI 工具的情况下重现此 PR 中包含的所有 AI 辅助内容。