上线与运维
数据库迁移
手动迁移
bash
# 生成迁移文件
npx drizzle-kit generate
# 执行迁移
npx drizzle-kit migrate生产环境迁移安全流程
text
┌─────── 迁移前 ──────────┐
│ │
│ 1. 备份数据库 │
│ 2. 在测试环境验证迁移 │
│ 3. 检查生成的 SQL 文件 │
│ │
├─────── 迁移中 ───────────┤
│ │
│ 4. 选择低峰时段 │
│ 5. 执行迁移 │
│ 6. 验证应用功能 │
│ │
├─────── 迁移后 ───────────┤
│ │
│ 7. 监控错误日志 │
│ 8. 如有问题,回滚 │
│ │
└──────────────────────────┘bash
# 备份数据库
pg_dump -U myuser flutter_api > backup_$(date +%Y%m%d).sql
# 执行迁移
npx drizzle-kit migrate
# 验证迁移
npx drizzle-kit studio # 打开数据库管理界面Drizzle 迁移原理
text
drizzle/ ← 迁移文件目录
├── 0000_create_users.sql
├── 0001_add_vip_expires.sql
└── meta/
├── _journal.json ← 记录已执行的迁移
└── ...generate:对比 schema 变更,生成新的 SQL 文件migrate:检查_journal.json,执行未运行的迁移文件- 已执行的迁移不会再执行(幂等)
为什么迁移文件要提交到 Git?
迁移文件记录了数据库的变更历史,团队成员拉取代码后执行 migrate 即可同步数据库结构。不提交的话,其他人的数据库会与代码不匹配。
危险迁移的识别
| 迁移类型 | 风险 | 建议 |
|---|---|---|
CREATE TABLE | 低 | 新增表,不影响现有数据 |
ADD COLUMN | 低 | 新增列,现有行新列为 NULL |
DROP COLUMN | ⚠️ 高 | 删除列,数据永久丢失 |
ALTER COLUMN | ⚠️ 高 | 修改类型可能导致数据截断 |
DROP TABLE | 🚨 极高 | 整表删除,不可恢复 |
删除列/表前一定要确认没有代码引用它
建议先在代码中移除引用,部署后再执行迁移——而不是同一次部署同时改代码和删列。
备份策略
数据库备份脚本
bash
#!/bin/bash
# backup.sh
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/backups"
DB_NAME="flutter_api"
DB_USER="myuser"
# 创建备份目录
mkdir -p $BACKUP_DIR
# 备份数据库
pg_dump -U $DB_USER $DB_NAME | gzip > $BACKUP_DIR/db_$DATE.sql.gz
# 保留最近 7 天的备份
find $BACKUP_DIR -name "db_*.sql.gz" -mtime +7 -delete
echo "备份完成: db_$DATE.sql.gz"定时备份(Cron)
bash
# 每天凌晨 2 点备份
crontab -e
0 2 * * * /app/scripts/backup.sh >> /var/log/backup.log 2>&1Docker 环境备份
bash
# 备份 PostgreSQL
docker compose exec db pg_dump -U postgres flutter_api | gzip > backup.sql.gz
# 恢复
gunzip < backup.sql.gz | docker compose exec -T db psql -U postgres flutter_api备份恢复验证
备份不验证等于没有备份
定期(如每月)在测试环境恢复备份,确认:
- 备份文件可以正常解压
- 数据库可以正常恢复
- 恢复后的数据完整可用
bash
# 测试恢复(在测试环境)
createdb flutter_api_test
gunzip < backup.sql.gz | psql -U myuser flutter_api_test
# 验证数据
psql -U myuser flutter_api_test -c "SELECT count(*) FROM users;"版本发布
语义化版本
text
主版本号.次版本号.修订号
1.0.0 → 首次发布
1.1.0 → 新增功能
1.1.1 → Bug 修复
2.0.0 → 破坏性变更发布流程
bash
#!/bin/bash
# release.sh
VERSION=$1
if [ -z "$VERSION" ]; then
echo "Usage: ./release.sh <version>"
exit 1
fi
# 1. 更新版本号
npm version $VERSION -no-git-tag-version
# 2. 运行测试
npm test
# 3. 构建
npm run build
# 4. 提交
git add .
git commit -m "release: v$VERSION"
git tag "v$VERSION"
git push origin main --tags
echo "发布 v$VERSION 完成"回滚方案
bash
# 方式一:Git 回滚(代码回退)
git revert HEAD
git push origin main
# 方式二:回滚到指定版本
git checkout v1.0.0
npm install
npm run build
pm2 restart flutter-api
# 方式三:Docker 回滚
docker compose down
docker compose up -d --build # 重新构建旧版本回滚验证清单
回滚后不要认为"完事了",需要验证:
- [ ] 应用正常启动(检查 PM2 状态)
- [ ] 健康检查通过(
curl http://localhost:3000/health) - [ ] 核心功能可用(登录、支付、API 响应正常)
- [ ] 错误日志无异常
- [ ] 数据库迁移是否需要回滚(如果新版本有迁移,回滚后可能不兼容)
数据库迁移的回滚难题
Git 可以回滚代码,但数据库迁移很难回滚(DROP TABLE 后数据已丢失)。建议:
- 新版本只加表/列,不删表/列(向后兼容迁移)
- 确认无问题后,下一个版本再清理废弃的表/列
- 这种策略叫"两阶段迁移"(expand + contract)
两阶段迁移示例
假设需要将 users.phone 改为 users.phone_number:
text
版本 1.1.0(expand):新增 phone_number 列,代码同时写入 phone 和 phone_number
版本 1.2.0(contract):确认 1.1.0 运行正常后,删除 phone 列,代码只使用 phone_number这样 1.1.0 可以安全回滚到 1.0.0(phone 列仍在),1.2.0 可以安全回滚到 1.1.0(phone_number 列仍在)。
监控面板
简易监控脚本
bash
#!/bin/bash
# monitor.sh
HEALTH_URL="http://localhost:3000/health"
ALERT_WEBHOOK="your-webhook-url"
response=$(curl -s -o /dev/null -w "%{http_code}" $HEALTH_URL)
if [ "$response" != "200" ]; then
echo "服务异常!状态码: $response"
# 发送告警
curl -X POST $ALERT_WEBHOOK \
-H 'Content-Type: application/json' \
-d "{\"content\": \"🚨 服务异常!状态码: $response\"}"
# 重启服务
pm2 restart flutter-api
fiCron 监控
bash
# 每 5 分钟检查一次
*/5 * * * * /app/scripts/monitor.sh >> /var/log/monitor.log 2>&1监控的监控
如果监控脚本本身出问题(如服务器宕机,Cron 也不运行),谁来发现?建议:
- 外部监控:使用 UptimeRobot、阿里云云监控等外部服务检测
/health - 多层级:本机 Cron + 外部监控,双重保障
- 告警升级:3 分钟内未恢复 → 企业微信通知;10 分钟内未恢复 → 电话通知
日志管理
bash
# PM2 日志
pm2 logs flutter-api --lines 100
# Nginx 日志
tail -f /var/log/nginx/access.log
tail -f /var/log/nginx/error.log
# 日志轮转(logrotate)
cat > /etc/logrotate.d/flutter-api << EOF
/var/log/flutter-api/*.log {
daily
rotate 30
compress
missingok
notifempty
}
EOF日志轮转参数说明
| 参数 | 值 | 含义 |
|---|---|---|
daily | - | 每天轮转一次 |
rotate 30 | 30 | 保留最近 30 个轮转文件 |
compress | - | 旧日志用 gzip 压缩 |
missingok | - | 日志文件不存在不报错 |
notifempty | - | 空文件不轮转 |
运维检查清单
日常检查
- [ ] 服务是否正常运行(
pm2 list) - [ ] 内存使用是否正常(
pm2 monit) - [ ] 数据库连接是否正常(
curl localhost:3000/health) - [ ] Redis 连接是否正常
- [ ] 错误日志是否异常(
pm2 logs --err)
周检查
- [ ] 慢请求分析(检查
performance中间件日志) - [ ] 数据库性能检查(
pg_stat_activity查看活跃连接) - [ ] 磁盘空间检查(
df -h) - [ ] 备份是否正常(检查备份文件是否存在且可解压)
月检查
- [ ] 安全补丁更新(
npm audit) - [ ] 依赖版本更新(
npm outdated) - [ ] SSL 证书续期(
certbot certificates) - [ ] 性能基准测试(对比上月的响应时间)
故障响应手册
常见故障与处理
| 故障现象 | 可能原因 | 处理步骤 |
|---|---|---|
| 服务返回 502 | Node.js 进程崩溃 | pm2 restart flutter-api → 查看错误日志 |
| 响应变慢 | 数据库慢查询 | 检查 pg_stat_activity → 杀死慢查询 → 添加索引 |
| 内存持续增长 | 内存泄漏 | pm2 reload flutter-api → 排查泄漏代码 |
| 登录全部失败 | JWT 密钥变更或数据库断连 | 检查 .env → curl localhost:3000/health |
| 上传文件失败 | 磁盘满或目录权限 | df -h → chmod → 清理旧文件 |
故障分级
| 级别 | 影响 | 响应时间 | 示例 |
|---|---|---|---|
| P0 | 核心功能不可用 | 5 分钟 | 支付失败、无法登录 |
| P1 | 部分功能受影响 | 30 分钟 | 搜索不工作、上传失败 |
| P2 | 体验问题 | 4 小时 | 页面加载慢、样式错乱 |
| P3 | 无影响 | 下一版本 | UI 细节、文案优化 |