猫爪聊天 · 项目状态与风险审查报告
审查性质:只读审查(未修改任何一行代码、未改任何配置、未部署) 审查时间:2026-10-25 23:30(北京时间) 审查范围:v1(worker
zhai)、v2(workerzhai-v2)、国内版(Node + SQLite)三套运行的完整代码与线上状态 审查方法:全量通读worker.js(2801 行)、index.html(12509 行,重点逻辑区)、deploy-web/server.js、deploy-web/kv-sqlite.js、build.js、两份 wrangler 配置、start-domestic.sh、10 个测试文件;用 Cloudflare API 拉取线上真实 bundle 逐字节比对;只读方式查询国内版 SQLite 库;回看聊天记录核对需求演变
0. 先更正我此前两个错误结论
你提醒得对,我此前有两处判断是错的,先把账认了:
| 我此前的说法 | 事实 | 说明 |
|---|---|---|
| 「v2 是空壳,昨天的搬迁没落到 v2 KV」 | v2 空是设计如此。10-24 深夜你让我重新部署一个完全全新的环境(worker zhai-v2 + 新库 chat_memory_v3),它本来就该是空库。 |
我把「按你要求新建的空环境」误判成「搬迁失败」。606 条从来不是要搬进 v2 KV,而是搬进国内版(SQLite)。搬迁工具 tools/migrate_v2_to_domestic.mjs 第 45 行同时读了 0a4c…(v3) 和 ec1a…(v2/v1 数据) 两个库,所以国内版拿到的是两库合并——这也是你说「你聊天库搬错了有是 v1 的」的原因:v1 的数据被一起搬进了国内版。 |
| 「前后端是一套」 | 前端与后端确实是两份独立真源,且存在口径不一致(详见 P0-1、P1-4、P1-5)。 | 前端 index.html 是唯一前端真源,后端 worker.js 是唯一后端真源;cat-chat-all-in-one.js = 后端 + base64(前端),是构建产物。但前端里「聊天」走 CONFIG.WORKER_URL,而「记忆库/归档/删除」走相对路径(永远打当前页面所在后端)——两套寻址规则并存。 |
1. 整体概览
1.1 三个运行版本对照
| 项目 | v1 | v2 | 国内版 |
|---|---|---|---|
| 入口 | https://zhai.beibbloveme.workers.dev |
https://zhai-v2.beibbloveme.workers.dev |
https://a0cd1755f00a22a37.app.workbuddy.host |
| 运行时 | Cloudflare Worker | Cloudflare Worker | Node 22(server.js)+ 内置 SQLite |
| 部署标识 | 版本 a3ef2a33(22:54 部署)etag b22ea7c0… |
版本 8c6fe98a(20:38 部署)etag b22ea7c0… |
进程 22:26 重启,已加载新代码 |
| 存储绑定 | KV ec1a…(chat_memory_v2) |
KV 0a4c…(chat_memory_v3,全新空库) |
/tmp/cat-chat-data/chat.db(SQLite,652 键) |
| 访问令牌 | 无 AUTH_TOKEN secret → 用代码内默认值 518effb1… |
AUTH_TOKEN secret(页面自动注入) |
代码内固定 cat-web-token |
| 模型 Key | secret 名是 DEEPSEEK_API_KEY,代码只读 env.API_KEY,永远读不到(P1-1) |
无任何模型 Key secret | 无(ALLOW_CUSTOM_API=1,靠前端自填) |
| 代码版本 | 与 bundle 一致,含配额修复 | 同左(etag 与 v1 相同) | worker.mjs 与 bundle 逐字节相同,含配额修复 |
| 数据状态 | 真实历史主库(约 307 条归档 + ~300 条记忆碎片) | 基本为空(10-25 傍晚使用过少量) | v1+v2 合并导入 307 条 + 今天新聊 13 条 + 312 条记忆 |
| 与前端关系 | 页面由 worker 自己吐出(内嵌) | 同左 | 页面由 server.js 吐出,读仓库里的 index.html |
| 当前可用性 | 可聊(KV 配额耗尽 → 降级模式 + 提示) | 可聊(同左,但库空) | 完全可用(SQLite 无配额) |
1.2 数据到底存在哪里
四份互不同步的数据副本:
| # | 位置 | 内容 | 性质 |
|---|---|---|---|
| ① | v1 KV ec1a… |
归档消息 msg:owner:*(约 307 条,其中 279 条带软删标记)、记忆碎片 mem:owner:*(约 300 条)、高光/经历/关系少量 |
云端主档(历史) |
| ② | v2 KV 0a4c… |
全新空库,仅 18:56 前后少量新数据 | 云端新档(按你要求重建) |
| ③ | 国内版 SQLite | msg 320 / mem 312 / hl 2 / exp 1 / meta 2 / rl 14 / tomb 0 |
国内直连主档 |
| ④ | 每台浏览器的 IndexedDB | 全量聊天记录 + 图片 base64(图片不入云) | 本机主副本 |
关键事实(只读查询得出):
- 国内版 320 条消息中,279 条是软删状态(删除时间集中在 10-23 22 点 68 条、10-24 13 点 211 条),真正有效的只有 41 条;这些软删来自 v1 的历史操作,导入时原样保留。
- 国内版库里 没有任何墓碑键(tomb:)= 0:说明今天上线的「彻底删除 + 墓碑」还没在这个库跑过。
- 图片只存
[图片]占位 + 字节数,真实图片只在本机浏览器里,换设备/清缓存即永久丢失。
1.3 代码构成与构建链
index.html (前端唯一真源, 12509 行, 230,966 字节)
│
├── build.js ──► cat-chat-all-in-one.js (= worker.js + base64(index.html) + serveFrontend)
│ │
│ ├── wrangler.toml → worker zhai-v2 (KV 0a4c)
│ └── wrangler.v1.toml → worker zhai (KV ec1a)
│
└── deploy-web/server.js 直接 fs.readFile('../index.html') 注入令牌后吐页面
│
└── deploy-web/worker.mjs(= cat-chat-all-in-one.js 的副本,逐字节相同)
└── kv-sqlite.js(KV 接口的 SQLite 实现)
三处「双份/多份」是本次审查的重点来源:清单文件双份(index.html 与 bundle)、后端双份(worker.js 与 worker.mjs)、三套部署三份数据。
1.4 进程与变更时间线(按你我的聊天记录 + git 提交核准)
| 时间 | 事件 |
|---|---|
| 10-05 ~ 10-14 | 初版:Pages 前端 + worker zhai + KV chat_memory_v2(即现在的「v1」数据) |
| 10-19 ~ 10-22 | UI 重构、记忆系统(高光/经历/关系)、P0/P1 安全加固 |
| 10-23 22 点 | v1 上批量「云端删除」68 条(当时只有软删,记录仍在库) |
| 10-24 13 点 | 再删 211 条(同上,仍物理存在) |
| 10-24 13:30 | 改为单文件构建(build.js),前端/后端分离成两个真源 |
| 10-24 晚 | 国内版 C 方案上线(server.js + SQLite) |
| 10-24 深夜 | 按你要求新建全新环境:worker zhai-v2 + KV chat_memory_v3(空库) |
| 10-25 06:34 ~ 09:01 | 全量档案补 mem:*、搬迁工具、606 条导入国内版(两库合并) |
| 10-25 13:33 | 前端默认后端改同源(旧默认写死 v2 地址导致国内版把请求打穿墙) |
| 10-25 13:52 | 桥接基准改用可信公网地址,修国内版 403 |
| 10-25 15:31 | worker 输出页面按本部署令牌改写,修 v1 401 |
| 10-25 14:30 ~ 18:00 | v1/v2 全量 500 排查 → 定位 KV 免费版每日 1000 次 list 配额 |
| 10-25 18:48 | 「彻底删除」改物理删除 + 墓碑防复活(先 v2、后 v1) |
| 10-25 20:36 | 配额修复提交:list 前缀缓存 + 降级 + 干净 503 |
| 10-25 20:38 | v2 部署 8c6fe98a |
| 10-25 22:26 | 国内版进程被重启(node server.js 启动于 22:26,晚于 20:29 的代码更新)→ 已加载新代码(我此前说「国内版未重启」已过时;线上页面已确认含 data.notice 逻辑、注入 cat-web-token) |
| 10-25 22:54 | v1 部署 a3ef2a33(修复到你正在用的入口) |
2 + 3. 问题清单与风险分级
分级标准:P0 致命(会导致数据错乱/丢失/越权,必须马上改)、P1 严重(功能不符合承诺或会踩坑)、P2 一般(边界、体验、配置)、P3 优化建议。 「位置」均为当前工作区文件的行号(行号会随修改漂移,请以函数名为准)。
P0(3 项)
P0-1 「Worker 地址」只对聊天生效,记忆/归档/删除永远打当前页面后端 → 一个设置就能把数据劈成两半
- 位置:
index.html:5206-5225(CONFIG.WORKER_URL)、7183(聊天用它);8079 / 8537 / 8903 / 9023 / 9549 / 9563 / 9577 / 9128(其余接口全用相对路径) - 现象:设置页允许填「Worker 地址」。一旦填写(哪怕是从 v2 页面填 v1 的地址),会出现:
- 聊天 → 发到填的地址(比如 v1)
- 记忆库 / 云端存档 / 恢复 / 彻底删除 / 收藏入库 → 仍打到页面所在后端(比如 v2)
- 结果:AI 的上下文记忆在 A 库,用户在记忆库里看到的和删掉的是 B 库;「删除了但 AI 还记得」正是这种分裂的典型表现
- 影响:数据一致性彻底破坏,且用户无法从 UI 看出发生了分裂
- 补充证据:v1 页面的
WORKER_URL默认取location.origin(同源),本身没问题;问题在可覆盖且只覆盖一半接口
P0-2 「彻底删除」不彻底:只删 msg:/hl:/exp:/rel:,不删 mem:<profile>:* —— 而聊天检索读的正是 mem:
- 位置:
worker.js:2409-2416(硬删只 deletemsgKey+ 墓碑)、128-164(saveMemory 写mem:<p>:<ts>_<rand>)、775-826(retrieveMemory 读 mem 键) - 现象:每条消息在云端存两份(
mem:记忆碎片 +msg:结构化归档)。彻底删除只清msg:与引用它的高光/经历/关系;mem:那份原封不动,而 AI 检索上下文时读的就是mem:。所以:用户删掉的话,下一轮聊天仍会被检索进提示词。 - 影响:功能承诺(「本机和云端都删,不可恢复」)不成立;隐私风险
- 验证方式:删一条刚发的消息,再聊一句相关话题,看是否仍被回忆(或直接查 KV 里该条
mem:是否还在)
P0-3 任何人拿到三端任一 URL,即获得全部数据权限(页面公开 + 令牌随页面下发)
- 位置:
worker.js:1501-1531(pageResponse 无鉴权,向任何 GET / 返回带令牌的页面)、2487-2489(GET 一律返页面)、deploy-web/server.js:382-384(同样公开) - 现象:访问
/不需要任何凭据,返回的 HTML 里就写着window.__CAT_TOKEN__ = "…";拿到它即可调用/archive、/export/full、/messages/delete,读走或删光全部记忆 - 影响:目前只靠「URL 没人知道」这一层遮蔽;一旦链接被转发/截图/出现在浏览器同步记录里,等于全库公开
- 说明:这不是本次改动引入的,是「个人单用户应用 + 无登录」的固有设计;但既然现在数据里有真实聊天,必须正视
P1(7 项)
P1-1 v1 的模型 Key secret 名字不对,代码永远读不到;v2 完全没有模型 Key
- 位置:
worker.js:985(opts.apiKey || env.API_KEY);线上 secret 名单:v1=DEEPSEEK_API_KEY,v2=AUTH_TOKEN - 现象:v1 里那个
DEEPSEEK_API_KEY是死配置(代码只认env.API_KEY)。目前聊天能通,纯粹因为前端每次都自带 Key;一旦前端没填(换设备、清缓存、新装),后端没有兜底 → 400「未配置 API Key」 - 影响:换设备首聊必失败;服务端兜底能力名存实亡
P1-2 记忆检索只扫「最旧的 1000 个」key —— 超过 1000 条后新记忆永远检索不到
- 位置:
worker.js:313-336(listNamesByPrefix的out.length < max截断)、725-747(listMemoryKeys(max=1000))、775-781(排序后取最近 300) - 现象:KV
list按 key 字典序升序返回,mem:owner:<13 位时间戳>_xxx的字典序≈时间序,所以先拿到的 1000 个是最旧的 1000 个;再按时间倒排取 300,得到的是「最旧 1000 条里最新的 300 条」。当前 v1 约 300 条记忆还没触发,长到 1000 条后,新说的话会静默不再被检索 - 影响:长期使用后 AI 会「失忆但自己不知道」;无任何报错
P1-3 国内版数据全在 /tmp,且同源基准写死 —— 容器重建即全丢;域名一变全部 POST 403
- 位置:
server.js:27-33(PUBLIC_ORIGIN写死、DATA_DIR=/tmp/cat-chat-data)、start-domestic.sh(同款默认值)、bridge()342-368 - 现象 a:
chat.db与 7 份快照都在/tmp。平台重建容器/清盘 → 库和快照一起消失(前端本机快照是最后一道,但它依赖浏览器 IndexedDB 没被清) - 现象 b:
isSameOrigin用PUBLIC_ORIGIN与浏览器Origin比对。平台换了访问域名(或你从别的域名打开),所有 POST 一律 403forbidden_origin——日志里已出现过{"ev":"forbidden_origin","path":"/chat"}(也可能来自此前的跨源测试,但机制成立) - 影响:一次平台变更 = 全库丢失 + 页面「发送失败」且原因不可见
P1-4 备份/恢复能力三端不一致:v1/v2 只能「导出本机」,云端全量档案只有国内版能导入
- 位置:
index.html:8531(if(isWebMode())才导云端)、8741-8745(restoreCloudFullFile非 web 模式直接拒绝) - 现象:同一个「备份」按钮,在国内版导的是云端全量(消息+高光+经历+关系+记忆),在 v1/v2 导的是浏览器本机数据;而
schema:2的云端档案在 v1/v2 无法导入 - 影响:换设备/换端时,v1/v2 用户拿不到完整备份;同一份文件在不同端行为不同
P1-5 跨源响应缺少 Access-Control-Allow-Origin → 任何「页面在 A、后端在 B」的组合都会发送失败
- 位置:
worker.js:66-74(json()只带Vary: Origin)、1287-1300(corsHeaders只在 OPTIONS 用) - 现象:预检 OPTIONS 返回了 ACAO,真正响应却没有 → 浏览器把响应拦掉,前端只看到「网络错误/发送失败」
- 影响:直接堵死「国内版页面 + CF 后端」「Pages/自定义域名 + worker」这类组合;也和 P0-1 的「切后端」互相引爆
P1-6 图片不入云:只有 [图片] 占位,图只存在发送它的那台浏览器
- 位置:
worker.js:2596-2609(归档只写message_type:'image'+attachment.bytes)、index.html:6696-6723(渲染读本机 base64) - 现象:换设备、清缓存、重装 → 历史里的图片全部变成空白/丢失;备份 JSON 里也没有图
- 影响:用户会以为「云端有备份」,实际图片没有任何备份
P1-7 首次「云端同步 / 恢复」是 O(n) 写入,1000 条就能一次打满免费版日配额
- 位置:
index.html:8874-8933(一次 POST 全部本地消息)、worker.js:1785-1815(逐条 get+put) - 现象:首次同步 1000 条 = 1000 次 get + 1000 次 put;免费版写配额 1000/天 → 一次同步当天再写就失败(聊天会降级提示)
- 影响:新设备首次同步后,当天聊天可能无法入库
P2(13 项)
| ID | 问题 | 位置 | 影响 |
|---|---|---|---|
| P2-1 | 部署产物与真源双轨:cat-chat-all-in-one.js 是生成物却直接部署;wrangler deploy --config 不会自动 build,忘了 build 就部署旧代码 |
wrangler*.toml main、deploy.sh |
线上/仓库漂移(今晚 v1 能上修复纯因为我先 build 过) |
| P2-2 | deploy-web/worker.mjs 是手工同步的副本(当前与 bundle 逐字节相同),无任何机制保证下次还一致 |
deploy-web/worker.mjs |
国内版可能悄悄跑旧后端 |
| P2-3 | 写入放大:每个 POST 先做 2 次限流写(rl_<ip> + rl_global),一次聊天约 6 写;限流写失败被 catch 吞掉 → 配额耗尽时限流静默失效 |
worker.js:1365-1399 |
日上限约 160 条聊天;配额耗尽后失去防刷 |
| P2-4 | ALLOW_CUSTOM_API="0" + 前端有「自定义」服务商:用户填非白名单域名必 403,提示却让去改 toml |
两份 wrangler [vars];worker.js:1455-1463 |
自选模型名不副实 |
| P2-5 | validateApiUrl 返回 u.origin + u.pathname,丢掉 query |
worker.js:1465 |
带查询参数的接口(部分 Azure/代理网关)不可用 |
| P2-6 | 主动消息只写 mem:,不写 msg: 归档 |
worker.js:2789-2792 |
/export/full 的 messages 缺主动消息;换设备恢复后聊天记录少几条 |
| P2-7 | 三套导入导出接口并存:/backup(旧) / /archive(中) / /export/full(新),语义重叠 |
worker.js:1534/1648/1839 |
维护成本、口径混乱 |
| P2-8 | /init 是空实现;/health 版本号写死 2.0 |
worker.js:51-54, 1602-1609 |
三端无法区分「跑的是哪个版本」 |
| P2-9 | 国内版回滚先 kv.clearAll() 再导入,导入失败即空库(靠 pre_rollback 快照兜底,而快照也在 /tmp) |
server.js:290-315 |
一次失败的回滚 = 全库清零 |
| P2-10 | 平台 sharecode 写死在代码里;manifest 的图标与 start_url 不带 sharecode |
server.js:46、104-106;worker.js:1184-1206 |
国内版「添加到主屏幕」可能 403/白屏 |
| P2-11 | v2 的 AUTH_TOKEN 明文写在 index.html 第 10 行,已进 git 历史 |
index.html:10 |
令牌事实上公开 |
| P2-12 | /export/full、/archive 无速率限制(只校验令牌) |
worker.js:1611-1621 |
令牌一旦泄露可被刷爆配额 |
| P2-13 | worker.js 依赖构建期注入的 serveFrontend(源码里未定义);直接 deploy worker.js 会在 GET / 时 ReferenceError |
worker.js:1505 |
绕开 build 就炸 |
P3(11 项)
| ID | 问题 | 说明 |
|---|---|---|
| P3-1 | mem: 与 msg: 双写冗余 |
同一句话存两份,KV 键数与写入次数翻倍;也是 P0-2 的根因。中长期应合并为一份 |
| P3-2 | 旧 mem_ 兼容读不走缓存 |
owner 每条聊天多 1 次 list(已刻意如此保证迁移可见,代价可控) |
| P3-3 | /backup、/init 可废弃 |
前端未使用,属历史包袱 |
| P3-4 | ALLOWED_ORIGINS 填的是 worker 自己的 origin |
无意义(同源本来就放行),建议留空或填真实跨源前端 |
| P3-5 | deploy-web/package.json 描述过时 |
写着「无 KV 记录,仅聊天 API 代理」,实际已有完整 KV |
| P3-6 | worker.mjs 内嵌 300KB FRONTEND_B64 |
Node 端用不到(页面由 server.js 读文件),属无效载荷 |
| P3-7 | 无 CI / 无 lint;测试未覆盖 CORS、令牌注入、WORKER_URL 覆盖 | 10 个测试文件全是「内存 Mock KV + Mock 模型」的契约测试,覆盖后端逻辑,不覆盖三端联调 |
| P3-8 | 限流常量与真实配额未联动 | RATE_DAY=400 与 KV 1000 写/天没有换算关系 |
| P3-9 | 重试路径不处理 notice |
retryMessage 未读 data.notice,降级提示只在首次发送出现 |
| P3-10 | resolveTime 信任客户端 time.local 并直接拼进提示词 |
理论可注入提示词(仅自伤),建议清洗 |
| P3-11 | 重试可能写入重复 mem 碎片;AI 归档时间戳复用请求时间 |
幂等只覆盖 msg:(worker.js:2586-2611),mem: 随机键无幂等;ai 记录用 time.ts(2702-2710),长回复时排序会抖 |
4. 修复方案(只给方案,不动手)
P0-1 统一「后端基准地址」
方案 A(推荐,一次性根治)
1. 前端新增 function apiBase():返回 CONFIG.WORKER_URL(去掉尾部 /)。
2. 把所有相对路径调用改为 apiBase() + '/xxx':/messages/delete、/archive、/export/full、/import/full、/highlights、/experiences、/relations、/proactive。
3. 设置页在「Worker 地址」变更时弹确认:「切换后端后,聊天、记忆库、云端存档将全部指向新地址(不同地址的数据互不可见)」。
4. 若判定「多后端」不是真实需求,更简单:删掉设置里的 Worker 地址输入,永远同源(三端各自自洽),从根上消除误配。
方案 B(最小改动):保留相对路径,但把设置页的 Worker 地址输入只读化/隐藏,并在页面加载时校验 CONFIG.WORKER_URL 与 location.origin 不一致时强制回退同源 + 提示。
验收:在 v2 页面把地址改成 v1 → 记忆库请求路径变为 v1(Network 面板可见),或按方案 B 直接被拒绝。
P0-2 让「彻底删除」真的彻底
方案 A(推荐,中期重构):把检索数据源统一到 msg:*
1. retrieveMemory 改为读 msg: 归档(已有 deleted_at 过滤),不再读 mem:*。
2. saveMemory 停止写入(或仅保留一段时间双写做回滚)。
3. 删除路径自然覆盖(msg: 删了就检索不到),不再需要扫 mem。
4. 迁移:把现有 mem:* 的内容按 content+ts 与 msg:* 对齐后清理(需 list,等配额恢复后分批做)。
方案 B(短期止血):
1. saveMemory 记录里增加 mid 字段(用户消息传 userMid,AI 消息传 aiMid)。
2. 硬删时:从最新往旧扫 mem: 键(最多 N 条),找到 mid 命中即 delete + 留墓碑;扫不到就记录告警。
3. 代价:每次删除多 1 次 list + 若干读写,且随库增长变慢。
验收:删一条消息后,/export/full 的 memories 与 messages 都不再包含该内容;再聊天时提示词里不出现。
P0-3 收敛访问权限(三选一,按你的使用场景定)
- 方案 A(最省事,推荐):给 v1/v2 套 Cloudflare Access(Zero Trust,免费档够用),只允许你的邮箱/设备;国内版在平台侧加访问控制(若有)。
- 方案 B(自建):加口令登录:
POST /login校验口令 → 下发HttpOnly; Secure; SameSite=Lax的签名 cookie;页面不再注入 token,改为所有 API 校验 cookie。工作量中等,但三端都能用(国内版同款逻辑)。 - 方案 C(最低成本缓解):页面不再明文注入 token,改为「首次访问输入访问码 → 存 localStorage」;同时把 worker 的 GET / 也要求带码(否则返回一个只有输入框的壳页)。能挡「随手点开链接的人」,挡不住有心人。
P1-1 修 Key
- v1:
wrangler secret put API_KEY --config wrangler.v1.toml(值 = 你的 DeepSeek Key),或把代码改成opts.apiKey || env.API_KEY || env.DEEPSEEK_API_KEY(更稳,兼容现状)。 - v2:同样加
API_KEY。 - 前端:当
CONFIG.API_KEY为空时,在输入框上方提示「未填 Key,将使用服务端默认 Key(若已配置)」。
P1-2 修检索「只看得到最旧 1000 条」
方案(推荐):按月分片
1. 写入键改为 mem:<profile>:<YYYYMM>:<ts>_<rand>。
2. 读取时从当前月开始往前取最多 3 个月(每月 ≤200 键,天然分页),合并后按时间倒排取最近 300。
3. 兼容:旧 mem:<p>:<ts>_... 仍按原前缀读一次(只在没有新分片数据时)。
4. 备选:维护一个 mem_recent:<p> 索引键(存最近 300 个键名,写入时更新,读时一次 get)——写放大 +1,但读取 O(1)。
P1-3 国内版持久化与同源
- 数据目录改到平台持久卷(若有);若无,增加「每日把快照 base64 POST 到 v2 的
/import/full」做异地副本(注意配额,可每周一次)。 PUBLIC_ORIGIN改为:优先环境变量 → 其次读平台注入的公开域名 → 兜底现值;isSameOrigin增加「同 host 的任意路径/端口」放行。- 403 时返回中文可读页:「当前访问域名与后端配置不一致,请用 xxx 访问」,而不是让前端显示「发送失败」。
P1-4 统一备份口径
- 去掉
exportBackup的isWebMode()限制:v1/v2 也调/export/full(失败再退回本机导出)。 - 去掉
restoreCloudFullFile的isWebMode()限制(/import/full三端都支持)。 schema:2文件在三端行为一致。
P1-5 修 CORS
json(data, status, request, env) 增加后两个参数,内部 Object.assign(headers, corsHeaders(request, env));或在路由出口统一包一层。注意:corsHeaders 只在 Origin 合法时回 ACAO,避免变成开放代理。
P1-6 图片
短期:UI 明确标注「图片只保存在本机,换设备不会同步」;备份说明里写清。
长期:接 R2(或对象存储),msg.attachment 存对象 key,渲染时按需取;KV 只存元数据。
P1-7 分批同步
cloudSyncToServer 改为:本地记录 lastSyncTs → 只上传之后的;每批 50 条,逐批 POST,失败可续传。
P2 / P3 速查
| ID | 修法 |
|---|---|
| P2-1 | deploy.sh 已含 build;给 v1 也加一个 deploy-v1.sh(build → deploy --config),并把「禁止直接 wrangler deploy」写进 README |
| P2-2 | 构建时顺手 cp cat-chat-all-in-one.js deploy-web/worker.mjs(build.js 加一行),消除手工同步 |
| P2-3 | 限流改成「一个键同时记分钟/天」(现在已是 1 键 2 写→可合并为 1 写);或把限流计数放模块内存(isolate 级,允许超发)以减少 KV 写 |
| P2-4 | 要么 ALLOW_CUSTOM_API=1,要么前端「自定义」时校验域名并在 UI 提示白名单 |
| P2-5 | return u.origin + u.pathname + (u.search || '') |
| P2-6 | handleProactive 里补 saveArchive(aiMid, ...),与聊天同款 |
| P2-7 | 保留 /export/full + /import/full,把 /backup、/archive 标 deprecated,前端统一 |
| P2-8 | /health 返回构建时间戳(build.js 注入 BUILD_ID) |
| P2-9 | 回滚改为「导入到临时库 → 校验 counts → 原子替换文件」,失败不 clearAll |
| P2-10 | manifest 动态生成时带上当前 query(sharecode);start_url 同样处理 |
| P2-11 | 轮换 v2 token,页面改成占位符;Pages 若已废弃则直接删掉硬编码 |
| P2-12 | 给 /export/full、/archive 加同款 checkRate |
| P2-13 | build.js 里给 worker.js 加一行 function serveFrontend(){...} 的桩,或直接禁止部署 worker.js |
| P3-1~11 | 见上表,随主修复顺带处理 |
5. 建议的执行顺序(等你决定,我不会擅自动手)
第一批(止血,1 次部署,30 分钟内)
1. P1-5 CORS(1 处函数签名)
2. P1-1 Key 读取兼容(1 行)+ 两个 worker 加 secret
3. P2-5 query 丢失(1 行)
4. P2-13 serveFrontend 桩(防误部署炸)
第二批(数据正确性,需要设计确认)
5. P0-1 统一后端基准(决定「保留多后端」还是「锁死同源」)
6. P0-2 删除彻底化(推荐方案 A:检索改读 msg:)
7. P1-2 检索分片(与 6 一起改,避免两次动检索)
第三批(部署与数据面) 8. P1-3 国内版持久化 + 同源兜底 9. P1-4 备份口径统一;P1-6 图片策略;P1-7 分批同步 10. 历史 279 条软删数据的清理(硬删 + 墓碑,必须分批:每批 ≤200 条,否则写配额当天就满)
第四批(安全) 11. P0-3 访问控制(建议 Cloudflare Access,最快)
配额相关注意:KV 免费版 1000 list / 1000 write / 10 万 read 每天(账户级,北京时间早 8 点重置)。任何「批量清理 / 全量搬迁 / 首次同步」都要按 ≤200 条/批、跨天做,否则第二天照样把配额打满。
6. 本次审查的边界(做不到的部分,如实说明)
| 项 | 原因 |
|---|---|
| 无法枚举 v1/v2 的精确键数与总量 | KV 免费版 list 配额已耗尽(code 10048),REST keys 端点同样被拒;只能靠已知样本与国内版导入结果推断(307 条归档来自 v1) |
| 无法查看线上运行时日志 | 沙箱到 workers.dev 与 CF 日志端点均超时,wrangler tail 不可用 |
| 无法读取 secret 明文 | CF API 不返回 secret 值;只能确认名称 |
| 无法在沙箱内直连 v1/v2 做端到端冒烟 | workers.dev 被网络阻断;已改用「CF API 拉取线上 bundle + 逐字节/语义比对」替代(结果:v1 前后端修复代码与本地完全一致) |
| 未验证真实设备上的表现 | 只读审查,未触发任何写操作 |
7. 一句话总结
三端代码现在是同一份修复版、都能跑;真正的风险不在「能不能用」,而在架构层面的三处不一致:①前端一半接口认「设置的地址」、一半认「当前页面」(P0-1);②消息存两份、删除只删一份(P0-2);③三端各有一份互不同步的数据,图片只在本机(P1-3/4/6)。这三件事不解决,后面每一次「修好了」都会在换端/换设备/换域名时重新裂开。