# 数据库迁移系统 ## 概述 本项目使用版本化的数据库迁移系统,支持增量更新和自动同步。 ## 特性 - ✅ 版本管理:每个迁移有唯一版本号 - ✅ 增量执行:只执行未执行的迁移 - ✅ 自动运行:服务器启动时自动执行迁移 - ✅ 幂等性:重复执行不会出错 - ✅ 迁移记录:记录所有已执行的迁移 ## 迁移文件命名规范 ``` XXXX-description.js ``` - `XXXX`: 4位数字版本号(如 0001, 0002) - `description`: 迁移描述(使用短横线分隔) 示例: - `0001-add-danmaku-fields.js` - `0002-add-user-avatar.js` ## 创建新迁移 ### 1. 创建迁移文件 在 `backend/migrations/` 目录下创建新文件: ```javascript /** * 迁移: 添加用户头像字段 * 版本: 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,回滚可能需要重建表。 ## 执行迁移 ### 自动执行(推荐) 服务器启动时会自动执行所有未执行的迁移: ```bash # 开发环境 cd backend npm start # 生产环境(Docker) docker-compose up -d # 或 docker-compose restart ``` ### 手动执行 #### 在容器内执行: ```bash # Windows migrate.bat # Linux/Mac chmod +x migrate.sh ./migrate.sh ``` #### 直接在容器中执行: ```bash docker exec quanyoumi-app node /app/backend/migrations/index.js ``` ## 迁移状态 迁移执行记录保存在 `migrations` 表中: ```sql 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`。 ## 最佳实践 1. **版本号递增**:新迁移的版本号应大于所有现有迁移 2. **描述清晰**:文件名和注释应清楚说明迁移目的 3. **测试迁移**:在开发环境测试后再部署到生产环境 4. **幂等性**:迁移应该可以安全地重复执行 5. **小步迭代**:每个迁移只做一件事,便于追踪和回滚 ## 示例 ### 添加新字段 ```javascript module.exports = { async up(manager) { await manager.executeSql(` ALTER TABLE Activities ADD COLUMN featured INTEGER DEFAULT 0; `); } }; ``` ### 创建新表 ```javascript 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 ); `); } }; ``` ### 修改数据 ```javascript module.exports = { async up(manager) { await manager.executeSql(` UPDATE Activities SET status = 'completed' WHERE endTime < datetime('now'); `); } }; ```