19 KiB
数字化桃育种系统 生产上线与运营手册
版本:3.0.1 适用:生产(Docker Compose)部署 前置阅读:
docker/README.md(部署操作)、doc/桃育种系统模块扩展需求规格.md(功能规格,v2.7,2026-08-04) 本文档基于 2026-08-04 安全加固后的代码与配置撰写,上线前请逐项核对。
1. 部署架构
1.1 组件清单
| 组件 | 技术栈 | 容器/进程 | 说明 |
|---|---|---|---|
| 前端 | Vue(Vite 构建) | 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──> nginx(TLS/限流/反代)──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 开发数据迁移到生产
# 开发机导出(需本机装有 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 配置落地
- 拷贝项目到服务器(当前仓库未接入 git,直接同步目录)。
- 准备编排层配置:
cd docker cp .env.example .env chmod 600 .env # 必改:REDIS_PASSWORD、SECRET_KEY(openssl rand -hex 32)、DATABASE_PASSWORD - 数据库已是 PostgreSQL 16(compose 已编排
postgres服务),无需改编排;DATABASE_*已在步骤 2 配置。 - 生产配置全部在
docker/.env(镜像已排除backend/env,无需改.env.prod):# 必改:SECRET_KEY(openssl rand -hex 32)、DATABASE_PASSWORD、REDIS_PASSWORD、 # PROD_CORS_ORIGINS(真实域名)、ALLOWED_HOSTS(真实域名)、SMTP_*/OPENAI_*(按需) - SSL 证书放入
docker/nginx/ssl/(server.pem+server.key),生产用正规 CA 证书。 - 修改
docker/nginx/nginx.conf两处server_name为真实域名。 - 构建前端(若单独维护):
cd frontend/web && npm install && npm run build cp -r dist ../docker/nginx/web/dist
4.3 建表与初始数据(自动完成,无需手操)
后端首次启动时 InitializeData.init_db() 会:
create_all自动创建全部表(仅建新表,不会修改已存在的表);- 导入种子数据:菜单、部门、字典、角色、系统参数、默认用户。
种子用户(backend/sql/data/sys_user.json):super、admin(均为超管)、user。
生产首次初始化时,种子账号的固定弱密码会被自动随机化(消除公开的 123456)。一次性初始密码写入容器 logs/prod_initial_passwords.txt(同时打印到后端启动日志),运维需在首次登录后立即修改,并删除该文件:
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 构建与启动
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 启停与状态
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 示例):
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
恢复演练(每季度一次,确保备份可用):
# 临时起一个空库恢复验证:
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 升级与表结构变更
常规升级(无模型变更):
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 文件重复冲突):
# 开发机应用(对 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 失效(全员需重新登录),在维护窗口操作:
# 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}。上一版本镜像仍在本地时:
# 用旧标签启动(先备份当前 .env 与数据库)
BACKEND_IMAGE_TAG=<上个版本号> docker compose --env-file .env up -d --no-deps backend
8.2 数据恢复
按 §6.2 备份恢复。注意:恢复会覆盖当前数据,恢复前先导出线上现状存档。
8.3 紧急下线
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 清单。