You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

将含reversion库的Django项目迁移至Gin(Golang):版本控制实现

Golang(Gin)中实现Django reversion等效的数据版本控制

一、是否有等效库?

Golang生态里没有像Django reversion那样开箱即用、深度集成ORM的版本控制库——毕竟Golang的ORM(比如GORM)本身不像Django ORM那样高度封装。不过可以基于GORM这类常用ORM自行实现类似逻辑,或者参考轻量版本控制工具的思路。

二、迁移实现方案(基于GORM)

针对你提供的Django模型,可通过以下步骤实现版本控制:

1. 设计版本表结构

对应原模型创建版本表,存储历史记录:

  • 对于DocumentRecommendationMedia,创建document_recommendation_media_versions表,包含原表字段+版本号+操作时间+操作类型(新增/修改/删除)
  • 对于DocumentRecommendation,创建document_recommendation_versions表,多对多字段media_files可存储关联ID的JSON数组,或单独创建关联版本表

示例GORM模型:

// 原DocumentRecommendationMedia模型
type DocumentRecommendationMedia struct {
    ID        uint      `gorm:"primaryKey"`
    DocumentID uint     `gorm:"column:document_id"`
    File      string    `gorm:"column:file"`
    FileName  string    `gorm:"column:file_name;size:100"`
    CreatedAt time.Time
    UpdatedAt time.Time
}

func (DocumentRecommendationMedia) TableName() string {
    return "document_recommendation_media"
}

// 版本表模型
type DocumentRecommendationMediaVersion struct {
    ID                uint      `gorm:"primaryKey"`
    OriginalID        uint      `gorm:"column:original_id"` // 关联原记录ID
    DocumentID        uint      `gorm:"column:document_id"`
    File              string    `gorm:"column:file"`
    FileName          string    `gorm:"column:file_name;size:100"`
    VersionNumber     int       `gorm:"column:version_number"` // 自增版本号
    OperationType     string    `gorm:"column:operation_type;size:20"` // create/update/delete
    OperationTime     time.Time `gorm:"column:operation_time"`
}

func (DocumentRecommendationMediaVersion) TableName() string {
    return "document_recommendation_media_versions"
}

// DocumentRecommendation及版本表
type DocumentRecommendation struct {
    ID             uint           `gorm:"primaryKey"`
    DocumentID     uint           `gorm:"column:document_id"`
    Recommendation string         `gorm:"column:recommendation;null"`
    Date           time.Time      `gorm:"column:date"`
    MediaFiles     []DocumentRecommendationMedia `gorm:"many2many:document_recommendation_media_files;"`
    CreatedAt      time.Time
    UpdatedAt      time.Time
}

func (DocumentRecommendation) TableName() string {
    return "document_recommendation"
}

type DocumentRecommendationVersion struct {
    ID                uint      `gorm:"primaryKey"`
    OriginalID        uint      `gorm:"column:original_id"`
    DocumentID        uint      `gorm:"column:document_id"`
    Recommendation    string    `gorm:"column:recommendation;null"`
    Date              time.Time `gorm:"column:date"`
    MediaFileIDs      []uint    `gorm:"column:media_file_ids;type:json"` // 存储关联的media ID数组
    VersionNumber     int       `gorm:"column:version_number"`
    OperationType     string    `gorm:"column:operation_type;size:20"`
    OperationTime     time.Time `gorm:"column:operation_time"`
}

func (DocumentRecommendationVersion) TableName() string {
    return "document_recommendation_versions"
}

2. 实现版本记录逻辑

通过GORM钩子或封装业务函数,在原模型增删改时自动生成版本记录:

(1)使用GORM钩子

在AfterCreate保存初始版本,BeforeUpdate/BeforeDelete保存变更前版本:

// DocumentRecommendationMedia的AfterCreate钩子
func (m *DocumentRecommendationMedia) AfterCreate(tx *gorm.DB) error {
    version := DocumentRecommendationMediaVersion{
        OriginalID:    m.ID,
        DocumentID:    m.DocumentID,
        File:          m.File,
        FileName:      m.FileName,
        VersionNumber: 1,
        OperationType: "create",
        OperationTime: time.Now(),
    }
    return tx.Create(&version).Error
}

// DocumentRecommendationMedia的BeforeUpdate钩子
func (m *DocumentRecommendationMedia) BeforeUpdate(tx *gorm.DB) error {
    var original DocumentRecommendationMedia
    if err := tx.First(&original, m.ID).Error; err != nil {
        return err
    }
    var latestVersion DocumentRecommendationMediaVersion
    tx.Where("original_id = ?", m.ID).Order("version_number desc").First(&latestVersion)
    newVersionNum := latestVersion.VersionNumber + 1
    version := DocumentRecommendationMediaVersion{
        OriginalID:    m.ID,
        DocumentID:    original.DocumentID,
        File:          original.File,
        FileName:      original.FileName,
        VersionNumber: newVersionNum,
        OperationType: "update",
        OperationTime: time.Now(),
    }
    return tx.Create(&version).Error
}

(2)封装业务函数

若钩子不够灵活,可封装专门的增删改函数,先处理版本记录再操作原数据:

func UpdateDocumentRecommendation(tx *gorm.DB, dr *DocumentRecommendation) error {
    // 读取原始数据
    var original DocumentRecommendation
    if err := tx.Preload("MediaFiles").First(&original, dr.ID).Error; err != nil {
        return err
    }
    // 提取关联的media ID
    mediaIDs := make([]uint, len(original.MediaFiles))
    for i, mf := range original.MediaFiles {
        mediaIDs[i] = mf.ID
    }
    // 获取最新版本号
    var latestVersion DocumentRecommendationVersion
    tx.Where("original_id = ?", dr.ID).Order("version_number desc").First(&latestVersion)
    newVersionNum := latestVersion.VersionNumber + 1
    // 保存版本记录
    version := DocumentRecommendationVersion{
        OriginalID:        dr.ID,
        DocumentID:        original.DocumentID,
        Recommendation:    original.Recommendation,
        Date:              original.Date,
        MediaFileIDs:      mediaIDs,
        VersionNumber:     newVersionNum,
        OperationType:     "update",
        OperationTime:     time.Now(),
    }
    if err := tx.Create(&version).Error; err != nil {
        return err
    }
    // 更新原记录
    return tx.Save(dr).Error
}

3. 版本恢复与查询

实现根据版本号恢复数据的逻辑:

func RestoreDocumentRecommendationVersion(tx *gorm.DB, originalID uint, versionNum int) error {
    var version DocumentRecommendationVersion
    if err := tx.Where("original_id = ? AND version_number = ?", originalID, versionNum).First(&version).Error; err != nil {
        return err
    }
    // 恢复主记录
    dr := DocumentRecommendation{
        ID:             originalID,
        DocumentID:     version.DocumentID,
        Recommendation: version.Recommendation,
        Date:           version.Date,
    }
    if err := tx.Save(&dr).Error; err != nil {
        return err
    }
    // 恢复多对多关联(先清空再重新添加)
    tx.Model(&dr).Association("MediaFiles").Clear()
    var mediaFiles []DocumentRecommendationMedia
    tx.Where("id IN ?", version.MediaFileIDs).Find(&mediaFiles)
    return tx.Model(&dr).Association("MediaFiles").Append(mediaFiles)
}

三、注意事项

  • 多对多字段的版本记录需特殊处理,可选择存储关联ID列表或创建关联关系版本表
  • 可参考原项目的fields参数,只存储需要版本化的字段,减少存储开销
  • 需保证版本号和操作类型的一致性,避免版本断层
  • 大文件字段(如原模型的file)若无需版本化,可跳过存储或仅记录路径历史

内容的提问来源于stack exchange,提问作者Piter Penn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 04:57:36