forked from wangziqi/gongxue-base
257 lines
15 KiB
Markdown
257 lines
15 KiB
Markdown
# 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,再按原生能力逐项验收。
|