Files
certificate-system/docs/正式部署操作手册.md
2026-06-23 12:54:14 +08:00

17 KiB
Raw Blame History

证书系统正式部署操作手册

本文档用于把“培训结业证书管理与查询系统”部署到正式服务器,并完成短信验证码配置、上线验收、日常运维和回滚准备。

适用范围:

  • 正式工程:backend/frontend/docker-compose.yml
  • 不适用于本地验收版:local_app/

1. 部署架构

推荐一期使用单机 Docker Compose

用户浏览器
  -> 域名 HTTPS
  -> 宿主机 Nginx
  -> frontend 容器,端口 8080内置 Nginx负责前端页面和 /api 代理
  -> backend 容器,端口 8000FastAPI
  -> MySQL 8Compose 内置 MySQL 或阿里云 RDS MySQL

正式环境不要直接运行:

python local_app/server.py

正式环境只运行:

docker compose up -d --build

2. 服务器准备

建议配置:

  • 操作系统Ubuntu 22.04 LTS 或同类 Linux。
  • 规格2 核 4G 起步。
  • 磁盘:建议至少 40G最好把 /data 放到数据盘。
  • 域名:已解析到服务器公网 IP。
  • HTTPS 证书:已准备好证书文件。

安装 Docker

apt update
apt install -y docker.io docker-compose-plugin
systemctl enable docker
systemctl start docker
docker --version
docker compose version

创建数据目录:

mkdir -p /data/certificate-system/uploads
mkdir -p /data/certificate-system/error-reports
mkdir -p /data/certificate-system/exports
mkdir -p /data/certificate-system/pdf-cache
mkdir -p /data/backups/mysql

3. 上传代码

推荐目录:

/opt/certificate-system

如果用 Git

cd /opt
git clone <你的仓库地址> certificate-system
cd /opt/certificate-system

如果手动上传压缩包:

cd /opt
unzip certificate-system.zip
cd /opt/certificate-system

4. 配置 .env

Docker Compose 使用项目根目录的 .env,不是 backend/.env

cd /opt/certificate-system
cp .env.example .env
vim .env

4.1 使用 Compose 内置 MySQL

.env 示例:

APP_ENV=production
DATABASE_URL=mysql+pymysql://certificate:请改成业务库强密码@mysql:3306/certificate_system
SECRET_KEY=请改成至少32字节随机字符串
PUBLIC_BASE_URL=https://你的域名
CERTIFICATE_NO_SECRET=请改成另一串随机字符串
PDF_CACHE_DAYS=30
PDF_CLEANUP_DAYS=5
DATA_ROOT=/data/certificate-system
CORS_ORIGINS=https://你的域名

SMS_MODE=local
ALIYUN_ACCESS_KEY_ID=
ALIYUN_ACCESS_KEY_SECRET=
ALIYUN_SMS_SIGN_NAME=
ALIYUN_SMS_TEMPLATE_CODE=

生成随机密钥:

openssl rand -hex 32
openssl rand -hex 32

同时修改 docker-compose.yml 里的 MySQL 密码,保证和 DATABASE_URL 一致:

MYSQL_ROOT_PASSWORD: 请改成root强密码
MYSQL_DATABASE: certificate_system
MYSQL_USER: certificate
MYSQL_PASSWORD: 请改成业务库强密码

4.2 使用阿里云 RDS MySQL

.env 示例:

APP_ENV=production
DATABASE_URL=mysql+pymysql://RDS账号:RDS密码@RDS内网地址:3306/certificate_system
SECRET_KEY=请改成至少32字节随机字符串
PUBLIC_BASE_URL=https://你的域名
CERTIFICATE_NO_SECRET=请改成另一串随机字符串
PDF_CACHE_DAYS=30
PDF_CLEANUP_DAYS=5
DATA_ROOT=/data/certificate-system
CORS_ORIGINS=https://你的域名

SMS_MODE=local
ALIYUN_ACCESS_KEY_ID=
ALIYUN_ACCESS_KEY_SECRET=
ALIYUN_SMS_SIGN_NAME=
ALIYUN_SMS_TEMPLATE_CODE=

RDS 检查点:

  • ECS 和 RDS 在同一个 VPC。
  • RDS 白名单加入 ECS 内网 IP。
  • RDS 中已创建数据库 certificate_system
  • 不建议开放 RDS 公网访问。

如果使用 RDS可以保留 docker-compose.yml 里的 mysql 服务但不使用;更干净的做法是拆一个生产 compose 文件去掉 mysql,一期也可以先保持现状,只要 DATABASE_URL 指向 RDS业务后端就不会连接 Compose 内置 MySQL。

5. 短信验证码配置

系统支持两种短信模式:

SMS_MODE=local

本地测试模式。验证码会打印在后端日志里,不会真的发短信。

SMS_MODE=aliyun

阿里云短信模式。系统会调用阿里云短信服务发送验证码。

5.1 开通阿里云短信服务

在阿里云控制台操作:

  1. 登录阿里云控制台。
  2. 搜索“短信服务”,开通短信服务。
  3. 进入“国内消息”。
  4. 申请短信签名。
  5. 申请短信模板。
  6. 创建 RAM 用户或 RAM 角色,并授予短信服务发送权限。
  7. 创建 AccessKey保存 AccessKey IDAccessKey Secret

安全建议:

  • 不要使用主账号 AccessKey。
  • 使用 RAM 用户,权限只给短信服务需要的最小权限。
  • AccessKey Secret 只会显示一次,要保存到密码管理工具或服务器安全配置中。

5.2 申请短信签名

签名是短信开头的 【xxx】

建议填写:

签名名称:与你公司/品牌/备案主体一致,例如 慧愈文化
适用场景:验证码
签名来源:企事业单位全称或简称
申请说明:用于学员查询培训结业证书时进行手机号验证码验证

审核通过后,把签名名称填到:

ALIYUN_SMS_SIGN_NAME=审核通过的签名名称

注意:这里不要写方括号,例如写 慧愈文化,不要写 【慧愈文化】

5.3 申请短信模板

当前代码发送的模板变量是:

{"code":"123456"}

所以阿里云模板必须包含变量:

${code}

推荐模板内容:

您的验证码是${code}5分钟内有效请勿泄露给他人。

审核通过后,拿到模板 Code例如

SMS_123456789

填到:

ALIYUN_SMS_TEMPLATE_CODE=SMS_123456789

5.4 配置生产 .env

正式发短信时,将 .env 改成:

SMS_MODE=aliyun
ALIYUN_ACCESS_KEY_ID=你的AccessKeyID
ALIYUN_ACCESS_KEY_SECRET=你的AccessKeySecret
ALIYUN_SMS_SIGN_NAME=你的短信签名
ALIYUN_SMS_TEMPLATE_CODE=你的短信模板Code

配置完成后必须重建后端镜像,因为阿里云 SDK 是后端依赖:

docker compose up -d --build backend
docker compose restart frontend

或者整体重建:

docker compose up -d --build

5.5 验证短信配置

先看配置是否进入容器:

docker compose exec backend env | grep -E "SMS_MODE|ALIYUN"

直接从后端容器发一条测试短信到自己的手机号:

docker compose exec backend python - <<'PY'
from app.services.sms import send_sms

phone = "请改成你的手机号"
ok = send_sms(phone, "123456")
print("send result:", ok)
PY

再看日志:

docker compose logs -f backend | grep -E "阿里云短信|短信验证码|未安装依赖"

成功时通常能看到:

[阿里云短信] 发送成功: 138xxxx0000

然后做完整业务验证:

  1. 打开 https://你的域名/query
  2. 切到“短信验证码查询”。
  3. 输入测试手机号。
  4. 获取验证码。
  5. 输入短信验证码。
  6. 查询证书。

5.6 短信常见问题

如果日志出现:

未安装依赖

说明镜像里没有安装阿里云 SDK。确认 backend/requirements.txt 包含:

alibabacloud-dysmsapi20170525==4.5.1

然后执行:

docker compose up -d --build backend

如果发送失败,优先检查:

  • SMS_MODE 是否等于 aliyun
  • ALIYUN_ACCESS_KEY_ID 是否正确。
  • ALIYUN_ACCESS_KEY_SECRET 是否正确。
  • ALIYUN_SMS_SIGN_NAME 是否是审核通过的签名名称,不带 【】
  • ALIYUN_SMS_TEMPLATE_CODE 是否是审核通过的模板 Code。
  • 模板变量是否是 ${code}
  • 阿里云账户是否欠费。
  • RAM 用户是否有短信发送权限。
  • 手机号是否触发阿里云风控。

系统自身限制:

  • 同一 IP每分钟最多 5 次发送短信。
  • 同一手机号5 分钟最多 3 次发送短信。
  • 短信验证码有效期5 分钟。
  • 当前验证码保存在后端进程内存中。单机部署没问题;如果后续扩成多个 backend 实例,需要把验证码存储迁移到 Redis 或数据库,否则不同实例之间验证码不共享。

6. 首次启动

在项目根目录:

cd /opt/certificate-system
docker compose up -d --build
docker compose ps

看后端日志:

docker compose logs -f backend

看前端日志:

docker compose logs -f frontend

7. 初始化数据库

首次部署执行:

docker compose exec backend python -m app.cli init-db --admin-username admin --admin-password "请换成强密码"

这个命令会:

  • 执行 Alembic 数据库迁移。
  • 创建基础角色。
  • 创建默认管理员。
  • 初始化默认项目配置。
  • 初始化系统设置,PDF 生成并发限制 默认值为 2

以后升级代码时,一般执行:

docker compose exec backend alembic upgrade head

8. 配置 HTTPS 和域名

8.1 临时访问

如果只做内部验收,可以临时访问:

http://服务器IP:8080/admin/login

此时安全组需要开放 8080

8.2 正式访问

正式建议宿主机 Nginx 负责 HTTPS反向代理到前端容器 127.0.0.1:8080

宿主机 Nginx 示例:

server {
  listen 80;
  server_name 你的域名;
  return 301 https://$host$request_uri;
}

server {
  listen 443 ssl http2;
  server_name 你的域名;

  ssl_certificate /etc/nginx/certs/fullchain.pem;
  ssl_certificate_key /etc/nginx/certs/privkey.pem;

  client_max_body_size 20m;

  location / {
    proxy_pass http://127.0.0.1:8080;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_connect_timeout 30s;
    proxy_send_timeout 120s;
    proxy_read_timeout 120s;
  }
}

检查并重载:

nginx -t
systemctl reload nginx

安全组建议开放:

22   SSH最好限制办公 IP
80   HTTP
443  HTTPS

正式环境不建议公网开放:

3306 MySQL
8000 后端
8080 前端容器端口

如果宿主机 Nginx 已经代理到 8080,可以在安全组关闭公网 8080

9. 上线验收

9.1 后台登录

访问:

https://你的域名/admin/login

检查:

  • 管理员账号可以登录。
  • 默认密码已改为强密码。
  • 进入“数据概览”没有 401、403、502。

9.2 项目管理

检查:

  • 能新增项目。
  • 能修改项目。
  • 能启用/停用项目。
  • 默认课程名称、阶段名称、发证单位保存后生效。

9.3 学员管理

检查:

  • 能新增学员。
  • 手机号格式正确。
  • 能修改学员。
  • 能删除测试学员。

9.4 证书管理和 PDF

检查:

  • 能新增证书。
  • 证书编号自动生成。
  • 能预览证书。
  • 能下载 PDF。
  • PDF 底图、姓名、日期、课程、导师、二维码清晰。
  • 作废证书后不能下载正常 PDF。
  • 重置链接后旧链接失效。

9.5 Excel 导入

检查模板字段:

姓名
手机号
项目代码
发证日期

检查:

  • 能下载模板。
  • 能上传 Excel。
  • 错误数据能生成错误报告。
  • 校验通过后能确认导入。
  • 确认导入后能生成学员和证书。
  • 能下载导入原文件。

9.6 公开查询

检查:

  • /query 可以打开。
  • 证书编号 + 姓名 + 图形验证码可以查询。
  • 手机号 + 短信验证码可以查询。
  • 有效证书可以下载 PDF。
  • 作废证书显示作废状态。
  • 证书直达链接 /cert/xxx 可以打开。
  • 二维码核验链接 /verify/xxx 可以打开。

10. 日常运维命令

查看容器:

docker compose ps

查看后端日志:

docker compose logs -f backend

查看前端日志:

docker compose logs -f frontend

重启后端:

docker compose restart backend

重启全部服务:

docker compose restart

更新代码:

cd /opt/certificate-system
git pull
docker compose up -d --build
docker compose exec backend alembic upgrade head

停止服务:

docker compose down

不要随便执行:

docker compose down -v

-v 会删除 Docker volume可能导致 Compose 内置 MySQL 数据丢失。

11. 备份

11.1 后台备份管理

正式后台已经提供可视化备份入口:

  • 登录后台。
  • 进入「备份管理」。
  • 点击「立即备份」。
  • 页面会列出本地已有数据库备份、备份时间、文件大小和来源。
  • 需要恢复时,点击对应备份的「回滚」,按弹窗输入确认语。

后台创建的数据库备份保存在:

/data/certificate-system/db-backups

回滚前系统会自动再创建一份当前数据库备份,来源显示为「回滚前自动备份」。这份备份是为了防止选错时间点后还能退回。

11.2 Compose 内置 MySQL 命令兜底

一般不用手写命令。只有在后台打不开、需要紧急从服务器层面导出数据库时,再使用下面的兜底方式:

mkdir -p /data/backups/mysql
cd /opt/certificate-system
docker compose exec -T mysql mysqldump -ucertificate -p你的业务库密码 certificate_system > /data/backups/mysql/certificate_system_$(date +%F).sql

如果后续仍希望服务器自动每天备份,可以加定时任务:

crontab -e

加入:

30 2 * * * cd /opt/certificate-system && docker compose exec -T mysql mysqldump -ucertificate -p你的业务库密码 certificate_system > /data/backups/mysql/certificate_system_$(date +\%F).sql
10 3 * * * find /data/backups/mysql -name "certificate_system_*.sql" -mtime +30 -delete

11.3 RDS MySQL

建议:

  • 开启自动备份。
  • 上线前手动创建一次快照。
  • 大版本升级前手动创建一次快照。
  • 保留至少 7 到 30 天备份。

11.4 文件数据

需要备份:

/data/certificate-system/uploads
/data/certificate-system/error-reports
/data/certificate-system/exports

可以不做长期备份:

/data/certificate-system/pdf-cache

PDF 缓存可以重新生成,核心数据仍以数据库和上传原文件为准。

12. 回滚

上线前记录当前版本:

cd /opt/certificate-system
git rev-parse HEAD

普通代码回滚:

cd /opt/certificate-system
git checkout 上一个稳定commit
docker compose up -d --build

如果本次上线执行过数据库迁移,先不要盲目回滚数据库。正确顺序:

  1. 上线前先备份数据库。
  2. 如果代码回滚后仍能兼容新表结构,只回滚代码。
  3. 如果迁移破坏兼容性,从备份恢复数据库。

恢复 Compose MySQL 备份示例:

cat /data/backups/mysql/certificate_system_YYYY-MM-DD.sql | docker compose exec -T mysql mysql -ucertificate -p你的业务库密码 certificate_system

如果后台可以正常打开,优先使用后台「备份管理」里的可视化回滚,不要直接执行恢复命令。

13. 常见问题排查

13.1 页面打不开

docker compose ps
docker compose logs -f frontend

检查:

  • 前端容器是否启动。
  • 服务器安全组是否开放 80/443。
  • 域名是否解析到服务器。
  • 宿主机 Nginx 是否重载成功。

13.2 后台接口 502

docker compose logs -f backend
docker compose ps

检查:

  • backend 容器是否启动。
  • 数据库是否连接成功。
  • DATABASE_URL 是否正确。
  • MySQL 密码是否和 docker-compose.yml 一致。

13.3 登录后 401 或 403

检查:

  • .envSECRET_KEY 是否重启前后变化。
  • 管理员账号是否 active。
  • 角色是否存在。
  • 是否执行过 init-db

13.4 PDF 中文乱码或不清楚

检查:

  • backend/Dockerfile 是否安装 fonts-noto-cjk
  • backend/app/assets/certificate-template.png 是否存在。
  • 是否重建了后端镜像。

命令:

docker compose exec backend ls -lh /app/app/assets
docker compose up -d --build backend

13.5 短信收不到

先确认是不是还在本地模式:

docker compose exec backend env | grep SMS_MODE

如果输出:

SMS_MODE=local

说明不会真实发送短信,只会打印到日志。

如果是阿里云模式:

docker compose logs -f backend | grep -E "阿里云短信|未安装依赖|发送失败"

重点检查签名、模板、AccessKey、账户余额和 RAM 权限。

14. 交接清单

交接给运维或客户前,至少交付:

  • 服务器 IP。
  • 域名。
  • HTTPS 证书位置。
  • 项目部署目录:/opt/certificate-system
  • 数据目录:/data/certificate-system
  • 数据库类型Compose MySQL 或 RDS。
  • 数据库备份策略。
  • 管理员账号,不要明文写在文档里。
  • .env 变量说明,不要泄露真实密钥。
  • 阿里云短信签名名称和模板 Code。
  • 阿里云 RAM 用户权限说明。
  • 回滚版本 commit。