签入版本

This commit is contained in:
2026-09-16 10:11:44 +08:00
parent bf08cf1248
commit 20d8a85989
205 changed files with 34363 additions and 0 deletions
@@ -0,0 +1,235 @@
# 备份与恢复
## 概述
定期备份是防止数据丢失的最后防线。本文介绍 MySQL 数据库备份、Redis 数据备份、上传文件备份的方案,以及自动化备份调度和恢复操作。
:::tip 备份策略 3-2-1 原则
- **3** 份数据副本(1 份生产 + 2 份备份)
- **2** 种不同存储介质(如本地磁盘 + 远程存储)
- **1** 份异地备份(防机房级灾难)
:::
## MySQL 数据库备份
### 全量备份
```bash
# 使用 mysqldump 备份单个数据库
mysqldump -h 127.0.0.1 -u root -p --single-transaction \
--routines --triggers --events \
djangoadmin_prod > backup_$(date +%Y%m%d_%H%M%S).sql
# 压缩备份
mysqldump -h 127.0.0.1 -u root -p --single-transaction \
djangoadmin_prod | gzip > backup_$(date +%Y%m%d_%H%M%S).sql.gz
```
:::tip --single-transaction 作用
`--single-transaction` 参数确保 InnoDB 表的备份一致性,不锁表,不影响线上服务。
:::
### 增量备份
基于 MySQL binlog 实现增量备份:
```bash
# 查看当前 binlog 位置
mysql -u root -p -e "SHOW MASTER STATUS;"
# 备份 binlog
mysqlbinlog --start-datetime="2026-09-05 00:00:00" \
mysql-bin.000001 > binlog_incremental.sql
```
:::tip binlog 配置
确保 MySQL 配置中启用了 binlog:
```ini
[mysqld]
server-id = 1
log-bin = mysql-bin
binlog-format = ROW
expire_logs_days = 7
```
:::
### 仅备份表结构
```bash
# 仅备份结构(不含数据)
mysqldump -h 127.0.0.1 -u root -p --no-data \
djangoadmin_prod > schema_$(date +%Y%m%d).sql
```
## Redis 数据备份
### RDB 快照
```bash
# 手动触发 RDB 快照
redis-cli BGSAVE
# 查看最后一次快照时间
redis-cli LASTSAVE
# 复制 RDB 文件
cp /var/lib/redis/dump.rdb /data/backup/redis_$(date +%Y%m%d).rdb
```
### AOF 持久化
```bash
# redis.conf 配置
appendonly yes
appendfsync everysec
# 备份 AOF 文件
cp /var/lib/redis/appendonly.aof /data/backup/redis_$(date +%Y%m%d).aof
```
:::tip Redis 备份建议
Redis 用于缓存和 token 黑名单,数据可从数据库重建。建议使用 RDB 快照即可,AOF 可选。
:::
## 上传文件备份
```bash
# 打包上传目录
tar -czf uploads_$(date +%Y%m%d).tar.gz /data/apps/uploads/
# 使用 rsync 增量同步到远程
rsync -avz --delete /data/apps/uploads/ backup@10.0.2.100:/data/backup/uploads/
```
## 自动化备份
### 备份脚本
创建 `/data/scripts/backup.sh`:
```bash
#!/bin/bash
# 备份目录
BACKUP_DIR="/data/backup"
DATE=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=7
# 创建备份目录
mkdir -p ${BACKUP_DIR}/{mysql,redis,uploads}
# MySQL 备份
mysqldump -h 127.0.0.1 -u djangoadmin -p'<密码>' \
--single-transaction djangoadmin_prod | gzip > \
${BACKUP_DIR}/mysql/backup_${DATE}.sql.gz
# Redis 备份
redis-cli BGSAVE
sleep 5
cp /var/lib/redis/dump.rdb ${BACKUP_DIR}/redis/redis_${DATE}.rdb
# 上传文件备份
tar -czf ${BACKUP_DIR}/uploads/uploads_${DATE}.tar.gz /data/apps/uploads/
# 清理过期备份
find ${BACKUP_DIR} -type f -mtime +${RETENTION_DAYS} -delete
echo "[$(date)] Backup completed" >> /var/log/backup.log
```
### Crontab 配置
```bash
# 编辑定时任务
crontab -e
# 每天凌晨 2 点执行备份
0 2 * * * /bin/bash /data/scripts/backup.sh >> /var/log/backup.log 2>&1
# 每周日凌晨 3 点执行全量备份
0 3 * * 0 /bin/bash /data/scripts/backup_full.sh >> /var/log/backup.log 2>&1
```
:::tip Crontab 说明
```
分 时 日 月 周 命令
0 2 * * * /bin/bash /data/scripts/backup.sh
│ │ │ │ │
│ │ │ │ └── 星期几 (0-7, 0和7都是周日)
│ │ │ └──── 月份 (1-12)
│ │ └─────── 日 (1-31)
│ └────────── 时 (0-23)
└───────────── 分 (0-59)
```
:::
## 数据恢复
### MySQL 恢复
```bash
# 从 SQL 文件恢复
mysql -h 127.0.0.1 -u root -p djangoadmin_prod < backup_20260905.sql
# 从压缩文件恢复
gunzip < backup_20260905.sql.gz | mysql -h 127.0.0.1 -u root -p djangoadmin_prod
```
### Redis 恢复
```bash
# 停止 Redis
redis-cli SHUTDOWN NOSAVE
# 替换 RDB 文件
cp /data/backup/redis/redis_20260905.rdb /var/lib/redis/dump.rdb
# 启动 Redis
redis-server /etc/redis/redis.conf
```
### 上传文件恢复
```bash
# 解压恢复
tar -xzf uploads_20260905.tar.gz -C /
```
## 恢复演练
:::warning 重要
定期进行恢复演练,验证备份文件的完整性和可用性。建议每季度至少演练一次。
:::
```bash
# 恢复演练步骤
# 1. 在测试环境创建空库
mysql -u root -p -e "CREATE DATABASE djangoadmin_restore;"
# 2. 恢复备份
mysql -u root -p djangoadmin_restore < backup_20260905.sql
# 3. 验证数据完整性
mysql -u root -p -e "
SELECT 'user' AS tbl, COUNT(*) AS cnt FROM djangoadmin_restore.tornado_user
UNION ALL
SELECT 'article', COUNT(*) FROM djangoadmin_restore.tornado_article;
"
# 4. 清理测试库
mysql -u root -p -e "DROP DATABASE djangoadmin_restore;"
```
## 备份监控
```bash
# 检查备份文件是否存在且非空
find /data/backup/mysql -name "*.sql.gz" -mtime -1 -size +0 | wc -l
# 返回 0 则告警
# 检查备份文件大小
ls -lh /data/backup/mysql/backup_$(date +%Y%m%d)*.sql.gz
```
## 总结
备份方案覆盖 MySQL(mysqldump 全量 + binlog 增量)、Redis(RDB 快照)、上传文件(tar 打包 + rsync 同步)。通过 Crontab 实现自动化调度,保留最近 7 天备份。关键要点:备份文件异地存储、定期恢复演练验证完整性、监控备份任务执行状态。
@@ -0,0 +1,83 @@
# 跨库数据迁移
:::tip 说明
项目提供 `scripts/migrate_db.py` 脚本,支持在不同数据库之间迁移数据(如 MySQL → PostgreSQL / SQL Server / SQLite / Oracle)。脚本独立运行,参数显式给出,不依赖 `.env`。
:::
## 使用场景
| 场景 | 说明 |
|:-----|:-----|
| MySQL → PostgreSQL | 生产环境切换数据库 |
| MySQL → SQL Server | 企业环境适配 |
| MySQL → SQLite | 开发/测试环境轻量化 |
| MySQL → Oracle | 企业级数据库适配 |
## 命令行参数
```bash
python scripts/migrate_db.py --help
```
| 参数 | 必填 | 说明 | 示例 |
|:-----|:-----|:-----|:-----|
| `--source-db` | 是 | 源数据库类型 | `mysql` |
| `--target-db` | 是 | 目标数据库类型 | `postgresql` |
| `--source-url` | 是 | 源数据库连接 URL | `mysql+pymysql://root:123456@localhost:3306/db` |
| `--target-url` | 是 | 目标数据库连接 URL | `postgresql://postgres:123456@localhost:5432/db` |
## 使用示例
### MySQL → PostgreSQL
```bash
python scripts/migrate_db.py \
--source-db mysql \
--target-db postgresql \
--source-url "mysql+pymysql://root:123456@localhost:3306/djangoadmin" \
--target-url "postgresql://postgres:123456@localhost:5432/djangoadmin"
```
### MySQL → SQLite
```bash
python scripts/migrate_db.py \
--source-db mysql \
--target-db sqlite \
--source-url "mysql+pymysql://root:123456@localhost:3306/djangoadmin" \
--target-url "sqlite:///djangoadmin.db"
```
## 迁移流程
```
1. 连接源数据库和目标数据库
2. 读取源数据库所有表结构
3. 在目标数据库创建对应表(Base.metadata.create_all)
4. 逐表读取源数据 → 批量插入目标数据库
5. 输出迁移统计(表数、行数、耗时)
```
## 注意事项
:::warning 迁移前备份
跨库迁移前务必备份目标数据库,避免数据丢失。
:::
- 迁移脚本会先在目标库创建表结构(如已存在则跳过)
- 数据迁移采用批量插入,大表迁移可能需要较长时间
- 不同数据库的字段类型差异由 SQLAlchemy 方言层自动处理
- 迁移完成后建议在目标库执行 `scripts/seed_dict.py` 补充字典数据
## 相关脚本
| 脚本 | 用途 |
|:-----|:-----|
| `scripts/migrate_db.py` | 跨库数据迁移 |
| `scripts/seed_dict.py` | 字典数据初始化 |
| `scripts/init_db.py` | 数据库初始化(建库建表) |
| `scripts/health_check.py` | 健康检查 |
## 总结
跨库数据迁移脚本支持 MySQL / PostgreSQL / SQL Server / SQLite / Oracle 之间的数据迁移,参数显式给出,不依赖 `.env` 配置。迁移前务必备份目标数据库。
@@ -0,0 +1,459 @@
# 数据库迁移
## 概述
使用 Alembic 管理数据库结构迁移,同时提供跨库数据迁移脚本,支持从 MySQL 迁移到 PostgreSQL / SQL Server / SQLite / Oracle 等多种数据库。
:::tip 迁移工具链
- **结构迁移**:Alembic(自动生成 DDL 差异)
- **数据迁移**:`scripts/migrate_db.py`(跨库数据泵)
- **初始化**:`scripts/init_db.py`(全新安装一键建库)
:::
## 全新安装初始化
首次部署时,使用初始化脚本一键完成建库、建表、Alembic stamp:
```bash
# 方式一:直接运行脚本
python scripts/init_db.py
# 方式二:Make 命令
make db-init
```
:::tip 初始化流程
1. 检测并创建数据库(如不存在)
2. 执行 `Base.metadata.create_all()` 建表
3. 导入基础 SQL 数据(如有)
4. Alembic stamp head(标记当前为最新版本)
:::
## Alembic 结构迁移
### 生成迁移脚本
修改模型后,执行以下命令自动生成迁移脚本:
```bash
# 生成增量迁移脚本
alembic revision --autogenerate -m "描述变更内容"
# 或使用 Make 命令
make migrate-revision
```
:::tip 注意事项
- 执行前确保数据库连接正常(`.env` 中的数据库配置正确)
- 自动生成的脚本需人工审查,确认无误后再执行
- 迁移脚本位于 `migrations/versions/` 目录
:::
### 应用迁移
```bash
# 应用所有待执行的迁移
alembic upgrade head
# 或使用 Make 命令
make migrate-upgrade
```
### 回滚迁移
```bash
# 回滚最近一次迁移
alembic downgrade -1
# 回滚到指定版本
alembic downgrade <revision_id>
```
### 查看迁移状态
```bash
# 查看当前版本
alembic current
# 查看迁移历史
alembic history
# 查看待执行的迁移
alembic history --indicate-current
```
### 手动标记版本
对于已有数据库,手动标记为最新版本:
```bash
alembic stamp head
# 或使用 Make 命令
make migrate-stamp
```
## 跨库数据迁移
将源库(通常 MySQL)的结构和数据整体迁移到目标库(PostgreSQL / SQL Server / SQLite / Oracle)。
### 脚本说明
`scripts/migrate_db.py` 是项目内置的跨库迁移脚本,核心特性:
- **目标库结构**:用模型 metadata 生成(`Base.metadata.create_all`),方言可移植,模型即 head
- **数据泵**:按拓扑序逐表 `SELECT ... ORDER BY id`,显式带 id 批量插入,保住无外键裸列引用(`user.dept_id`、`role_menu.role_id/menu_id`、`menu.parent_id` 等)
- **自增序列重置**:迁移后按方言自动处理(详见下文)
- **进度提示**:全部耗时步骤带缓冲提示(`[·]` 请勿中断 → `[✓]` 完成),不会显得卡死
- **连接独立**:源/目标连接全部由 CLI 显式参数给出,不依赖 `.env`,也无需先切 `DB_DRIVER`
### 前置准备
1. 源库(MySQL)可正常访问
2. 目标库所需驱动已包含在 `requirements.txt`:
| 目标库 | 驱动包 | 说明 |
|:----|:----|:----|
| PostgreSQL | psycopg[binary] | psycopg3 |
| SQL Server | pymssql | - |
| SQLite | 内置 | 无需额外驱动 |
| Oracle | oracledb | thin 模式,无需安装 Oracle Client |
```bash
pip install -r requirements.txt
```
### 驱动与默认端口
| 目标库 | `--dst-driver` | 默认端口 | `--dst-db` 填什么 |
|:----|:----|:----|:----|
| MySQL | `mysql` | 3306 | 数据库名 |
| PostgreSQL | `postgresql` | 5432 | 数据库名(含点号会自动双引号) |
| SQL Server | `mssql` | 1433 | 数据库名 |
| SQLite | `sqlite` | 无端口 | SQLite 文件路径(相对/绝对皆可) |
| Oracle | `oracle` | 1521 | SERVICE_NAME(如 ORCLPDB1、XE,非库名) |
驱动别名:`postgres` / `pg` → `postgresql`;`sqlserver` / `sql_server` → `mssql`;`sqlite3` → `sqlite`
### 迁移流程
迁移分 5 个阶段自动执行:
```
1. 建目标库(create_database_if_missing)
├─ MySQL: CREATE DATABASE IF NOT EXISTS
├─ PG: 连接 postgres 维护库 → CREATE DATABASE
├─ MSSQL: 连接 master 库 → CREATE DATABASE
├─ Oracle: 不建库,只清理旧表(schema 即连接用户)
└─ SQLite: 创建文件目录 + 可选删旧文件
2. 建表结构(Base.metadata.create_all)
└─ 方言适配,自动生成对应数据库的 DDL
3. 逐表泵数据(拓扑序,分块 executemany)
└─ 显式带 id 插入,保住引用完整性
4. 重置自增序列(按方言处理)
├─ PostgreSQL: setval(seq, MAX(id))
├─ SQL Server: IDENTITY_INSERT ON + DBCC CHECKIDENT RESEED
├─ Oracle: 补 sequence + BEFORE INSERT 触发器
└─ MySQL/SQLite: 自动跟随,无需处理
5. 逐表 COUNT(*) 校验
└─ source vs target 行数一致 → PASS
```
### 常用参数
| 参数 | 说明 |
|:----|:----|
| `--src-driver` / `--src-host` / `--src-port` / `--src-db` / `--src-user` / `--src-pass` | 源库连接参数 |
| `--dst-driver` / `--dst-host` / `--dst-port` / `--dst-db` / `--dst-user` / `--dst-pass` | 目标库连接参数 |
| `--dry-run` | 干跑模式,只连源库逐表数行数,不碰目标库 |
| `--drop-target-first` | 迁移前删除目标库所有表(幂等重跑) |
| `--only-tables a,b` | 仅迁移指定表(续传用) |
| `--skip-existing` | 目标表行数已等于源表则跳过 |
| `--skip-verify` | 跳过迁移后的逐表行数校验 |
| `--chunk-size N` | 每批插入行数,默认 1000 |
| `--single-transaction` | 全部表包进一个大事务(默认每表一个事务) |
口令脱敏:`--src-pass` / `--dst-pass` 留空时回落环境变量 `SRC_DB_PASS` / `DST_DB_PASS`,避免口令进 shell 历史。
### 干跑示例
迁移前建议先干跑核对计划:
```bash
# 干跑:核对源库表和行数
python scripts/migrate_db.py --dry-run \
--src-driver=mysql --src-host=127.0.0.1 --src-port=3306 \
--src-user=root --src-pass=root --src-db=djangoadmin.tornado.elevue2
# 或使用 Make 命令
make db-migrate-dry-run ARGS="\
--src-driver=mysql --src-host=127.0.0.1 --src-port=3306 \
--src-user=root --src-pass=root --src-db=djangoadmin.tornado.elevue2"
```
### MySQL → PostgreSQL
```bash
python scripts/migrate_db.py \
--src-driver=mysql --src-host=127.0.0.1 --src-port=3306 \
--src-db=djangoadmin.tornado.elevue2 --src-user=root --src-pass=root \
--dst-driver=postgresql --dst-host=localhost --dst-port=5432 \
--dst-db=djangoadmin.tornado.elevue2 --dst-user=postgres --dst-pass=你的密码 \
--drop-target-first
```
输出示例:
```
==============================================================
[迁移开始] 源: mysql:djangoadmin.tornado.elevue2 → 目标: postgresql:djangoadmin.tornado.elevue2
[迁移开始] 待迁移 23 张表
==============================================================
[·] [1/5] 建目标库...
· 正在连接 postgres 维护库...
· 正在创建目标库 "djangoadmin.tornado.elevue2"(UTF8)...
[✓] [1/5] 建目标库 完成(耗时 947ms)
[·] [2/5] 建表结构(23 张表 create_all)...
[✓] [2/5] 建表结构 完成(耗时 1.1s),23 张表
[·] [3/5] 逐表迁移数据...
· [1/23] 正在迁移 tornado_dept...
[✓ tornado_dept] 共 23 行(耗时 12ms)
· [3/23] 正在迁移 tornado_city...
[tornado_city] +1000(累计 1000)
[tornado_city] +1000(累计 2000)
[tornado_city] +1000(累计 3000)
[tornado_city] +1000(累计 4000)
[tornado_city] +150(累计 4150)
[✓ tornado_city] 共 4150 行(耗时 850ms)
...
[✓] [3/5] 逐表迁移数据 完成(耗时 3.5s),共 4843 行
[·] [4/5] 重置自增序列...
[✓] [4/5] 重置自增序列 完成(耗时 412ms) ← PG setval 到 MAX(id)
[·] [5/5] 逐表行数校验...
[tornado_dept] source=23 target=23 PASS
[tornado_city] source=4150 target=4150 PASS
...
[✓] [5/5] 逐表行数校验 完成(耗时 103ms),23 张表全部 PASS
==============================================================
[迁移完成] 成功:4843 行 / 23 张表 / 总耗时 6.0s
==============================================================
```
### MySQL → SQL Server
```bash
python scripts/migrate_db.py \
--src-driver=mysql --src-host=127.0.0.1 --src-port=3306 \
--src-db=djangoadmin.tornado.elevue2 --src-user=root --src-pass=root \
--dst-driver=mssql --dst-host=localhost --dst-port=1433 \
--dst-db=djangoadmin.tornado.elevue2 --dst-user=sa --dst-pass=你的密码 \
--drop-target-first
```
:::tip SQL Server 特殊处理
迁移时自动 `SET IDENTITY_INSERT ON` 保留原 id,迁移后 `DBCC CHECKIDENT RESEED` 重置自增。
:::
输出示例:
```
==============================================================
[迁移开始] 源: mysql → 目标: mssql:djangoadmin.tornado.elevue2
[迁移开始] 待迁移 23 张表
==============================================================
[·] [1/5] 建目标库...
· 正在连接 SQL Server(master 维护库)...
· 正在删除旧目标库 [djangoadmin.tornado.elevue2](SINGLE_USER + DROP)...
· 正在创建目标库 [djangoadmin.tornado.elevue2]...
[✓] [1/5] 建目标库 完成(耗时 304ms)
[·] [2/5] 建表结构(23 张表 create_all)...
[✓] [2/5] 建表结构 完成(耗时 743ms),23 张表
[·] [3/5] 逐表迁移数据...
...
[✓ tornado_city] 共 4150 行(耗时 3.9s)
...
[✓] [3/5] 逐表迁移数据 完成(耗时 5.3s),共 4843 行
[·] [4/5] 重置自增序列...
[✓] [4/5] 重置自增序列 完成(耗时 90ms)
[·] [5/5] 逐表行数校验...
[✓] [5/5] 逐表行数校验 完成(耗时 100ms),23 张表全部 PASS
==============================================================
[迁移完成] 成功:4843 行 / 23 张表 / 总耗时 6.6s
==============================================================
```
### MySQL → SQLite
```bash
python scripts/migrate_db.py \
--src-driver=mysql --src-host=127.0.0.1 --src-port=3306 \
--src-db=djangoadmin.tornado.elevue2 --src-user=root --src-pass=root \
--dst-driver=sqlite --dst-db=D:/data/djangoadmin.tornado.elevue2.db \
--drop-target-first
```
:::tip SQLite 说明
- `--dst-db` 即数据库文件完整路径(相对/绝对皆可)
- SQLite 无建库概念,`--drop-target-first` 时删除旧文件后重建
- 自增 id 由 rowid 自动跟随,无需重置序列
:::
输出示例:
```
==============================================================
[迁移开始] 源: mysql → 目标: sqlite:D:/data/djangoadmin.tornado.elevue2.db
[迁移开始] 待迁移 23 张表
==============================================================
[·] [1/5] 建目标库...
· 检查/准备 sqlite 文件...
· 删除旧 sqlite 文件...
[✓] [1/5] 建目标库 完成(耗时 8ms)
[·] [2/5] 建表结构(23 张表 create_all)...
[✓] [2/5] 建表结构 完成(耗时 96ms),23 张表
[·] [3/5] 逐表迁移数据...
[✓ tornado_city] 共 4150 行(耗时 850ms)
...
[✓] [3/5] 逐表迁移数据 完成(耗时 1.2s),共 4843 行
[·] [4/5] 重置自增序列...
· MySQL/SQLite 自动跟随,无需处理
[✓] [4/5] 重置自增序列 完成(耗时 0ms)
[·] [5/5] 逐表行数校验...
[✓] [5/5] 逐表行数校验 完成(耗时 100ms),23 张表全部 PASS
==============================================================
[迁移完成] 成功:4843 行 / 23 张表 / 总耗时 1.4s
==============================================================
```
### MySQL → Oracle
```bash
python scripts/migrate_db.py \
--src-driver=mysql --src-host=127.0.0.1 --src-port=3306 \
--src-db=djangoadmin.tornado.elevue2 --src-user=root --src-pass=root \
--dst-driver=oracle --dst-host=localhost --dst-port=1521 \
--dst-db=ORCLPDB1 --dst-user=tornado --dst-pass=你的密码 \
--drop-target-first
```
:::warning Oracle 特殊说明
- Oracle 无"数据库名",`--dst-db` 填 **SERVICE_NAME**(如 `ORCLPDB1`、`XE`)
- schema 即 `--dst-user` 连接的用户(无需也无法"建库",连上即视为就绪)
- 该用户需有 `CONNECT`、`RESOURCE` 权限
- `create_all` 对 Integer 主键只生成 `INTEGER NOT NULL`(无 identity),迁移后脚本自动补 sequence(`START WITH MAX(id)+1`)+ `BEFORE INSERT` 触发器
- `--drop-target-first` 时尽力 `DROP TABLE ... PURGE` 清理旧表
:::
输出示例:
```
==============================================================
[迁移开始] 源: mysql → 目标: oracle:ORCLPDB1
[迁移开始] 待迁移 23 张表
==============================================================
[·] [1/5] 建目标库...
· 正在连接 Oracle 服务 ORCLPDB1(schema=tornado)...
· drop-first:清理 23 张旧项目表(DROP TABLE ... PURGE)...
[✓] [1/5] 建目标库 完成(耗时 4.6s)
[·] [2/5] 建表结构(23 张表 create_all)...
[✓] [2/5] 建表结构 完成(耗时 2.4s),23 张表
[·] [3/5] 逐表迁移数据...
[✓ tornado_city] 共 4150 行(耗时 2m12s)
...
[✓] [3/5] 逐表迁移数据 完成(耗时 2m41s),共 4843 行
[·] [4/5] 重置自增序列... ← 补 sequence + BEFORE INSERT 触发器
[✓] [4/5] 重置自增序列 完成(耗时 6.2s)
[·] [5/5] 逐表行数校验...
[✓] [5/5] 逐表行数校验 完成(耗时 2.6s),23 张表全部 PASS
==============================================================
[迁移完成] 成功:4843 行 / 23 张表 / 总耗时 2m54s
==============================================================
```
### 幂等重跑
迁移失败或需重跑时,加 `--drop-target-first` 可安全重跑:
```bash
python scripts/migrate_db.py ... --drop-target-first
```
:::warning 警告
`--drop-target-first` 会删除目标库所有表(Oracle 清旧表、SQLite 删旧文件),仅在确认目标库无生产数据时使用。
:::
### 断点续传
迁移过程被中断时,已迁移的表按每表一个事务提交,未迁完的表可配合 `--only-tables` 和 `--skip-existing` 断点续传:
```bash
# 只迁移未完成的表
python scripts/migrate_db.py ... --only-tables user,role,article --skip-existing
```
### 迁移拓扑顺序
数据泵按拓扑序迁移,确保引用完整性:
```
基础表(先迁移):dept, menu, role, level, position, city, category, dict, config, ...
依赖表(后迁移):role_menu, dict_item, config_item, user, user_role, article, notice, ...
```
:::tip 无外键设计
数据库不使用物理外键,依赖关系通过业务代码维护。迁移时显式带 id 插入,保住引用完整性。
:::
### 各驱动自增序列处理
| 驱动 | 处理方式 | 说明 |
|:----|:----|:----|
| PostgreSQL | `setval(pg_get_serial_sequence(...), MAX(id))` | SERIAL 序列重置到最大值 |
| SQL Server | `IDENTITY_INSERT ON` + `DBCC CHECKIDENT RESEED` | 显式插 id + 重置种子 |
| Oracle | 补 sequence + BEFORE INSERT 触发器 | `create_all` 无原生自增,脚本自动补建 |
| MySQL / SQLite | 自动跟随 | 显式插 id 后 auto_increment / rowid 自动跟随 |
如需手动修复自增:
```sql
-- PostgreSQL
SELECT setval(pg_get_serial_sequence('tornado_user', 'id'), (SELECT max(id) FROM tornado_user));
-- SQL Server
DBCC CHECKIDENT ('tornado_user', RESEED, <max_id>)
-- Oracle
DROP SEQUENCE tornado_user_id_seq;
CREATE SEQUENCE tornado_user_id_seq START WITH <max_id+1>;
```
## 常见问题与排错
| 问题 | 原因 | 解决方案 |
|:----|:----|:----|
| 目标库数据叠加/主键冲突 | 未加 `--drop-target-first`,已有数据与迁移数据冲突 | 加 `--drop-target-first` 重跑 |
| 迁移后报 "no such table" | `--dst-db` 指向错误 | Oracle 填 SERVICE_NAME,SQLite 填文件路径 |
| 中文乱码 | 目标库编码非 UTF8 | 删除重建(`--drop-target-first`),PG 建库强制 UTF8 |
| Oracle 新增记录 id 异常 | sequence 未正确创建 | 脚本自动补建;若手动建过同名 sequence 会先删后建 |
| 自增 id 未延续原值 | 序列未重置 | 参考上表手动修复 |
| 迁移中断 | 网络或超时 | 配合 `--only-tables` + `--skip-existing` 断点续传 |
## 常见操作速查
| 场景 | 命令 |
|------|------|
| 全新安装 | `make db-init` |
| 模型变更后生成迁移 | `make migrate-revision` |
| 应用迁移 | `make migrate-upgrade` |
| 回滚一次迁移 | `alembic downgrade -1` |
| 标记已有库为最新 | `make migrate-stamp` |
| 跨库迁移干跑 | `make db-migrate-dry-run ARGS="..."` |
| 跨库迁移执行 | `make db-migrate ARGS="..."` |
## 总结
数据库迁移分为结构迁移(Alembic)和数据迁移(`migrate_db.py`)两部分。全新安装用 `make db-init`,模型变更用 Alembic 自动生成迁移脚本,跨库切换用 `migrate_db.py` 数据泵。建议每次迁移前先干跑确认,生产环境务必做好备份。
@@ -0,0 +1,206 @@
:::tip 本章概要
从服务器准备到应用上线的完整部署操作清单,适用于生产环境首次部署和后续版本更新。
:::
# 部署 Checklist
本章提供从服务器准备到应用上线的完整部署操作清单,适用于生产环境首次部署和后续版本更新。
## 首次部署 Checklist
### 第 1 阶段:服务器准备
- [ ] 服务器操作系统:CentOS 7+ / Ubuntu 20.04+ / Debian 11+
- [ ] 防火墙开放端口:80(HTTP)、443(HTTPS)、22(SSH)
- [ ] 创建部署用户(非 root)
- [ ] 配置 SSH 密钥登录
- [ ] 配置时区:`timedatectl set-timezone Asia/Shanghai`
### 第 2 阶段:环境安装
**Python 环境**:
- [ ] 安装 Python 3.12+
- [ ] 安装 pip:`apt install python3-pip` 或 `yum install python3-pip`
- [ ] 创建虚拟环境:`python3 -m venv /opt/venv`
- [ ] 激活虚拟环境:`source /opt/venv/bin/activate`
- [ ] 安装依赖:`pip install -r requirements.txt`
**数据库**:
- [ ] 安装 MySQL 8.0+
- [ ] 创建数据库:`CREATE DATABASE djangoadmin.tornado.elevue2 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;`
- [ ] 创建数据库用户并授权
- [ ] 修改 `.env` 中的数据库配置
- [ ] 初始化数据库:`python scripts/init_db.py`
**Redis**:
- [ ] 安装 Redis 6.0+
- [ ] 配置密码
- [ ] 修改 `.env` 中的 Redis 配置
- [ ] 测试连接:`redis-cli ping`
**Node.js(前端构建)**:
- [ ] 安装 Node.js 18+
- [ ] 安装 pnpm:`npm install -g pnpm`
- [ ] 安装前端依赖:`cd ui && pnpm install`
- [ ] 构建前端:`pnpm build`
### 第 3 阶段:应用部署
**后端**:
- [ ] 上传项目代码到服务器
- [ ] 配置 `.env` 文件(数据库、Redis、JWT_SECRET 等)
- [ ] 生成 JWT 密钥:`python -c "import secrets; print(secrets.token_urlsafe(48))"`
- [ ] 测试启动:`python app.py`
- [ ] 配置 Supervisor 进程管理
- [ ] 启动后端服务:`supervisorctl start djangoadmin-tornado`
**前端**:
- [ ] 构建产物上传到服务器(`ui/dist/` 目录)
- [ ] 配置 Nginx 指向前端产物目录
- [ ] 配置 Nginx 反向代理后端 API
### 第 4 阶段:Nginx 配置
```nginx
server {
listen 80;
server_name your-domain.com;
# 前端静态文件
root /opt/project/ui/dist;
index index.html;
# 前端路由
location / {
try_files $uri $uri/ /index.html;
}
# 后端 API 反向代理
location /api/ {
proxy_pass http://127.0.0.1:8041;
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;
}
# 上传文件访问
location /uploads/ {
alias /opt/project/uploads/;
expires 30d;
}
}
```
- [ ] 配置 Nginx
- [ ] 测试 Nginx 配置:`nginx -t`
- [ ] 重载 Nginx:`nginx -s reload`
### 第 5 阶段:SSL 证书(推荐)
- [ ] 申请 SSL 证书(Let's Encrypt / 购买)
- [ ] 配置 HTTPS
- [ ] 配置 HTTP 自动跳转 HTTPS
- [ ] 测试 HTTPS 访问
### 第 6 阶段:验证
- [ ] 访问前端页面,确认正常加载
- [ ] 登录测试(admin / 123456)
- [ ] 修改默认密码
- [ ] 测试核心功能(用户管理、角色管理等)
- [ ] 检查后端日志:`tail -f logs/app.log`
- [ ] 检查 Supervisor 状态:`supervisorctl status`
## 版本更新 Checklist
### 后端更新
- [ ] 拉取最新代码:`git pull origin main`
- [ ] 安装新依赖(如有):`pip install -r requirements.txt`
- [ ] 执行数据库迁移(如有):`alembic upgrade head`
- [ ] 重启后端服务:`supervisorctl restart djangoadmin-tornado`
- [ ] 检查日志:`tail -f logs/app.log`
### 前端更新
- [ ] 拉取最新代码:`git pull origin main`
- [ ] 安装新依赖(如有):`pnpm install`
- [ ] 构建前端:`pnpm build`
- [ ] 替换服务器上的 `ui/dist/` 目录
- [ ] 清除浏览器缓存测试
### 数据库迁移
- [ ] 备份数据库:`mysqldump -uroot -p djangoadmin.tornado.elevue2 > backup.sql`
- [ ] 执行迁移:`alembic upgrade head`
- [ ] 验证迁移结果
## Docker 部署 Checklist
- [ ] 安装 Docker 和 Docker Compose
- [ ] 配置 `.env` 文件
- [ ] 构建镜像:`docker-compose build`
- [ ] 启动服务:`docker-compose up -d`
- [ ] 检查容器状态:`docker-compose ps`
- [ ] 查看日志:`docker-compose logs -f`
- [ ] 初始化数据库(首次):`docker-compose exec app python scripts/init_db.py`
## 监控 Checklist
- [ ] 配置健康检查端点
- [ ] 配置日志轮转(logrotate)
- [ ] 配置数据库自动备份(cron)
- [ ] 配置 Redis 持久化(AOF/RDB)
- [ ] 配置磁盘空间监控
- [ ] 配置内存使用监控
## 安全 Checklist
- [ ] 修改默认密码(admin / 123456)
- [ ] 设置强密码的 JWT_SECRET
- [ ] 设置 `TORNADO_DEBUG=False`
- [ ] 设置 `TORNADO_DEMO=False`(生产环境)
- [ ] 配置 CORS 白名单(非 `*`)
- [ ] 配置 HTTPS
- [ ] 关闭不必要的端口
- [ ] 定期更新系统和依赖包
## 回滚方案
### 后端回滚
```bash
# 回滚代码
git checkout <上一个稳定版本tag>
# 回滚数据库迁移
alembic downgrade -1
# 重启服务
supervisorctl restart djangoadmin-tornado
```
### 前端回滚
```bash
# 替换为上一版本的构建产物
cp -r /opt/backup/dist /opt/project/ui/dist
```
### 数据库回滚
```bash
# 从备份恢复
mysql -uroot -p djangoadmin.tornado.elevue2 < backup.sql
```
## 总结
部署 Checklist 覆盖了从服务器准备到应用上线的完整流程。首次部署建议按顺序逐项检查,版本更新时使用更新 Checklist 快速操作。生产环境务必做好备份和监控。
@@ -0,0 +1,206 @@
# Docker容器化
## 概述
提供完整的 Docker 容器化方案,包括 Dockerfile 镜像构建和 docker-compose.yml 编排配置,支持一键部署后端服务。
:::tip 容器化优势
- 环境一致性:消除"在我机器上能跑"的问题
- 快速部署:一条命令启动完整服务
- 资源隔离:应用与宿主机环境解耦
:::
## Dockerfile 详解
项目根目录 `Dockerfile` 完整内容:
```dockerfile
# 基础镜像:Python 3.11-slim(python:3.9 已于 2025-10 停止安全更新)
FROM python:3.11-slim
LABEL maintainer="管理员"
# 环境变量:禁用 Python 缓存和字节码写入
ENV PYTHONUNBUFFERED 1
ENV PYTHONDONTWRITEBYTECODE 1
ENV TZ Asia/Shanghai
# 国内镜像源加速(使用 https,避免 http 明文传输被篡改)
RUN pip config set global.index-url https://mirrors.aliyun.com/pypi/simple
RUN pip config set install.trusted-host mirrors.aliyun.com
# 工作目录
WORKDIR /data/apps
# 先拷贝依赖清单并安装,利用 Docker 层缓存(源码变更不会触发重复安装依赖)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 拷贝全部源码(.dockerignore 已排除上传目录/缓存/测试等,避免镜像膨胀)
COPY . /data/apps
# 暴露应用端口
EXPOSE 8041
# 启动命令:Tornado 为 ASGI 应用,使用 tornado.httpserver
# 绑定 0.0.0.0 使 docker run -p 8041:8041 可直接访问;
# compose 场景下由 docker-compose.yml 的 command 覆盖
CMD ["tornado.httpserver", "main:app", "--app-dir", "src", "--host", "0.0.0.0", "--port", "8041"]
```
:::tip 构建优化
Dockerfile 将 `requirements.txt` 和源码分两步 COPY,利用 Docker 层缓存机制——源码变更不会触发依赖重新安装,显著加快构建速度。镜像源使用 HTTPS 协议,防止明文传输被篡改。
:::
## docker-compose.yml 结构
```yaml
version: '3.7'
services:
tornado_elevue: # 服务名称
image: tornado_elevue # 镜像名称
container_name: tornado_elevue # 容器名称
build: # 构建配置
context: ./ # 构建上下文路径
dockerfile: Dockerfile # Dockerfile 文件名
restart: always # 重启策略:总是重启
network_mode: host # 网络模式:使用宿主机网络
ports: # 端口映射
- 8041:8041 # 宿主机端口:容器端口
volumes: # 数据卷挂载
- $PWD/uploads:/data/apps/uploads # 上传文件持久化存储
env_file: # 环境变量文件
- .env # 加载 .env 文件中的环境变量
command: python app.py # 容器启动命令
logging: # 日志配置
driver: "json-file" # 日志驱动:json-file
options:
max-size: "500m" # 单个日志文件最大大小
max-file: "10" # 保留日志文件数量
```
### 配置说明
| 配置项 | 说明 |
|--------|------|
| `restart: always` | 容器异常退出后自动重启 |
| `network_mode: host` | 使用宿主机网络,可直接访问 localhost 的 MySQL/Redis |
| `volumes` | 挂载上传目录,确保文件持久化 |
| `env_file` | 从 `.env` 注入环境变量 |
| `logging` | 日志轮转:单文件最大 500MB,保留 10 个文件 |
:::warning 网络模式说明
`host` 模式下 `ports` 映射被忽略(容器直接使用宿主机端口)。若改为 `bridge` 网络,需将 `DB_HOST`/`REDIS_HOST` 指向 `host.docker.internal` 并启用端口映射。
:::
## 常用操作命令
### 构建与启动
```bash
# 构建镜像并启动
docker-compose up --build
# 后台启动
docker-compose up --build -d
# 仅构建镜像
docker-compose build
```
### 日常管理
```bash
# 查看运行状态
docker-compose ps
# 查看日志
docker-compose logs -f tornado_elevue
# 停止服务
docker-compose down
# 重启服务
docker-compose restart tornado_elevue
# 进入容器
docker exec -it tornado_elevue bash
```
### 查看容器资源
```bash
# 查看容器资源占用
docker stats tornado_elevue
# 查看容器日志大小
docker inspect --format='{{.LogPath}}' tornado_elevue
```
## 环境变量注入
Docker 通过 `env_file: .env` 将宿主机的 `.env` 文件注入容器。容器内的应用通过 `os.getenv()` 读取,与非 Docker 部署完全一致。
:::tip .env 文件位置
`.env` 文件应放在 `docker-compose.yml` 同级目录下。部署时目录结构:
```
/data/apps/djangoadmin
├── .env # 环境变量
├── Dockerfile
├── docker-compose.yml
├── requirements.txt
├── src/
└── uploads/ # 上传文件目录(挂载卷)
```
:::
## 上传文件持久化
通过 `volumes` 将容器内的上传目录挂载到宿主机,确保容器重建后文件不丢失:
```yaml
volumes:
- $PWD/uploads:/data/apps/uploads
```
## 日志管理
容器日志使用 `json-file` 驱动,配置轮转防止磁盘占满:
```yaml
logging:
driver: "json-file"
options:
max-size: "500m" # 单文件最大 500MB
max-file: "10" # 保留 10 个文件
```
:::tip 生产环境日志
生产环境建议将日志输出到 stdout/stderr,由 Docker 日志驱动统一收集。如需集中式日志,可配置 `fluentd` 或 `syslog` 驱动对接 ELK/Loki。
:::
## 多容器编排(扩展)
如需同时部署前端和 Nginx,可扩展 `docker-compose.yml`:
```yaml
services:
tornado_elevue:
# ... 后端配置 ...
nginx:
image: nginx:alpine # Nginx 镜像(轻量版)
ports: # 端口映射
- 80:80 # HTTP
- 443:443 # HTTPS
volumes: # 挂载配置
- ./nginx.conf:/etc/nginx/conf.d/default.conf # Nginx 配置文件
- ./dist:/usr/share/nginx/html # 前端构建产物
depends_on: # 依赖服务
- tornado_elevue # 等待后端服务启动
```
## 总结
Docker 容器化通过 Dockerfile 构建镜像、docker-compose.yml 编排服务,实现一键部署。关键点:利用层缓存优化构建速度、host 网络模式简化数据库连接、volumes 持久化上传文件、日志轮转防磁盘占满。生产环境建议配合 Nginx 反向代理使用。
@@ -0,0 +1,132 @@
# 环境规划
## 概述
项目支持多环境部署,通过 `.env` 文件管理各环境的配置差异。合理的环境规划能确保开发、测试、生产环境的隔离性,降低配置错误导致的线上风险。
:::tip 核心原则
各环境之间保持代码一致,仅通过环境变量(`.env`)区分配置。禁止在代码中硬编码环境相关的值。
:::
## 环境划分
| 环境 | 用途 | 域名示例 | DEBUG | 数据库 | Redis | 日志级别 |
|------|------|---------|-------|--------|-------|---------|
| Dev(开发) | 本地开发调试 | localhost:8041 | True | 开发库 | 开发实例 | DEBUG |
| Test(测试) | 功能/集成测试 | test.example.com | False | 测试库 | 测试实例 | INFO |
| Prod(生产) | 正式对外服务 | www.example.com | False | 生产库 | 生产集群 | WARNING |
:::tip 环境隔离要求
1. 三个环境的数据库必须完全隔离,禁止共用同一库。
2. Redis 实例建议分离,避免测试数据污染生产缓存。
3. 生产环境的 `.env` 文件权限应设为 `600`,仅运行用户可读。
:::
## .env 配置示例
### Dev 环境
```bash
# 应用配置
TORNADO_ENV=dev
TORNADO_DEBUG=True
TORNADO_DEMO=False
TORNADO_PORT=8041
# 数据库配置
DB_DRIVER=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USER=root
DB_PASS=root
DB_NAME=djangoadmin_dev
DB_PREFIX=tornado_
# Redis 配置
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_DB=0
# JWT 配置
JWT_SECRET=dev-salt-change-in-production
JWT_EXPIRE_MINUTES=1440
```
### Prod 环境
```bash
# 应用配置
TORNADO_ENV=prod
TORNADO_DEBUG=False
TORNADO_DEMO=False
TORNADO_PORT=8041
# 数据库配置
DB_DRIVER=mysql
DB_HOST=10.0.1.100
DB_PORT=3306
DB_USER=djangoadmin
DB_PASS=<强密码>
DB_NAME=djangoadmin_prod
DB_PREFIX=tornado_
# Redis 配置
REDIS_HOST=10.0.1.101
REDIS_PORT=6379
REDIS_PASSWORD=<强密码>
REDIS_DB=0
# JWT 配置
JWT_SECRET=<随机生成的64位字符串>
JWT_EXPIRE_MINUTES=720
```
:::tip 安全提醒
生产环境的 `JWT_SECRET` 必须使用随机强密码(推荐 64 位以上),禁止与开发环境共用同一值。可通过以下命令生成:
```bash
python -c "import secrets; print(secrets.token_urlsafe(48))"
```
:::
## 环境切换机制
项目通过 `python-dotenv` 加载 `.env` 文件,启动时自动读取当前目录下的 `.env`:
```bash
# Dev:本地启动
python app.py
# Prod:指定 .env 路径(或在部署目录放置 .env)
cd /data/apps/djangoadmin && python app.py
```
:::tip Docker 环境
Docker 部署时,`docker-compose.yml` 通过 `env_file: .env` 注入环境变量,容器内无需额外配置。详见 [Docker容器化](/zh/volume6/docker) 章节。
:::
## 数据库驱动切换
通过 `DB_DRIVER` 环境变量切换数据库类型(配置在 `src/config/database.py`):
| DB_DRIVER | 数据库 | 依赖包 | 连接串格式 |
|-----------|--------|--------|-----------|
| mysql(默认) | MySQL 8.x | PyMySQL | `mysql+pymysql://user:pass@host:3306/db` |
| postgresql | PostgreSQL 14+ | psycopg[binary] | `postgresql+psycopg://user:pass@host:5432/db` |
| mssql | SQL Server 2019+ | pymssql | `mssql+pymssql://user:pass@host:1433/db` |
| sqlite | SQLite 3 | 内置 | `sqlite:///path/to/db.sqlite3` |
| oracle | Oracle 12c+ | oracledb | `oracle+oracledb://user:pass@host:1521/db` |
:::tip 切换数据库后
切换数据库驱动后,需要重新初始化数据库结构。项目 Makefile 提供常用命令:
```bash
make db-init # 初始化数据库(建库 + SQL 导入 + 补表 + alembic stamp)
make migrate-revision # 生成增量迁移脚本
make migrate-upgrade # 应用迁移
make migrate-stamp # 标记当前库已到基线
make db-migrate ARGS="..." # 跨库数据迁移
```
:::
## 总结
环境规划的核心是隔离与一致性:代码统一,配置分离。通过 `.env` 文件管理各环境差异,`DB_DRIVER` 支持多数据库切换。生产环境务必关注安全配置(DEBUG=False、强密码、JWT_SECRET 独立),确保线上服务稳定可靠。
@@ -0,0 +1,164 @@
# 健康检查与监控
## 概述
生产环境的健康检查与监控是保障服务可用性的关键环节。本文介绍 Tornado 应用的健康检查端点、监控指标采集、日志聚合方案。
:::tip 监控体系层次
```
1. 应用层:健康检查端点、业务指标
2. 系统层:CPU/内存/磁盘/网络
3. 中间件:MySQL/Redis 连接状态
4. 日志:集中式日志收集与分析
```
:::
## 健康检查端点
Tornado 内置健康检查路由,可用于负载均衡器或容器编排的探针配置:
```bash
# 基础健康检查
GET /api/v1/health
# 响应示例
{
"code": 0,
"data": {
"status": "healthy",
"version": "1.0.0",
"uptime": 3600
},
"msg": "操作成功",
"ok": true
}
```
:::tip Docker 健康检查
在 Dockerfile 中配置 HEALTHCHECK:
```dockerfile
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
CMD curl -f http://localhost:8041/api/v1/health || exit 1
```
:::
## 监控指标
### 应用指标
| 指标 | 说明 | 采集方式 |
|------|------|---------|
| 请求总量 | API 调用次数 | Nginx access log / 应用中间件 |
| 响应时间 | P50/P95/P99 延迟 | 应用中间件 / APM |
| 错误率 | 5xx 响应占比 | Nginx error log / 应用日志 |
| 活跃连接数 | 当前并发请求 | Tornado stats |
### 系统指标
| 指标 | 说明 | 告警阈值 |
|------|------|---------|
| CPU 使用率 | 进程 CPU 占用 | > 80% |
| 内存使用率 | 进程内存占用 | > 85% |
| 磁盘使用率 | 磁盘空间占用 | > 90% |
| 网络流量 | 入站/出站带宽 | 根据带宽设定 |
### 中间件指标
| 指标 | 说明 | 告警阈值 |
|------|------|---------|
| MySQL 连接数 | 活跃/空闲连接 | > max_connections * 0.8 |
| MySQL 慢查询 | 执行时间 > 1s 的查询 | > 10/min |
| Redis 内存使用 | Redis 内存占用 | > maxmemory * 0.8 |
| Redis 连接数 | 活跃客户端连接 | > maxclients * 0.8 |
## 日志聚合
### 应用日志格式
项目使用 Python `logging` 模块输出结构化日志:
```
2026-09-05 10:30:15 [INFO] core.app: Application startup complete
2026-09-05 10:30:16 [WARNING] middleware.rate_limit: Rate limit exceeded for 192.168.1.100
2026-09-05 10:30:17 [ERROR] modules.auth.service: Login failed for user admin
```
### ELK 方案
Elasticsearch + Logstash + Kibana 集中日志收集:
```yaml
# Filebeat 配置示例
filebeat.inputs:
- type: log
enabled: true
paths:
- /data/apps/logs/*.log
fields:
app: djangoadmin
multiline.pattern: '^\d{4}-\d{2}-\d{2}'
multiline.negate: true
multiline.match: after
output.elasticsearch:
hosts: ["http://elasticsearch:9200"]
index: "djangoadmin-%{+yyyy.MM.dd}"
```
### Loki 方案
轻量级日志聚合(配合 Grafana):
```yaml
# docker-compose.yml 添加 Loki 和 Grafana
services:
loki:
image: grafana/loki:latest
ports:
- "3100:8001"
promtail:
image: grafana/promtail:latest
volumes:
- /data/apps/logs:/var/log/app
- ./promtail-config.yml:/etc/promtail/config.yml
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
```
:::tip Loki 优势
相比 ELK,Loki 更轻量——不索引日志内容,仅索引标签,存储成本低,适合中小规模部署。
:::
## 告警配置
### 常用告警规则
```yaml
# Prometheus AlertManager 规则示例
groups:
- name: djangoadmin
rules:
- alert: HighErrorRate
expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.1
for: 5m
labels:
severity: critical
annotations:
summary: "错误率超过10%"
- alert: HighResponseTime
expr: histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) > 2
for: 5m
labels:
severity: warning
annotations:
summary: "P95响应时间超过2秒"
```
## 总结
健康检查与监控是生产环境的基础设施:通过健康检查端点实现负载均衡探针,通过系统/应用/中间件指标全面掌握服务状态,通过 ELK 或 Loki 实现日志集中分析。建议中小项目优先采用 Loki+Grafana 轻量方案,大型项目使用 ELK 全家桶。
@@ -0,0 +1,177 @@
# Nginx 反向代理
## 概述
Nginx 作为反向代理服务器,承担静态资源服务、API 请求转发、HTTPS 终端等职责。生产环境推荐使用 Nginx 作为前端入口,将 API 请求代理到后端 Tornado 服务。
:::tip 架构示意
```
客户端 → Nginx(80/443)
├─ / → 前端静态资源(dist/)
├─ /api/v1/... → Tornado 后端(8041)
└─ /api/file/ → 上传文件目录
```
:::
## 基础配置
项目提供了 Nginx 配置模板 `deploy/nginx.conf`,按需修改即可使用:
```nginx
upstream tornado_backend {
server 127.0.0.1:8041;
keepalive 16;
}
server {
listen 80;
server_name your-domain.com;
charset utf-8;
client_max_body_size 50m;
# 安全头
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
# Gzip 压缩
gzip on;
gzip_min_length 1k;
gzip_comp_level 6;
gzip_types text/plain text/css application/json application/javascript text/xml image/svg+xml;
gzip_vary on;
# 前端静态资源
root /data/apps/djangoadmin/frontend/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /assets/ {
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
}
# API 反向代理
location /api/ {
proxy_pass http://tornado_backend/api/;
proxy_http_version 1.1;
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_set_header Connection "";
proxy_connect_timeout 60s;
proxy_read_timeout 120s;
proxy_send_timeout 60s;
}
# 上传文件访问
location /api/file/ {
alias /data/apps/djangoadmin/uploads/;
expires 7d;
access_log off;
limit_except GET { deny all; }
}
# 禁止访问隐藏文件
location ~ /\. { deny all; }
}
```
:::tip 配置说明
- `upstream` + `keepalive`:长连接池减少 TCP 握手开销
- `location /`:`try_files` 支持 SPA 路由回退
- `location /api/`:只代理 API 请求,前端静态资源由 Nginx 直接服务
- `location /api/file/`:通过 `alias` 访问上传文件目录,只允许 GET
- `client_max_body_size 50m`:与后端 `UPLOAD_MAX_SIZE_MB` 保持一致
- `proxy_read_timeout 120s`:适应大数据量导出等长耗时操作
:::
## 部署步骤
```bash
# 1. 前端构建
cd ui && pnpm build
# 2. 拷贝构建产物
mkdir -p /data/apps/djangoadmin/frontend
cp -r ui/dist /data/apps/djangoadmin/frontend/
# 3. 拷贝 Nginx 配置
cp deploy/nginx.conf /etc/nginx/conf.d/djangoadmin.conf
# 4. 修改配置中的实际值(server_name、root 等)
# 5. 检查并重载
nginx -t && nginx -s reload
```
## HTTPS 配置
```nginx
server {
listen 443 ssl http2;
server_name your-domain.com;
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
# HSTS(强制 HTTPS)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# ... 其他 location 配置同上 ...
}
# HTTP 自动跳转 HTTPS
server {
listen 80;
server_name your-domain.com;
return 301 https://$server_name$request_uri;
}
```
:::warning SSL 证书安全
- 证书文件权限设为 `600`,仅 Nginx 用户可读
- 禁用 TLSv1.0/1.1,仅保留 TLSv1.2+
- 推荐 Let's Encrypt 免费证书 + certbot 自动续期
:::
## 常用命令
```bash
nginx -t # 检查配置语法
nginx -s reload # 重新加载(不停服)
nginx -s stop # 停止
nginx -v # 查看版本
```
## 常见问题
### 502 Bad Gateway
后端 Tornado 服务未启动或端口不对。检查:`curl http://127.0.0.1:8041/api/v1/captcha`
### 413 Request Entity Too Large
上传文件超过 `client_max_body_size` 限制,调大该值与后端 `UPLOAD_MAX_SIZE_MB` 一致。
### 刷新页面 404
缺少 `try_files $uri $uri/ /index.html`,SPA 路由需要回退到 `index.html`。
### API 请求走到了前端
`location /api/` 必须在 `location /` 之前定义,Nginx 按优先级匹配。
## 总结
Nginx 反向代理是生产部署的标准配置:`/` 服务前端静态资源、`/api/` 反向代理到 Tornado 后端、`/api/file/` 访问上传文件。关键配置包括 `try_files` 支持 SPA 路由、`client_max_body_size` 匹配上传限制、Gzip 压缩和静态资源缓存。
@@ -0,0 +1,208 @@
# 性能调优
## 概述
Tornado 应用的性能调优涉及 Tornado HTTPServer 配置、数据库连接池、Redis 缓存策略、慢查询优化等多个层面。合理的调优能显著提升并发处理能力和响应速度。
:::tip 性能调优原则
1. 先测量,后优化:用数据说话,避免盲目调参
2. 瓶颈优先:找到最大瓶颈,集中优化
3. 渐进式:每次只调整一个参数,观察效果
:::
## Tornado HTTPServer 配置
### Worker 数量
```bash
# 单 Worker(开发环境)
python app.py
# 多 Worker(生产环境)
tornado.httpserver main:app --app-dir src --host 0.0.0.0 --port 8041 --workers 4
```
:::tip Worker 数量公式
推荐 Worker 数量 = `CPU 核心数 * 2 + 1`
例如 4 核 CPU:`workers = 4 * 2 + 1 = 9`
查看 CPU 核心数:
```bash
# Linux
nproc
# Python
import multiprocessing; print(multiprocessing.cpu_count())
```
:::
### 启动方式选择
| 方式 | 适用场景 | 特点 |
|------|---------|------|
| `python app.py` | 开发调试 | 单进程,自动重载 |
| `tornado.httpserver --workers N` | 生产部署 | 多进程,无自动重载 |
| `python app.py` | 生产部署 | 更成熟的进程管理 |
### Gunicorn 配置示例
```bash
pip install gunicorn
gunicorn main:app \
--chdir src \
--workers 4 \
----port=8041 \
--bind 0.0.0.0:8041 \
--timeout 120 \
--keep-alive 5 \
--max-requests 1000 \
--max-requests-jitter 50
```
:::tip max-requests 作用
`--max-requests 1000` 表示每个 Worker 处理 1000 个请求后自动重启,防止内存泄漏累积。`--max-requests-jitter 50` 添加随机抖动,避免所有 Worker 同时重启。
:::
## 数据库连接池
SQLAlchemy 连接池配置(在 `core/config/database.py` 中):
```python
# 连接池配置
SQLALCHEMY_POOL_SIZE = 20 # 连接池大小
SQLALCHEMY_MAX_OVERFLOW = 10 # 超出池大小的最大连接数
SQLALCHEMY_POOL_TIMEOUT = 30 # 获取连接超时(秒)
SQLALCHEMY_POOL_RECYCLE = 3600 # 连接回收时间(秒)
```
:::warning 连接池大小
连接池总连接数 = `pool_size + max_overflow`。需确保不超过数据库的 `max_connections` 限制,否则会报连接数超限错误。
MySQL 查看最大连接数:
```sql
SHOW VARIABLES LIKE 'max_connections';
```
:::
### 连接池监控
```python
# 获取连接池状态
from core.database import engine
pool = engine.pool
print(f"池大小: {pool.size()}")
print(f"已签出: {pool.checkedout()}")
print(f"空闲数: {pool.checkedin()}")
print(f"溢出数: {pool.overflow()}")
```
## Redis 缓存优化
### 缓存策略
| 策略 | 适用场景 | 实现方式 |
|------|---------|---------|
| 读缓存 | 字典、配置等读多写少 | 查询时先读 Redis,未命中再查 DB |
| 写失效 | 数据更新时 | 更新 DB 后删除 Redis 缓存 |
| 预热 | 服务启动时 | 启动后主动加载热点数据到 Redis |
### TTL 设置建议
```python
# 字典数据:较长 TTL(变更频率低)
DICT_CACHE_TTL = 3600 # 1 小时
# 用户权限:中等 TTL(登录时刷新)
PERM_CACHE_TTL = 1800 # 30 分钟
# 验证码:短 TTL(一次性使用)
CAPTCHA_CACHE_TTL = 300 # 5 分钟
```
### 缓存预热
```python
# 服务启动时预热缓存
async def warmup_cache():
"""预热字典、配置等热点数据"""
from utils.dict_util import refresh_dict_cache
await refresh_dict_cache()
```
## 慢查询优化
### 开启慢查询日志
MySQL:
```sql
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
```
### 常见优化手段
| 问题 | 优化方案 |
|------|---------|
| 缺少索引 | 为 WHERE/ORDER BY 字段添加索引 |
| 全表扫描 | 使用 EXPLAIN 分析查询计划 |
| N+1 查询 | 使用 joinedload/subqueryload 预加载关联数据 |
| 大分页 | 使用游标分页或延迟关联 |
| SELECT * | 只查询需要的字段 |
### 索引优化示例
```python
# 模型定义中添加索引
class Article(BaseModel):
__tablename__ = DB_PREFIX + "article"
title = Column(String(255), index=True) # 单列索引
status = Column(Integer, index=True)
category_id = Column(Integer, index=True)
# 复合索引(在 __table_args__ 中定义)
__table_args__ = (
Index('idx_status_category', 'status', 'category_id'),
)
```
### 分页查询优化
```python
# 避免大分页:使用游标分页
# 差:OFFSET 100000 LIMIT 10(扫描大量数据)
# 好:WHERE id > last_id LIMIT 10(利用主键索引)
def paginate_after_id(self, after_id: int, limit: int = 10):
"""游标分页:基于上次最后一条记录的 ID"""
return self.db.query(self.model).filter(
self.model.id > after_id,
self._soft_col() == 0
).order_by(self.model.id.asc()).limit(limit).all()
```
## 异步与并发
### 同步/异步选择
```python
# 纯 DB CRUD:同步 def(Tornado 自动线程池执行)
def get_position_list(request):
return position_service.get_page(request)
# 含 Redis/文件 IO:async def + await
async def get_captcha(request):
redis = await get_redis()
# ...
```
:::tip 禁止混用
禁止在 `async def` 端点中直接调用同步 DB 查询(会阻塞事件循环)。同步 DB 操作要么用 `def` 端点(自动线程池),要么用 `asyncio.to_thread()` 包裹。
:::
## 总结
性能调优的核心路径:Tornado 多进程 利用多核 CPU、数据库连接池匹配并发需求、Redis 缓存减少 DB 压力、索引和查询优化降低慢查询。建议先用监控数据定位瓶颈,再针对性调优,每次只调整一个参数并观察效果。
@@ -0,0 +1,200 @@
# 安全加固
## 概述
生产环境的安全加固是防止数据泄露和服务被攻击的关键措施。本文从应用配置、传输安全、访问控制、数据保护等维度介绍安全加固实践。
:::tip 安全原则
- 最小权限:只开放必要的端口和权限
- 纵深防御:多层安全措施叠加
- 默认安全:配置默认值应为安全选项
:::
## 应用层安全
### 关闭 DEBUG 模式
```bash
# .env 生产环境配置
TORNADO_DEBUG=False
```
:::warning 重要
DEBUG 模式会暴露详细的错误堆栈、SQL 查询等敏感信息,生产环境必须关闭。
:::
### JWT 安全
```bash
# .env 配置
JWT_SECRET=<随机生成的64位强密码>
JWT_EXPIRE_MINUTES=720
```
```bash
# 生成强密码
python -c "import secrets; print(secrets.token_urlsafe(48))"
```
:::tip JWT 安全要点
1. `JWT_SECRET` 必须使用随机强密码,禁止使用默认值
2. 不同环境使用不同的 `JWT_SECRET`
3. 定期轮换 `JWT_SECRET`(需同时使所有已发 token 失效)
4. token 过期时间不宜过长,建议 12 小时以内
:::
### 演示模式
```bash
# 演示环境:禁止写操作
TORNADO_DEMO=True
```
演示模式下所有写操作(新增/编辑/删除/状态变更/导入/导出)都会被 `@check_demo` 装饰器拦截。
## CORS 配置
```python
# core/config/app.py 中配置
CORS_ORIGINS = [
"https://www.example.com",
"https://admin.example.com",
]
```
:::warning CORS 安全
- 生产环境禁止使用 `allow_origins=["*"]`(允许任意来源)
- 仅列出信任的前端域名
- 如需支持多子域,使用正则匹配而非通配符
:::
## .env 文件安全
### 文件权限
```bash
# 设置 .env 文件权限(仅运行用户可读写)
chmod 600 .env
# 查看权限
ls -la .env
# -rw------- 1 app app 1024 Sep 5 10:00 .env
```
### 敏感信息管理
```bash
# .env 中的敏感字段
DB_PASS=<数据库密码>
REDIS_PASSWORD=<Redis密码>
JWT_SECRET=<JWT密钥>
```
:::tip 安全建议
1. `.env` 文件禁止提交到 Git(已在 `.gitignore` 中排除)
2. 不同环境的 `.env` 独立管理,禁止共用
3. 生产环境密码至少 16 位,包含大小写字母、数字、特殊字符
4. 定期轮换数据库密码和 JWT 密钥
:::
## 数据库安全
### MySQL 安全配置
```sql
-- 创建专用数据库用户(禁止使用 root)
CREATE USER 'djangoadmin'@'%' IDENTIFIED BY '<强密码>';
GRANT SELECT, INSERT, UPDATE, DELETE ON djangoadmin_prod.* TO 'djangoadmin'@'%';
FLUSH PRIVILEGES;
-- 禁止远程 root 登录
DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1');
FLUSH PRIVILEGES;
```
:::tip 最小权限原则
应用数据库用户只需 SELECT/INSERT/UPDATE/DELETE 权限,禁止 GRANT/CREATE/DROP 等管理权限。
:::
### Redis 安全
```bash
# redis.conf 安全配置
# 设置密码
requirepass <强密码>
# 禁止危险命令
rename-command FLUSHDB ""
rename-command FLUSHALL ""
rename-command CONFIG ""
rename-command DEBUG ""
# 绑定地址
bind 127.0.0.1
# 禁用 protected-mode(设置了密码后)
protected-mode no
```
## 防火墙配置
### iptables 基础规则
```bash
# 仅开放必要端口
iptables -A INPUT -p tcp --dport 22 -j ACCEPT # SSH
iptables -A INPUT -p tcp --dport 80 -j ACCEPT # HTTP
iptables -A INPUT -p tcp --dport 443 -j ACCEPT # HTTPS
iptables -A INPUT -p tcp --dport 8041 -j ACCEPT # Tornado(如不走 Nginx)
iptables -A INPUT -i lo -j ACCEPT # 本地回环
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -P INPUT DROP # 默认拒绝
# 保存规则
iptables-save > /etc/iptables/rules.v4
```
:::tip 端口规划
| 端口 | 服务 | 是否对外 | 备注 |
|------|------|---------|------|
| 22 | SSH | 是(限制 IP) | 建议修改默认端口 |
| 80 | Nginx HTTP | 是 | 跳转 HTTPS |
| 443 | Nginx HTTPS | 是 | 主入口 |
| 8041 | Tornado | 否 | 仅 Nginx 可访问 |
| 3306 | MySQL | 否 | 仅应用可访问 |
| 6379 | Redis | 否 | 仅应用可访问 |
:::
## 上传文件安全
```python
# 文件类型白名单
ALLOWED_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif', '.pdf', '.doc', '.docx', '.xls', '.xlsx'}
# 文件大小限制
MAX_FILE_SIZE = 10 * 1024 * 1024 # 10MB
```
:::tip 文件安全措施
1. 上传文件重命名为 UUID,防止路径遍历攻击
2. 验证文件类型(检查文件头,不依赖扩展名)
3. 上传目录禁止执行权限
4. 图片文件重新编码(防止嵌入恶意代码)
:::
## 安全检查清单
- [ ] `TORNADO_DEBUG=False`
- [ ] `JWT_SECRET` 使用随机强密码
- [ ] `.env` 文件权限 `600`
- [ ] CORS 不使用 `*` 通配符
- [ ] 数据库用户非 root 且最小权限
- [ ] Redis 设置密码并禁用危险命令
- [ ] 防火墙仅开放必要端口
- [ ] 上传文件类型白名单验证
- [ ] 日志不记录敏感信息(密码、token)
- [ ] HTTPS 启用且禁用 TLSv1.0/1.1
## 总结
安全加固的核心是纵深防御:应用层关闭 DEBUG、配置强 JWT 密钥、限制 CORS;数据层数据库最小权限、Redis 密码保护;网络层防火墙仅开放必要端口;文件层上传白名单验证。生产环境务必逐项检查安全清单,定期审计。
@@ -0,0 +1,195 @@
# Supervisor进程管理
## 概述
Supervisor 是一个 Python 编写的进程管理工具,用于在 Linux 服务器上管理 Tornado 应用进程。它提供自动重启、日志管理、进程监控等功能,是生产环境部署的推荐方案之一。
:::tip 适用场景
- Linux 服务器直接部署(非 Docker)
- 需要进程自动重启和日志轮转
- 管理多个相关进程
:::
## 配置文件示例
项目提供了 Supervisor 配置模板 `deploy/supervisor.conf`,完整内容:
```ini
; Supervisor 进程管理示例配置
; 将 command / directory 替换为实际路径后复制到 /etc/supervisor/conf.d/
[program:djangoadmin-tornado]
; 启动命令:使用项目虚拟环境中的 tornado.httpserver
command=/path/to/venv/bin/tornado.httpserver src.main:app --host 127.0.0.1 --port 8041
; 项目根目录
directory=/data/apps/djangoadmin
; 自动启动
autostart=true
; 异常退出后自动重启
autorestart=true
; 停止时终止子进程
stopasgroup=true
killasgroup=true
; 错误输出重定向到标准输出
redirect_stderr=true
; 日志文件
stdout_logfile=/path/to/logs/supervisor.out.log
; 日志轮转:单文件最大 50MB,保留 10 个备份
stdout_logfile_maxbytes=50MB
stdout_logfile_backups=10
; 环境变量
environment=PYTHONUNBUFFERED=1
```
:::tip 配置路径
将修改后的配置文件复制到 `/etc/supervisor/conf.d/` 目录下,需修改两处路径:
```bash
# 1. 修改 command 中的虚拟环境路径
# 2. 修改 directory 为项目实际路径
# 3. 修改 stdout_logfile 为日志实际路径
cp deploy/supervisor.conf /etc/supervisor/conf.d/djangoadmin-tornado.conf
```
:::
## 安装 Supervisor
```bash
# Ubuntu/Debian
sudo apt-get install supervisor
# CentOS/RHEL
sudo yum install supervisor
# pip 安装
pip install supervisor
```
## 常用命令
### 进程管理
```bash
# 读取配置并启动进程
sudo supervisorctl reread
sudo supervisorctl update
# 启动进程
sudo supervisorctl start djangoadmin-tornado
# 停止进程
sudo supervisorctl stop djangoadmin-tornado
# 重启进程
sudo supervisorctl restart djangoadmin-tornado
# 查看进程状态
sudo supervisorctl status
# 停止所有进程
sudo supervisorctl stop all
# 重启所有进程
sudo supervisorctl restart all
```
### 服务管理
```bash
# 启动 Supervisor 服务
sudo systemctl start supervisord
# 停止 Supervisor 服务
sudo systemctl stop supervisord
# 设置开机自启
sudo systemctl enable supervisord
# 查看服务状态
sudo systemctl status supervisord
```
## 日志轮转
Supervisor 内置日志轮转功能,通过以下配置控制:
```ini
; 单个日志文件最大 50MB
stdout_logfile_maxbytes=50MB
; 保留 10 个历史日志文件
stdout_logfile_backups=10
```
:::tip 日志文件命名
当 `stdout_logfile_maxbytes` 达到上限时,Supervisor 自动轮转:
- `supervisor.out.log`(当前日志)
- `supervisor.out.log.1`(上一个)
- `supervisor.out.log.2`(更早的)
- ...最多保留 `stdout_logfile_backups` 个
:::
## Web 管理界面
Supervisor 提供可选的 Web 管理界面:
```ini
[inet_http_server]
port=127.0.0.1:9001
username=admin
password=your_password
```
:::warning 安全提醒
Web 界面仅监听 127.0.0.1,禁止暴露到公网。如需远程访问,应通过 SSH 隧道或 VPN。
:::
## 多进程管理
如需同时管理多个进程(如应用 + Celery Worker):
```ini
[program:djangoadmin-tornado]
command=/path/to/venv/bin/tornado.httpserver src.main:app --host 127.0.0.1 --port 8041
directory=/data/apps/djangoadmin
autostart=true
autorestart=true
[program:djangoadmin-worker]
command=/path/to/venv/bin/celery -A tasks worker
directory=/data/apps/djangoadmin
autostart=true
autorestart=true
numprocs=4
```
## 常见问题
### 进程启动失败
```bash
# 查看详细错误日志
sudo supervisorctl tail -f djangoadmin-tornado
# 检查配置语法
sudo supervisorctl status
```
:::tip 常见原因
1. Python 虚拟环境路径错误
2. 项目目录路径错误
3. 端口被占用
4. `.env` 文件缺失或配置错误
:::
### 进程频繁重启
```bash
# 查看退出原因
sudo supervisorctl tail djangoadmin-tornado stderr
# 调整启动等待时间(给应用足够的启动时间)
startsecs=10
```
## 总结
Supervisor 是 Linux 环境下管理 Tornado 进程的可靠方案,提供自动重启、日志轮转、进程监控等能力。配置文件位于 `deploy/supervisor.conf`,修改路径后复制到 `/etc/supervisor/conf.d/` 即可使用。生产环境建议配合 systemd 管理 Supervisor 自身的启动。
@@ -0,0 +1,289 @@
# 故障排查手册
## 概述
本文列出 常见故障及排查方案,帮助运维和开发人员快速定位和解决问题。
:::tip 排查思路
1. 查看日志:`tail -f logs/supervisor.out.log` 或 `docker-compose logs -f`
2. 检查配置:`.env` 文件、数据库连接、Redis 连接
3. 检查依赖:Python 包版本、系统服务状态
4. 逐步排除:从最简单的可能性开始
:::
## 1. 启动失败:端口被占用
**现象**:启动时报 `OSError: [Errno 98] Address already in use`
**排查**:
```bash
# 查看端口占用
lsof -i :8041
# 或
netstat -tlnp | grep 8041
# 杀掉占用进程
kill -9 <PID>
```
:::tip 常见原因
- 上次启动未正常退出,进程残留
- 其他服务占用了 8041 端口
- Supervisor 或 Docker 重复启动
:::
## 2. 启动失败:数据库连接失败
**现象**:启动时报 `OperationalError: (pymysql.err.OperationalError) (2003, "Can't connect to MySQL server")`
**排查**:
```bash
# 1. 检查数据库服务是否运行
systemctl status mysql
# 2. 检查网络连通性
telnet 127.0.0.1 3306
# 3. 检查 .env 配置
cat .env | grep DB_
# 4. 检查数据库用户权限
mysql -u root -p -e "SELECT user, host FROM mysql.user;"
```
:::tip 常见原因
- MySQL 服务未启动
- `.env` 中 DB_HOST/DB_PORT 配置错误
- 数据库用户密码错误
- 防火墙阻止连接
:::
## 3. 启动失败:Redis 连接失败
**现象**:启动时报 `redis.exceptions.ConnectionError`
**排查**:
```bash
# 1. 检查 Redis 服务
systemctl status redis
# 2. 测试连接
redis-cli -h 127.0.0.1 -p 6379 ping
# 3. 检查 Redis 密码
redis-cli -h 127.0.0.1 -p 6379 -a <password> ping
```
## 4. 401 未认证错误
**现象**:API 返回 `{"code": 401, "msg": "未登录或 token 已过期"}`
**排查**:
```bash
# 1. 检查 token 是否正确传递
curl -H "Authorization: Bearer <token>" http://127.0.0.1:8041/api/v1/user/info
# 2. 检查 token 是否过期(解码 JWT)
python -c "
import jwt
token = '<your-token>'
payload = jwt.decode(token, options={'verify_signature': False})
print(payload)
"
# 3. 检查 Redis 中 token 黑名单
redis-cli GET "token:blacklist:<token>"
```
:::tip 常见原因
- token 已过期(JWT_EXPIRE_MINUTES 到期)
- token 被加入黑名单(用户已退出登录)
- 前端未正确传递 Authorization 头
- JWT_SECRET 配置变更导致旧 token 失效
:::
## 5. 403 无权限错误
**现象**:API 返回 `{"code": 403, "msg": "暂无权限访问"}`
**排查**:
```bash
# 1. 检查用户角色配置
# 登录后台 → 系统管理 → 用户管理 → 查看用户角色
# 2. 检查角色权限配置
# 系统管理 → 角色管理 → 查看角色菜单权限
# 3. 检查菜单权限标识
# 系统管理 → 菜单管理 → 查看权限标识(如 sys:position:page)
# 4. 清除权限缓存
redis-cli KEYS "perm:*" | xargs redis-cli DEL
```
:::tip admin 用户
用户 ID 为 1 的管理员账号跳过所有权限检查。排查权限问题时,先用 admin 账号确认功能正常。
:::
## 6. 验证码不显示或校验失败
**现象**:验证码图片加载失败或提交后提示验证码错误
**排查**:
```bash
# 1. 检查验证码接口
curl http://127.0.0.1:8041/api/v1/captcha -o captcha.png
# 2. 检查 Redis 连接(验证码存储在 Redis)
redis-cli GET "captcha:<uuid>"
# 3. 检查 Pillow 依赖
pip show Pillow
```
:::tip 常见原因
- Pillow 未安装或版本不兼容
- Redis 连接异常
- 验证码过期(默认 5 分钟)
- 前端传递的验证码 UUID 不匹配
:::
## 7. 文件上传失败
**现象**:上传文件返回 500 错误或文件大小为 0
**排查**:
```bash
# 1. 检查上传目录权限
ls -la /data/apps/uploads/
chmod 755 /data/apps/uploads/
# 2. 检查磁盘空间
df -h
# 3. 检查 Nginx 上传限制
grep client_max_body_size /etc/nginx/nginx.conf
# 4. 检查上传接口请求
curl -X POST -F "file=@test.jpg" \
-H "Authorization: Bearer <token>" \
http://127.0.0.1:8041/api/v1/upload/file
```
:::tip 常见原因
- 上传目录不存在或无写权限
- 磁盘空间不足
- Nginx `client_max_body_size` 限制过小
- 文件大小超过后端限制
:::
## 8. 慢查询导致接口超时
**现象**:列表接口响应时间超过 3 秒
**排查**:
```bash
# 1. 开启 MySQL 慢查询日志
mysql -u root -p -e "
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;
"
# 2. 查看慢查询
tail -f /var/log/mysql/slow.log
# 3. 分析查询计划
mysql -u root -p -e "EXPLAIN SELECT * FROM tornado_article WHERE status = 1;"
# 4. 检查索引
mysql -u root -p -e "SHOW INDEX FROM tornado_article;"
```
:::tip 优化建议
- 为 WHERE/ORDER BY 字段添加索引
- 避免 `SELECT *`,只查需要的字段
- 大分页使用游标分页
- 热点数据使用 Redis 缓存
:::
## 9. 内存持续增长(内存泄漏)
**现象**:进程内存持续增长,最终 OOM
**排查**:
```bash
# 1. 监控内存变化
watch -n 5 'ps aux | grep tornado.httpserver'
# 2. 查看进程内存
cat /proc/<PID>/status | grep VmRSS
# 3. 使用 memory_profiler 分析
pip install memory_profiler
python -m memory_profiler app.py
```
:::tip 常见原因
- 全局变量不断累积数据
- 数据库连接未正确释放
- 大文件读入内存未释放
- 日志 Handler 积累
解决方案:使用 `--max-requests` 参数定期重启 Worker。
:::
## 10. Docker 容器启动失败
**现象**:`docker-compose up` 后容器立即退出
**排查**:
```bash
# 1. 查看容器日志
docker-compose logs tornado_elevue
# 2. 进入容器调试
docker run -it --rm tornado_elevue bash
# 3. 检查 .env 文件
cat .env
# 4. 检查 Docker 网络
docker network ls
docker network inspect host
```
:::tip 常见原因
- `.env` 文件未正确挂载或配置错误
- 容器内无法访问宿主机的 MySQL/Redis(host 网络模式需检查)
- 依赖安装失败(网络问题)
- Python 版本不兼容
:::
## 快速排查命令速查
| 场景 | 命令 |
|------|------|
| 查看服务日志 | `tail -f logs/supervisor.out.log` |
| 查看 Docker 日志 | `docker-compose logs -f` |
| 检查端口占用 | `lsof -i :8041` |
| 检查进程状态 | `ps aux \| grep tornado.httpserver` |
| 检查磁盘空间 | `df -h` |
| 检查内存使用 | `free -h` |
| 测试数据库连接 | `mysql -u root -p -e "SELECT 1"` |
| 测试 Redis 连接 | `redis-cli ping` |
| 检查 Nginx 配置 | `nginx -t` |
| 重启应用 | `supervisorctl restart djangoadmin-tornado` |
## 总结
故障排查的核心思路:先看日志定位错误,再检查配置排除低级问题,最后逐步深入分析。本文覆盖了启动失败、认证授权、验证码、文件上传、慢查询、内存泄漏、Docker 等 10 类常见故障,每类均提供排查步骤和常见原因。建议收藏本文作为运维应急参考。
@@ -0,0 +1,224 @@
# 版本升级流程
## 概述
本文介绍版本升级标准流程,包括升级前准备、执行步骤、验证检查和回滚方案。遵循规范的升级流程可最大程度降低线上风险。
:::tip 升级原则
1. 先备份,后升级
2. 先测试环境,后生产环境
3. 每次升级可回滚
4. 低峰期执行升级
:::
## 升级前准备
### 备份
```bash
# 1. 数据库全量备份
mysqldump -h 127.0.0.1 -u root -p --single-transaction \
djangoadmin_prod | gzip > backup_$(date +%Y%m%d_%H%M%S).sql.gz
# 2. 上传文件备份
tar -czf uploads_$(date +%Y%m%d).tar.gz /data/apps/uploads/
# 3. 应用目录备份(含 .env 和配置)
tar -czf app_$(date +%Y%m%d).tar.gz \
--exclude='uploads' --exclude='__pycache__' --exclude='.git' \
/data/apps/
```
### 查看变更日志
```bash
# 查看当前版本
git describe --tags --always
# 查看提交记录
git log --oneline -20
# 查看变更文件
git diff --stat HEAD~10
```
:::tip 重点关注
- 是否有数据库结构变更(Alembic 迁移脚本)
- 是否有 `.env` 新增配置项
- 是否有依赖包更新(requirements.txt)
- 是否有破坏性变更(Breaking Changes)
:::
## 标准升级步骤
### 步骤一:拉取代码
```bash
cd /data/apps/djangoadmin
# 拉取最新代码
git fetch origin
git pull origin main
# 或切换到指定版本
git checkout v1.2.0
```
### 步骤二:更新依赖
```bash
# 激活虚拟环境
source venv/bin/activate
# 更新依赖
pip install -r requirements.txt
```
### 步骤三:更新配置
```bash
# 检查是否有新增的 .env 配置项
git diff HEAD~1 .env.example
# 如有新增项,手动添加到 .env
vim .env
```
### 步骤四:执行数据库迁移
```bash
# 查看待执行的迁移
alembic current
alembic history --indicate-current
# 执行迁移
alembic upgrade head
# 或使用 Make 命令
make migrate-upgrade
```
:::warning 迁移前确认
- 迁移脚本需提前在测试环境验证
- 确认迁移不会锁表影响线上服务
- 大表结构变更建议在低峰期执行
:::
### 步骤五:重启服务
```bash
# Supervisor 管理
sudo supervisorctl restart djangoadmin-tornado
# Docker 部署
docker-compose down && docker-compose up --build -d
# Systemd 管理
sudo systemctl restart djangoadmin-tornado
```
### 步骤六:验证
```bash
# 1. 检查服务状态
sudo supervisorctl status djangoadmin-tornado
# 2. 检查健康端点
curl -s http://127.0.0.1:8041/api/v1/health | python -m json.tool
# 3. 检查日志
tail -f /data/apps/logs/supervisor.out.log
# 4. 验证核心功能
# - 登录接口
# - 列表查询接口
# - 新增/编辑/删除接口
```
## 回滚方案
### 代码回滚
```bash
# 回退到上一个版本
git checkout HEAD~1
# 或回退到指定版本
git checkout v1.1.0
# 重新安装依赖
pip install -r requirements.txt
# 重启服务
sudo supervisorctl restart djangoadmin-tornado
```
### 数据库回滚
```bash
# 回滚最近一次迁移
alembic downgrade -1
# 回滚到指定版本
alembic downgrade <revision_id>
# 查看可用版本
alembic history
```
### 完整回滚
```bash
# 1. 停止服务
sudo supervisorctl stop djangoadmin-tornado
# 2. 恢复应用目录
cd /data/apps/
tar -xzf app_20260905.tar.gz
# 3. 恢复数据库
gunzip < backup_20260905.sql.gz | mysql -u root -p djangoadmin_prod
# 4. 恢复上传文件
tar -xzf uploads_20260905.tar.gz -C /
# 5. 启动服务
sudo supervisorctl start djangoadmin-tornado
```
## 灰度升级策略
对于重要生产环境,建议采用灰度升级:
```
1. 部署新版本到 1 台服务器
2. Nginx 将 10% 流量切到新版本
3. 观察 30 分钟,无异常后逐步增加流量
4. 流量 100% 切到新版本
5. 下线旧版本
```
```nginx
# Nginx 灰度配置
upstream backend {
server 10.0.1.10:8041 weight=9; # 旧版本 90%
server 10.0.1.11:8041 weight=1; # 新版本 10%
}
```
## 升级检查清单
- [ ] 升级前已完成数据库备份
- [ ] 升级前已完成应用目录备份
- [ ] 变更日志已审查,了解所有变更
- [ ] 新增 `.env` 配置项已添加
- [ ] 数据库迁移已在测试环境验证
- [ ] 依赖包已更新
- [ ] 服务已重启
- [ ] 健康检查通过
- [ ] 核心功能验证通过
- [ ] 日志无异常报错
## 总结
版本升级的核心流程:备份 → 拉代码 → 更新依赖 → 更新配置 → 数据库迁移 → 重启 → 验证。每一步都需要可回滚,完整回滚方案应提前准备并验证。生产环境建议在低峰期执行,重要系统采用灰度升级策略。