OceanWay · sub2api 账户数据承接 R2
状态:
LOCAL_TEST_CANDIDATE / CANDIDATE_EVIDENCE / NOT_PRODUCTION_READY本文记录一次隔离测试环境中的只读盘点、全量导入演练和幂等核对。它不是生产迁移授权、切换批准或正式 Testing/Operations 结论。
目标与边界
本轮把 OceanWay 定制 sub2api 作为一次性、只读的数据源,将用户、Key、用量、订单、订阅和登录身份复制到独立 OceanWay 测试库的内部审计表,并建立稳定的 sub2api-user-{source_id} 映射。用户端只呈现当前 OceanWay 账号、余额、套餐、订单和用量,不展示迁移来源。
以下数据明确排除:new-api 技术用户、账号池、Group/Channel/Proxy、OAuth 凭据、内部 Token、支付二维码/跳转地址及其它供应商专用配置。源库没有写入,迁移工具不是运行时依赖。
sub2api 使用 bcrypt,而 OceanWay 新注册用户使用 PBKDF2。数据承接候选对严格匹配的 bcrypt 哈希启用兼容校验:哈希只进入目标用户的受保护 password_hash 字段,不进入脱敏元数据、日志或输出;用户首次成功登录后立即重哈希为 PBKDF2。缺失或不支持的源哈希仍进入账号恢复流程,Session 不承接。账户余额采用 1.00 = 1.00 积分,通过 --balance-mode source-as-balance 按原值写入当前统一账本。
工具与安全
入口为 web/scripts/sub2api-migration.mjs,包装命令为:
pnpm --dir web run migrate:sub2api --table users --mode dry-rundry-run 和 apply 从标准输入读取经过显式列白名单的 NDJSON;金额、额度、成本和 Token 数量先转为字符串,再写入 PostgreSQL numeric/bigint。Key 使用目标 VOZEB_PRO_ENCRYPTION_KEY 以 AES-256-GCM 加密,同时保存 HMAC、前缀和末四位,数据库中没有明文 Key。元数据递归移除 password、token、secret、credential、authorization、二维码、支付地址和技术实体字段。
当前工具对账户余额、Key quota、订单金额和用量成本使用 PostgreSQL numeric 保存源事实;写入可消费 OceanWay 余额的 opening entry 使用 NUMERIC(38,18),最多保留 18 位小数。格式非法的数值不会被当作 0,dry-run 会记录脱敏的 rejected 行,apply 直接失败关闭。
verify 不重新读取仍可能变化的在线源,而是使用 apply 输出的行数与 SHA-256 指纹核对目标历史表:
pnpm --dir web run migrate:sub2api --table usage_logs --mode verify \
--expected-count <apply-count> \
--expected-fingerprint <apply-fingerprint>生产流程仍需在冻结/检查点窗口重新导出各表,保存 Manifest、Checkpoint、Checksum、例外 Case 和源库只读审计证据;不得把在线源变化静默覆盖为成功。
隔离测试结果
测试目标为独立 Docker PostgreSQL 16.6 与 VOZEB PRO App/Worker,不监听公网端口;首次管理员未创建,/api/health/live 返回 200,/api/health/ready 因 firstAdminRequired 返回 503,符合未完成安装的预期。
| 数据集 | 目标行数 | 关键聚合 | Apply 指纹 | 结果 |
|---|---|---|---|---|
| users | 318 | balance 4018779.48747119;frozen 0.00000000;recharged 19675.00000000 | 7b3fe08e845ac3d30502eecc24b4c066054f721e431129fea7414e9a7502d632 | Apply + Verify |
| api_keys | 449 | quota 1026365.56612620;used 3747.98581470 | 8f24918755c77e5ba579a33c11646611708fa515ca302145dcf44e4caacdab06 | Apply + Verify |
| usage_logs | 557494 | total cost 62142.3415514500;actual 62631.6869851900;input tokens 6102974673 | dd1ac550972960c6438acc1ed6f99b0eabb12405939320ba6c8f087386538b83 | Apply + Verify |
| payment_orders | 344 | amount 36341.60;paid 23041.60;refund 0.00 | 4efb8e4f974c6fc2de54f19e9e0b38987e0b2738de74ddafdea1ae6488843ab7 | Apply + Verify |
| subscriptions | 183 | daily 2771.4078532000;weekly 7003.6595542000;monthly 17394.3905539250 | 5aad77656f2bac53a6a5eeaf0b39ef84cd9fb46c31f991f3218fa776d333bae4 | Apply + Verify |
| auth_identities | 307 | provider subject 已按稳定字段保存 | fe9552ba52e1dbd33c52bacbb01894b5b99704691b0b5fa0565cc7ba95106347 | Apply + Verify |
表内聚合值是最终 Apply 快照在目标库的直接聚合结果;在线源在不同导出时刻可能变化,不能用后续源库即时总数替换这组已绑定指纹的证据。
用量导入快照的最大源 ID 为 651270;源查询 WHERE id <= 651270 与目标行数、成本和 Token 聚合完全一致,证明本次在线导入没有缺页。API Key 历史表的 449 行全部使用 vozeb-pro-secret:v1 密文前缀,未发现 sk- 明文或敏感元数据残留。
目标库已生成权限为 0600 的 PostgreSQL custom-format 备份;在一次性临时 PostgreSQL 16.6 容器中执行 pg_restore --exit-on-error --no-owner --no-privileges 后,六类历史表计数仍为 318/449/557494/344/183/307,临时容器随后销毁。该恢复演练不替代生产备份保留、异地复制和正式 Operations 验收。
R1 认证兼容性演练(2026-08-29)
在不写入旧库的前提下重新导出 users 的受控列并执行 sub2api-password-r1 Apply。旧库密码记录中 310 条为 bcrypt,目标库保留其中 302 条 active 和 8 条 disabled;另有 8 条 customer_type=managed 记录使用非密码占位值,目标库未把这 8 条伪装成可登录密码。测试部署镜像 vozeb-pro:test-bcrypt-migration-r1 已通过 TypeScript、ESLint、Prettier、Vitest(562 files / 2478 passed / 9 skipped)和 66/66 生产构建。
目标库核对结果为 318 个用户、302 条活动/可兼容 bcrypt 哈希、8 条 reset-required,管理员 admin@sub2api.local 仍为 active 且保留 13 项权限。使用一次性合成用户走真实登录接口后,目标哈希从 bcrypt 自动升级为 PBKDF2,随后删除合成用户与会话。该结果证明兼容校验和透明升级链路可工作,但不替代每个真实用户的登录回访、邮件恢复和正式 Testing/Operations 验收;不支持密码占位的 managed 用户仍需单独身份恢复方案。
幂等与已知问题
- 用户、Key、订单、订阅、身份和用量均以源稳定 ID 为主键执行 upsert;重复 Apply 不新增目标行。用量已额外重放一个 272 行子集,目标总行数保持 557494。
- 旧源在导出期间仍有写入,直接用新的在线源重新计算指纹会发生漂移;因此 Verify 固定比较 Apply 快照,而不是把漂移误判为目标损坏。正式迁移必须增加冻结或增量 checkpoint。
- 用户名/邮箱冲突会生成可追溯的迁移用户名或清空冲突邮箱;源角色只保存在历史元数据,目标权限统一为普通用户。
- 订单和订阅目前保存为历史事实,尚未映射到 VOZEB 商品 SKU、价格版本、
user_plan_assignments或 Retail 订单状态机;切换前旧责任方仍需处理未终态旧订单。
进入正式迁移前必须决策
- bcrypt 用户的无感登录与首次登录升级、非密码 managed 用户的身份恢复、邮件/短信送达和 Session 不承接方案。
- OceanWay 公共 Key 表/API 的正式承载方式,以及 Key 过期、撤销、Quota、Used 和限速字段的产品语义。
- 订阅/Group 商业语义到 Product、SKU、权益快照的逐条映射;订单、支付、退款的责任边界和切换增量协议。
- 全历史用量的用户可见展示、90 天热数据与更早加密归档的存储策略、校验和及保留期限。
- 独立 Testing/Operations 对备份、恢复、监控、回滚和切换窗口的批准;生产旧库只读审计、明文 Key 短暂处理和最终增量均需当次授权。
迁移器当前还会把每行归类为密码兼容/需恢复、账号状态、冻结或负余额、Key 状态/过期、API/网页用量、订阅映射和订单终态;订阅 dry-run 会完整统计未映射 Group,Apply 对任何未映射记录 fail-closed。生产切换必须确认 rejected 为空,并抽样用旧 Key 调用 /v1/models 与代表性生成入口。
用户界面的积分金额统一四舍五入显示两位;Token、图片/视频次数和视频秒数仍展示实际计量。账本、单笔结算和校验始终保留最多 18 位十进制,显示值不会写回账本。
当前结论
隔离测试已证明六类数据可以按稳定 ID、精确数值、密文 Key 和可复核指纹导入,且重复 Apply 不重复创造记录。余额单位已经固定为 1:1 积分;认证恢复、商业套餐映射、源一致性窗口和正式备份/切换仍需验收。因此当前结果只能作为本地候选证据,不能宣称生产数据承接完成、生产可用或账务已切换。