# 证书系统正式部署操作手册 本文档用于把“培训结业证书管理与查询系统”部署到正式服务器,并完成短信验证码配置、上线验收、日常运维和回滚准备。 适用范围: - 正式工程:`backend/`、`frontend/`、`docker-compose.yml` - 不适用于本地验收版:`local_app/` ## 1. 部署架构 推荐一期使用单机 Docker Compose: ```text 用户浏览器 -> 域名 HTTPS -> 宿主机 Nginx -> frontend 容器,端口 8080,内置 Nginx,负责前端页面和 /api 代理 -> backend 容器,端口 8000,FastAPI -> MySQL 8,Compose 内置 MySQL 或阿里云 RDS MySQL ``` 正式环境不要直接运行: ```bash python local_app/server.py ``` 正式环境只运行: ```bash docker compose up -d --build ``` ## 2. 服务器准备 建议配置: - 操作系统:Ubuntu 22.04 LTS 或同类 Linux。 - 规格:2 核 4G 起步。 - 磁盘:建议至少 40G,最好把 `/data` 放到数据盘。 - 域名:已解析到服务器公网 IP。 - HTTPS 证书:已准备好证书文件。 安装 Docker: ```bash apt update apt install -y docker.io docker-compose-plugin systemctl enable docker systemctl start docker docker --version docker compose version ``` 创建数据目录: ```bash 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. 上传代码 推荐目录: ```text /opt/certificate-system ``` 如果用 Git: ```bash cd /opt git clone <你的仓库地址> certificate-system cd /opt/certificate-system ``` 如果手动上传压缩包: ```bash cd /opt unzip certificate-system.zip cd /opt/certificate-system ``` ## 4. 配置 .env Docker Compose 使用项目根目录的 `.env`,不是 `backend/.env`。 ```bash cd /opt/certificate-system cp .env.example .env vim .env ``` ### 4.1 使用 Compose 内置 MySQL `.env` 示例: ```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= ``` 生成随机密钥: ```bash openssl rand -hex 32 openssl rand -hex 32 ``` 同时修改 `docker-compose.yml` 里的 MySQL 密码,保证和 `DATABASE_URL` 一致: ```yaml MYSQL_ROOT_PASSWORD: 请改成root强密码 MYSQL_DATABASE: certificate_system MYSQL_USER: certificate MYSQL_PASSWORD: 请改成业务库强密码 ``` ### 4.2 使用阿里云 RDS MySQL `.env` 示例: ```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. 短信验证码配置 系统支持两种短信模式: ```env SMS_MODE=local ``` 本地测试模式。验证码会打印在后端日志里,不会真的发短信。 ```env SMS_MODE=aliyun ``` 阿里云短信模式。系统会调用阿里云短信服务发送验证码。 ### 5.1 开通阿里云短信服务 在阿里云控制台操作: 1. 登录阿里云控制台。 2. 搜索“短信服务”,开通短信服务。 3. 进入“国内消息”。 4. 申请短信签名。 5. 申请短信模板。 6. 创建 RAM 用户或 RAM 角色,并授予短信服务发送权限。 7. 创建 AccessKey,保存 `AccessKey ID` 和 `AccessKey Secret`。 安全建议: - 不要使用主账号 AccessKey。 - 使用 RAM 用户,权限只给短信服务需要的最小权限。 - AccessKey Secret 只会显示一次,要保存到密码管理工具或服务器安全配置中。 ### 5.2 申请短信签名 签名是短信开头的 `【xxx】`。 建议填写: ```text 签名名称:与你公司/品牌/备案主体一致,例如 慧愈文化 适用场景:验证码 签名来源:企事业单位全称或简称 申请说明:用于学员查询培训结业证书时进行手机号验证码验证 ``` 审核通过后,把签名名称填到: ```env ALIYUN_SMS_SIGN_NAME=审核通过的签名名称 ``` 注意:这里不要写方括号,例如写 `慧愈文化`,不要写 `【慧愈文化】`。 ### 5.3 申请短信模板 当前代码发送的模板变量是: ```json {"code":"123456"} ``` 所以阿里云模板必须包含变量: ```text ${code} ``` 推荐模板内容: ```text 您的验证码是${code},5分钟内有效,请勿泄露给他人。 ``` 审核通过后,拿到模板 Code,例如: ```text SMS_123456789 ``` 填到: ```env ALIYUN_SMS_TEMPLATE_CODE=SMS_123456789 ``` ### 5.4 配置生产 .env 正式发短信时,将 `.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 是后端依赖: ```bash docker compose up -d --build backend docker compose restart frontend ``` 或者整体重建: ```bash docker compose up -d --build ``` ### 5.5 验证短信配置 先看配置是否进入容器: ```bash docker compose exec backend env | grep -E "SMS_MODE|ALIYUN" ``` 直接从后端容器发一条测试短信到自己的手机号: ```bash docker compose exec backend python - <<'PY' from app.services.sms import send_sms phone = "请改成你的手机号" ok = send_sms(phone, "123456") print("send result:", ok) PY ``` 再看日志: ```bash docker compose logs -f backend | grep -E "阿里云短信|短信验证码|未安装依赖" ``` 成功时通常能看到: ```text [阿里云短信] 发送成功: 138xxxx0000 ``` 然后做完整业务验证: 1. 打开 `https://你的域名/query`。 2. 切到“短信验证码查询”。 3. 输入测试手机号。 4. 获取验证码。 5. 输入短信验证码。 6. 查询证书。 ### 5.6 短信常见问题 如果日志出现: ```text 未安装依赖 ``` 说明镜像里没有安装阿里云 SDK。确认 `backend/requirements.txt` 包含: ```text alibabacloud-dysmsapi20170525==4.5.1 ``` 然后执行: ```bash 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. 首次启动 在项目根目录: ```bash cd /opt/certificate-system docker compose up -d --build docker compose ps ``` 看后端日志: ```bash docker compose logs -f backend ``` 看前端日志: ```bash docker compose logs -f frontend ``` ## 7. 初始化数据库 首次部署执行: ```bash docker compose exec backend python -m app.cli init-db --admin-username admin --admin-password "请换成强密码" ``` 这个命令会: - 执行 Alembic 数据库迁移。 - 创建基础角色。 - 创建默认管理员。 - 初始化默认项目配置。 以后升级代码时,一般执行: ```bash docker compose exec backend alembic upgrade head ``` ## 8. 配置 HTTPS 和域名 ### 8.1 临时访问 如果只做内部验收,可以临时访问: ```text http://服务器IP:8080/admin/login ``` 此时安全组需要开放 `8080`。 ### 8.2 正式访问 正式建议宿主机 Nginx 负责 HTTPS,反向代理到前端容器 `127.0.0.1:8080`。 宿主机 Nginx 示例: ```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; } } ``` 检查并重载: ```bash nginx -t systemctl reload nginx ``` 安全组建议开放: ```text 22 SSH,最好限制办公 IP 80 HTTP 443 HTTPS ``` 正式环境不建议公网开放: ```text 3306 MySQL 8000 后端 8080 前端容器端口 ``` 如果宿主机 Nginx 已经代理到 `8080`,可以在安全组关闭公网 `8080`。 ## 9. 上线验收 ### 9.1 后台登录 访问: ```text https://你的域名/admin/login ``` 检查: - 管理员账号可以登录。 - 默认密码已改为强密码。 - 进入“数据概览”没有 401、403、502。 ### 9.2 项目管理 检查: - 能新增项目。 - 能修改项目。 - 能启用/停用项目。 - 默认课程名称、阶段名称、发证单位保存后生效。 ### 9.3 学员管理 检查: - 能新增学员。 - 手机号格式正确。 - 能修改学员。 - 能删除测试学员。 ### 9.4 证书管理和 PDF 检查: - 能新增证书。 - 证书编号自动生成。 - 能预览证书。 - 能下载 PDF。 - PDF 底图、姓名、日期、课程、导师、二维码清晰。 - 作废证书后不能下载正常 PDF。 - 重置链接后旧链接失效。 ### 9.5 Excel 导入 检查模板字段: ```text 姓名 手机号 项目代码 发证日期 ``` 检查: - 能下载模板。 - 能上传 Excel。 - 错误数据能生成错误报告。 - 校验通过后能确认导入。 - 确认导入后能生成学员和证书。 - 能下载导入原文件。 ### 9.6 公开查询 检查: - `/query` 可以打开。 - 证书编号 + 姓名 + 图形验证码可以查询。 - 手机号 + 短信验证码可以查询。 - 有效证书可以下载 PDF。 - 作废证书显示作废状态。 - 证书直达链接 `/cert/xxx` 可以打开。 - 二维码核验链接 `/verify/xxx` 可以打开。 ## 10. 日常运维命令 查看容器: ```bash docker compose ps ``` 查看后端日志: ```bash docker compose logs -f backend ``` 查看前端日志: ```bash docker compose logs -f frontend ``` 重启后端: ```bash docker compose restart backend ``` 重启全部服务: ```bash docker compose restart ``` 更新代码: ```bash cd /opt/certificate-system git pull docker compose up -d --build docker compose exec backend alembic upgrade head ``` 停止服务: ```bash docker compose down ``` 不要随便执行: ```bash docker compose down -v ``` `-v` 会删除 Docker volume,可能导致 Compose 内置 MySQL 数据丢失。 ## 11. 备份 ### 11.1 后台备份管理 正式后台已经提供可视化备份入口: - 登录后台。 - 进入「备份管理」。 - 点击「立即备份」。 - 页面会列出本地已有数据库备份、备份时间、文件大小和来源。 - 需要恢复时,点击对应备份的「回滚」,按弹窗输入确认语。 后台创建的数据库备份保存在: ```text /data/certificate-system/db-backups ``` 回滚前系统会自动再创建一份当前数据库备份,来源显示为「回滚前自动备份」。这份备份是为了防止选错时间点后还能退回。 ### 11.2 Compose 内置 MySQL 命令兜底 一般不用手写命令。只有在后台打不开、需要紧急从服务器层面导出数据库时,再使用下面的兜底方式: ```bash 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 ``` 如果后续仍希望服务器自动每天备份,可以加定时任务: ```bash crontab -e ``` 加入: ```cron 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 文件数据 需要备份: ```text /data/certificate-system/uploads /data/certificate-system/error-reports /data/certificate-system/exports ``` 可以不做长期备份: ```text /data/certificate-system/pdf-cache ``` PDF 缓存可以重新生成,核心数据仍以数据库和上传原文件为准。 ## 12. 回滚 上线前记录当前版本: ```bash cd /opt/certificate-system git rev-parse HEAD ``` 普通代码回滚: ```bash cd /opt/certificate-system git checkout 上一个稳定commit docker compose up -d --build ``` 如果本次上线执行过数据库迁移,先不要盲目回滚数据库。正确顺序: 1. 上线前先备份数据库。 2. 如果代码回滚后仍能兼容新表结构,只回滚代码。 3. 如果迁移破坏兼容性,从备份恢复数据库。 恢复 Compose MySQL 备份示例: ```bash cat /data/backups/mysql/certificate_system_YYYY-MM-DD.sql | docker compose exec -T mysql mysql -ucertificate -p你的业务库密码 certificate_system ``` 如果后台可以正常打开,优先使用后台「备份管理」里的可视化回滚,不要直接执行恢复命令。 ## 13. 常见问题排查 ### 13.1 页面打不开 ```bash docker compose ps docker compose logs -f frontend ``` 检查: - 前端容器是否启动。 - 服务器安全组是否开放 80/443。 - 域名是否解析到服务器。 - 宿主机 Nginx 是否重载成功。 ### 13.2 后台接口 502 ```bash docker compose logs -f backend docker compose ps ``` 检查: - backend 容器是否启动。 - 数据库是否连接成功。 - `DATABASE_URL` 是否正确。 - MySQL 密码是否和 `docker-compose.yml` 一致。 ### 13.3 登录后 401 或 403 检查: - `.env` 的 `SECRET_KEY` 是否重启前后变化。 - 管理员账号是否 active。 - 角色是否存在。 - 是否执行过 `init-db`。 ### 13.4 PDF 中文乱码或不清楚 检查: - `backend/Dockerfile` 是否安装 `fonts-noto-cjk`。 - `backend/app/assets/certificate-template.png` 是否存在。 - 是否重建了后端镜像。 命令: ```bash docker compose exec backend ls -lh /app/app/assets docker compose up -d --build backend ``` ### 13.5 短信收不到 先确认是不是还在本地模式: ```bash docker compose exec backend env | grep SMS_MODE ``` 如果输出: ```text SMS_MODE=local ``` 说明不会真实发送短信,只会打印到日志。 如果是阿里云模式: ```bash 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。