将含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
相关产品推荐
相关产品推荐

