# tiku-supabase

这是题库项目从 PocketBase/SQLite 重构到 Supabase/PostgreSQL 的新后端仓库。

当前仓库重点承载“商用 SaaS 版本”的新架构代码，包括多租户数据库、业务 API、PocketBase 数据导入器、本地验证脚本和重构进度文档。旧 PocketBase/React 项目仍保留在原工作区作为功能参照和迁移来源，但这个 Git 仓库不打算作为旧项目全量镜像。

## 当前状态

更新时间：2026-06-30

目前已经完成并在本地验证通过的内容：

- Supabase/PostgreSQL 多租户数据库 schema、RLS、索引、触发器。
- `apps/api` 独立业务 API，后续供 H5、Taro 小程序、管理后台统一调用；已支持 Supabase Auth JWT 和迁移期 `tk_` session 双入口。
- 租户后台能力：品牌、主题模板/草稿/发布、域名、公开设置、支付账户、登录配置、私密密钥掩码、活动内容、考试日期、题目反馈处理、用户站内通知查看、激活码、优惠券规则/核销报表、勋章管理/手动发放/签到积分反馈自动发放、成员权限、自定义角色模板、班级/教师/学生范围权限、学生批量导入、批量分班、学生备注、跟进任务、跟进效果统计、学习督导自动化预览/生成、督导规则模板、学生批量 CRM 推送、审计日志。
- 租户内容能力：可配置题库入口、任意深度分类树、考试意向标记、题目集合、顺序/随机/全真模拟蓝图、题目录入/更新、视频绑定、分数线、单词、知识手册、资料资源台账、题目/单词/知识手册/分数线/视频 JSON/CSV/Excel 批量导入。
- 学生端能力：题库入口、分类树、题目集合、顺序/随机/模考 session 组卷快照、答题、错题本、收藏夹、背单词卡片学习/发音/收藏练习、个人中心、男女默认预设头像、站内通知、勋章、考试倒计时、签到积分、积分活动任务、积分兑换、题目反馈、排行榜接口（租户默认关闭）、分数线、AI 择校推荐、题目视频、订单详情/状态轮询、优惠券领取/抵扣、权益、激活码预检查/兑换、资料下载；签到、积分阈值、反馈解决和积分活动可返回自动获得勋章结果，反馈处理/奖励、勋章发放和积分兑换会写入用户站内通知。学生头像不支持上传或第三方头像落库，学生激励默认以勋章自动发放为主，不默认启用排行榜。
- 平台后台能力：租户管理、租户详情、账务资料维护、平台员工创建/授权/启停、平台细粒度权限点、平台审计日志查询和 CSV/JSON 导出、平台审计告警规则/开放告警查询/确认/解决、平台审计告警外部通知渠道和发送事件、SaaS 套餐、订阅、订阅账单候选预览/dry-run/批量生成、自动计费 worker、账单、服务费收款、逾期标记、内部催缴台账、平台催缴外部通知渠道和发送事件、平台用量自动采集 worker、用量记录、SaaS 套餐额度判定、用量超额账单候选预览/dry-run/生成、自动开票 worker 和失败审计告警、公共题库授权。
- 公共题库商业化能力：租户可采纳平台授权题库为本租户副本，并可手动或由 worker 自动同步平台新增/更新题目；同步会保护租户自改题目，返回冲突而不覆盖，后台可查询冲突明细；worker 失败会生成租户 `public_question_bank_sync_failed` 通知，恢复成功自动关闭失败通知，平台可用 `/api/platform-admin/question-bank-sync-status` 按 `platform:question_bank:ops` 查看跨租户同步运营摘要。
- 题库导出能力：租户内容编辑可按题目集合、内容入口或分类节点导出 JSON、`paper_json`、打印 payload、PDF、Word 和每日一练图片 ZIP 素材包，后端强制租户隔离、答案/解析开关、复合题子题脱敏、导出 job 和审计；PDF/Word/ZIP 由 exports worker 生成水印文件或运营素材并发布到 `content_assets`；`daily_practice` 支持每日一练九宫格 metadata、PDF/Word 版式、9 张 PNG/SVG 卡片和拼图包。
- 销售/代理/CRM 增长链路：邀请码、扫码/分享事件、首绑客资保护、销售统计、团队关系、CRM 配置、跟进分配策略、客资队列和学生批量 CRM 跟进推送。
- `apps/worker` 后台任务进程：CRM webhook 队列消费、`lead.created` 客资事件、`student.crm_push` 学生跟进事件、generic/钉钉/飞书/企微机器人发送、签名、失败重试和日志；student-supervision worker 可按租户督导规则模板定时生成学习跟进任务；commerce worker 可补偿查询微信/支付宝支付和退款状态；provider-bills worker 可下载微信/支付宝官方账单并导入资金对账；platform-billing worker 可自动为即将到期且未开票的 SaaS 订阅生成服务费账单并写审计；platform-usage worker 可按月从权威业务表自动采集学生数、活跃学生、题量、资源数、存储 GB、视频数、视频播放、视频次数消耗、已支付订单、GMV 和有效权益，并写入用量台账和审计；platform-usage-overage worker 可按上月账期自动生成 `usage_overage` 账单，执行失败会写入脱敏平台审计日志并由审计告警 worker 转为开放告警；platform-dunning worker 可扫描逾期未结清服务费账单、标记 overdue、写内部催缴记录和审计；platform-dunning-notifications worker 可把内部催缴记录按平台渠道推送到 generic/钉钉/飞书/企微 webhook，并记录幂等发送事件；platform-audit-alerts worker 可把高风险平台审计动作和自动化失败转换为内部告警并递归脱敏告警 details；platform-audit-notifications worker 可把开放审计告警按平台渠道推送到 generic/钉钉/飞书/企微 webhook，并记录幂等发送事件；assets worker 可复检托管资源元数据、执行内置安全扫描并自动下架异常资源；imports worker 可执行大批量导入；public-banks worker 可自动同步公共题库采纳副本；exports worker 可渲染 PDF/Word 导出文件和每日一练 ZIP 图片素材包。
- 销售/代理分佣和转化看板基础闭环：租户默认比例、成员比例、激活码批次比例、订单/激活码归因、结算单生成、审核、线下打款状态、CSV/JSON 导出、打款凭证登记/复核、销售/代理转化报表、近期未成交客资、CRM 失败/跟进积压和本人/全局权限隔离。
- 订单售后基础闭环：退款请求、审核、处理状态流、微信/支付宝发起退款、微信/支付宝退款查询确认、微信/支付宝退款通知 webhook、退款金额累计、部分/全额退款订单状态、全额退款权益撤销、退款事件和审计日志。
- 资金对账、异常订单和财务凭证闭环：租户财务/运营可通过 `/api/commerce/reconciliation/*` 导入或预览支付/退款账单行，也可创建微信/支付宝官方账单下载任务；后端按租户隔离比对本地订单、支付、退款记录，识别已匹配、金额不一致、状态不一致、供应商有本地无、本地有供应商无、重复行和无效行，并写入对账批次、明细和审计日志；异常明细可创建差错工单，支持分配、开始处理、升级、解决、忽略、重开和事件留痕；`/api/commerce/operations/anomalies` 聚合异常订单风险，`/api/commerce/adjustment-vouchers*` 支持人工调整凭证、复核、事件轨迹和报表。工单和凭证只做财务审核闭环，不直接修改订单、支付、退款或权益。
- PocketBase SQLite 只读导出、JSON dry-run、标准化导入和导入后校验脚本；真实旧库 248555 条业务记录已能在干净本地 Supabase 中完成全量导入。
- 本地 Supabase reset、烟测 seed、API 集成测试、完整重构检查命令。

还没有达到生产交付的部分：

- Supabase Auth/JWT、租户角色模板、班级/教师/学生范围权限已可联调；生产前还要做真实云端 Auth/JWKS 回归和 RLS 深测。
- 阿里云/腾讯云短信、微信小程序登录、微信网页登录、QQ 登录、手机号绑定/换绑、微信支付、支付宝主链路、微信/支付宝发起退款/查询确认/退款通知、支付/退款补偿 worker 已完成本地适配；本地阶段使用 mock/fake provider 和回调后业务链路验收，不要求真实平台密钥。资金对账已支持手工/API 账单导入、微信/支付宝官方账单下载任务、provider-bills worker 自动导入比对、差错工单处理、异常订单运营台和人工调整凭证复核报表；真实生产账号、真实回调域名和真实生产账单抽样验收等上线后密钥联调还没接完。
- OSS/COS/Supabase Storage 上传下载签名 provider 已接入；上传后校验、PDF/图片预览、资源访问事件、动态水印上下文、锁定资源 CDN 边界、资源复检 worker、内置 `metadata_rules` 安全扫描和外部 HTTP 杀毒/内容安全 scanner 接入层已完成；Taro 学生资料页已按短期签名和 `watermark.traceId` 渲染可见水印确认/预览第一版。生产还要配置真实扫描服务 endpoint/token，并继续补转码/CDN 级水印、CDN 刷新和对象生命周期策略。
- Excel/CSV 导入解析已完成并复用 `content_import_jobs/items/issues` 管线；大批量异步导入 worker 基础已接入，支持 queued job 消费、重试和审计；导入后复检、模板下载、字段映射 API 和 Taro 租户内容页第一版导入操作台已完成。
- 题库导出已完成服务端结构化 payload、PDF/Word 二进制 worker、每日一练基础导出和每日一练 ZIP 图片素材包；后续还要补更精细试卷模板、多模板排版和导出操作台体验。
- 优惠券复杂规则和核销报表已可联调，包含状态启停、活动分组、最低订单金额、优惠封顶、单用户限次、首单限制、适用套餐/地区、核销明细和活动报表；Taro 租户营销中心已接优惠券规则表单、筛选、核销明细和报表第一版。
- 勋章管理、手动发放、签到连续天数、积分阈值、反馈解决、积分活动任务、练习次数、单词掌握和模考成绩系统触发勋章已可联调；积分活动任务、积分兑换商品、兑换订单、优惠券兑换履约、租户后台配置和用户站内通知第一版已完成，Taro 学生个人中心已接积分任务/兑换/积分明细和消息中心第一版，租户营销中心已接积分任务/兑换操作台和用户通知查看第一版。学生激励默认以后台配置勋章自动发放为主，排行榜默认不开启也不在学生端默认请求。CRM 死信运营第一版已完成失败池、日志脱敏、重试/忽略和审计闭环，销售/代理转化看板第一版已完成。后续还要补更细活动效果看板、外部微信订阅消息/短信推送、分佣真实打款 provider、发票、批量凭证上传、CRM 富卡片模板、外部失败告警升级、公共题库版本通知和冲突处理操作台。
- `apps/taro` 已建立 Taro 4 React 跨端前端地基，包含 H5 学生端、租户后台、平台后台三套构建入口、租户解析、统一 API client 和 Supabase Auth client 初始化；学生端第一批页面已接入登录、首页、题库、练习、背单词、知识手册、分数线、AI 择校推荐、资料、独立消息中心和个人中心，已新增 `RichContent` 安全渲染组件用于题干、选项、解析、知识手册和逐题复盘，H5 端已用 KaTeX 渲染 `$...$`、`$$...$$`、`\(...\)`、`\[...\]` 公式，私有题图可用 `asset:<uuid>`/`content_asset:<uuid>` 资源引用走短期预览签名，已升级背单词为今日计划/单元学习/收藏练习、学习概览、掌握率、收藏数、计划拆分、卡片翻转、发音、美/英音切换和本地位置恢复第一版，知识手册已接章节内搜索、安全文本摘要高亮和目录定位第一版，分数线已接目标地区默认筛选、院校/专业/年份 chip、租户动态字段筛选、结果字段 chip 和趋势摘要第一版，AI 择校已接报告生成、历史报告和 Markdown/HTML 导出第一版，资料页已补齐预览/下载的短签名、水印 traceId 和强制水印容器第一版，个人中心已接学习报告、14 天趋势、题型表现、最近练习、男女预设头像选择、积分任务/兑换/积分明细和消息中心摘要第一版，独立消息中心已接状态/类型筛选、批量已读、归档/忽略和站内安全跳转第一版；学生端不默认请求排行榜，仅在租户显式开启 `enableLeaderboard` 并完成压测后进入独立排行榜页或活动页；租户后台第一批页面已接入工作台、数据看板、学生/班级、题库内容、营销中心、财务运营和租户设置，学生运营页已接跟进看板、学习督导自动化、督导规则保存和批量 CRM 推送第一版，营销中心已接 CRM 配置、队列筛选、死信失败池、日志查看、重试/忽略、分佣结算、优惠券规则/核销报表、积分任务/兑换操作台和用户通知查看第一版，财务运营已接退款状态机、官方账单任务、对账异常、差错工单和调整凭证第一版，设置页已接主题模板、草稿预览/发布、角色模板和成员绑定第一版；平台后台已接入工作台、租户管理、账务中心、公共题库授权、平台员工管理，以及创建租户、租户详情、状态变更、账务资料维护、平台员工创建/编辑/禁用恢复、权限点勾选、平台审计查询/CSV 导出、开放审计告警确认/解决、审计告警外部通知渠道/事件状态摘要、订阅、订阅账单候选/dry-run/批量生成、自动计费 worker 生成结果查看、收款、逾期预览/催缴记录、催缴外部通知渠道/事件摘要、用量和题库授权第一版写操作。
- 根目录已清理为新 Supabase SaaS monorepo 编排层；旧 PocketBase/React 项目和旧构建产物仅保留在 `参考/` 目录作为迁移参考，不进入 Git 提交。

## 商用功能完成度总览

| 模块 | 当前状态 | 说明 |
| --- | --- | --- |
| 多租户 SaaS 底座 | √ 可联调 | PostgreSQL schema、RLS、租户、品牌、域名、主题、成员权限、平台/租户/学生三类身份边界已建立 |
| 学生刷题主链路 | √ 可联调 | 入口、分类、集合、顺序/随机/模考、答题、错题、收藏、报告、视频、资料、个人中心、勋章、站内通知已接 API |
| 背单词/知识手册/分数线 | √ 可联调 | 列表、学习/阅读、动态筛选、JSON/CSV/Excel 导入和 Taro 第一版页面已具备 |
| 会员/订单/优惠券/激活码 | √ 可联调 | 下单、订单详情/状态轮询、优惠券规则/核销、激活码、权益、退款状态机和对账地基已完成 |
| 国内登录/支付 provider | √ 本地可跑，待真实密钥 | 阿里云/腾讯云短信、微信小程序/网页、QQ、微信支付、支付宝均有 adapter/fake 测试；生产账号和回调域名上云后联调 |
| 租户后台运营 | √ 可联调 | 学生/班级、内容导入导出、营销、优惠券、积分、勋章、CRM、分佣、销售/代理转化、财务运营、主题和角色模板已具备第一版 |
| 平台 SaaS 账务 | √ 可联调 | 套餐、订阅、订阅账单、自动计费、用量采集、超额账单 API/worker、收款、逾期催缴、外部通知和审计已具备 |
| 公共题库商业化 | √ 可联调 | 平台题库授权、单地区/全国 SaaS 范围、租户采纳、手动/自动同步、冲突处理和通知已完成基础闭环 |
| 对象存储/资料安全 | √ 可联调，待生产 AV/CDN | OSS/COS/Supabase Storage 签名、上传确认、短签名预览下载、水印 traceId、复检和安全扫描地基已完成 |
| PocketBase 真实数据迁移 | √ 本地跑通，待人工复核 blocker | SQLite 导出、标准化导入、校验和抽样脚本已跑通；正式切换前处理缺用户订单和缺归属手册章节 |
| Taro H5 三端前端 | √ 第一版可构建 | 学生端、租户后台、平台后台均有真实 API 页面；已补 H5 `index.html` 模板和发布产物守卫；后续继续补小程序兼容、视觉精修、状态管理、包体优化和端到端测试 |
| 生产安全/压测交付 | △ 本地真实数据压测已跑，云端待复测 | 本地 Docker/Supabase 已完成真实迁移数据 30/50/100/150 并发混合读写压测；已补 PostgreSQL 调参证据脚本和上线前角色旅程烟测；上云后仍需执行生产 readiness、远程 Auth/RLS、4c16g 压测、PostgreSQL 调优和上线证据门禁。`@codex-security` 只有在 Codex 环境暴露扫描工具时才能标记完成 |

更完整的进度看这些文档：

- `docs/refactor/implementation-status.md`
- `docs/refactor/backend-progress.md`
- `docs/refactor/backend-handoff-roadmap.md`
- `docs/refactor/ai-development-guardrails.md`
- `docs/refactor/content-import-contract.md`
- `docs/refactor/object-storage.md`
- `docs/refactor/object-storage-production-runbook.md`
- `docs/refactor/project-structure.md`
- `docs/refactor/frontend-handoff-index.md`
- `docs/refactor/backend-capability-status.md`
- `docs/refactor/legacy-feature-gap-matrix.md`
- `docs/refactor/supabase-frontend-access-strategy.md`
- `docs/refactor/taro-frontend-integration.md`
- `docs/refactor/multitenant-auth-security-contract.md`
- `docs/refactor/next-development-todo.md`
- `docs/refactor/blueprint-coverage.md`
- `docs/refactor/api-structure.md`
- `docs/refactor/web-launch-acceptance-checklist.md`

## 目录结构

```text
apps/api/                   Node.js 业务 API
apps/taro/                  Taro 4 React 跨端前端，H5 三入口，后续扩展小程序
apps/worker/                后台异步任务：CRM webhook、支付/退款补偿、官方账单下载、平台计费/用量/催缴、平台审计告警/外部通知、资源复检、导入执行、公共题库同步、题库导出渲染等
packages/config/            共享配置
packages/db/                PostgreSQL 连接池和查询封装
packages/domain/            领域常量和共享类型
supabase/migrations/        数据库迁移：schema、RLS、索引、触发器
supabase/seed.sql           最小租户 seed
scripts/import-pocketbase/  PocketBase schema/数据导入器和校验器
scripts/smoke-seed.js       本地集成测试 seed 数据
scripts/api-integration-test.js
docs/refactor/              重构架构、进度、治理文档
docker-compose.api.yml      API 容器化运行配置
```

旧项目参考文件在本机 `F:\project\参考\旧题库项目`，旧前端构建产物在 `F:\project\参考\旧构建产物`。这两个目录都只用于对照和迁移，不作为当前新项目源码。

## 本地开发

前置要求：

- Node.js 20+
- Docker Desktop
- Supabase CLI

启动本地 Supabase 和 API：

```bash
npm install
npm run supabase:start
npm run supabase:reset
npm run db:smoke-seed
npm run dev:api
```

Taro H5 本地开发：

```bash
npm run dev:taro:h5
```

三套 H5 构建：

```bash
npm run build:taro:h5:student
npm run build:taro:h5:tenant
npm run build:taro:h5:platform
```

对应产物：

```text
apps/taro/dist/h5-student
apps/taro/dist/h5-tenant-admin
apps/taro/dist/h5-platform-admin
```

推荐分别部署到学生端域名、租户后台域名、平台后台域名；三者共用 `apps/taro/src/services/api.ts` 请求层，业务数据默认调用 `apps/api`，不要在页面里直写 Supabase 表。

构建后可以先跑静态启动烟测，确认三套 H5 产物能被普通静态服务器托管、`runtime-config.json` 只含公开字段、JS/CSS 资源不 404，并用 mock 后端验证 `/api/tenant/resolve` 契约：

```bash
npm run smoke:taro:h5
```

H5 线上推荐每个静态目录放独立 `runtime-config.json` 覆盖公开配置，避免 API/Auth 域名变化时重打包：

```text
apps/taro/deploy/h5-student.runtime-config.example.json
apps/taro/deploy/h5-tenant-admin.runtime-config.example.json
apps/taro/deploy/h5-platform-admin.runtime-config.example.json
```

部署时把示例复制为对应 Web 根目录的 `runtime-config.json`，只填写 `portal`、`apiBaseUrl`、`supabaseUrl`、`supabasePublishableKey`、`tenantCode` 这类公开字段。完整 Nginx、CSP、缓存、CORS 和三域名部署说明见：

```text
docs/refactor/taro-h5-deployment.md
```

学生端当前页面：

```text
apps/taro/src/pages/student/login
apps/taro/src/pages/student/home
apps/taro/src/pages/student/catalog
apps/taro/src/pages/student/practice
apps/taro/src/pages/student/vocabulary
apps/taro/src/pages/student/handbook
apps/taro/src/pages/student/scoreline
apps/taro/src/pages/student/ai-school
apps/taro/src/pages/student/assets
apps/taro/src/pages/student/profile
```

租户后台当前页面：

```text
apps/taro/src/pages/tenant-admin/workbench
apps/taro/src/pages/tenant-admin/dashboard
apps/taro/src/pages/tenant-admin/students
apps/taro/src/pages/tenant-admin/content
apps/taro/src/pages/tenant-admin/marketing
apps/taro/src/pages/tenant-admin/finance
apps/taro/src/pages/tenant-admin/settings
```

平台后台当前页面：

```text
apps/taro/src/pages/platform-admin/workbench
apps/taro/src/pages/platform-admin/tenants
apps/taro/src/pages/platform-admin/billing
apps/taro/src/pages/platform-admin/question-banks
apps/taro/src/pages/platform-admin/staff
```

单次运行 CRM worker：

```bash
npm --workspace @tiku-saas/worker run crm:once
```

单次运行支付/退款补偿 worker：

```bash
npm --workspace @tiku-saas/worker run commerce:once
```

单次运行微信/支付宝官方账单下载 worker：

```bash
npm --workspace @tiku-saas/worker run provider-bills:once
```

单次运行平台 SaaS 订阅自动计费 worker：

```bash
npm --workspace @tiku-saas/worker run platform-billing:once
```

生产定时任务建议每天低峰运行一次 `node dist/apps/worker/src/index.js --once --job platform-billing`。可用环境变量控制批量大小和提前开票窗口：

```text
WORKER_PLATFORM_BILLING_BATCH_SIZE=50
WORKER_PLATFORM_BILLING_DAYS_AHEAD=45
WORKER_PLATFORM_BILLING_DUE_DAYS=15
WORKER_PLATFORM_BILLING_ID=platform-billing-prod-1
```

单次运行平台 SaaS 用量超额自动开票 worker：

```bash
npm --workspace @tiku-saas/worker run platform-usage-overage:once
```

生产定时任务建议每月 1 日低峰先用 `WORKER_PLATFORM_USAGE_MONTH=上月 YYYY-MM` 运行 `platform-usage` 采集完整用量快照，再运行 `node dist/apps/worker/src/index.js --once --job platform-usage-overage` 自动生成上一个自然月的 `usage_overage` 账单。该 worker 复用后端统一超额计算服务，只读取 `tenant_usage_records`、SaaS 套餐 `included_quotas/overage_prices` 和订阅 metadata 覆盖，使用唯一索引和账单查重防重复开票；账期月份必须是 `YYYY-MM` 且月份在 `01..12`。worker 执行失败会写入 `platform.invoice.usage_overage_worker_failed` 脱敏平台审计日志，后续由 `platform-audit-alerts` 转为高优先级开放告警，并可继续通过 `platform-audit-notifications` 推送到钉钉/飞书/企微。

```text
WORKER_PLATFORM_USAGE_OVERAGE_BATCH_SIZE=100
WORKER_PLATFORM_USAGE_OVERAGE_MONTH=
WORKER_PLATFORM_USAGE_OVERAGE_DUE_DAYS=15
WORKER_PLATFORM_USAGE_OVERAGE_ID=platform-usage-overage-prod-1
```

单次运行平台 SaaS 逾期催缴 worker：

```bash
npm --workspace @tiku-saas/worker run platform-dunning:once
```

生产定时任务建议每天在自动计费之后运行一次 `node dist/apps/worker/src/index.js --once --job platform-dunning`。它只处理已过 `due_date` 且未结清的服务费账单：把账单标记为 `overdue`、将租户 `billing_status` 推为 `past_due`、写入 `tenant_invoice_reminders` 内部催缴台账和审计，不会自动停用租户。

```text
WORKER_PLATFORM_DUNNING_BATCH_SIZE=100
WORKER_PLATFORM_DUNNING_ID=platform-dunning-prod-1
```

单次运行平台 SaaS 逾期催缴外部通知 worker：

```bash
npm --workspace @tiku-saas/worker run platform-dunning-notifications:once
```

生产定时任务建议在 `platform-dunning` 之后每 5 到 15 分钟运行一次 `node dist/apps/worker/src/index.js --once --job platform-dunning-notifications`。它会把 `tenant_invoice_reminders` 中待发送或失败的内部催缴记录按 `platform_dunning_notification_channels` 配置入队到 `platform_dunning_notification_events`，支持 generic、钉钉、飞书和企业微信 webhook；发送成功后会把对应催缴记录标记为 `sent`，发送失败会按退避策略重试并在终止失败时标记 `failed`。渠道密钥必须写入 `app_private.platform_secrets`，API 只返回 `secretRef` 和 webhook host/path，事件查询会递归脱敏 request payload。

```text
WORKER_PLATFORM_DUNNING_NOTIFICATION_BATCH_SIZE=50
WORKER_PLATFORM_DUNNING_NOTIFICATION_MAX_ATTEMPTS=5
WORKER_PLATFORM_DUNNING_NOTIFICATION_BACKOFF_SECONDS=10,60,300,900,1800
WORKER_PLATFORM_DUNNING_NOTIFICATION_REQUEST_TIMEOUT_MS=10000
WORKER_PLATFORM_DUNNING_NOTIFICATION_ALLOW_INSECURE_LOCALHOST=false
```

单次运行平台审计告警 worker：

```bash
npm --workspace @tiku-saas/worker run platform-audit-alerts:once
```

生产定时任务建议每 5 到 15 分钟运行一次 `node dist/apps/worker/src/index.js --once --job platform-audit-alerts`。它只扫描 `platform.%` 审计日志，把命中启用规则的高风险动作写入 `platform_audit_alerts`；告警 details 会递归脱敏 token、secret、password、key、authorization、cookie、session、cert、signature 等敏感字段。

```text
WORKER_PLATFORM_AUDIT_ALERT_BATCH_SIZE=200
WORKER_PLATFORM_AUDIT_ALERT_LOOKBACK_DAYS=14
WORKER_PLATFORM_AUDIT_ALERT_ID=platform-audit-alerts-prod-1
```

单次运行平台审计告警外部通知 worker：

```bash
npm --workspace @tiku-saas/worker run platform-audit-notifications:once
```

生产定时任务建议在 `platform-audit-alerts` 后每 5 到 15 分钟运行一次 `node dist/apps/worker/src/index.js --once --job platform-audit-notifications`。它会把开放告警按 `platform_audit_notification_channels` 配置入队到 `platform_audit_notification_events`，支持 generic、钉钉、飞书和企业微信 webhook，发送请求和事件台账都会递归脱敏敏感字段。钉钉/飞书签名密钥必须存入 `app_private.platform_secrets`，API 只返回 `secretRef` 和 webhook host/path。

```text
WORKER_PLATFORM_AUDIT_NOTIFICATION_BATCH_SIZE=50
WORKER_PLATFORM_AUDIT_NOTIFICATION_MAX_ATTEMPTS=5
WORKER_PLATFORM_AUDIT_NOTIFICATION_BACKOFF_SECONDS=10,60,300,900,1800
WORKER_PLATFORM_AUDIT_NOTIFICATION_REQUEST_TIMEOUT_MS=10000
WORKER_PLATFORM_AUDIT_NOTIFICATION_ALLOW_INSECURE_LOCALHOST=false
```

单次运行内容资源复检 worker：

```bash
npm --workspace @tiku-saas/worker run assets:once
```

单次运行内容导入 worker：

```bash
npm --workspace @tiku-saas/worker run imports:once
```

单次运行公共题库自动同步 worker：

```bash
npm --workspace @tiku-saas/worker run public-banks:once
```

单次运行学习督导规则 worker：

```bash
npm run build:worker
node apps/worker/dist/apps/worker/src/index.js --once --job student-supervision
```

生产定时任务建议每 15 到 60 分钟运行一次 `node dist/apps/worker/src/index.js --once --job student-supervision`。它只处理启用状态的租户学习督导规则，按规则阈值扫描未学习、错题积压、低正确率、单词待复习和超期未完成练习的学生，并幂等生成 `learning` 跟进任务；教师/班主任范围规则必须绑定班级，生成结果、失败原因和 worker 信息会写入规则 `last_result/metadata` 与审计。

```text
WORKER_STUDENT_SUPERVISION_BATCH_SIZE=20
WORKER_STUDENT_SUPERVISION_ID=student-supervision-prod-1
WORKER_STUDENT_SUPERVISION_CLAIM_STALE_SECONDS=900
```

单次运行题库 PDF/Word/每日一练 ZIP 导出 worker：

```bash
npm --workspace @tiku-saas/worker run exports:once
```

默认本地数据库：

```text
postgresql://postgres:postgres@127.0.0.1:54322/postgres
```

默认 API 地址：

```text
http://127.0.0.1:8787
```

## 验证命令

完整后端重构检查：

```bash
npm run check:refactor
```

这个命令会依次执行：

- API TypeScript 检查
- PocketBase importer TypeScript 检查
- PocketBase 导入后校验
- 本地 smoke seed
- API 构建
- 本地 API 集成测试

常用单项命令：

```bash
npm run check:api
npm run check:worker
npm run check:importer
npm run check:taro
npm run test:readiness
npm run test:auth:remote-smoke
npm run test:launch-gate
npm run smoke:auth:remote
npm run audit:runtime
npm run pb:import:dry-run
npm run pb:import:validate
npm run test:pb:dry-run
npm run test:api
npm run test:worker:crm
npm run test:worker:commerce
npm run test:worker:platform-billing
npm run test:worker:platform-usage
npm run test:worker:platform-dunning
npm run test:worker:platform-dunning-notifications
npm run test:worker:platform-audit-alerts
npm run test:worker:platform-audit-notifications
npm run test:worker:assets
npm run test:worker:exports
npm run test:worker:imports
npm run test:worker:public-banks
npm run test:worker:student-supervision
npm run test:rls
```

## 生产就绪检查

填好生产 `.env` 后，先跑环境变量级检查：

```bash
npm run readiness:production
```

确认 `DATABASE_URL` 指向生产 Supabase/PostgreSQL 后，再跑数据库配置检查：

```bash
npm run readiness:production:db
```

这个检查会阻断默认弱密钥、`CORS=*`、mock 短信、legacy 身份头、local_dev 存储、对象存储未配置、CRM insecure localhost、平台审计/催缴通知 localhost 等生产风险；带 `:db` 的版本还会检查租户 provider 公开配置是否混入密钥、活跃短信/OAuth/支付 provider 是否缺少 `app_private.tenant_secrets`、平台通知 webhook 是否为生产 HTTPS、钉钉/飞书平台通知是否缺少 `app_private.platform_secrets`、域名是否未验证。

Supabase Auth/JWKS 上云后需要用真实 access token 跑远程验收：

```bash
AUTH_SMOKE_API_BASE_URL=https://api.example.com \
AUTH_SMOKE_TENANT_ID=<tenant-uuid> \
AUTH_SMOKE_STUDENT_ACCESS_TOKEN=<student-supabase-access-token> \
AUTH_SMOKE_TENANT_ADMIN_ACCESS_TOKEN=<tenant-admin-supabase-access-token> \
AUTH_SMOKE_PLATFORM_ADMIN_ACCESS_TOKEN=<platform-admin-supabase-access-token> \
AUTH_SMOKE_WRONG_TENANT_ID=<another-tenant-uuid> \
AUTH_SMOKE_REQUIRE_ADMIN_TOKENS=true \
npm run smoke:auth:remote
```

这个命令会验证真实 Supabase JWT 能访问 `/api/auth/me`、`/api/profile/me`，学生不能访问租户后台/平台后台，租户管理员不能访问平台后台，平台管理员能访问平台后台，坏 token 和错租户上下文会被拒绝。真实 access token 只允许在验收命令行临时提供，不要写入仓库、前端 `runtime-config.json` 或长期 `.env`。

RLS 需要同时跑动态隔离验收：

```bash
npm run test:rls
```

这个命令会先执行本地 smoke seed，再在事务内模拟 Supabase `authenticated/anon/platform_admin` JWT claims，验证主租户和合作商租户的品牌、设置、域名、成员、题库、订单、资源、SaaS 账单等代表性表不会跨租户泄露；同时验证无 `tenant_id` claim 不能读取租户数据，普通租户上下文不能跨租户写入。脚本里的临时 grant 会随事务回滚，不会改变实际 schema 权限。

## 生产上线证据门禁

正式切换前不要只看“口头跑过测试”。把真实生产/预生产验收结果整理成证据文件，再运行上线门禁：

```bash
cp docs/refactor/production-launch-evidence.template.json docs/refactor/production-launch-evidence.json
npm run launch:gate -- --evidence docs/refactor/production-launch-evidence.json
```

`production-launch-evidence.json` 不入 Git，里面只记录验收摘要、artifact 路径、审批人和时间，不保存真实 access token、支付密钥、对象存储密钥或用户隐私明细。门禁会要求以下证据全部齐备并通过：`readiness:production`、`readiness:production:db`、真实 `smoke:auth:remote`、`test:rls`、PocketBase production dry-run、`pb:import:validate`、`pb:import:sample`、真实数据 API 读路径压测、API/worker/Taro 构建、`audit:runtime`、`@codex-security` 扫描，以及备份、回滚、真实数据抽样、生产 provider、对象存储控制、支付对账和三套 H5 `runtime-config.json` 人工确认。

补充说明：当前 Codex 环境如果没有暴露 `@codex-security` 可调用工具，不能把插件扫描写成已完成；只能先用 `npm run audit:runtime`、`npm run test:readiness`、`npm run test:rls` 和代码审查作为临时安全证据，并在上线证据里保留插件扫描待补项。

模板文件：

```text
docs/refactor/production-launch-evidence.template.json
```

## PocketBase 迁移 Dry-Run

如果旧数据源是 SQLite，先从旧 PocketBase 数据目录只读导出业务 collection。默认读取 `F:\project\参考\旧题库数据库文件`，输出到已被 `.gitignore` 覆盖的 `pb_export/`：

```powershell
$env:PB_SQLITE_DIR="F:\project\参考\旧题库数据库文件"
$env:PB_EXPORT_DIR="F:\project\pb_export"
npm run pb:export:sqlite
```

导出会生成：

```text
pb_export/*.json                 各 PocketBase collection 的普通业务 JSON
pb_export/sqlite-export-manifest.json
pb_export/storage-manifest.json
pb_export/pb_schema.sqlite.json
```

默认导出会移除 `password/token/secret/sessionKey/openid/unionid` 等敏感身份或密钥字段，只在 manifest 中记录脱敏字段数量；手机号等迁移必需字段会保留。不要把 `pb_export/`、manifest 或真实迁移报告提交到 Git。

把旧 PocketBase 导出的集合 JSON 放到仓库根目录 `pb_export/` 后，执行不写数据库的静态 dry-run：

```bash
npm run pb:import:dry-run
```

需要给 CI 或脚本读取时：

```bash
npm run pb:import:dry-run -- --json
```

dry-run 会检查导出目录、JSON 形态、核心集合缺失、重复/缺失旧 ID、敏感字段、旧 schema 关系断裂和未映射集合。存在 blocker 时命令返回非 0；所有 blocker 处理完后，再执行 `npm run pb:import:json` 和 `npm run pb:import:validate`。

默认 dry-run 使用 `development` profile；正式迁移、预生产验收和 CI 应使用 `production` profile。生产 profile 会额外输出 `migrationReadiness`，检查用户、题目、科目、分类、订单、套餐、激活码、单词和知识手册等必需集合，以及用户手机号、题目归属、订单套餐、激活码、单词和手册归属等关键字段覆盖率。`--profile` 只接受 `development` 或 `production`，拼写错误会按 blocker 失败。

当前真实 SQLite 基线已经跑通只读导出和干净库全量导入：58 个业务 collection、248555 条记录、9 个 storage 原始资源文件；本地 `npx supabase db reset` 后执行 `npm run pb:import:json` 最近用时约 10 分 11 秒，`npm run pb:import:validate` 结果为 0 failures、3 warnings。`user_answer_records`、`mock_exam_configs`、`referral_qrcodes`、`commission_settings` 已进入标准化导入，导入后核心计数包括 3670 用户、74102 题目、85199 条旧答题记录、38205 条错题、636 订单、447 权益、79 个推广码和 1 条租户分佣设置。导入器现在还会从旧 `region_modules/module_nodes/subjects/categories/questions.nodeId` 生成新架构 `content_entries/content_nodes/question_collections/practice_blueprints`，最新导入计数为 11 个题库入口、2830 个内容节点、1597 个题目合集、82106 条合集题目关系、3102 个顺序/随机练习蓝图；所有已发布旧题都会写入 `entry_id/content_node_id/primary_collection_id`，供 Taro 前端按新模型直接消费。

导入结构校验后，还要运行真实业务抽样：

```powershell
$env:DATABASE_URL="postgresql://postgres:postgres@127.0.0.1:54322/postgres"
npm run pb:import:sample
```

`pb:import:sample` 是只读脚本，用来证明迁移数据能被新 SaaS 业务模型消费：题库入口、分类节点、题目合集、顺序/随机/全真模拟蓝图、题目当前版本、答题记录、错题、收藏、单词、知识手册、分数线、订单、支付、权益、激活码、资源台账和敏感字段泄露都会被抽样检查。当前真实迁移库最近结果为 `0 failures, 6 warnings, 1 skipped, 39 passed`；warning 均为旧数据人工复核项或上线前留档项，详见 runbook。

如需生成本地报告：

```powershell
$env:PB_SAMPLE_WRITE_REPORT="true"
npm run pb:import:sample
Remove-Item Env:\PB_SAMPLE_WRITE_REPORT
```

报告输出到已忽略的 `docs/refactor/migration-reports/`，不要提交真实用户、订单或学习数据样本。

production dry-run 目前仍有 2 个真实数据 blocker：30 个订单缺用户、22 个知识手册章节缺所属手册；导入器会把它们隔离到财务复核/迁移待复核手册并写入 `pb_import_issues`，其中最新导入 run 的 critical issue 剩余 29 个：7 个已支付订单缺用户、22 个手册章节缺所属手册。正式切换前仍必须人工确认，详见：

```text
docs/refactor/pocketbase-real-data-migration-runbook.md
docs/refactor/next-development-todo.md
```

真实生产数据迁移不要只看命令是否能跑完，需要按迁移验收 runbook 执行 dry-run、正式导入演练、导入后校验、业务抽样、Taro 联调、冻结切换和回滚准备：

```text
docs/refactor/pocketbase-real-data-migration-runbook.md
```

### API 压测和 4 核 16G 评估

默认本地烟测会构建 API 并自动启动临时端口，使用当前 `DATABASE_URL` 的真实数据做只读混合请求：

```powershell
$env:DATABASE_URL="postgresql://postgres:postgres@127.0.0.1:54322/postgres"
npm run perf:api:local
```

报告输出到已忽略的 `docs/refactor/performance-reports/`。可以用 `npm run perf:summary -- --input <api-benchmark.json> --json` 自动提取 `launch:gate` 需要的错误率、P95/P99、并发和时长摘要。4 核 16G 云服务器应按压测 runbook 跑 6/30/50/100 阶梯并发，并结合 PostgreSQL 调参文档观察慢 SQL、连接数、锁等待和 P95/P99：

```text
docs/refactor/postgresql-4c16g-tuning.md
docs/refactor/performance-benchmark-runbook.md
```

PostgreSQL 调参与运行证据采集：

```powershell
$env:DATABASE_URL="postgresql://postgres:postgres@127.0.0.1:54322/postgres"
npm run perf:postgres:evidence
```

上线前角色旅程烟测：

```powershell
$env:DATABASE_URL="postgresql://postgres:postgres@127.0.0.1:54322/postgres"
npm run smoke:launch-persona
```

`smoke:launch-persona` 会从普通学生、租户管理员、平台管理员三个视角调用真实 API，覆盖 SVIP 后刷题、收藏、错题复习入口、租户数据看板/主题/学生/销售转化、平台租户/套餐/审计入口和越权拒绝。它会写入少量 `launch_persona_smoke` 测试记录，生产只建议在灰度或演练租户运行。

Taro H5 发布产物守卫：

```powershell
npm run build:taro:h5:student
npm run build:taro:h5:tenant
npm run build:taro:h5:platform
npm run smoke:taro:h5
node scripts\taro-h5-release-guardrails-test.js --require-dist
```

`smoke:taro:h5` 会启动临时静态服务器和 mock API，验证三套 H5 的 `index.html`、静态资源、history fallback、公开 runtime config 和租户解析契约。`taro-h5-release-guardrails-test` 会确认三套 H5 目录存在 `index.html`，并扫描源码/产物是否混入旧 PocketBase、`x-user-id`、平台本地 key、数据库连接串或服务端密钥形态。正式部署时还必须在每个 H5 目录根部放置对应的 `runtime-config.json`。

最近一次本地真实迁移库数据规模约为 74,117 题、1,601 个题目合集、3,106 个练习蓝图、3,505 个单词、2,678 条知识手册、3,690 个用户、113,810 条答题记录、38,207 条错题和 458 条权益。压测 worker 是无停顿请求流，不能直接等同于真实在线学生数；前端完成后需要用真实页面埋点估算单个学生平均 RPS，再折算在线容量。

| 并发 worker | 时长 | 刷题写入比例 | 请求数 | 错误率 | 吞吐 | P95 | P99 |
| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| 30 | 120s | 0% | 108,336 | 0.00% | 897.04 req/s | 68.32 ms | 84.50 ms |
| 50 | 60s | 10% | 52,979 | 0.00% | 870.42 req/s | 104.92 ms | 136.22 ms |
| 50 | 60s | 10% | 51,425 | 0.00% | 845.06 req/s | 110.39 ms | 138.79 ms |
| 100 | 60s | 8% | 51,103 | 0.00% | 838.42 req/s | 210.07 ms | 272.15 ms |
| 150 | 60s | 6% | 44,889 | 0.00% | 735.22 req/s | 357.74 ms | 469.28 ms |
| 30 | 120s | 10% | 112,896 | 0.00% | 934.74 req/s | 64.26 ms | 81.14 ms |
| 50 | 120s | 10% | 92,737 | 0.00% | 767.24 req/s | 121.51 ms | 159.12 ms |
| 100 | 120s | 8% | 84,608 | 0.00% | 699.27 req/s | 254.69 ms | 331.84 ms |
| 150 | 120s | 6% | 82,693 | 0.00% | 682.40 req/s | 369.90 ms | 493.69 ms |

只读上线门禁继续要求 `includeWrites=false`；混合读写报告需要显式使用 `--allow-writes` 做人工容量观察，例如：

```powershell
npm run perf:summary -- --input docs/refactor/performance-reports/api-benchmark-20260701-002524.json --json --allow-writes --min-duration-seconds=60 --min-concurrency=50 --max-p95-ms=500 --max-p99-ms=1200
```

使用 `--allow-writes` 时会输出 `capacityObservation`，不输出 `launchGateCheck`，不能把写入场景误填成生产上线门禁的只读证据。

本地结论：100 个无停顿 worker 内 P95 仍低于 300ms；150 worker 零错误但 P95 已明显上升，可作为本机 Docker 环境的压力拐点参考。最新只读 30 worker/120s 为 897.04 req/s，最新混合读写 100 worker/60s/8% 写入为 838.42 req/s、P95 210.07ms、0 错误。粗略按单学生 0.05 到 0.2 req/s 可换算为数千到一万多名活跃在线学生请求吞吐，但这不是生产 SLA。当前 PostgreSQL evidence 提醒本地默认库仍是 `jit=on` 且 `statement_timeout`、`idle_in_transaction_session_timeout`、`lock_timeout` 未设置，上云必须按调参文档复核。正式对外容量承诺必须在目标 4 核 16G 云服务器、生产 PostgreSQL 参数、生产对象存储/CDN 和真实前端请求节奏下复跑。脱敏摘要见：

```text
docs/refactor/performance-benchmark-summary-20260630.md
```

正式切换前建议使用 production 严格模式：

```bash
npm run pb:import:dry-run -- --profile=production --json --fail-on-warnings
```

导入后校验建议在预生产/生产切换前把 warning 也作为阻断：

```powershell
$env:FAIL_ON_WARNINGS="true"
npm run pb:import:validate
Remove-Item Env:\FAIL_ON_WARNINGS
```

## API 模块

当前 API 目录：

```text
apps/api/src/features/
  auth/              短信登录、迁移期 session、微信小程序登录、微信网页登录、QQ 登录
  catalog/           学生端目录、内容入口、分类树、题目集合、资料、商城只读接口
  commerce/          订单、支付确认、退款、激活码、优惠券规则/核销、权益、资金对账和差错工单
  health/            健康检查
  learning/          练习 session 组卷、答题、错题、收藏、学习进度、排行榜
  platform-admin/    平台方权限、租户、SaaS 套餐、订阅、订阅账单候选/批量生成、账单、用量
  profile/           学生个人中心、勋章
  referral/          销售/代理客资追踪、CRM 队列
  referral/commission.ts
                     分佣设置、汇总、来源明细、结算单、审核/打款、导出和凭证复核
  scoreline/         分数线
  tenant/            租户解析
  tenant-admin/      租户后台配置、主题、成员权限、班级学生、活动、勋章和审计
  tenant-content/    租户内容导航、题库维护、资源管理、批量导入和题库导出
  video/             题目视频讲解
```

API 身份上下文：

- 推荐：`Authorization: Bearer <supabase_access_token>`，可配合 `x-tenant-id` 提供当前租户上下文。
- 本地/迁移期：`Authorization: Bearer <tk_session>`。
- 兼容旧测试：`x-user-id`、`x-platform-admin-key` 仅允许在 `ALLOW_LEGACY_AUTH_HEADERS=true`、`ALLOW_PLATFORM_ADMIN_KEY=true` 的非生产环境使用。

生产环境必须设置 `ALLOW_LEGACY_AUTH_HEADERS=false` 和 `ALLOW_PLATFORM_ADMIN_KEY=false`，前端不能再传 `x-user-id` 代表当前用户。

平台后台权限：

- 平台账号以后端 `platform_users.primary_role='platform_admin'` 为准，不只信 JWT claim。
- 平台账号通过 `platform_users.platform_permissions` 控制细粒度能力，`{"*":true}` 表示超级管理员。
- 平台员工通过 `GET/PUT/PATCH /api/platform-admin/staff` 管理，必须绑定 Supabase Auth 用户 ID；禁用员工会使 `status='disabled'`，后续 Supabase JWT 映射和迁移期 session 都会被拒绝。
- 平台后台启动后可调用 `GET /api/platform-admin/permissions` 获取 `catalog/effective`，用于隐藏不可见菜单和按钮。
- 后端接口继续按 `platform:staff:read/write/status`、`platform:tenant:read/write/status/billing_profile`、`platform:billing:read/write/payment/dunning/notification`、`platform:audit:read/export/alert/notification`、`platform:question_bank:read/grant/ops` 等权限点强制校验。
- `x-platform-admin-key` 只允许本地兼容，生产必须关闭。

## 重要安全约定

- 租户公开配置和主题配置不能存放密钥；主题 token 只能是后端允许的颜色、半径、安全 CSS 变量、图标 token 和公开素材引用。
- 商户密钥、短信密钥、OAuth app secret 等必须进入 `app_private.tenant_secrets`，或后续生产 KMS/Vault。
- 资料、PDF、视频等资源必须先进入 `content_assets` 台账，再由 API 校验权限并下发签名 URL；学生端预览、锁定资料和视频会使用短 TTL，并返回带 `traceId` 的 `watermark` 上下文供前端渲染可见水印。`members/svip/private` 外部 CDN URL 默认拒绝，除非显式登记 provider-managed 访问；所有上传签名、上传确认、下载/预览 granted/denied 都写入 `content_asset_access_events`。托管对象必须 `uploadStatus=verified` 且 `securityScanStatus=passed` 后才能发布、下载、预览或播放；生产环境应定时运行 assets worker 复检对象元数据，执行 `metadata_rules` 和外部 HTTP scanner，异常资源会被标记 failed/skipped 并退回 draft。
- `NODE_ENV=production` 下 API 和 worker 都会拒绝 `STORAGE_DEFAULT_PROVIDER=local_dev`、空 bucket 或关闭 `STORAGE_REQUIRE_TENANT_PREFIX`；worker 还会拒绝未接入外部 HTTP 安全扫描或开启 fail-open 的生产配置。
- 题库入口和分类使用 `content_entries/content_nodes`；题目列表和练习规则使用 `question_collections/practice_blueprints`，前端不要再把旧树字段当成唯一业务结构。
- 批量导入必须先写 `content_import_jobs/items/issues`，保留原始 payload、规范化 payload、逐行问题和审计记录。题目、单词、知识手册、分数线和视频 JSON/CSV/Excel 导入已走这套后台校验管线；大批量任务可提交 `executionMode=async`，由 imports worker 消费，前端只轮询 job 状态和展示 issues。学生端题干/解析/手册内容统一走 `apps/taro/src/components/RichContent.tsx` 做受控渲染，不执行导入内容中的任意 HTML/JS；公式只渲染解析出的 LaTeX token，私有题图只接受资源 ID 引用并走后端短签名。
- 题库导出必须由后端按权限生成，不允许前端直接读取数据库拼导出文件；不开启答案/解析时，顶层题目和复合题子题都必须脱敏；PDF/Word/每日一练 ZIP 只通过 exports worker 写入 `content_assets` 后再签名下载/预览。
- 支付 webhook 必须先设计幂等键和验签流程，再进入生产使用；生产环境还应定时运行 commerce worker 兜底供应商漏通知和处理中退款，并定时运行 provider-bills worker 下载官方账单核对本地订单。官方账单下载任务只保存下载域名、hash 和对账批次 ID，不向前端暴露下载 URL 或商户密钥。优惠券状态、最低金额、封顶、单用户限次、首单、适用套餐/地区和订单抵扣都由后端重新校验，前端只能展示后端返回金额。对账差错工单和人工调整凭证只允许记录财务处理结论、附件引用和审计事件，不允许前端、工单接口或凭证审批接口直接篡改订单、支付、退款或权益状态。

## 最近一次验证

最近本地验证命令：

```text
npx supabase db reset
npm run check:api
npm run check:worker
npm run test:worker:commerce
npm run test:worker:platform-billing
npm run test:worker:platform-usage
npm run test:worker:platform-usage-overage
npm run test:worker:platform-dunning
npm run test:worker:platform-dunning-notifications
npm run test:worker:platform-audit-alerts
npm run test:worker:platform-audit-notifications
npm run test:worker:assets
npm run test:worker:exports
npm run test:auth:remote-smoke
npm run test:rls
npm run test:api
npm run check:refactor
npm run audit:runtime
git diff --check
```

结果：通过。最近一轮真实迁移专项验证已通过 `npx supabase db reset`、`npm run pb:import:json`、`npm run pb:import:validate`、`npm run pb:import:sample` 和 `npm run check:importer`；`pb:import:sample` 当前为 0 failures、6 warnings、1 skipped、39 passed；production dry-run 仍按预期返回非 0，因为旧数据本身还剩订单缺用户和手册章节缺归属两个 blocker。`npm run test:auth:remote-smoke` 覆盖远程 Auth/JWKS 验收脚本自身。`npm run test:rls` 覆盖 75 条运行时 RLS 断言，包含主租户、合作商租户、无租户 claim、平台管理员旁路和跨租户写入拒绝。`npm run test:api` 覆盖平台细粒度权限、公共题库跨租户同步运营状态、资源访问事件、锁定 CDN 资源拒绝、provider-managed CDN 显式放行、学生短 TTL 下载/预览、访问记录查询、安全扫描门禁、官方账单下载任务权限和脱敏响应、异常订单运营台、人工调整凭证提交/复核/事件/报表、销售/代理转化报表本人/全局权限、平台账单逾期 dry-run/催缴记录、平台审计告警查询/状态更新/越权拒绝/敏感 details 脱敏、平台审计告警通知渠道/事件查询和密钥不回显、平台催缴通知渠道/事件查询和密钥不回显、租户隔离，以及凭证审批不修改订单/支付/权益。`npm run test:worker:commerce` 覆盖支付/退款补偿、微信/支付宝官方账单下载、账单 hash 校验、导入 `provider_download` 对账批次和密钥不泄露。`npm run test:worker:platform-billing` 覆盖平台 SaaS 订阅自动计费、重复开票保护、账单明细和审计。`npm run test:worker:platform-usage` 覆盖平台 SaaS 月度用量自动采集、11 类指标、手工调整记录不覆盖和重复运行幂等。`npm run test:worker:platform-usage-overage` 覆盖平台 SaaS 超额账单明细、重复开票保护、非法账期失败审计、审计告警生成和敏感错误信息脱敏。`npm run test:worker:platform-dunning` 覆盖平台 SaaS 逾期账单标记、内部催缴记录、租户 `past_due` 状态和每日催缴幂等。`npm run test:worker:platform-dunning-notifications` 覆盖平台 SaaS 催缴外部通知入队、generic webhook 发送、幂等、防重复、联系方式掩码、签名密钥不泄露和请求 payload 脱敏。`npm run test:worker:platform-audit-alerts` 覆盖平台审计告警生成、规则匹配、幂等、防重复和告警 details 脱敏。`npm run test:worker:platform-audit-notifications` 覆盖平台审计告警外部通知入队、generic webhook 发送、幂等、防重复、签名密钥不泄露和请求 payload 脱敏。`npm run test:worker:assets` 覆盖托管资源复检、内置安全扫描、外部 HTTP scanner 通过/失败/不可用 fail-closed、扫描失败/跳过事件和异常资源自动下架。`npm run test:worker:public-banks` 覆盖公共题库自动同步、失败通知和恢复自动关闭。`npm run test:worker:exports` 覆盖导出 worker 生成可信资源并标记 `securityScanStatus=passed`。`npm run audit:runtime` 当前为 0 vulnerabilities；Excel 解析已从 `exceljs` 切换为 `read-excel-file`，避免生产运行时携带 `exceljs -> uuid` 的已知中危依赖。

注意：`apps/taro` 是静态构建工程，线上发布 `apps/taro/dist/**`，不发布 `node_modules`。Taro 4.2.0 当前构建工具链仍会触发 `npm run audit:taro:toolchain` 的上游 high/critical 提示，不能用 `npm audit fix --force` 降级到 Taro 3 破坏构建；上线验收时以 `audit:runtime`、构建产物、前端密钥检查和静态服务器配置为准，并持续跟进 Taro 官方修复。

## 下一步建议

优先继续补：

1. 真实云端 Auth/JWKS 回归、RLS 深测和生产环境配置验收。
2. 继续补 Taro 前端：学生端小程序公式真机验收、题图资源后台字段化、小程序支付与分享，租户后台更细导入体验/数据范围 UI/主题素材库/财务复核细节，平台后台在线收款、审计报表增强、审计告警通知升级策略、催缴通知操作台细节和小程序兼容验证。
3. 对象存储真实 AV/内容安全扫描服务联调、CDN 防盗链、转码/CDN 级水印和生命周期策略。
4. 题库导出模板精排、导出操作台、导入字段映射 UI 和复检结果操作台；继续对真实迁移数据做题目、订单、权益、错题、资料和视频抽样验收。
5. 上云后接真实 OAuth/短信/支付生产账号、回调域名和真实生产账单抽样验收；本地阶段继续用 mock/fake provider 验证回调后业务链路、幂等、审计、密钥不泄露和权益开通/撤销。后续还要补真实打款 provider、发票、公共题库版本通知/冲突处理操作台、积分活动风控、连续签到奖励深化、销售/代理转化预聚合和更细团队数据范围。排行榜不是默认主线功能，仅在租户显式购买/开启活动并完成压测后，才进入防刷、日/周榜预聚合和运营看板开发。

旧原生小程序前端位于 `F:\project\参考\旧题库小程序前端文件`，后续 Taro H5/小程序补体验时只作为页面状态、微信平台能力和交互参考，不继承旧 PocketBase 直连和旧鉴权逻辑。
