Files
photography/backend/pkg/migration/README.md
xujiang 84e778e033 feat: 完成数据库迁移系统开发
- 创建完整的迁移框架 (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%
2025-07-11 13:41:52 +08:00

287 lines
6.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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)