Files
photography/backend/pkg/migration
xujiang 5dd0bc19e4
Some checks failed
部署管理后台 / 🧪 测试和构建 (push) Failing after 1m5s
部署管理后台 / 🔒 安全扫描 (push) Has been skipped
部署后端服务 / 🧪 测试后端 (push) Failing after 3m13s
部署前端网站 / 🧪 测试和构建 (push) Failing after 2m10s
部署管理后台 / 🚀 部署到生产环境 (push) Has been skipped
部署后端服务 / 🚀 构建并部署 (push) Has been skipped
部署管理后台 / 🔄 回滚部署 (push) Has been skipped
部署前端网站 / 🚀 部署到生产环境 (push) Has been skipped
部署后端服务 / 🔄 回滚部署 (push) Has been skipped
style: 统一代码格式化 (go fmt + 配置更新)
- 后端:应用 go fmt 自动格式化,统一代码风格
- 前端:更新 API 配置,完善类型安全
- 所有代码符合项目规范,准备生产部署
2025-07-14 10:02:04 +08:00
..

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) 手动修复数据

更多资源