跳转到内容

软删除

Pie 提供了内置的软删除功能,允许您”删除”数据而不实际从数据库中移除,保护重要数据并提供数据恢复能力。

type User struct {
ID bson.ObjectID `bson:"_id,omitempty"`
Name string `bson:"name"`
Email string `bson:"email"`
DeletedAt *time.Time `bson:"deleted_at,omitempty" pie:"soft_delete"`
}
type Product struct {
ID bson.ObjectID `bson:"_id,omitempty"`
Name string `bson:"name"`
Price float64 `bson:"price"`
DeletedAt *time.Time `bson:"deleted_at,omitempty" pie:"soft_delete"`
}
// 软删除用户
err := session.Where("email", "test@example.com").SoftDelete(ctx)
// 软删除多个用户
err := session.Where("status", "inactive").SoftDeleteMany(ctx)
// 软删除特定用户
err := session.Where("_id", userID).SoftDelete(ctx)
// 恢复软删除的用户
err := session.Where("email", "test@example.com").Restore(ctx)
// 恢复多个用户
err := session.Where("status", "inactive").RestoreMany(ctx)
// 恢复特定用户
err := session.Where("_id", userID).Restore(ctx)
// 强制删除(物理删除)
err := session.Where("email", "test@example.com").ForceDelete(ctx)
// 强制删除多个
err := session.Where("status", "inactive").ForceDeleteMany(ctx)
// 普通查询自动排除软删除的记录
users, err := session.Find(ctx) // 自动过滤 deleted_at 不为 null 的记录
// 条件查询也自动过滤
activeUsers, err := session.Where("status", "active").Find(ctx)
// 聚合查询也自动过滤
result, err := session.
GroupStage().
By("role", "$role").
Count("total").
Done().
Exec(ctx)
// 使用 WithTrashed 包含软删除的记录
allUsers, err := session.WithTrashed().Find(ctx)
// 只查询软删除的记录
deletedUsers, err := session.OnlyTrashed().Find(ctx)
// 在条件查询中包含软删除
users, err := session.
WithTrashed().
Where("email", "test@example.com").
Find(ctx)
func deleteUser(userID bson.ObjectID) error {
session := pie.Table[User](engine)
// 软删除用户
err := session.Where("_id", userID).SoftDelete(ctx)
if err != nil {
return fmt.Errorf("failed to soft delete user: %w", err)
}
// 记录删除日志
logUserDeletion(userID)
return nil
}
func restoreUser(userID bson.ObjectID) error {
session := pie.Table[User](engine)
// 恢复用户
err := session.Where("_id", userID).Restore(ctx)
if err != nil {
return fmt.Errorf("failed to restore user: %w", err)
}
// 记录恢复日志
logUserRestoration(userID)
return nil
}
func permanentlyDeleteUser(userID bson.ObjectID) error {
session := pie.Table[User](engine)
// 强制删除用户
err := session.Where("_id", userID).ForceDelete(ctx)
if err != nil {
return fmt.Errorf("failed to permanently delete user: %w", err)
}
// 清理相关数据
cleanupUserRelatedData(userID)
return nil
}
func cancelOrder(orderID bson.ObjectID) error {
session := pie.Table[Order](engine)
// 软删除订单
err := session.Where("_id", orderID).SoftDelete(ctx)
if err != nil {
return fmt.Errorf("failed to cancel order: %w", err)
}
// 恢复库存
if err := restoreOrderStock(orderID); err != nil {
log.Printf("Failed to restore stock for order %s: %v", orderID.Hex(), err)
}
// 发送取消通知
go sendOrderCancellationNotification(orderID)
return nil
}
func restoreOrder(orderID bson.ObjectID) error {
session := pie.Table[Order](engine)
// 恢复订单
err := session.Where("_id", orderID).Restore(ctx)
if err != nil {
return fmt.Errorf("failed to restore order: %w", err)
}
// 重新扣减库存
if err := deductOrderStock(orderID); err != nil {
log.Printf("Failed to deduct stock for order %s: %v", orderID.Hex(), err)
}
return nil
}
func cleanupOldSoftDeletedData() error {
session := pie.Table[User](engine)
// 查找30天前软删除的用户
cutoffDate := time.Now().AddDate(0, 0, -30)
deletedUsers, err := session.
OnlyTrashed().
Where("deleted_at", pie.Lt("deleted_at", cutoffDate)).
Find(ctx)
if err != nil {
return err
}
// 永久删除这些用户
for _, user := range deletedUsers {
if err := session.Where("_id", user.ID).ForceDelete(ctx); err != nil {
log.Printf("Failed to permanently delete user %s: %v", user.ID.Hex(), err)
}
}
log.Printf("Permanently deleted %d old soft-deleted users", len(deletedUsers))
return nil
}
type CustomUser struct {
ID bson.ObjectID `bson:"_id,omitempty"`
Name string `bson:"name"`
Email string `bson:"email"`
DeletedAt *time.Time `bson:"deleted_at,omitempty" pie:"soft_delete"`
DeletedBy *bson.ObjectID `bson:"deleted_by,omitempty"`
DeleteReason string `bson:"delete_reason,omitempty"`
}
// 自定义软删除逻辑
func (u *CustomUser) BeforeSoftDelete(ctx context.Context) error {
// 设置删除者
if userID := getCurrentUserID(ctx); userID != nil {
u.DeletedBy = userID
}
// 设置删除原因
if reason := getDeleteReason(ctx); reason != "" {
u.DeleteReason = reason
}
return nil
}
func (u *User) BeforeSoftDelete(ctx context.Context) error {
// 检查是否可以删除
if u.Email == "admin@example.com" {
return errors.New("cannot delete admin user")
}
// 记录删除前的状态
logUserSoftDeletion(u.ID, u.Name)
return nil
}
func (u *User) AfterSoftDelete(ctx context.Context) error {
// 清理相关数据
go func() {
cleanupUserSessions(u.ID)
cleanupUserCache(u.ID)
}()
return nil
}
func (u *User) BeforeRestore(ctx context.Context) error {
// 检查是否可以恢复
if u.Email == "banned@example.com" {
return errors.New("cannot restore banned user")
}
return nil
}
func (u *User) AfterRestore(ctx context.Context) error {
// 重新初始化用户数据
go func() {
reinitializeUserData(u.ID)
}()
return nil
}
func batchSoftDeleteUsers(userIDs []bson.ObjectID) error {
session := pie.Table[User](engine)
// 批量软删除
err := session.WhereIn("_id", userIDs).SoftDeleteMany(ctx)
if err != nil {
return fmt.Errorf("failed to batch soft delete users: %w", err)
}
// 记录批量删除日志
logBatchUserDeletion(userIDs)
return nil
}
func batchRestoreUsers(userIDs []bson.ObjectID) error {
session := pie.Table[User](engine)
// 批量恢复
err := session.WhereIn("_id", userIDs).RestoreMany(ctx)
if err != nil {
return fmt.Errorf("failed to batch restore users: %w", err)
}
// 记录批量恢复日志
logBatchUserRestoration(userIDs)
return nil
}
func getSoftDeleteStatistics() (*SoftDeleteStats, error) {
session := pie.Table[User](engine)
stats := &SoftDeleteStats{}
// 总用户数(包括软删除)
totalCount, err := session.WithTrashed().Count(ctx)
if err != nil {
return nil, err
}
stats.TotalCount = totalCount
// 活跃用户数
activeCount, err := session.Count(ctx)
if err != nil {
return nil, err
}
stats.ActiveCount = activeCount
// 软删除用户数
deletedCount, err := session.OnlyTrashed().Count(ctx)
if err != nil {
return nil, err
}
stats.DeletedCount = deletedCount
// 按删除时间分组统计
var deleteStats []bson.M
err = session.
OnlyTrashed().
GroupStage().
By("deleteDate", pie.DateToString("$deleted_at", "%Y-%m-%d", "UTC")).
Count("count").
Done().
Exec(ctx, &deleteStats)
if err != nil {
return nil, err
}
stats.DeleteStatsByDate = deleteStats
return stats, nil
}
// 为软删除字段创建索引
func createSoftDeleteIndexes() error {
indexes := pie.MustIndexes(engine)
// 为 deleted_at 字段创建索引
err := indexes.CreateIndex(ctx, "users", bson.D{
{"deleted_at", 1},
})
if err != nil {
return err
}
// 为复合查询创建索引
err = indexes.CreateIndex(ctx, "users", bson.D{
{"status", 1},
{"deleted_at", 1},
})
if err != nil {
return err
}
return nil
}
// 使用投影减少数据传输
func getActiveUsers() ([]User, error) {
session := pie.Table[User](engine)
users, err := session.
Select("name", "email", "status"). // 只选择需要的字段
Find(ctx)
return users, err
}
// 使用分页避免大量数据
func getActiveUsersPaginated(page, pageSize int) (*PaginatedResult[User], error) {
session := pie.Table[User](engine)
result, err := session.
Paginate(ctx, pie.PaginateParams{
Page: page,
PageSize: pageSize,
})
return result, err
}
func getCachedActiveUsers() ([]User, error) {
session := pie.Table[User](engine).WithCache(5 * time.Minute)
users, err := session.
Cache("active_users").
Find(ctx)
return users, err
}
func invalidateUserCache() error {
cache := engine.Cache()
// 清除相关缓存
cache.Delete("active_users")
cache.DeleteByPattern("users_*")
return nil
}
// 好的做法:对重要数据使用软删除
type User struct {
ID bson.ObjectID `bson:"_id,omitempty"`
Name string `bson:"name"`
Email string `bson:"email"`
DeletedAt *time.Time `bson:"deleted_at,omitempty" pie:"soft_delete"`
}
// 避免的做法:对临时数据使用软删除
type TempData struct {
ID bson.ObjectID `bson:"_id,omitempty"`
Data string `bson:"data"`
DeletedAt *time.Time `bson:"deleted_at,omitempty" pie:"soft_delete"` // 不需要
}
func scheduleSoftDeleteCleanup() {
// 每天凌晨2点清理30天前的软删除数据
go func() {
ticker := time.NewTicker(24 * time.Hour)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if time.Now().Hour() == 2 { // 凌晨2点
if err := cleanupOldSoftDeletedData(); err != nil {
log.Printf("Failed to cleanup soft deleted data: %v", err)
}
}
}
}
}()
}
func canSoftDeleteUser(userID bson.ObjectID, currentUserID bson.ObjectID) bool {
// 检查权限
if !hasPermission(currentUserID, "delete_user") {
return false
}
// 不能删除自己
if userID == currentUserID {
return false
}
// 不能删除管理员
user, err := getUserByID(userID)
if err != nil {
return false
}
if user.Role == "admin" {
return false
}
return true
}
type SoftDeleteAudit struct {
ID bson.ObjectID `bson:"_id,omitempty"`
EntityID bson.ObjectID `bson:"entity_id"`
EntityType string `bson:"entity_type"`
Action string `bson:"action"` // "soft_delete" or "restore"
DeletedBy bson.ObjectID `bson:"deleted_by"`
DeletedAt time.Time `bson:"deleted_at"`
Reason string `bson:"reason,omitempty"`
}
func logSoftDeleteAudit(entityID bson.ObjectID, entityType, action string, reason string) {
audit := &SoftDeleteAudit{
EntityID: entityID,
EntityType: entityType,
Action: action,
DeletedBy: getCurrentUserID(ctx),
DeletedAt: time.Now(),
Reason: reason,
}
session := pie.Table[SoftDeleteAudit](engine)
session.Insert(ctx, audit)
}