Files
cmhub/docs/env.md
T

157 lines
11 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.
# 环境变量与配置
> 本文集中约定运行 `cmhub` 所需配置项。真实密钥、数据库密码、支付凭证不得写入代码、文档样例或提交记录;本文件只写变量名、用途和占位示例。
## 一、配置来源
- Django 运行级配置走环境变量或 `.env`(`.env` 不提交)。
- 仓库根目录提供 `.env.example` 作为无密钥样例;新增配置项时同步更新 `.env.example` 与本文。
- 上游 AI 模型的 `api_key` 存入数据库前必须用 `AI_KEY_ENCRYPTION_KEY` 加密,admin 脱敏展示且不回显明文。
- 微信、支付宝商户密钥/证书走环境变量或部署机安全文件路径,不写入数据库明文字段。
- 本地开发、测试、生产使用同一套变量名;差异只在变量值。
## 二、Django 基础配置
| 变量 | 必填 | 示例 | 说明 |
| --- | --- | --- | --- |
| `DJANGO_SECRET_KEY` | 是 | `change-me` | Django SECRET_KEY;生产必须使用高强度随机值 |
| `DJANGO_DEBUG` | 是 | `false` | 生产必须为 `false` |
| `DJANGO_ALLOWED_HOSTS` | 是 | `cmhub.example.com,127.0.0.1` | 逗号分隔 |
| `DJANGO_CSRF_TRUSTED_ORIGINS` | 生产是 | `https://cmhub.example.com` | 用户端表单、admin、充值页需要 |
| `DJANGO_TIME_ZONE` | 否 | `Asia/Shanghai` | 默认按中国业务时区 |
| `DJANGO_EMAIL_BACKEND` | 否 | `django.core.mail.backends.console.EmailBackend` | allauth 注册邮箱验证发信后端;生产应改为真实 SMTP / 邮件服务 |
| `DJANGO_DEFAULT_FROM_EMAIL` | 生产是 | `noreply@cmhub.example.com` | allauth 邮件默认发件人 |
| `DJANGO_SESSION_COOKIE_SECURE` | 生产是 | `true` | 生产 HTTPS 下 session cookie 仅允许安全连接传输 |
| `DJANGO_CSRF_COOKIE_SECURE` | 生产是 | `true` | 生产 HTTPS 下 CSRF cookie 仅允许安全连接传输 |
| `DJANGO_SECURE_SSL_REDIRECT` | 否 | `false` | 若 Nginx/宝塔已强制 HTTPS,可保持 `false`;若由 Django 强制跳转则设 `true` |
| `DJANGO_SECURE_HSTS_SECONDS` | 否 | `31536000` | 全站确认只走 HTTPS 后再启用;未确认前保持 `0`,避免 HSTS 误锁域名 |
| `DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS` | 否 | `false` | 是否把 HSTS 应用于子域名 |
| `DJANGO_SECURE_HSTS_PRELOAD` | 否 | `false` | 是否声明 HSTS preload;提交 preload 前必须确认全部子域长期 HTTPS |
| `DJANGO_SECURE_PROXY_SSL_HEADER` | 反代 HTTPS 是 | `true` | Nginx 反代并传 `X-Forwarded-Proto https` 时开启,避免 Django 误判当前请求为 HTTP |
## 三、数据库配置
开发和生产都使用 MySQL 8.4 LTS 独立实例,不使用 SQLite。
| 变量 | 必填 | 示例 | 说明 |
| --- | --- | --- | --- |
| `MYSQL_HOST` | 是 | `127.0.0.1` | cmhub 专用 MySQL 实例地址 |
| `MYSQL_PORT` | 是 | `3306` | MySQL 默认端口;若与已有实例共存,可改为专用独立端口 |
| `MYSQL_DATABASE` | 是 | `cmhub` | 数据库名 |
| `MYSQL_USER` | 是 | `cmhub` | 应用账号 |
| `MYSQL_PASSWORD` | 是 | `change-me` | 数据库密码 |
| `MYSQL_CHARSET` | 是 | `utf8mb4` | 必须为 `utf8mb4` |
| `MYSQL_CONNECT_TIMEOUT` | 否 | `30` | 客户端连接 MySQL 的超时秒数;远程测试库建议显式设置,避免默认值过短造成误判 |
| `MYSQL_READ_TIMEOUT` | 否 | `120` | 客户端等待 MySQL 响应的读取超时秒数 |
| `MYSQL_WRITE_TIMEOUT` | 否 | `120` | 客户端向 MySQL 写入数据的超时秒数 |
## 四、AI 与加密配置
| 变量 | 必填 | 示例 | 说明 |
| --- | --- | --- | --- |
| `AI_KEY_ENCRYPTION_KEY` | 是 | `base64-fernet-key` | Fernet 主密钥,用于加密 `AiModel.api_key_encrypted`;生产不可更换,除非完成密钥轮换 |
`AI_KEY_ENCRYPTION_KEY` 必须是 `cryptography.fernet.Fernet.generate_key()` 生成的 base64 字符串,可用 `py -3.12 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"` 生成。
`AiModel.url`、`AiModel.model`、`AiModel.api_type`、`AiModel.capabilities` 与加密后的 `api_key` 由后台或数据迁移维护,不通过环境变量硬编码具体模型。
AI 上游连接超时与读取超时不走全局环境变量:连接超时由 `AiModel.connect_timeout_seconds` 控制,读取超时由 `AiModel.timeout_seconds` 控制;当读取超时为 `0` 时,Provider 按分辨率使用内置默认值。
## 五、对外 API 安全配置
| 变量 | 必填 | 示例 | 说明 |
| --- | --- | --- | --- |
| `API_GENERATE_THROTTLE_RATE` | 否 | `60/min` | 生成标题 / 图片接口按 API Key 或用户限流,DRF throttle rate 格式 |
| `API_AUTH_FAILURE_THROTTLE_RATE` | 否 | `30/min` | 缺失、畸形或无效 API Key 的认证失败按 IP 限流 |
| `IMAGE_URL_MAX_BYTES` | 否 | `10485760` | `image_url` 服务端下载的最大响应字节数,默认 10 MiB |
| `IMAGE_URL_MAX_REDIRECTS` | 否 | `3` | `image_url` 手动跟随重定向次数上限;每跳都会重新校验目标地址 |
| `IMAGE_URL_CONNECT_TIMEOUT_SECONDS` | 否 | `10` | `image_url` 下载连接超时秒数 |
| `IMAGE_URL_READ_TIMEOUT_SECONDS` | 否 | `60` | `image_url` 下载读取超时秒数 |
| `RECHARGE_MAX_AMOUNT_CNY` | 否 | `100000.00` | 用户端单笔充值金额上限,超过则拒绝创建订单 |
`image_url` 只允许 `http` / `https`,服务端会在请求前解析域名,拒绝私有网段、回环、链路本地、保留地址、组播、未指定地址;重定向后的目标地址也会重复执行同样校验。内网图片不应通过 `image_url` 传入,调用方应改用 `image_base64`。
## 六、内容安全 / 本地敏感词配置
| 变量 | 必填 | 示例 | 说明 |
| --- | --- | --- | --- |
| `MODERATION_ENABLED` | 否 | `false` | 内容安全总开关;默认关闭时所有审核 no-op |
| `MODERATION_PROVIDER` | 启用时是 | `keyword` | T-604 只支持 `keyword` 本地敏感词 provider;云厂商后续扩展 |
| `MODERATION_FAIL_CLOSED` | 否 | `true` | 审核 provider 不可用时是否拦截;生产建议 `true` |
| `MODERATION_BLOCK_ON_REVIEW` | 否 | `false` | `review` 是否按拦截处理;T-604 keyword MVP 不使用 review |
| `MODERATION_CACHE_VERSION_KEY` | 否 | `moderation:sensitive_words:version` | 共享 cache 中的词库版本号 key,用于多 worker matcher 失效 |
T-604 只做 prompt 本地敏感词快筛。命中时返回 `content_blocked`,不扣点、不写调用记录、不调上游;并且必须在下载 `image_url` 前执行 prompt 审核。详细规则见 [`moderation.md`](moderation.md)。
生产多 Gunicorn worker 下,敏感词 matcher 刷新依赖共享 cache 的版本号;若仍使用 `LocMemCache`,只能刷新当前进程,不能保证所有 worker 及时更新。
## 七、静态文件与限流缓存
| 变量 | 必填 | 示例 | 说明 |
| --- | --- | --- | --- |
| `STATIC_URL` | 否 | `/static/` | 静态资源 URL 前缀 |
| `STATIC_ROOT` | 生产是 | `/www/wwwroot/cmhub/staticfiles` | `collectstatic` 输出目录,Nginx/宝塔需托管此目录 |
| `DJANGO_CACHE_BACKEND` | 生产是 | `django.core.cache.backends.db.DatabaseCache` | Django cache 后端;开发可用 locmem,生产多 Gunicorn worker 必须用共享后端 |
| `DJANGO_CACHE_LOCATION` | 生产是 | `cmhub_cache` | cache 位置;DatabaseCache 时为表名,Redis/Memcached 时为连接地址 |
生产限流依赖 Django cache。默认 `LocMemCache` 只适合单进程本地开发;多 worker 部署时每个进程各算一份限流,会放大实际请求速率。MVP 可先用 MySQL 的 `DatabaseCache`,部署时执行 `python3.12 manage.py createcachetable cmhub_cache`;高并发后再换 Redis / Memcached 等共享 cache,并同步安装对应 backend 依赖。
## 八、支付配置
| 变量 | 必填 | 示例 | 说明 |
| --- | --- | --- | --- |
| `PAYMENT_CALLBACK_MODE` | 是 | `mock` / `sdk` | 回调验签模式;本地/测试可用 `mock`,生产必须为 `sdk` |
| `PAYMENT_MOCK_CALLBACK_SECRET` | mock 是 | `change-me` | mock 回调 HMAC 密钥;仅用于本地/测试,不得冒充真实支付验签 |
| `PAYMENT_QR_EXPIRES_MINUTES` | 否 | `10` | 扫码下单返回的二维码本地有效期提示;不作为自动阻断延迟回调的依据 |
### 微信 V3 native
| 变量 | 必填 | 示例 | 说明 |
| --- | --- | --- | --- |
| `WECHAT_PAY_APPID` | 真实支付是 | `wx...` | 微信 appid |
| `WECHAT_PAY_MCHID` | 真实支付是 | `1900000001` | 商户号 |
| `WECHAT_PAY_API_V3_KEY` | 真实支付是 | `change-me` | API v3 key |
| `WECHAT_PAY_CERT_SERIAL_NO` | 真实支付是 | `ABC...` | 商户证书序列号 |
| `WECHAT_PAY_PRIVATE_KEY_PATH` | 真实支付是 | `/secure/wechat/apiclient_key.pem` | 私钥文件路径 |
| `WECHAT_PAY_NOTIFY_URL` | 真实支付是 | `https://cmhub.example.com/api/v1/recharge/callback/wechat` | 公网回调地址 |
### 支付宝当面付
| 变量 | 必填 | 示例 | 说明 |
| --- | --- | --- | --- |
| `ALIPAY_APPID` | 真实支付是 | `202100...` | 支付宝 appid |
| `ALIPAY_APP_PRIVATE_KEY_PATH` | 真实支付是 | `/secure/alipay/app_private_key.pem` | 应用私钥路径 |
| `ALIPAY_PUBLIC_KEY_PATH` | 真实支付是 | `/secure/alipay/alipay_public_key.pem` | 支付宝公钥路径 |
| `ALIPAY_NOTIFY_URL` | 真实支付是 | `https://cmhub.example.com/api/v1/recharge/callback/alipay` | 公网回调地址 |
| `ALIPAY_DEBUG` | 否 | `false` | 生产必须为 `false` |
缺少真实商户配置时,充值任务只能使用 mock 支付客户端;mock 必须在代码和测试中标明,不得伪装成真实支付。
## 九、对象存储配置
图片结果默认返回 URL,避免大 base64 进入同步响应体。对象存储选型未最终落地前,可使用本地开发存储,但生产必须给出可公开访问或可签名访问的 URL。
| 变量 | 必填 | 示例 | 说明 |
| --- | --- | --- | --- |
| `STORAGE_BACKEND` | 否 | `local` | `local` / `s3`;MVP 可先 `local` |
| `MEDIA_ROOT` | local 是 | `D:\chengma\cmhub\media` | 本地媒体文件目录 |
| `MEDIA_URL` | local 是 | `/media/` | 本地媒体 URL 前缀 |
| `S3_ENDPOINT_URL` | s3 是 | `https://s3.example.com` | S3 兼容 endpoint |
| `S3_BUCKET_NAME` | s3 是 | `cmhub-media` | bucket |
| `S3_ACCESS_KEY_ID` | s3 是 | `change-me` | access key |
| `S3_SECRET_ACCESS_KEY` | s3 是 | `change-me` | secret key |
## 十、上线前检查
- `DJANGO_DEBUG=false`。
- `DJANGO_SECRET_KEY`、`AI_KEY_ENCRYPTION_KEY`、数据库密码、支付密钥均已使用生产值。
- `ALLOWED_HOSTS`、`CSRF_TRUSTED_ORIGINS`、支付 `notify_url` 使用同一公网域名。
- `STATIC_ROOT` 已执行 `collectstatic`,Nginx/宝塔已托管 `/static/`;若继续用本地媒体存储,也必须托管 `/media/` 或切换对象存储。
- `DJANGO_CACHE_BACKEND` 已切到共享后端(如 DatabaseCache / Redis / Memcached),不是默认 `LocMemCache`。
- 生产 HTTPS 下 `DJANGO_SESSION_COOKIE_SECURE=true`、`DJANGO_CSRF_COOKIE_SECURE=true`,反代场景按需开启 `DJANGO_SECURE_PROXY_SSL_HEADER=true`。
- 全站 HTTPS 稳定后再设置 `DJANGO_SECURE_HSTS_SECONDS`;未确认子域名 HTTPS 前不要启用 includeSubDomains / preload。
- MySQL 为 8.4 LTS / InnoDB / `utf8mb4`,不是 SQLite 或已有 MySQL 5.7。
- `image_url` 下载上限、生成限流、认证失败限流、单笔充值金额上限已按生产容量调整。
- 若启用 `MODERATION_ENABLED`,`MODERATION_PROVIDER=keyword`,敏感词词库已配置,且共享 cache 可用于多 worker matcher 版本失效。
- 图片同步链路的客户端、Nginx、Gunicorn、上游 read timeout 均按最慢图片模型放大到同一量级。