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

6.3 KiB
Raw Blame History

Migration Package Documentation

概述

这是摄影作品集项目的数据库迁移包,提供了完整的数据库版本管理和迁移功能。

快速开始

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

创建迁移器

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 结构

type Migration struct {
    Version     string    // 版本号 (YYYYMMDD_HHMMSS)
    Description string    // 描述
    UpSQL       string    // 向前迁移 SQL
    DownSQL     string    // 回滚 SQL
    Timestamp   time.Time // 时间戳
}

开发新迁移

1. 添加到 migrations.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)

-- 使用 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)

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

解决方案

使用表重建模式:

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

测试

单元测试

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"))
}

集成测试

# 在测试数据库上运行迁移
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. 数据安全

// 在重要操作前备份
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. 性能优化

-- 大表迁移时删除索引
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) 手动修复数据

更多资源