- 创建完整的迁移框架 (pkg/migration/) - 版本管理系统,时间戳版本号 (YYYYMMDD_HHMMSS) - 事务安全的上下迁移机制 (Up/Down) - 迁移状态跟踪和记录 (migration_records 表) - 命令行迁移工具 (cmd/migrate/main.go) - 生产环境迁移脚本 (scripts/production-migrate.sh) - 生产环境初始化脚本 (scripts/init-production-db.sh) - 迁移测试脚本 (scripts/test-migration.sh) - Makefile 集成 (migrate-up, migrate-down, migrate-status) - 5个预定义迁移 (基础表、默认数据、元数据、收藏、用户资料) - 自动备份机制、预览模式、详细日志 - 完整文档 (docs/DATABASE_MIGRATION.md) 任务13完成,项目完成率达到42.5%
287 lines
6.3 KiB
Markdown
287 lines
6.3 KiB
Markdown
# 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) |