duoqi-api/docs/database-migration-guide.md
Wang Zhuoxuan 5b1f0848ac
Some checks failed
CI/CD Pipeline / Code Quality (push) Failing after 17s
CI/CD Pipeline / Unit Tests (push) Has been skipped
CI/CD Pipeline / Build & Deploy Test (push) Has been skipped
CI/CD Pipeline / Build & Deploy Production (push) Has been skipped
feat: 添加管理员修改自己密码的接口
新增 PUT /v1/admin/change-password 端点,允许已登录管理员
(admin / super_admin)修改自己的密码。需验证旧密码,
且新旧密码不能相同。错误由全局 errorHandler 统一处理。
2026-04-23 12:32:31 +08:00

11 KiB
Raw Permalink Blame History

数据库初始化与迁移指南

本地开发机 → 阿里云 RDS MySQL 8.0+ 的数据库操作完整指南

目录


概述

技术选型

组件 说明
ORM Drizzle ORM — 类型安全schema 即代码
Schema 真相源 src/db/schema.ts — 所有表定义的唯一来源
迁移工具 drizzle-kit — 生成和执行 SQL 迁移文件
迁移文件目录 db/migrations/ — 版本化的 SQL 变更记录
种子数据 content/*.json + db/seeds/index.ts

表结构总览15 张表)

类别 表名 说明
核心 users, categories, questions, knowledge_cards, user_progress, skill_tree, user_chapter_progress 用户、题目、进度
反馈 question_ratings, user_feedback 评价与反馈
游戏化 achievements, user_achievements, leaderboard_snapshots 成就与排行
商业 subscriptions 订阅与 IAP
管理员 admin_users, admin_audit_log 后台管理与审计

两条核心命令

场景 命令 说明
开发/首次部署 bun run db:push 直接从 schema.ts 同步到数据库,适合快速迭代
生产增量变更 bun run db:migrate 执行 db/migrations/ 中的 SQL 文件,幂等且可审计

选择原则:首次初始化用 db:push(简单直接),后续变更用 db:migrate(安全可控)。


前置条件

1. 本地环境

# 确认 bun 已安装
bun --version

# 安装项目依赖
cd duoqi-api
bun install

2. 阿里云 RDS

  • 已创建 MySQL 8.0+ 实例
  • 已在 RDS 控制台 白名单设置 中添加本机公网 IP
  • 已获取连接信息:地址、端口、用户名、密码

3. 网络连通性验证

# 替换为实际 RDS 地址
mysql -h rm-xxxxx.mysql.rds.aliyuncs.com -u root -p -e "SELECT 1;"

如果连接超时,检查:

  • RDS 白名单是否已添加本机 IP阿里云 RDS 控制台 → 数据安全性 → 白名单设置)
  • 本机防火墙/代理是否出站 3306 端口

首次初始化

适用于:全新的 RDS 实例,数据库为空。

Step 0创建 RDS 数据库

在阿里云 RDS 控制台的 DMS 数据管理 或本地 MySQL 客户端中执行:

-- 生产库
CREATE DATABASE duoqi_prod CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- 测试库
CREATE DATABASE duoqi_test CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- 创建用户并授权(如使用 root 可跳过)
CREATE USER 'duoqi_prod'@'%' IDENTIFIED BY 'your-secure-password';
GRANT ALL PRIVILEGES ON duoqi_prod.* TO 'duoqi_prod'@'%';

CREATE USER 'duoqi_test'@'%' IDENTIFIED BY 'your-secure-password';
GRANT ALL PRIVILEGES ON duoqi_test.* TO 'duoqi_test'@'%';

FLUSH PRIVILEGES;

Step 1配置本地连接串

编辑本地项目 .env 文件,将 DATABASE_URL 指向目标 RDS 数据库:

# 初始化生产库
DATABASE_URL=mysql://duoqi_prod:your-password@rm-xxxxx.mysql.rds.aliyuncs.com:3306/duoqi_prod

# 或初始化测试库
# DATABASE_URL=mysql://duoqi_test:your-password@rm-xxxxx.mysql.rds.aliyuncs.com:3306/duoqi_test

注意:操作完成后,记得将 DATABASE_URL 改回本地开发值,避免后续开发误操作生产数据库。

Step 2推送表结构

# 从 schema.ts 直接同步全部 15 张表 + 外键 + 索引
bun run db:push

drizzle-kit push 会:

  1. 读取 src/db/schema.ts 中的表定义
  2. 连接数据库,对比当前状态
  3. 创建所有缺失的表、列、索引和外键

输出示例:

[✓] Changes applied successfully. The following changes were made:
  └─ Table "users" was created
  └─ Table "categories" was created
  └─ ... (共 15 张表)

替代方案:如果不想用 db:push,也可以用 db:migrate 执行已有的迁移文件:

bun run db:migrate

效果相同,区别在于 db:migrate 执行的是 db/migrations/0000_melodic_blacklash.sql 这个固定文件。

Step 3导入种子数据

bun run db:seed

种子脚本 (db/seeds/index.ts) 按依赖拓扑顺序导入,幂等安全(可重复执行):

Step 0: admin_users    → 默认管理员 (admin / admin123)
Step 1: categories     → 4 个分类(历史、戏曲、相声...
Step 2: skill_tree     → 技能树节点
Step 3: questions      → 题目
        knowledge_cards → 知识卡片(依赖 questions
Step 4: achievements   → 成就定义

输出示例:

Admin user seeded: username=admin, password=admin123 (CHANGE IN PRODUCTION!)
Categories: 4 inserted, 0 skipped
Skill tree: 12 inserted, 0 skipped
Questions: 60 inserted, 0 skipped
Achievements: 8 inserted, 0 skipped

Seed data import complete!

Warning

默认管理员密码 admin123 仅用于初始化,首次登录后必须立即修改

Step 4验证

# 方式一Drizzle Studio浏览器可视化
bun run db:studio
# 访问 https://local.drizzle.studio → 浏览所有表和数据

# 方式二:命令行查询
mysql -h rm-xxxxx.mysql.rds.aliyuncs.com -u duoqi_prod -p duoqi_prod \
  -e "SHOW TABLES; SELECT COUNT(*) AS question_count FROM questions;"

预期结果:SHOW TABLES 输出 15 张表,questions 有数据。


日常表结构变更

适用于schema 已初始化,后续迭代需要新增/修改表结构。

开发流程

修改 schema.ts → 生成迁移文件 → 本地验证 → 提交代码

1. 修改 Schema

编辑 src/db/schema.ts,例如新增一张表:

// src/db/schema.ts — 示例:新增通知表
export const notifications = mysqlTable('notifications', {
  id: char('id', { length: 36 }).primaryKey(),
  userId: char('user_id', { length: 36 }).notNull(),
  title: varchar('title', { length: 200 }).notNull(),
  content: text('content'),
  read: tinyint('read').default(0),
  createdAt: datetime('created_at').default(sql`CURRENT_TIMESTAMP`),
});

2. 生成迁移文件

bun run db:generate

drizzle-kit generate 会对比 schema.ts 与上一次快照,在 db/migrations/ 中生成新的 SQL 文件:

db/migrations/
├── 0000_melodic_blacklash.sql   ← 初始化(已存在)
├── 0001_add_notifications.sql   ← 新增
└── meta/
    ├── 0001_snapshot.json
    └── _journal.json

Important

生成的 SQL 文件必须提交到 Git。迁移文件是生产部署的变更记录,不可遗漏。

3. 本地验证

# 方式 A在本地 MySQL 验证
bun run db:push

# 方式 B模拟生产流程推荐
bun run db:migrate

验证通过后,将迁移文件和 schema 变更一起提交:

git add src/db/schema.ts db/migrations/
git commit -m "feat: 新增通知表"

生产部署流程

迁移文件随代码提交后,通过以下两种方式应用到生产数据库:

方式 ACI 自动执行(推荐)

.gitea/workflows/deploy.yml 的部署 Job 中,在构建镜像之前执行迁移:

# deploy.yml — 在 build-and-deploy-prod 和 build-and-deploy-test 中添加
steps:
  - name: Checkout code
    uses: actions/checkout@v4

  - name: Install dependencies
    run: bun install --frozen-lockfile

  - name: Run database migrations
    env:
      DATABASE_URL: ${{ secrets.DATABASE_URL }}
    run: bun run db:migrate

  # ... 后续 build / deploy 步骤

为什么迁移必须在部署前执行? 新代码可能依赖新表/新列。先部署新代码再迁移,中间会有服务报错窗口。先迁移则旧代码仍可正常运行(向后兼容的 schema 变更)。

方式 B手动从本地执行紧急/首次)

从本地开发机直接连接 RDS 执行:

# 1. 临时将 .env 的 DATABASE_URL 改为生产 RDS
# 2. 执行迁移
bun run db:migrate
# 3. 改回本地 DATABASE_URL

或通过环境变量覆盖(不修改 .env

DATABASE_URL=mysql://duoqi_prod:password@rm-xxxxx:3306/duoqi_prod bun run db:migrate

常用操作速查

操作 命令 说明
推送表结构 bun run db:push 从 schema.ts 同步到数据库(开发用)
生成迁移文件 bun run db:generate 对比 schema 差异,生成 SQL 文件
执行迁移 bun run db:migrate 执行未应用的迁移 SQL 文件
导入种子数据 bun run db:seed 幂等导入分类、题目、成就等
可视化浏览 bun run db:studio 启动 Drizzle Studio Web UI
类型检查 bun run typecheck 确认 schema.ts 无类型错误

Drizzle Kit 命令对比

schema.ts (源码真相)
    │
    ├── db:push ────────→ 直接同步到数据库(无迁移文件,开发用)
    │
    ├── db:generate ────→ 生成 SQL 迁移文件到 db/migrations/
    │                       (需要提交到 Git
    │
    └── db:migrate ────→ 执行 db/migrations/ 中的 SQL
                          (生产部署用,幂等)

故障排查

1. db:push 报连接超时

# 检查网络连通性
mysql -h rm-xxxxx.mysql.rds.aliyuncs.com -u root -p -e "SELECT 1;"

# 如果超时,检查 RDS 白名单
# 阿里云控制台 → RDS → 数据安全性 → 白名单设置
# 添加本机公网 IP可通过 curl ifconfig.me 查看)
curl ifconfig.me

2. db:push 报表已存在

Error: Table 'users' already exists

说明数据库中已有表结构。如果是首次初始化,可能是之前执行过。db:push 是幂等的,已有表不会重复创建,可以安全忽略。

3. db:migrate 报迁移已应用

No pending migrations to execute

所有迁移文件已执行过,这是正常输出。

4. db:seed 重复执行

种子脚本是幂等的 — 每条数据插入前都会检查是否已存在,输出 skipped 计数。可安全重复运行。

5. db:generate 未生成新文件

Everything is fine, no changes detected

说明 schema.ts 没有变更,或变更已生成过迁移文件。检查:

  • 是否修改了 src/db/schema.ts 并保存
  • db/migrations/meta/_journal.json 中是否已记录该变更

6. Schema 变更后类型报错

# 重新运行类型检查
bun run typecheck

# 常见原因schema.ts 中修改了字段,但引用该字段的 service/routes 未更新

7. 本地误操作生产数据库

如果不小心对生产数据库执行了破坏性操作:

# 1. 立即停止操作,不要重复执行
# 2. 从备份恢复
mysql -h rm-xxxxx -u duoqi_prod -p duoqi_prod < /opt/backups/duoqi_prod_YYYYMMDD.sql

# 3. 如果没有备份,在 RDS 控制台使用「数据恢复」功能(需已开启 binlog

文档版本: v1.0.0 最后更新: 2026-04-22 维护者: Duoqi Team