当前位置: 首页 > news >正文

GORM 删除的重要特性:软删除实践案例(优化版)

在实际项目中,软删除是一种非常实用的数据管理技术,它允许我们在不真正删除数据的情况下标记数据为 "已删除",从而保留数据的可追溯性和可恢复性。

这个特性在项目的开发中很常见,也非常重要,因此我单独出了一篇关于软删除的实践案例,以帮助大家更好地理解。


以下是一些使用 GORM 进行软删除的实际案例:

一、电商系统中的商品软删除管理

在电商平台中,商品的上下架管理是核心业务场景。软删除完美适配这一需求,既能实现商品状态管理,又能保留历史销售数据。

1.1 商品模型设计

type Product struct {ID          uint           `gorm:"primaryKey"`Name        string         `gorm:"not null;index"`             // 商品名称(索引字段)Price       float64        `gorm:"not null"`                  // 商品价格Description string         `gorm:"type:text"`                 // 商品描述Stock       uint           `gorm:"default:0"`                 // 库存数量Status      string         `gorm:"default:'active';index"`    // 商品状态(活跃/下架)CreatedAt   time.Time                              // 创建时间UpdatedAt   time.Time                              // 更新时间DeletedAt   gorm.DeletedAt `gorm:"index"`         // 软删除时间戳(索引字段)
}

1.2 核心业务场景实现

1.2.1 商品下架处理
// 商品服务层 - 下架商品
func (s *ProductService) DisableProduct(productID uint) error {// 开启事务确保数据一致性return s.db.Transaction(func(tx *gorm.DB) error {// 1. 查找商品var product Productif err := tx.First(&product, productID).Error; err != nil {return fmt.Errorf("商品不存在: %w", err)}// 2. 检查商品状态(防止重复下架)if !product.DeletedAt.IsZero() {return errors.New("商品已下架")}// 3. 执行软删除if err := tx.Delete(&product).Error; err != nil {return fmt.Errorf("下架失败: %w", err)}// 4. 记录操作日志(含操作人信息)s.logger.WithFields(zap.Fields{"product_id": productID,"operator":   s.currentUser.ID,}).Info("商品已下架")return nil})
}
1.2.2 商品查询策略
// 查询所有活跃商品(含分页)
func (s *ProductService) GetActiveProducts(page, pageSize int) ([]Product, int64, error) {var products []Productvar total int64// 构建查询query := s.db.Where("status = ? AND deleted_at IS NULL", "active")// 计算总数if err := query.Model(&Product{}).Count(&total).Error; err != nil {return nil, 0, err}// 分页查询if err := query.Offset((page - 1) * pageSize).Limit(pageSize).Order("created_at DESC").Find(&products).Error; err != nil {return nil, 0, err}return products, total, nil
}// 管理员查询所有商品(含已下架)
func (s *ProductService) GetAllProducts() ([]Product, error) {var products []Product// 使用Unscoped绕过软删除约束if err := s.db.Unscoped().Order("deleted_at IS NULL DESC, created_at DESC").Find(&products).Error; err != nil {return nil, err}return products, nil
}
1.2.3 商品恢复机制
// 恢复已下架商品
func (s *ProductService) RestoreProduct(productID uint) error {// 1. 查找软删除的商品var product Productif err := s.db.Unscoped().First(&product, productID).Error; err != nil {return fmt.Errorf("商品不存在: %w", err)}// 2. 检查是否已软删除if product.DeletedAt.IsZero() {return errors.New("商品未下架")}// 3. 恢复商品(设置DeletedAt为NULL)if err := s.db.Unscoped().Model(&product).Update("DeletedAt", nil).Error; err != nil {return fmt.Errorf("恢复失败: %w", err)}// 4. 重置状态为活跃if err := s.db.Model(&product).Update("Status", "active").Error; err != nil {return fmt.Errorf("状态重置失败: %w", err)}s.logger.WithField("product_id", productID).Info("商品已恢复")return nil
}

二、用户管理系统的账户软删除方案

用户账户管理对数据保留有严格要求,软删除既能满足用户注销需求,又能保留交易记录等历史数据。

2.1 用户模型增强设计

type User struct {ID           uint           `gorm:"primaryKey"`Username     string         `gorm:"unique;not null;index"`    // 用户名(唯一索引)Email        string         `gorm:"unique;not null;index"`    // 邮箱(唯一索引)Phone        string         `gorm:"index"`                   // 手机号(索引)Status       string         `gorm:"default:'active';index"`   // 账户状态LastLoginAt  time.Time                              // 最后登录时间CreatedAt    time.Time                              // 创建时间UpdatedAt    time.Time                              // 更新时间DeletedAt    gorm.DeletedAt `gorm:"index"`          // 软删除时间戳// 敏感信息单独处理(如密码加密存储)EncryptedPassword string `gorm:"column:password_hash"` // 加密密码
}

2.2 账户生命周期管理

2.2.1 账户注销流程
// 账户服务 - 用户注销
func (s *UserService) CloseAccount(userID uint) error {return s.db.Transaction(func(tx *gorm.DB) error {// 1. 查找用户var user Userif err := tx.First(&user, userID).Error; err != nil {return fmt.Errorf("用户不存在: %w", err)}// 2. 检查账户状态if !user.DeletedAt.IsZero() {return errors.New("账户已注销")}// 3. 软删除用户(级联处理关联数据)if err := tx.Delete(&user).Error; err != nil {return fmt.Errorf("注销失败: %w", err)}// 4. 软删除用户订单if err := tx.Where("user_id = ?", userID).Delete(&Order{}).Error; err != nil {return fmt.Errorf("订单处理失败: %w", err)}// 5. 记录审计日志(含IP等操作信息)s.auditLogger.WithFields(zap.Fields{"user_id":  userID,"operator": s.currentUser.ID,"ip":       s.requestIP,}).Info("用户账户已注销")return nil})
}
2.2.2 合规性数据清理
// 定期清理过期软删除账户(数据合规需求)
func (s *UserService) PurgeExpiredAccounts(retentionDays int) error {// 计算时间阈值(如180天前)cutoffTime := time.Now().AddDate(0, 0, -retentionDays)// 1. 查找所有超过保留期的软删除用户var users []Userif err := s.db.Unscoped().Where("deleted_at < ?", cutoffTime).Find(&users).Error; err != nil {return fmt.Errorf("查询过期账户失败: %w", err)}if len(users) == 0 {return nil}// 2. 开启事务进行物理删除return s.db.Transaction(func(tx *gorm.DB) error {// 2.1 物理删除用户关联数据userIDs := make([]uint, len(users))for i, user := range users {userIDs[i] = user.ID}if err := tx.Unscoped().Where("user_id IN ?", userIDs).Delete(&Order{}).Error; err != nil {return fmt.Errorf("删除关联订单失败: %w", err)}// 2.2 物理删除用户(使用批量删除优化性能)if err := tx.Unscoped().Delete(&users).Error; err != nil {return fmt.Errorf("删除用户失败: %w", err)}// 2.3 记录清理日志s.logger.WithFields(zap.Fields{"count":      len(users),"retention":  retentionDays,"deleted_at": cutoffTime,}).Info("完成过期账户清理")return nil})
}
2.2.3 账户恢复与数据审查
// 管理员恢复已注销账户
func (s *UserService) RestoreAccount(userID uint) error {// 1. 查找软删除的账户var user Userif err := s.db.Unscoped().First(&user, userID).Error; err != nil {return fmt.Errorf("账户不存在: %w", err)}// 2. 检查是否已注销if user.DeletedAt.IsZero() {return errors.New("账户未注销")}// 3. 开启事务恢复账户return s.db.Transaction(func(tx *gorm.DB) error {// 3.1 恢复用户if err := tx.Unscoped().Model(&user).Update("DeletedAt", nil).Error; err != nil {return fmt.Errorf("恢复账户失败: %w", err)}// 3.2 恢复用户状态为活跃if err := tx.Model(&user).Update("Status", "active").Error; err != nil {return fmt.Errorf("更新状态失败: %w", err)}// 3.3 记录操作日志s.auditLogger.WithFields(zap.Fields{"user_id":  userID,"operator": s.currentUser.ID,}).Info("已恢复用户账户")return nil})
}

三、内容管理系统的文章软删除实践

在 CMS 系统中,文章的删除与恢复是常见需求,软删除能有效保留内容历史,支持版本追溯。

3.1 文章模型设计

type Article struct {ID          uint           `gorm:"primaryKey"`Title       string         `gorm:"not null;index"`             // 文章标题(索引)Content     string         `gorm:"type:text"`                 // 文章内容AuthorID    uint           `gorm:"not null;index"`            // 作者ID(索引)CategoryID  uint           `gorm:"index"`                    // 分类ID(索引)Status      string         `gorm:"default:'published';index"` // 文章状态ViewCount   uint           `gorm:"default:0"`                // 浏览量CreatedAt   time.Time                              // 创建时间UpdatedAt   time.Time                              // 更新时间DeletedAt   gorm.DeletedAt `gorm:"index"`         // 软删除时间戳// 多版本支持(简化示例)Version     int            `gorm:"default:1"`                 // 版本号
}

3.2 内容生命周期管理

3.2.1 文章软删除实现
// 文章服务 - 删除文章
func (s *ArticleService) DeleteArticle(articleID uint) error {return s.db.Transaction(func(tx *gorm.DB) error {// 1. 查找文章var article Articleif err := tx.First(&article, articleID).Error; err != nil {return fmt.Errorf("文章不存在: %w", err)}// 2. 检查是否已删除if !article.DeletedAt.IsZero() {return errors.New("文章已删除")}// 3. 执行软删除if err := tx.Delete(&article).Error; err != nil {return fmt.Errorf("删除失败: %w", err)}// 4. 记录操作日志(含操作类型)s.logger.WithFields(zap.Fields{"article_id": articleID,"operator":   s.currentUser.ID,"type":       "soft_delete",}).Info("文章已删除")return nil})
}
3.2.2 内容恢复与版本管理
// 恢复已删除文章
func (s *ArticleService) RestoreArticle(articleID uint) error {// 1. 查找软删除的文章var article Articleif err := s.db.Unscoped().First(&article, articleID).Error; err != nil {return fmt.Errorf("文章不存在: %w", err)}// 2. 检查是否已删除if article.DeletedAt.IsZero() {return errors.New("文章未删除")}// 3. 恢复文章if err := s.db.Transaction(func(tx *gorm.DB) error {// 3.1 清除删除标记if err := tx.Unscoped().Model(&article).Update("DeletedAt", nil).Error; err != nil {return fmt.Errorf("恢复失败: %w", err)}// 3.2 更新状态为发布if err := tx.Model(&article).Update("Status", "published").Error; err != nil {return fmt.Errorf("状态更新失败: %w", err)}// 3.3 增加版本号if err := tx.Model(&article).Update("Version", gorm.Expr("version + 1")).Error; err != nil {return fmt.Errorf("版本更新失败: %w", err)}s.logger.WithField("article_id", articleID).Info("文章已恢复")return nil}); err != nil {return err}return nil
}
3.2.3 内容审计与永久删除
// 审核违规文章并永久删除
func (s *ArticleService) PurgeViolatingArticle(articleID uint) error {// 1. 查找文章(包括软删除的)var article Articleif err := s.db.Unscoped().First(&article, articleID).Error; err != nil {return fmt.Errorf("文章不存在: %w", err)}// 2. 记录审核日志auditLog := AuditLog{ArticleID:  articleID,AuditorID:  s.currentUser.ID,Reason:     "content_violation",Action:     "permanent_delete",CreatedAt:  time.Now(),}if err := s.db.Create(&auditLog).Error; err != nil {return fmt.Errorf("记录审计日志失败: %w", err)}// 3. 物理删除文章if err := s.db.Unscoped().Delete(&article).Error; err != nil {return fmt.Errorf("永久删除失败: %w", err)}s.auditLogger.WithFields(zap.Fields{"article_id": articleID,"auditor":    s.currentUser.Username,"reason":     "违规内容",}).Info("已永久删除违规文章")return nil
}

四、软删除最佳实践与架构设计

4.1 软删除核心优势总结

  1. 数据可追溯性:保留完整数据历史,支持审计和分析
  2. 操作可逆性:误删除可快速恢复,提升系统容错能力
  3. 业务连续性:下架 / 注销等操作不影响历史数据关联
  4. 合规性支持:满足数据保留法规要求(如 GDPR)

4.2 高级架构设计

4.2.1 软删除全局作用域
// 全局软删除作用域
func SoftDeleteScope(db *gorm.DB) *gorm.DB {return db.Where("deleted_at IS NULL")
}// 应用全局作用域
func SetupSoftDelete(db *gorm.DB) {db.Use(func(db *gorm.DB) {db.Scopes(SoftDeleteScope)})
}
4.2.2 软删除与缓存集成
// 软删除钩子清理缓存
func (u *User) AfterDelete(tx *gorm.DB) error {// 清理用户相关缓存cacheKey := fmt.Sprintf("user:%d", u.ID)if err := cacheClient.Del(cacheKey).Err(); err != nil {return fmt.Errorf("清理缓存失败: %w", err)}// 清理用户列表缓存if err := cacheClient.Del("users:active").Err(); err != nil {return fmt.Errorf("清理用户列表缓存失败: %w", err)}return nil
}

4.3 性能优化要点

  1. 索引策略:在DeletedAt字段创建索引,提升查询性能
  2. 批量操作:使用FindInBatches处理大规模软删除数据
  3. 分表设计:长期软删除数据可迁移至历史表
  4. 查询优化:避免Unscoped滥用,明确需要查询软删除数据时再使用

这些案例展示了软删除在不同业务场景中的应用,包括商品管理、用户账户管理、内容管理等。软删除的核心优势在于它允许我们在不丢失历史数据的情况下管理数据的可见性,同时提供了数据恢复的可能性。在实际应用中,需要根据业务需求合理设计软删除的策略,并注意处理好与软删除相关的查询和事务操作。 

http://www.lqws.cn/news/593623.html

相关文章:

  • 前端计算机视觉:使用 OpenCV.js 在浏览器中实现图像处理
  • React:利用React.memo和useCallback缓存弹窗组件
  • Oracle 常用函数
  • 设置linux静态IP
  • 测试第六讲-测试模型分类
  • RabbitMQ - SpringAMQP及Work模型
  • 信息化项目验收,软件工程评审和检查表单
  • Qt中使用QSettings数据或结构体到INI文件
  • 边缘人工智能与医疗AI融合发展路径:技术融合与应用前景(下)
  • 区块链存证:数字时代的法律盾牌还是技术乌托邦?
  • 数据结构day5——队列和树
  • 县级智慧水务一体化方案及落地案例PPT(39页)
  • 8.Docker镜像讲解
  • 高强螺栓的计算与选用
  • 深入金融与多模态场景实战:金融文档分块技术与案例汇总
  • Qt时间显示按钮功能详解
  • 【docker】unknown shorthand flag: ‘f‘ in -f See ‘docker --help‘.
  • 实变与泛函题解-心得笔记【16】
  • Electron 应用中的内容安全策略 (CSP) 全面指南
  • MySQL索引深度解析:B+树、B树、哈希索引怎么选?
  • 机器学习在智能金融风险评估中的应用:信用评分与欺诈检测
  • day48
  • C++ 网络编程(13) asio多线程模型IOServicePool
  • CAU数据挖掘实验 表分析数据插件
  • 零信任安全管理系统介绍
  • 安防监控视频汇聚平台EasyCVR v3.7.2版云端录像无法在web端播放的原因排查和解决方法
  • 笔记本电脑怎样投屏到客厅的大电视?怎样避免将电脑全部画面都投出去?
  • Rust 是什么
  • [C#] WPF - 自定义样式(Slider篇)
  • WPF学习(三)