Files
dpb/doc/桃育种系统生产上线与运营手册.md
T
34047007@qq.com b95053c52c init: 初始化 dpb 桃育种系统代码库
前后端 + 后端 FastAPI 全量源码、部署脚本与文档。
2026-08-06 00:17:49 +08:00

372 lines
19 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.
# 数字化桃育种系统 生产上线与运营手册
> 版本:3.0.1 适用:生产(Docker Compose)部署
> 前置阅读:`docker/README.md`(部署操作)、`doc/桃育种系统模块扩展需求规格.md`(功能规格,v2.72026-08-04
> 本文档基于 2026-08-04 安全加固后的代码与配置撰写,上线前请逐项核对。
---
## 1. 部署架构
### 1.1 组件清单
| 组件 | 技术栈 | 容器/进程 | 说明 |
|------|--------|-----------|------|
| 前端 | VueVite 构建) | nginx 托管静态文件 | 经 `/web` 路径访问 |
| 反向代理 | nginx 1.25-alpine | 容器 `nginx` | TLS 终结、HTTP→HTTPS、API 反代、限流 |
| 后端 | FastAPI + SQLAlchemy async + Redis | 容器 `backend` | `python main.py run --env=prod` |
| 数据库 | PostgreSQL 16 | 容器 `postgres` | 见 §3 |
| 缓存 | Redis 7 | 容器 `redis` | 会话、验证码、登录锁定计数、系统参数缓存 |
### 1.2 端口暴露(已收紧到本机回环)
| 服务 | 容器端口 | 宿主机绑定 | 说明 |
|------|----------|-----------|------|
| nginx | 80 / 443 | `0.0.0.0`(公网) | 唯一对外入口 |
| backend | 8001 | `127.0.0.1:8001` | 仅本机/容器网络可访问 |
| 数据库 | 5432 | `127.0.0.1` | 仅本机可连,公网不可达 |
| Redis | 6379 | `127.0.0.1` | 仅本机可连 |
> 安全边界:公网只能到达 nginx。后端 8001 直连会被跳过 Host 头/CORS/HTTPS 约束,因此**切勿把 backend 端口改成公网绑定**。
### 1.3 数据流
```
浏览器 ──443──> nginxTLS/限流/反代)──8001──> backend ──> PostgreSQL 16
└──> Redis
```
---
## 2. 配置体系
项目有两层配置,容易混淆,务必分清:
### 2.1 编排层:`docker/.env`
Docker Compose 使用(`docker compose --env-file .env up -d`)。必填项缺失时 compose 直接报错。
| 变量 | 必填 | 说明 |
|------|------|------|
| `DATABASE_USER` | 否 | 数据库用户(默认 `dpb` |
| `DATABASE_PASSWORD` | **是** | 数据库口令,强随机 |
| `DATABASE_NAME` | 否 | 数据库名(默认 `dpb` |
| `DATABASE_PORT` | 否 | 数据库端口(默认 `5432` |
| `REDIS_PASSWORD` | **是** | Redis 口令,强随机 |
| `SECRET_KEY` | **是** | JWT 签名密钥,`openssl rand -hex 32`,勿用示例值 |
| `BACKEND_PORT` / `HTTP_PORT` / `HTTPS_PORT` | 否 | 默认 8001 / 80 / 443 |
| `DEPLOY_ENV` | 否 | 固定 `prod` |
### 2.2 应用层配置(容器部署:全部收敛到 `docker/.env`
**容器镜像已排除 `backend/env` 目录**(构建镜像不携带任何 .env 文件,防止凭据进镜像),生产全部配置由 `docker-compose` 以环境变量注入后端。因此 **`docker/.env` 是生产唯一配置源**`backend/env/.env.prod` 仅用于裸机/非容器部署。
> 镜像内 `backend/env/.env.prod` 不存在时,`main.py` 的 env_file 加载被跳过,配置完全来自 compose 注入的环境变量(compose 已注入下述全部字段)。
| 变量 | 生产要求 | 说明 |
|------|----------|------|
| `ENVIRONMENT` | `prod` | compose 已注入 |
| `DEBUG` | `false` | 开启则 prod 校验拒绝启动 |
| `SECRET_KEY` | ≥32 位独立随机 | compose 已注入,兜底校验 |
| `PROD_CORS_ORIGINS` | 具体域名,逗号分隔 | 空则拒绝启动 |
| `ALLOWED_HOSTS` | 真实域名列表 | nginx 反代时后端据此校验 Host 头,未配置则请求 403 |
| `IP_TRUST_PROXY_HEADERS` | `true` | 经 nginx 转发时必须开启;公网直连必须 `false` |
| `LOGIN_MAX_FAILURES` 等 | 保持默认 | 登录锁定策略,见 §5 |
| `DATABASE_TYPE` | `postgres` | compose 已注入,固定不变 |
| `DATABASE_*` | — | compose 注入(容器部署时不读 `.env.prod` |
| `REDIS_PASSWORD` | — | compose 注入 |
| `DEV_DEFAULT_PASSWORD` | **必须留空** | 非空 = 把密码明文下发浏览器 |
| `SMTP_*` / `OPENAI_*` | 真实密钥 | 按需在 `docker/.env` 配置 |
### 2.3 加载优先级
容器部署(镜像不含 env 文件):`docker-compose` 注入的环境变量 > `setting.py` 默认值。
裸机部署(本地起 prod):`backend/env/.env.prod` > `setting.py` 默认值。
---
## 3. 数据库(PostgreSQL 16
生产固定使用 PostgreSQL 16,与开发环境(`localhost:5432`,库/角色 `dpb`)一致,可用 `pg_dump`/`pg_restore` 无缝迁移。
`docker/docker-compose.yaml` 已编排 `postgres:16` 服务:
| 配置 | 值 |
|------|-----|
| 镜像 | `postgres:16`(容器名 `postgres` |
| 库名 / 用户 | 默认 `dpb``docker/.env``DATABASE_NAME` / `DATABASE_USER` |
| 口令 | `docker/.env``DATABASE_PASSWORD`(compose 强制必填,缺失即报错) |
| 宿主机端口 | `127.0.0.1:5432`(仅本机可连,公网不可达) |
| 数据卷 | `docker/postgres/data`bind,容器内 `/var/lib/postgresql/data` |
| 健康检查 | `pg_isready`backend 容器依赖其 healthy 才启动) |
首次启动时 postgres 容器自动初始化库与角色(`POSTGRES_DB/USER/PASSWORD`),无需手动建库。后端按 `DATABASE_TYPE=postgres` + `asyncpg` 驱动连接(compose 已注入),首次启动自动建表并导入种子数据(见 §4.3)。
> **数据目录**`docker/postgres/data` 需存在(`deploy.sh` 会自动创建)。postgres 镜像启动时会自动把该目录 chown 到 postgres 用户(uid 999);若手动建目录后遇到权限错误,执行 `sudo chown -R 999:999 docker/postgres/data`。
### 3.1 开发数据迁移到生产
```bash
# 开发机导出(需本机装有 PostgreSQL 客户端 pg_dump
pg_dump -h localhost -p 5432 -U dpb -d dpb --no-owner -F c -f dpb.dump
# 生产导入(进 postgres 容器;生产服务器同样需要 pg_restore)
docker compose exec postgres pg_restore -U dpb -d dpb --no-owner /tmp/dpb.dump
# 或先将 dump 拷入容器:docker compose cp dpb.dump postgres:/tmp/
```
> 注意中文编码:Windows 下 psql 交互易乱码,用 `pg_restore -F c`(二进制格式)导入可规避。生产服务器如缺 `pg_restore`,先 `apt-get install -y postgresql-client`。
---
## 4. 首次上线步骤
### 4.1 服务器准备
- Docker ≥ 20.10 + Docker Compose v2;建议 4 核 / 8GB 起步。
- 防火墙/安全组**只开放 80、443**,其余端口(8001、5432、6379 等)一律不放行。
- 确认服务器时区、磁盘空间(数据库卷建议 ≥50GB,监控磁盘水位)。
### 4.2 配置落地
1. 拷贝项目到服务器(当前仓库未接入 git,直接同步目录)。
2. 准备编排层配置:
```bash
cd docker
cp .env.example .env
chmod 600 .env
# 必改:REDIS_PASSWORD、SECRET_KEYopenssl rand -hex 32)、DATABASE_PASSWORD
```
3. 数据库已是 PostgreSQL 16compose 已编排 `postgres` 服务),无需改编排;`DATABASE_*` 已在步骤 2 配置。
4. 生产配置全部在 `docker/.env`(镜像已排除 `backend/env`,无需改 `.env.prod`):
```bash
# 必改:SECRET_KEYopenssl rand -hex 32)、DATABASE_PASSWORD、REDIS_PASSWORD、
# PROD_CORS_ORIGINS(真实域名)、ALLOWED_HOSTS(真实域名)、SMTP_*/OPENAI_*(按需)
```
5. SSL 证书放入 `docker/nginx/ssl/``server.pem` + `server.key`),生产用正规 CA 证书。
6. 修改 `docker/nginx/nginx.conf` 两处 `server_name` 为真实域名。
7. 构建前端(若单独维护):
```bash
cd frontend/web && npm install && npm run build
cp -r dist ../docker/nginx/web/dist
```
### 4.3 建表与初始数据(自动完成,无需手操)
后端首次启动时 `InitializeData.init_db()` 会:
1. `create_all` **自动创建全部表**(仅建新表,不会修改已存在的表);
2. 导入种子数据:菜单、部门、字典、角色、系统参数、默认用户。
种子用户(`backend/sql/data/sys_user.json`):`super`、`admin`(均为超管)、`user`。
**生产首次初始化时,种子账号的固定弱密码会被自动随机化**(消除公开的 `123456`)。一次性初始密码写入容器 `logs/prod_initial_passwords.txt`(同时打印到后端启动日志),运维需在首次登录后立即修改,并**删除该文件**:
```bash
docker compose exec backend cat /home/logs/prod_initial_passwords.txt # 查看随机初始密码
# 登录修改密码后删除:
docker compose exec backend rm /home/logs/prod_initial_passwords.txt
```
> 表结构变更:`create_all` 只建新表,**不会更新已存在的表**。模型字段变更走 `backend/sql/weld_*.sql` 幂等 ALTER(见 §6.4),不在本机执行;首次上线后新列的 SQL 由运维/开发在升级时统一应用。
### 4.4 构建与启动
```bash
cd docker
docker compose --env-file .env build backend
docker compose --env-file .env up -d
docker compose ps # 全部 healthy 为佳
```
### 4.5 上线验证清单
- [ ] `docker compose ps` 四个服务均 `Up`backend 至少 `starting`→`healthy`
- [ ] 健康检查:`curl -s https://域名/api/v1/common/health/check` 返回 200(就绪含依赖状态:`/api/v1/common/health/ready`
- [ ] 前端 `https://域名/web` 可打开登录页
- [ ] 用 `super`/`admin` 登录成功(初始密码见 §4.3,生产默认开启滑块验证码,登录前需先完成滑块)
- [ ] 登录后立即修改初始密码,并删除 `logs/prod_initial_passwords.txt`
- [ ] 生产文档已关闭:`https://域名/docs` 应返回 404(符合预期)
- [ ] `docker compose exec backend python -c "from app.config.setting import settings; print(settings.SECRET_KEY[:4]+'...')"` 确认不是默认密钥
- [ ] 改掉种子账号默认密码,删除/停用不需要的账号(如 `user`
- [ ] 验证上传:控制台上传一张图片,确认 `backend/static/upload` 有文件且可访问
---
## 5. 安全加固核对表
### 5.1 代码层已加固(本次安全修复,无需重复操作)
| 项 | 加固内容 | 位置 |
|----|----------|------|
| 生产配置 fail-safe | SECRET_KEY 非默认/≥32 位、DEBUG=false、CORS 非空,否则**拒绝启动** | `setting.py` |
| 日志脱敏 | 密码/token/验证码等字段掩码为 `***`refresh/logout 裸 body 整体掩码 | `router_class.py` |
| 登录暴力破解 | 同一用户名+IP 连续失败 5 次锁定 15 分钟(Redis);Redis 故障时自动降级不锁死 | `auth/service.py` |
| 生产关闭文档 | `/docs`、`/redoc` 生产不注册 | `init_app.py` |
| 客户端 IP 防伪造 | 默认不信任 XFF;`IP_TRUST_PROXY_HEADERS=true` 才读取 | `ip_local_util.py` |
| 端口收内网 | 数据库/Redis/后端均绑 `127.0.0.1` | `docker-compose.yaml` |
| 镜像自包含 | Dockerfile `COPY ./backend/`,生产不依赖宿主机代码挂载 | `Dockerfile` |
| Redis 危险命令禁用 | `CONFIG`/`FLUSHALL`/`FLUSHDB` 重命名禁用;`protected-mode yes` | `redis.conf` |
| 镜像排除凭据 | `.dockerignore` 排除 `backend/env``.env.dev`(含真实 SMTP 密码)等不进镜像;生产配置全走 `docker/.env` | `.dockerignore` |
| 容器非 root 运行 | Dockerfile `USER app` + compose `user: 1001:1001`;配合 `cap_drop: ALL`、`no-new-privileges`、`tmpfs /tmp` | `Dockerfile` / `docker-compose.yaml` |
| 种子账号弱口令消除 | 生产首次初始化把公开的 `123456` 种子密码随机化,一次性初始密码写 `logs/prod_initial_passwords.txt` | `initialize.py` |
| HSTS / 权限策略 | nginx 补 `Strict-Transport-Security`、`Permissions-Policy`,移除已废弃的 `X-XSS-Protection` | `nginx.conf` |
| 无用数据库依赖 | 移除 `aiomysql`/`pymysql`/`aiosqlite`(仅 PostgreSQL 驱动 `asyncpg`/`psycopg` | `requirements.txt` / `pyproject.toml` |
### 5.2 运维层待办(上线时逐项落实)
- [ ] `docker/.env` 权限 `chmod 600`,禁止进版本库
- [ ] 防火墙仅放行 80/443
- [ ] 使用正规 CA 的 SSL 证书
- [ ] 从 `logs/prod_initial_passwords.txt` 获取随机初始密码登录,立即修改并删除该文件
- [ ] 数据库账号使用独立强口令,不用 root 直连业务
- [ ] 配置备份任务并做一次恢复演练(§6.2)
---
## 6. 日常运营
### 6.1 启停与状态
```bash
cd docker
docker compose --env-file .env ps # 状态
docker compose --env-file .env up -d # 启动
docker compose --env-file .env restart backend # 仅重启后端
docker compose --env-file .env stop # 停止(保留数据卷)
docker compose --env-file .env down # 停止并移除容器(卷保留)
docker compose logs -f backend # 实时日志
```
### 6.2 备份与恢复
**备份优先级**:数据库 > 上传文件 > 配置 > 前端产物(可重建)> Redis(会话可重建)。
| 数据 | 位置 | 策略 |
|------|------|------|
| 数据库(核心) | 数据库卷 | 每日全量 + 保留 7~14 天,异地一份 |
| 上传文件 | `backend/static/upload`(容器内 `/home/static/upload` | 每日增量同步 |
| 配置 | `docker/.env`、`backend/env/.env.prod` | 每次变更后备份 |
| Redis | 会话/缓存 | 无需备份(丢失仅踢下线) |
**PostgreSQL 每日备份**(宿主机 crontab 示例):
```bash
0 2 * * * cd /opt/dpb/docker && docker compose exec -T postgres pg_dump -U dpb -d dpb --no-owner -F c \
> /backup/dpb_$(date +\%F).dump && \
find /backup -name 'dpb_*.dump' -mtime +14 -delete
```
**恢复演练**(每季度一次,确保备份可用):
```bash
# 临时起一个空库恢复验证:
docker compose exec postgres pg_restore -U dpb -d dpb --no-owner /backup/dpb_最近.dump
# 上传文件:解压覆盖 backend/static/upload 即可
```
### 6.3 日志与监控
- **容器日志**:Docker 已配置轮转(每容器最多 3 个文件 × 10MB)。查看:`docker compose logs --tail=200 backend`。
- **后端应用日志**`backend/logs/`(容器内 `/home/logs`)。
- **监控项**
- 健康检查:`/api/v1/common/health/check`(存活)与 `/api/v1/common/health/ready`(就绪,含 DB/Redis/磁盘,依赖未就绪返回 503);compose 已内置存活探针(15s 间隔,探测 `/common/health/check`
- 磁盘水位(数据库卷最易满)
- 登录失败/锁定:日志关键字 `登录失败次数过多`、`已锁定`
- 资源:compose 已设内存上限(backend 1G / postgres 1G / redis 512M / nginx 256M
### 6.4 升级与表结构变更
**常规升级**(无模型变更):
```bash
cd docker
docker compose --env-file .env build backend # 重新构建新镜像
docker compose --env-file .env up -d --no-deps backend
```
**模型字段变更**`create_all` 只建新表、不更新已存在的表。本项目实际变更约定为**幂等 ALTER SQL 文件**`backend/sql/weld_*.sql``ADD COLUMN IF NOT EXISTS`),新增列均以 psql 手动应用(**Alembic 脚手架存在但从未使用**——`backend/app/alembic/versions/` 为空,勿在本项目启用,与既有 weld 文件重复冲突):
```bash
# 开发机应用(对 dev 库):
PGPASSWORD=dpb psql -h localhost -p 5432 -U dpb -d dpb -w -f backend/sql/weld_xxx.sql
# 生产应用(先进容器,SQL 已随镜像/同步目录带入):
docker compose cp backend/sql/weld_xxx.sql postgres:/tmp/weld_xxx.sql
docker compose exec postgres psql -U dpb -d dpb -f /tmp/weld_xxx.sql
# 清理:
docker compose exec postgres rm /tmp/weld_xxx.sql
```
> 新增列示例:`weld_prediction_rigor.sql`PA/溯源)、`weld_rootstock.sql`(矮化类/砧穗亲和性)、`weld_trait_direction.sql`(方向标注)、`weld_combining_design.sql`(交配设计)——文件幂等,重跑无害,可批量串行应用。**升级前先备份数据库(§6.2)**;涉及大表加列建议维护窗口执行(Postgres 11+ `ADD COLUMN` 默认仅加元数据,不需重写表,风险低)。
### 6.5 密钥轮换
**SECRET_KEY 轮换**:改动会令所有已签发 JWT 失效(全员需重新登录),在维护窗口操作:
```bash
# 1. 备份当前 .env → 2. 生成新密钥写入 docker/.env → 3. 重启 backend → 4. 验证登录
openssl rand -hex 32
docker compose --env-file .env up -d --no-deps backend
```
**REDIS_PASSWORD 轮换**:先改 `docker/.env` 重启 redis,再改 backend 注入(compose 已自动传递),最后重启 backend。
---
## 7. 故障排查
| 症状 | 原因 | 处理 |
|------|------|------|
| backend 启动即退出,日志提示 SECRET_KEY 配置不安全 | `.env` 未设置/长度不足/命中开发默认值 | 生成 ≥32 位随机密钥,写 `docker/.env` 后重建启动 |
| 日志提示 PROD_CORS_ORIGINS 必须配置 | `.env.prod` 未填域名白名单 | 填具体域名(逗号分隔),勿用 `*` |
| backend 连不上数据库 | 连接参数与 postgres 容器不一致(`DATABASE_USER`/`PASSWORD`/`NAME`),或容器未 healthy | `docker compose logs postgres` 看初始化是否成功;核对 `docker/.env` 的 `DATABASE_*` |
| 能登录但请求 403 / 跨域失败 | CORS 白名单没包含当前域名;或 Host 头不在 `ALLOWED_HOSTS` | 核对 `PROD_CORS_ORIGINS`、`ALLOWED_HOSTS` |
| 登录提示"账号已临时锁定" | 连续失败 5 次触发锁定(15 分钟) | 正常防护;等 TTL 或 `redis-cli DEL login:lock:*`(生产禁止清库) |
| 不知道初始密码 | 生产首次初始化已随机化种子密码 | `docker compose exec backend cat /home/logs/prod_initial_passwords.txt`(仅首次初始化生成,若已删除需走数据库重置密码) |
| `/docs` 404 | 生产已关闭文档 | 符合预期;如需排障临时 `DEBUG=true` 后恢复 |
| 日志出现 `unknown command HELLO` | 连到了不支持 RESP3 的旧 Redis | 生产用 `redis:7` 无此问题;连接已固定 `protocol=2` 兜底 |
| 中文内容乱码 | 数据库/迁移窗口编码问题 | 用二进制 dump(`-F c`)导入;后端连接串为 UTF-8,无需额外设置 |
| 上传失败/图片 404 | 上传目录权限 | `docker compose exec backend ls /home/static/upload`,检查卷或 `chown` |
| 公网访问 https 打不开 | 防火墙/安全组未放行 80/443,或证书未配置 | 核对安全组、`docker/nginx/ssl/` 证书、nginx `server_name` |
---
## 8. 应急预案与回滚
### 8.1 回滚镜像
镜像带标签 `backend:${BACKEND_IMAGE_TAG:-3.0.0}`。上一版本镜像仍在本地时:
```bash
# 用旧标签启动(先备份当前 .env 与数据库)
BACKEND_IMAGE_TAG=<上个版本号> docker compose --env-file .env up -d --no-deps backend
```
### 8.2 数据恢复
按 §6.2 备份恢复。注意:恢复会**覆盖当前数据**,恢复前先导出线上现状存档。
### 8.3 紧急下线
```bash
cd docker && docker compose --env-file .env stop # 服务停、数据卷保留
# 或直接封防火墙 80/443(保留进程,便于排查)
```
---
## 9. 常用命令速查
| 操作 | 命令 |
|------|------|
| 查看状态 | `cd docker && docker compose --env-file .env ps` |
| 看后端日志 | `docker compose --env-file .env logs -f backend` |
| 进后端容器 | `docker compose --env-file .env exec backend sh` |
| 数据库 psql | `docker compose --env-file .env exec postgres psql -U dpb -d dpb` |
| Redis 客户端 | `docker compose --env-file .env exec redis redis-cli -a "$REDIS_PASSWORD" ping` |
| 生产健康检查 | `curl -s https://域名/api/v1/common/health/check` |
| 生成新密钥 | `openssl rand -hex 32` |
---
*维护:本手册随配置变更更新;涉及安全项的修改请同步核对 §5 清单。*