Skip to content

上线与运维

数据库迁移

手动迁移

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>&1

Docker 环境备份

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

备份恢复验证

备份不验证等于没有备份

定期(如每月)在测试环境恢复备份,确认:

  1. 备份文件可以正常解压
  2. 数据库可以正常恢复
  3. 恢复后的数据完整可用
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 后数据已丢失)。建议:

  1. 新版本只加表/列,不删表/列(向后兼容迁移)
  2. 确认无问题后,下一个版本再清理废弃的表/列
  3. 这种策略叫"两阶段迁移"(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
fi

Cron 监控

bash
# 每 5 分钟检查一次
*/5 * * * * /app/scripts/monitor.sh >> /var/log/monitor.log 2>&1

监控的监控

如果监控脚本本身出问题(如服务器宕机,Cron 也不运行),谁来发现?建议:

  1. 外部监控:使用 UptimeRobot、阿里云云监控等外部服务检测 /health
  2. 多层级:本机 Cron + 外部监控,双重保障
  3. 告警升级: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 3030保留最近 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
  • [ ] 性能基准测试(对比上月的响应时间)

故障响应手册

常见故障与处理

故障现象可能原因处理步骤
服务返回 502Node.js 进程崩溃pm2 restart flutter-api → 查看错误日志
响应变慢数据库慢查询检查 pg_stat_activity → 杀死慢查询 → 添加索引
内存持续增长内存泄漏pm2 reload flutter-api → 排查泄漏代码
登录全部失败JWT 密钥变更或数据库断连检查 .envcurl localhost:3000/health
上传文件失败磁盘满或目录权限df -hchmod → 清理旧文件

故障分级

级别影响响应时间示例
P0核心功能不可用5 分钟支付失败、无法登录
P1部分功能受影响30 分钟搜索不工作、上传失败
P2体验问题4 小时页面加载慢、样式错乱
P3无影响下一版本UI 细节、文案优化

基于 Nuxt 4 官方文档整理编写