docs/上线配置清单.md
# wm-geo 上线配置清单
> **用途**:在已有代码基础上,把环境变量、Cron、安全头配齐后即可跑通主链路。
> **不覆盖**:P0 代码缺口(Chrome 扩展真发、服务端 PDF 引擎、微信/支付宝生产支付)——见 [`当前交付边界.md`](./当前交付边界.md)。
> **变量全集与注释**:仓库根 [`.env.example`](../.env.example)。
**最后更新**:2026-05-19(批次 DK / DL)
---
## 相关文档(先读这些)
| 文档 | 何时看 |
|------|--------|
| [README.md](../README.md) | 本地启动、目录、Cron 摘要 |
| [业务跑通手册.md](./业务跑通手册.md) | 按角色页面路径、API、验收清单 |
| [当前交付边界.md](./当前交付边界.md) | 哪些能演示、P0 仍缺什么 |
| [aliyun-d1-checklist.md](./aliyun-d1-checklist.md) | PolarDB / Tair / OSS / FC 控制台勾选 |
| [scripts/README-cron.md](../scripts/README-cron.md) | 三条 Cron 脚本运维细节 |
| [`.env.production.example`](../.env.production.example) | 生产最小 env 模板(从 `.env.example` 摘取) |
---
## 0. 生产最小集(速查)
上线前至少确认:
| 类别 | 变量 |
|------|------|
| 核心 | `DATABASE_URL`、`JWT_SECRET`(≥32 推荐)、`INTERNAL_JOB_SECRET`(≥16) |
| 站点 | `APP_URL` 或 `NEXT_PUBLIC_APP_URL`(HTTPS) |
| 登录 | `WECHAT_APP_ID` + `WECHAT_APP_SECRET` **或** 阿里云短信四元组 |
| 计费 | `STRIPE_SECRET_KEY`、`STRIPE_WEBHOOK_SECRET`、各 `STRIPE_PRICE_*` |
| 运营 | `ADMIN_API_KEY`(**禁止** `ADMIN_DEV_BYPASS`) |
| Worker | Cron 跑 `process-async-jobs`(见 §9) |
| 安全 | 全程 HTTPS 时可开 `WM_GEO_ENABLE_HSTS=1`;CSP 建议先 Report-Only |
本地演示最小集见 [README · 全链路演示](../README.md#全链路业务演示本地):`DATABASE_URL`、`JWT_SECRET`、`INTERNAL_JOB_SECRET`;可选 `ADMIN_DEV_BYPASS=1`、`MOCK_LLM=1`。
---
## 1. 数据库(PolarDB / Postgres)
| 变量 | 必填 | 不配则怎样 |
|------|------|------------|
| `DATABASE_URL` | **生产必填** | 几乎所有 `/api/v1/app/*`、`/admin/*`、`/vendor/*`、联系表单、OTP 写库等返回 **503** `misconfigured`;`GET /api/v1/health` 的 `database` 为不可用;匿名诊断不落库、无 `shareToken` |
**迁移**:生产执行 `npx prisma migrate deploy`;种子 `npx prisma db seed`(行业 + 六引擎)。
**对照**:[`aliyun-d1-checklist.md` §2 PolarDB](./aliyun-d1-checklist.md#2-polardb-postgresql-serverless)。
---
## 2. 会话与 JWT
| 变量 | 必填 | 不配则怎样 |
|------|------|------------|
| `JWT_SECRET` | **生产必填**(≥16,建议 ≥32) | 签发/校验 `wm_geo_session` 抛错;登录、中间件鉴权失败;`CONTACT_LOG_PEPPER` / `OTP_PEPPER` 无独立配置时会弱回退到此密钥 |
| `VENDOR_JWT_SECRET` | 可选 | 未设时与 `JWT_SECRET` 相同,但 `aud=wm_geo_vendor` 独立;供应商 Cookie 仍依赖有效密钥长度 |
| `OTP_PEPPER` | 可选 | 回退 `JWT_SECRET` → 开发占位;生产建议与 JWT 分离 |
| `REDIS_URL` | 可选 | OTP/匿名限流、微信 OAuth ticket、JWT 吊销黑名单键降级为**进程内 Map**;多实例时限流不准、ticket 不共享、吊销可能漏检 |
**开发专用(生产禁止)**:
| 变量 | 不配则怎样 |
|------|------------|
| `ALLOW_DEV_LOGIN=true` | 生产 `NODE_ENV=production` 且非 localhost 时,`POST /api/v1/auth/dev-session` **403** |
| `AUTH_OTP_DEV_CODE` / `AUTH_OTP_DEV_BYPASS` | 仅 `NODE_ENV=development` 生效;生产无效 |
| `AUTH_WECHAT_DEV_BYPASS=1` | 仅非 production;生产无效 |
---
## 3. 大模型(监测 / 生成 / 诊断)
| 变量 | 必填 | 不配则怎样 |
|------|------|------------|
| `MOCK_LLM=1` | 可选 | 强制六家全部 mock |
| `OPENAI_API_KEY` | 按需 | 无任一 Key 时,对应引擎及 `generate_run`、扫描等 **mock** 回答(`meta.allMocked` / `mocked: true`) |
| `OPENAI_MODEL` | 可选 | 默认 `gpt-4o-mini`(OpenAI 回退路径) |
| `DEEPSEEK_API_KEY` | 按需 | DeepSeek 槽位 mock;可被 `OPENAI_API_KEY` 回退(OpenAI 兼容接口) |
| `DASHSCOPE_API_KEY` | 按需 | 通义 mock |
| `ZHIPU_API_KEY` | 按需 | 智谱 mock |
| `MOONSHOT_API_KEY` | 按需 | Kimi mock |
| `ARK_API_KEY` / `ARK_MODEL` | 按需 | 豆包 mock;模型默认 `doubao-1-5-pro-32k-250115` |
| `QIANFAN_BEARER_TOKEN` / `QIANFAN_MODEL` | 按需 | 文心 mock;模型默认 `ernie-speed-8k` |
**说明**:演示可只设 `MOCK_LLM=1`;要验收真实引擎回复,至少配置被测引擎对应 Key。逻辑见 `lib/engines/client.ts`。
---
## 4. 认证:微信扫码
| 变量 | 必填 | 不配则怎样 |
|------|------|------------|
| `WECHAT_APP_ID` | 生产扫码登录必填 | `POST /api/v1/auth/wechat/qrcode` → **501**;用户无法用微信登录 |
| `WECHAT_APP_SECRET` | 同上 | 同上;回调换 token 失败 |
| `WECHAT_REDIRECT_URI` | 可选 | 默认 `{APP_URL}/api/v1/auth/wechat/callback` |
| `APP_URL` / `NEXT_PUBLIC_APP_URL` | 强烈建议 | `redirect_uri` 拼错导致微信开放平台校验失败;Stripe Checkout 回跳异常 |
| `AUTH_WECHAT_DEV_BYPASS=1` | **仅开发** | 无 AppId 时 mock 扫码;**生产切勿开启** |
---
## 5. 认证:手机 OTP(阿里云短信)
| 变量 | 必填 | 不配则怎样 |
|------|------|------------|
| `ALIYUN_SMS_ACCESS_KEY_ID` | 生产 OTP 必填 | `NODE_ENV=production` 且未配齐短信四元组 → `POST /api/v1/auth/otp/send` **503** `sms_not_configured` |
| `ALIYUN_SMS_ACCESS_KEY_SECRET` | 同上 | 同上 |
| `ALIYUN_SMS_SIGN_NAME` | 同上 | 同上 |
| `ALIYUN_SMS_TEMPLATE_CODE_OTP` | 同上 | 同上 |
| `ALIYUN_ACCESS_KEY_ID` / `ALIYUN_ACCESS_KEY_SECRET` | 可选别名 | 与上组二选一,供 SMS SDK 复用 |
| `ALIYUN_SMS_REGION_ID` | 可选 | 默认 `cn-hangzhou` |
| `ALIYUN_SMS_ENDPOINT` | 可选 | 不设则由 SDK 按地域解析 |
**开发**:`NODE_ENV=development` 时不发短信,验证码打日志;可设 `AUTH_OTP_DEV_CODE` 固定码。
---
## 6. Stripe 计费
| 变量 | 必填 | 不配则怎样 |
|------|------|------------|
| `STRIPE_SECRET_KEY` | Checkout / Portal **必填** | `POST .../billing/create-checkout-session` 等返回 **503**;Admin 计费摘要无 Stripe 数据 |
| `STRIPE_WEBHOOK_SECRET` | 生产 Webhook **必填** | `POST /api/v1/webhooks/stripe` → **503**;订阅状态不同步。开发未配时返回 200 占位(不验签),**勿把未验签部署暴露公网** |
| `STRIPE_PRICE_FREE` / `SOLO` / `STORE` / `PRO` / `ENTERPRISE` | 上架套餐建议填 | `GET /api/v1/public/plans` 对应项 `stripePriceId: null`,Checkout 无法选价 |
| `STRIPE_CHECKOUT_SUCCESS_PATH` / `STRIPE_CHECKOUT_CANCEL_PATH` | 可选 | 默认 `/app/settings?checkout=success|canceled` |
| `STRIPE_PORTAL_RETURN_PATH` | 可选 | 默认 `/app/settings?portal=return` |
| `PUBLIC_PLANS_DEFAULT_CURRENCY` | 可选 | 默认 `cny` |
**缺口**:国内微信/支付宝商户支付 P0 仍须真实商户号与 HTTPS 回调;env 校验见 **`GET /api/v1/admin/billing/config-status`** 与 [`支付接入路线图.md`](./支付接入路线图.md)。微信:`POST …/wechat-pay/create`(无 `WECHAT_PAY_*` → **503**;齐全 + mock/私钥 → **200 dev_mock**);支付宝:`POST …/alipay/create`(无 `ALIPAY_*` → **503**;`ALIPAY_DEV_MOCK=1` → **200 mock**)。见 [`当前交付边界.md` §4](./当前交付边界.md#4-prd-p0-仍缺勿与能演示混淆)。
---
## 7. 报告导出对象存储(OSS / S3)
| 变量 | 必填 | 不配则怎样 |
|------|------|------------|
| `WM_GEO_OSS_REGION` + `WM_GEO_OSS_BUCKET` + `WM_GEO_OSS_ACCESS_KEY_ID` + `WM_GEO_OSS_ACCESS_KEY_SECRET` | 可选 | 四元组不齐时,`GET .../reports/:id/export` 仍返回 **HTML/JSON 附件**(浏览器下载),无 `downloadUrl` |
| `WM_GEO_OSS_PUBLIC_BASE_URL` | 可选 | 未设则用 PUT 后对象 URL |
| `WM_GEO_OSS_KEY_PREFIX` / `WM_GEO_OSS_ENDPOINT` | 可选 | 默认 `reports/exports/` 与 `{bucket}.oss-{region}.aliyuncs.com` |
| `OSS_*` | 兼容别名 | 同 `WM_GEO_OSS_*` |
| `S3_ENDPOINT` + `S3_BUCKET` + `S3_ACCESS_KEY_ID` + `S3_SECRET_ACCESS_KEY` | 可选 | OSS 未配时 MinIO 等 S3 兼容;仍不齐则附件模式 |
**说明**:无 OSS 不影响主链路演示;生产 CDN 加速下载建议配齐。服务端 Puppeteer PDF **未实现**。
---
## 8. 运营 Admin
| 变量 | 必填 | 不配则怎样 |
|------|------|------------|
| `ADMIN_API_KEY` | 生产建议 | 无 Key 且无 `users.is_admin` 会话时,`/api/v1/admin/*` **401/403**;脚本/网关需 `Authorization: Bearer` 或 `X-Admin-Api-Key` |
| `ADMIN_DEV_BYPASS=1` | **生产禁止** | 仅 `NODE_ENV=development` 跳过 admin 门禁;生产设置**不生效**(middleware / `requireAdminRoles` 仍校验)。误开在 production 环境无意义,但暴露习惯风险——**切勿写入生产 env** |
本地演示:可 `ADMIN_DEV_BYPASS=1` + dev-session,见 [业务跑通手册 §0](./业务跑通手册.md#0-前置与环境)。
---
## 9. 内部任务机与 Cron(必配)
| 变量 | 必填 | 不配则怎样 |
|------|------|------------|
| `INTERNAL_JOB_SECRET` | **生产必填**(≥16) | 三条 internal 路由 **503** `misconfigured`;`async_jobs` 永不执行 → 内容生成、监测扫描、定时发文卡在 `pending` |
| `ASYNC_JOB_BATCH_LIMIT` | 可选 | 默认每轮 10 条(上限 100) |
| `SCHEDULED_SCAN_BRAND_BATCH_LIMIT` | 可选 | 周扫入队批量默认见代码 |
| `SCHEDULED_PUBLISH_BATCH_LIMIT` | 可选 | 定时发文入队批量默认见代码 |
| `MONITOR_KEYWORD_INTERVAL_DAYS` | 可选 | 关键词监测间隔默认见 `lib/scans/keyword-scan-schedule.ts` |
### 三条脚本 + 一键顺序执行
| 顺序 | 脚本 | API | 不配 Worker 的后果 |
|------|------|-----|-------------------|
| 1(可选) | `scripts/cron-process-scheduled-scans.sh` | `POST /api/v1/internal/process-scheduled-scans` | 到期关键词不入队 `monitored_scan` |
| 2(**必须**) | `scripts/cron-process-async-jobs.sh` | `POST /api/v1/internal/process-async-jobs` | 队列不消化(生成/扫描/发文全挂) |
| 3(可选) | `scripts/cron-process-scheduled-publications.sh` | `POST /api/v1/internal/process-scheduled-publications` | 定时发文不入队 |
| 脚本 | 环境 |
|------|------|
| `scripts/run-all-cron.sh` | 按 1→2→3 顺序调用上表(本地联调);生产仍建议**分开 crontab** |
Cron 环境:`INTERNAL_JOB_SECRET`(与运行时一致)、`BASE_URL` / `WM_GEO_BASE_URL`(站点根,默认 `http://127.0.0.1:3000`)。
详见 [scripts/README-cron.md](../scripts/README-cron.md)、[业务跑通手册 §5](./业务跑通手册.md#5-定时任务cron)。
**故障**:`401` 密钥不一致;`503` 未配或 <16 字符;任务一直 `pending` → 未跑第 2 条或 Worker 报错。
---
## 10. 站点 URL 与公开页
| 变量 | 必填 | 不配则怎样 |
|------|------|------------|
| `NEXT_PUBLIC_SITE_URL` | 建议 | 匿名诊断 `shareUrl`、`metadataBase`、`/sitemap.xml` 回退 `APP_URL` → `VERCEL_URL` → `http://localhost:3000`,生产易出现错误域名 |
| `APP_URL` / `NEXT_PUBLIC_APP_URL` | 建议 | 微信 OAuth、Stripe 回跳、绝对链接 |
| `ANON_DAILY_LIMIT` | 可选 | 默认 10 次/IP/日;超限 **429** |
| `CONTACT_LOG_PEPPER` | 建议 | 联系表单 IP 哈希;未设回退 `JWT_SECRET` |
| `NEXT_PUBLIC_INSTALL_VIDEO_URL` | 可选 | 扩展安装页无演示视频 |
---
## 11. HTTPS / HSTS / CSP
| 变量 | 必填 | 不配则怎样 |
|------|------|------------|
| `WM_GEO_ENABLE_HSTS=1` | 可选 | 未设:无 `Strict-Transport-Security`。**仅**在站点**全程 HTTPS** 时开启;本地 `http://` 勿开,否则浏览器可能锁死 https |
| `WM_GEO_CSP_REPORT_ONLY=1` | 可选 | 下发 `Content-Security-Policy-Report-Only`(只上报不拦截) |
| `WM_GEO_CSP_ENFORCE=1` | 可选 | 生产下发 enforce CSP;开发默认降级为 Report-Only(防 HMR 白屏) |
| `WM_GEO_CSP_ENFORCE_IN_DEV=1` | 可选 | 开发机也 enforce(仅本地验证拦截时用) |
解析逻辑:`lib/security/http-response-security.mjs`;由 `next.config.mjs` → `headers()` 注入。
---
## 12. 供应商 Vendor(可选)
| 变量 | 不配则怎样 |
|------|------------|
| `VENDOR_JWT_SECRET` | 见 §2 |
| `VENDOR_TASK_LIST_STUB` / `VENDOR_SETTLEMENTS_STUB` | 显式 `true` 时列表/结算走 stub;无 DB 时部分路由有降级行为 |
---
## 13. 上线后自检顺序
1. `GET /api/v1/health` → `database: ok`,`redis` 按是否配 `REDIS_URL`
2. `npx prisma migrate deploy` + seed(若新环境)
3. 配置 `INTERNAL_JOB_SECRET` 后执行 `./scripts/run-all-cron.sh` 或至少 `cron-process-async-jobs.sh`
4. `./scripts/demo-full-business-path.sh`(生产改 `BASE_URL`、去掉 `ADMIN_DEV_BYPASS`)
5. 对照 [业务跑通手册 §6 验收清单](./业务跑通手册.md#6-验收清单手工)
---
## 14. 与阿里云 D1 清单的分工
| 文档 | 内容 |
|------|------|
| **本文** | 应用 env、Cron、安全头、「不配会怎样」 |
| [`aliyun-d1-checklist.md`](./aliyun-d1-checklist.md) | VPC、PolarDB、Tair、OSS 桶、FC 触发器、控制台规格 |
控制台开通后,把连接串/密钥填入本文各节变量即可。
---
*本文档随仓库实现更新;与 PRD 冲突以 [`PRD.md`](./PRD.md) 为准。*