Files
certificate-system/docs/正式部署操作手册.md

784 lines
16 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.
# 证书系统正式部署操作手册
本文档用于把“培训结业证书管理与查询系统”部署到正式服务器,并完成短信验证码配置、上线验收、日常运维和回滚准备。
适用范围:
- 正式工程:`backend/``frontend/``docker-compose.yml`
- 不适用于本地验收版:`local_app/`
## 1. 部署架构
推荐一期使用单机 Docker Compose
```text
用户浏览器
-> 域名 HTTPS
-> 宿主机 Nginx
-> frontend 容器,端口 8080内置 Nginx负责前端页面和 /api 代理
-> backend 容器,端口 8000FastAPI
-> MySQL 8Compose 内置 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 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.2 RDS MySQL
建议:
- 开启自动备份。
- 上线前手动创建一次快照。
- 大版本升级前手动创建一次快照。
- 保留至少 7 到 30 天备份。
### 11.3 文件数据
需要备份:
```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。