Files
gongxue-base/docs/refactor/taro-h5-deployment.md
2026-07-12 19:26:57 +08:00

257 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Taro H5 三入口与学生微信小程序部署说明
更新时间2026-07-11
当前 `apps/taro` 采用一个 Taro 4 React 工程、三套 H5 产物的方式交付:
- 学生学习端:刷题、背单词、知识手册、分数线、资料、会员、个人中心。
- 租户后台品牌、主题、域名、题库、导入、学生、订单、营销、销售、CRM、财务和数据看板。
- 平台后台租户、SaaS 套餐、订阅账单、公共题库授权和平台审计。
学生微信小程序复用同一套业务 services、权限、主题和页面逻辑复杂租户后台与平台后台仍优先发布桌面 H5。
## 构建命令
```bash
npm run build:taro:h5:student
npm run build:taro:h5:tenant
npm run build:taro:h5:platform
npm run build:taro:weapp:student
```
输出目录:
```text
apps/taro/dist/h5-student
apps/taro/dist/h5-tenant-admin
apps/taro/dist/h5-platform-admin
apps/taro/dist/weapp-student
```
三套 H5 会按 portal 裁剪实际注册页面;微信小程序主包只保留 `pages/bootstrap/index`,学生页面放入 `pages/student` 分包并启用组件按需注入。每种 target/portal 都有独立输出目录,连续构建不会互相覆盖。
注意Taro H5 入口依赖 `apps/taro/src/index.html` 模板生成 `index.html`。如果构建产物目录里只有 `js/css/assets` 而没有 `index.html`,不要发布;重新构建并运行发布守卫脚本。
推荐部署:
| 域名 | 静态目录 | 说明 |
| --- | --- | --- |
| `www.example.com` 或租户自有学生端域名 | `h5-student` | 面向学生和 C 端用户 |
| `admin.example.com` | `h5-tenant-admin` | 面向租户公司运营、教师、销售、代理、管理员 |
| `console.example.com` | `h5-platform-admin` | 面向平台超级管理员 |
三个入口可以放在同一台服务器的三个静态目录,也可以分别放到不同服务器或 CDN/对象存储静态网站。API 推荐独立域名,例如 `api.example.com`
## 运行时配置
H5 产物支持运行时覆盖公开配置。每个静态目录根部放一个 `runtime-config.json`
```text
/www/tiku/h5-student/runtime-config.json
/www/tiku/h5-tenant-admin/runtime-config.json
/www/tiku/h5-platform-admin/runtime-config.json
```
示例文件:
```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
```
生产示例:
```json
{
"portal": "student",
"apiBaseUrl": "https://api.example.com",
"supabaseUrl": "https://auth.example.com",
"supabasePublishableKey": "replace-with-supabase-publishable-key",
"tenantCode": ""
}
```
允许字段:
```text
portal student | tenant-admin | platform-admin
apiBaseUrl apps/api 公开 HTTPS 地址
supabaseUrl Supabase Auth/API 公开 HTTPS 地址
supabasePublishableKey Supabase publishable/anon key
tenantCode H5 生产必须为空;仅小程序或本地预览可用
```
这些字段是前端公开配置,不是密钥。`apps/taro/src/env.ts` 会拒绝 `runtime-config.json` 中出现服务端密钥类字段,例如:
```text
SUPABASE_SERVICE_ROLE_KEY
SUPABASE_SECRET_KEY
DATABASE_URL
ALIYUN_OSS_ACCESS_KEY_SECRET
TENCENT_COS_SECRET_KEY
WECHAT_PAY_PRIVATE_KEY
ALIPAY_APP_PRIVATE_KEY
AUTH_SESSION_SECRET
PLATFORM_ADMIN_API_KEY
```
构建时仍可以设置同名 `TARO_APP_*` 变量作为默认值,但生产更推荐用 `runtime-config.json`。这样 API 域名、Auth 域名、租户预览码变化时不需要重新构建 H5。
## Nginx 示例
H5 使用 browser history 路由时,静态服务器需要把未知路径回退到 `index.html`。同时对 `runtime-config.json` 禁用缓存,对 hash 后的 JS/CSS 长缓存。
```nginx
server {
listen 443 ssl http2;
server_name www.example.com;
root /www/tiku/h5-student;
add_header X-Content-Type-Options nosniff always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https: blob:; font-src 'self' data:; connect-src 'self' https://api.example.com https://auth.example.com; media-src 'self' https: blob:; frame-ancestors 'none'; base-uri 'self'; form-action 'self'" always;
location = /runtime-config.json {
add_header Cache-Control "no-store" always;
try_files $uri =404;
}
location ~* \.(?:js|css|woff2?|png|jpg|jpeg|gif|svg)$ {
add_header Cache-Control "public, max-age=31536000, immutable" always;
try_files $uri =404;
}
location / {
try_files $uri $uri/ /index.html;
}
}
```
租户后台和平台后台复用同样规则,替换 `server_name``root` 和 CSP 中的 `connect-src` 域名即可。若学生端需要打开 OSS/COS/CDN 签名资源,`img-src/media-src` 可加入对应 HTTPS 域名;不要加入通配符 `*`
## CORS 和 Cookie
API 的生产 `CORS_ORIGIN` 只维护数量很少、由平台自己管理的中央平台/运维 Origin
```text
CORS_ORIGIN=https://platform-admin.example.com,https://ops.example.com
CORS_TENANT_DOMAINS_ENABLED=true
CORS_TENANT_DOMAIN_CACHE_TTL_MS=60000
CORS_TENANT_DOMAIN_NEGATIVE_CACHE_TTL_MS=10000
CORS_TENANT_DOMAIN_CACHE_MAX_ENTRIES=10000
```
学生端和租户后台的业务 Origin 不展开写入 `CORS_ORIGIN`。API 使用 Origin 的规范化 hostname 查询 `active tenant_domains + active tenants`,并使用有界的 LRU TTL 正/负缓存。未知域名、`pending/failed/disabled` 域名、非 active 租户、非 HTTPS 或使用非默认端口的动态租户 Origin 都返回 `403 CORS_ORIGIN_DENIED`。本地开发端口只能作为完整 Origin 显式写入静态列表。
正缓存 TTL 是域名禁用后最长的准入传播时间;负缓存 TTL 是新域名启用后最长的生效等待时间。数据库查询异常会 fail closed 并短暂负缓存,不会因数据库故障放开未验证 Origin。紧急禁用域名时可在修改数据库状态后重启 API 进程立即清空进程内缓存。
禁止生产环境使用:
```text
CORS_ORIGIN=*
```
H5 租户解析和 CORS 都以浏览器自动发送的 `Origin` hostname 为权威域名:只查询已启用的 `tenant_domains` 和已启用租户未绑定域名不回退主租户。CORS 不读取 `Host`/`X-Forwarded-Host`/`x-tenant-code` 来决定准入,伪造这些头无法绕过未知 Origin 拒绝。Nginx/CDN 不得覆盖或伪造 `Origin`,并应在 API 反代层清空客户端传入的 `X-Forwarded-Host``host` query 只用于 localhost 开发合同;无浏览器 Origin 的健康检查、服务端客户端和微信小程序不会被 CORS 拦截,小程序使用 `tenantCode` 解析租户。
当前前端以 `Authorization: Bearer <supabase_access_token 或 tk_session>` 调用 API`x-tenant-id` 只作为租户上下文,不作为身份来源。生产建议:
```text
ALLOW_LEGACY_AUTH_HEADERS=false
ALLOW_PLATFORM_ADMIN_KEY=false
```
H5 正式回归时建议把前端登录态切到 Supabase Auth并观察业务接口请求是否都发送 `Bearer <supabase_access_token>``tk_` session 只作为迁移/本地兜底,不应成为线上长期依赖。
## 前端请求边界
- 所有页面统一通过 `apps/taro/src/services/api.ts` 调用后端。
- 默认请求模式是 `authMode='auto'`H5 先用 Supabase JWT没有 JWT 才兜底迁移期 `tk_` session。
- 公共接口、短信登录、租户解析必须显式 `authMode='none'`;平台全局或租户解析不应带租户上下文时必须显式 `tenantId: null`
- H5 可以用 Supabase client 管理 Auth session/JWT但业务数据默认走 `apps/api`
- 页面代码不能通过 `headers` 覆盖 `Authorization``x-tenant-id`,身份和租户上下文只能走统一 client 的 token provider、`authMode``tenantId` 参数。
- 订单、支付、权益、内容导入、后台配置、CRM、对象存储签名、视频播放签名必须走后端命令层。
- 登录后禁止传 `x-user-id` 或 body/query `userId` 表示当前用户。
- 私有 PDF、图片、视频不能由前端拼接 URL必须使用 `content_assets` 和后端短签名。
## 发布步骤
1. 在新服务器或 CI 环境构建三套 H5 和学生微信小程序:
```bash
npm ci --workspaces --include-workspace-root --include=dev
npm run audit:taro:supply-chain
npm run build:taro:h5:student
npm run build:taro:h5:tenant
npm run build:taro:h5:platform
npm run build:taro:weapp:student
```
2. 拷贝静态产物到对应 Web 根目录。
3. 根据示例文件创建每个目录的 `runtime-config.json`。
4. 配置 Nginx history fallback、缓存策略、安全响应头和 HTTPS。
5. 配置 API 的 `CORS_ORIGIN`、Supabase Auth 回调域名、微信/QQ/支付宝/微信支付回调域名。
6. 运行生产就绪检查:
```bash
npm run readiness:production
npm run readiness:production:db
npm run check:taro
```
构建后再运行 H5 发布守卫脚本:
```bash
npm run smoke:taro:h5
npm run smoke:taro:h5:interaction
npm run manifest:taro:h5
node scripts/taro-h5-release-guardrails-test.js --require-dist
```
`smoke:taro:h5` 会用临时静态服务器检查三套 H5 产物可托管、资源可加载、history fallback 可用,并用 mock API 验证租户解析契约。`smoke:taro:h5:interaction` 会用真实 Chrome/Edge 打开三套发布产物并点击 33 项关键路径:学生登录 401、刷题、收藏、错题/收藏复习、背单词、知识手册、资料、视频、分数线、AI 择校、消息、会员下单和订单状态,租户后台内容导入、公共题库采纳/同步/冲突处理、学生运营、营销/CRM/分佣、主题/角色/成员写操作,以及平台后台租户、账务、公共题库授权和员工写操作。它还会跨三个门户切换桌面/移动视口,验证 Input 挂载前后同步,并将 Button loading 连续切换 200 次,要求 loading 节点和子节点数量保持稳定任何浏览器异常、console error、非允许 HTTP 错误都会失败并记录时间戳、行列号和 stack。若服务器没有默认浏览器可设置 `TARO_H5_SMOKE_BROWSER=/path/to/chrome`。`manifest:taro:h5` 会生成三套 H5 的部署清单,包含构建命令、发布目录、入口路由、`index.html` hash、资源数量、runtime-config 是否存在、租户解析模式和公开配置状态。发布守卫会检查三套 H5 产物是否存在 `index.html`,源码和产物是否混入 `x-user-id`、`x-platform-admin-key`、PocketBase 引用、数据库连接串、服务端密钥形态,并检查运行时配置示例只包含公开字段。若还没有把真实 `runtime-config.json` 放入静态目录,会显示 warning正式发布前必须在每个 H5 目录根部补齐该文件。
写入 `production-launch-evidence.json` 的正式证据必须使用严格模式,确保三套发布目录都已放置真实公开 `runtime-config.json` 且没有 warning
```bash
npm --silent run smoke:taro:h5 -- --json > docs/refactor/launch-artifacts/taro-h5-static-smoke.json
npm --silent run smoke:taro:h5:interaction -- --json > docs/refactor/launch-artifacts/taro-h5-interaction-smoke.json
node scripts/taro-h5-release-guardrails-test.js --require-dist --require-runtime-config --json > docs/refactor/launch-artifacts/taro-h5-release-guardrails.json
npm --silent run manifest:taro:h5 -- --require-dist --require-runtime-config --json --write docs/refactor/launch-artifacts/taro-h5-release-manifest.json > docs/refactor/launch-artifacts/taro-h5-release-manifest.stdout.json
```
7. 收集生产上线证据并运行 launch gate
```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
```
证据文件只保存命令摘要、artifact 路径、审批人和时间,不保存真实 access token、支付密钥、对象存储密钥或用户隐私明细。真实 artifact 建议放在 `docs/refactor/launch-artifacts/`,该目录不入 Git。
8. 打开三个域名,确认 `index.html` 正常加载,启动页能解析租户,登录后接口请求使用 `Authorization` 和正确的 `x-tenant-id`。
9. 用微信开发者工具打开 `apps/taro/dist/weapp-student`,再用真机验证租户解析、登录、刷题、支付、文件、音视频和分享。`apps/taro/project.config.json` 的 `miniprogramRoot` 已指向该目录;正式发布前必须替换测试 AppID并配置 API、下载、上传、媒体和业务回调合法域名。
## 安全审计边界
H5 线上只发布 `apps/taro/dist/**` 静态文件和每个目录自己的 `runtime-config.json`,不要把 `apps/taro/node_modules`、源码目录、`.env`、部署脚本缓存放进 Web 根目录。
后端/API/worker 的生产依赖审计使用:
```bash
npm run audit:runtime
```
Taro 4.2.0 当前构建工具链仍会触发 `npm run audit:taro:toolchain` 的上游 high/critical 告警;`npm audit --omit=dev` 已验证生产运行时为 0 漏洞。不要使用 `npm audit fix --force` 将 Taro 降级到 3.x。`swiper@12.1.2`、`lodash-es@4.18.1` 和两项 H5 runtime patch 已完成可重复干净安装、三端构建、静态/交互 smoke 和小程序构建验证,正式基线以 `npm run audit:taro:supply-chain` 的精确 allowlist/hash 为准。部署必须允许 workspace postinstall不得使用 `--ignore-scripts`。构建机必须隔离、不得对公网暴露 dev server、不得处理不可信模板/压缩包或执行不可信 CLI 参数;上线阻断项仍包括 production runtime audit、静态产物与前端密钥检查、CORS/CSP、三端烟测和 release manifest。工具链风险需持续跟踪不能把运行时 0 漏洞表述成工具链 0 漏洞。
## 微信小程序发布边界
`runtime-config.json` 只用于 H5。微信小程序通过 `TARO_APP_TENANT_CODE`、小程序启动参数或受控后台配置传入 `tenantCode`,再调用 `GET /api/tenant/resolve?tenantCode=...`。小程序登录优先走 `apps/api/auth/*` 的短信或 `code2Session` 适配层,不把 AppSecret、session_key、service role key 或数据库连接信息放进小程序包。
当前只提供学生微信小程序构建。`npm run build:taro:weapp:student` 仅用于本地预览;正式上传必须设置 `TARO_APP_API_BASE_URL=https://...`、`WECHAT_MINIAPP_APP_ID` 并运行 `npm run build:taro:weapp:student:production`。单租户品牌包使用默认 `fixed` 模式并设置 `TARO_APP_TENANT_CODE`;面向数百租户的共享小程序使用 `TARO_APP_WEAPP_TENANT_MODE=launch`,从小程序码 query/scene 或 `referrerInfo.extraData.tenantCode` 解析租户码。launch 模式启动时缺少租户码会直接阻断,不会回退默认租户。严格构建会拒绝 localhost、测试 AppID、关闭合法域名检查或超出 4 MiB 总包/2 MiB 主包预算的产物。租户后台和平台后台包含大表格、批量导入、财务与运营工作流,不应为了“多端一致”强行塞进小程序;后续 App 同样优先复用学生端页面和共享 services再按原生能力逐项验收。