|
|
||
|---|---|---|
| .. | ||
| 0000-init-schema.js | ||
| 0001-add-danmaku-fields.js | ||
| 0002-add-sponsor-sortorder.js | ||
| 0003-add-checkin-fields.js | ||
| 0004-add-prize-table.js | ||
| 0005-add-prize-source.js | ||
| 0006-add-grand-prize-level.js | ||
| 0007-add-admin-role.js | ||
| 0008-add-fourth-prize-level.js | ||
| 0009-add-group-status.js | ||
| README.md | ||
| index.js | ||
README.md
数据库迁移系统
概述
本项目使用版本化的数据库迁移系统,支持增量更新和自动同步。
特性
- ✅ 版本管理:每个迁移有唯一版本号
- ✅ 增量执行:只执行未执行的迁移
- ✅ 自动运行:服务器启动时自动执行迁移
- ✅ 幂等性:重复执行不会出错
- ✅ 迁移记录:记录所有已执行的迁移
迁移文件命名规范
XXXX-description.js
XXXX: 4位数字版本号(如 0001, 0002)description: 迁移描述(使用短横线分隔)
示例:
0001-add-danmaku-fields.js0002-add-user-avatar.js
创建新迁移
1. 创建迁移文件
在 backend/migrations/ 目录下创建新文件:
/**
* 迁移: 添加用户头像字段
* 版本: 0002
* 日期: 2025-01-09
*/
module.exports = {
async up(manager) {
console.log(' - 添加 avatar 字段...');
await manager.executeSql(`
ALTER TABLE Users ADD COLUMN avatar TEXT;
`);
},
async down(manager) {
// 回滚逻辑(可选)
console.log(' - 回滚不支持');
}
};
2. 迁移方法
up(manager) - 升级
执行数据库变更的方法。
可用的 manager 方法:
executeSql(sql): 执行SQL语句db: 直接访问数据库连接
down(manager) - 回滚(可选)
回滚数据库变更的方法。
注意:SQLite 不支持 DROP COLUMN,回滚可能需要重建表。
执行迁移
自动执行(推荐)
服务器启动时会自动执行所有未执行的迁移:
# 开发环境
cd backend
npm start
# 生产环境(Docker)
docker-compose up -d
# 或
docker-compose restart
手动执行
在容器内执行:
# Windows
migrate.bat
# Linux/Mac
chmod +x migrate.sh
./migrate.sh
直接在容器中执行:
docker exec quanyoumi-app node /app/backend/migrations/index.js
迁移状态
迁移执行记录保存在 migrations 表中:
SELECT * FROM migrations;
输出示例:
id | version | name | executed_at
---+---------+-----------------------+---------------------
1 | 1 | add-danmaku-fields | 2025-01-09 10:30:00
2 | 2 | add-user-avatar | 2025-01-09 11:00:00
常见问题
Q: 迁移失败怎么办?
A: 检查错误日志,修复问题后重新启动服务器。已执行的迁移不会重复执行。
Q: 如何回滚迁移?
A: 目前不支持自动回滚。如需回滚,请手动修改数据库或创建新的迁移来撤销更改。
Q: 可以修改已执行的迁移吗?
A: 不建议。已执行的迁移不应修改。如需更改,请创建新的迁移。
Q: 迁移在什么时候执行?
A: 服务器启动时自动执行。也可以手动运行 migrate.bat 或 migrate.sh。
最佳实践
- 版本号递增:新迁移的版本号应大于所有现有迁移
- 描述清晰:文件名和注释应清楚说明迁移目的
- 测试迁移:在开发环境测试后再部署到生产环境
- 幂等性:迁移应该可以安全地重复执行
- 小步迭代:每个迁移只做一件事,便于追踪和回滚
示例
添加新字段
module.exports = {
async up(manager) {
await manager.executeSql(`
ALTER TABLE Activities ADD COLUMN featured INTEGER DEFAULT 0;
`);
}
};
创建新表
module.exports = {
async up(manager) {
await manager.executeSql(`
CREATE TABLE IF NOT EXISTS Comments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
activityId INTEGER NOT NULL,
userId INTEGER NOT NULL,
content TEXT NOT NULL,
createdAt DATETIME DEFAULT CURRENT_TIMESTAMP
);
`);
}
};
修改数据
module.exports = {
async up(manager) {
await manager.executeSql(`
UPDATE Activities SET status = 'completed' WHERE endTime < datetime('now');
`);
}
};