# Migration Package Documentation ## 概述 这是摄影作品集项目的数据库迁移包,提供了完整的数据库版本管理和迁移功能。 ## 快速开始 ```go package main import ( "photography-backend/pkg/migration" "photography-backend/pkg/utils/database" ) func main() { // 创建数据库连接 db, err := database.NewDB(dbConfig) if err != nil { panic(err) } // 创建迁移器 migrator := migration.NewMigrator(db) // 添加迁移 migrations := migration.GetAllMigrations() for _, m := range migrations { migrator.AddMigration(m) } // 执行迁移 if err := migrator.Up(); err != nil { panic(err) } } ``` ## API 参考 ### Migrator #### 创建迁移器 ```go migrator := migration.NewMigrator(db) ``` #### 主要方法 | 方法 | 描述 | 示例 | |------|------|------| | `AddMigration(migration)` | 添加迁移 | `migrator.AddMigration(m)` | | `Up()` | 执行所有待处理迁移 | `migrator.Up()` | | `Down(steps)` | 回滚指定步数迁移 | `migrator.Down(2)` | | `Status()` | 显示迁移状态 | `migrator.Status()` | | `Reset()` | 重置数据库 | `migrator.Reset()` | | `Migrate(steps)` | 执行指定数量迁移 | `migrator.Migrate(3)` | ### Migration 结构 ```go type Migration struct { Version string // 版本号 (YYYYMMDD_HHMMSS) Description string // 描述 UpSQL string // 向前迁移 SQL DownSQL string // 回滚 SQL Timestamp time.Time // 时间戳 } ``` ## 开发新迁移 ### 1. 添加到 migrations.go ```go { Version: "20250111_120000", Description: "Add user avatar field", Timestamp: time.Date(2025, 1, 11, 12, 0, 0, 0, time.UTC), UpSQL: ` ALTER TABLE user ADD COLUMN avatar VARCHAR(255) DEFAULT ''; CREATE INDEX IF NOT EXISTS idx_user_avatar ON user(avatar); `, DownSQL: ` DROP INDEX IF EXISTS idx_user_avatar; -- SQLite specific: recreate table without avatar field CREATE TABLE user_temp AS SELECT id, username, password, email, status, created_at, updated_at FROM user; DROP TABLE user; ALTER TABLE user_temp RENAME TO user; `, }, ``` ### 2. 版本号规范 - 格式: `YYYYMMDD_HHMMSS` - 示例: `20250111_120000` (2025年1月11日 12:00:00) - 确保唯一性和时间顺序 ### 3. SQL 编写规范 #### UP Migration (UpSQL) ```sql -- 使用 IF NOT EXISTS 确保幂等性 CREATE TABLE IF NOT EXISTS new_table (...); ALTER TABLE user ADD COLUMN IF NOT EXISTS new_field VARCHAR(255); CREATE INDEX IF NOT EXISTS idx_name ON table(field); -- 数据迁移使用 OR IGNORE INSERT OR IGNORE INTO table (field) VALUES ('value'); UPDATE table SET field = 'value' WHERE condition; ``` #### DOWN Migration (DownSQL) ```sql -- SQLite 不支持 DROP COLUMN,需要重建表 CREATE TABLE table_temp ( -- 列出所有保留的字段 id INTEGER PRIMARY KEY, existing_field VARCHAR(255) ); INSERT INTO table_temp SELECT id, existing_field FROM table; DROP TABLE table; ALTER TABLE table_temp RENAME TO table; -- 重建索引 CREATE INDEX IF NOT EXISTS idx_name ON table(field); ``` ## SQLite 特殊考虑 ### 不支持的操作 SQLite 不支持以下操作: - `ALTER TABLE DROP COLUMN` - `ALTER TABLE MODIFY COLUMN` - `ALTER TABLE ADD CONSTRAINT` ### 解决方案 使用表重建模式: ```sql -- 1. 创建新表结构 CREATE TABLE table_new ( id INTEGER PRIMARY KEY, field1 VARCHAR(255), -- 新字段或修改的字段 field2 INTEGER DEFAULT 0 ); -- 2. 复制数据 INSERT INTO table_new (id, field1, field2) SELECT id, field1, COALESCE(old_field, 0) FROM table; -- 3. 删除旧表 DROP TABLE table; -- 4. 重命名新表 ALTER TABLE table_new RENAME TO table; -- 5. 重建索引和触发器 CREATE INDEX IF NOT EXISTS idx_field1 ON table(field1); ``` ## 测试 ### 单元测试 ```go func TestMigration(t *testing.T) { // 创建内存数据库 db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) require.NoError(t, err) // 创建迁移器 migrator := migration.NewMigrator(db) // 添加测试迁移 migrator.AddMigration(testMigration) // 测试向前迁移 err = migrator.Up() assert.NoError(t, err) // 验证表结构 assert.True(t, db.Migrator().HasTable("test_table")) // 测试回滚 err = migrator.Down(1) assert.NoError(t, err) // 验证回滚结果 assert.False(t, db.Migrator().HasTable("test_table")) } ``` ### 集成测试 ```bash # 在测试数据库上运行迁移 go run cmd/migrate/main.go -f etc/test.yaml -c up # 验证数据完整性 sqlite3 test.db "SELECT COUNT(*) FROM user;" # 测试回滚 go run cmd/migrate/main.go -f etc/test.yaml -c down -s 1 ``` ## 最佳实践 ### 1. 迁移设计 - **小步快跑**: 每次迁移只做一件事 - **可回滚**: 确保每个迁移都可以安全回滚 - **向后兼容**: 新字段使用默认值,避免破坏现有功能 - **测试优先**: 在开发环境充分测试后再部署 ### 2. 数据安全 ```go // 在重要操作前备份 migrator.CreateBackup() // 使用事务 tx := db.Begin() defer func() { if r := recover(); r != nil { tx.Rollback() } }() // 执行迁移 err := migrator.Up() if err != nil { tx.Rollback() return err } tx.Commit() ``` ### 3. 性能优化 ```sql -- 大表迁移时删除索引 DROP INDEX idx_large_table_field; -- 执行数据迁移 UPDATE large_table SET new_field = 'value'; -- 重建索引 CREATE INDEX idx_large_table_field ON large_table(field); ``` ## 常见问题 ### Q: 如何处理冲突的迁移版本? A: 使用时间戳版本号,确保每个版本唯一。如果出现冲突,重新生成版本号。 ### Q: SQLite 如何删除字段? A: SQLite 不支持 DROP COLUMN,需要重建表。参考上面的表重建模式。 ### Q: 如何在生产环境安全迁移? A: 1) 备份数据库 2) 在测试环境验证 3) 使用预览模式检查 4) 执行迁移 5) 验证结果 ### Q: 迁移失败如何恢复? A: 1) 使用 Down 迁移回滚 2) 从备份恢复 3) 手动修复数据 ## 更多资源 - [完整文档](../docs/DATABASE_MIGRATION.md) - [命令行工具](../cmd/migrate/main.go) - [生产脚本](../scripts/production-migrate.sh) - [Makefile 集成](../Makefile)