什么是SoftDelete

SoftDelete软删除,即不真正删除数据,而在某行数据上增加类型is_deleted的删除标识,一般使用UPDATE语句。

启用Interceptor

在使用SoftDelete之前,首先需要启用Interceptor,即拦截器,SoftDelete正是Interceptor和Hook的典型示例

go run -mod=mod entgo.io/ent/cmd/ent generate --template glob="./rpc/ent/template/*.tmpl" ./rpc/ent/schema --feature sql/execquery,intercept

或者在Go-Zero的Makefile做如下修改(仅在使用Goctls的情况下支持)

# Ent enabled features | Ent 启用的官方特性
ENT_FEATURE=sql/execquery,intercept

添加之后执行 make gen-ent , 生成 Interceptor.

添加 Mixin

Ent提供了官方的Mixin,只需要引入即可。

// SoftDeleteMixin implements the soft delete pattern for schemas.
type SoftDeleteMixin struct {
    mixin.Schema
}

// Fields of the SoftDeleteMixin.
func (SoftDeleteMixin) Fields() []ent.Field {
    return []ent.Field{
        field.Time("delete_time").
            Optional(),
    }
}

type softDeleteKey struct{}

// SkipSoftDelete returns a new context that skips the soft-delete interceptor/mutators.
func SkipSoftDelete(parent context.Context) context.Context {
    return context.WithValue(parent, softDeleteKey{}, true)
}

// Interceptors of the SoftDeleteMixin.
func (d SoftDeleteMixin) Interceptors() []ent.Interceptor {
    return []ent.Interceptor{
        intercept.TraverseFunc(func(ctx context.Context, q intercept.Query) error {
            // Skip soft-delete, means include soft-deleted entities.
            if skip, _ := ctx.Value(softDeleteKey{}).(bool); skip {
                return nil
            }
            d.P(q)
            return nil
        }),
    }
}

// Hooks of the SoftDeleteMixin.
func (d SoftDeleteMixin) Hooks() []ent.Hook {
    return []ent.Hook{
        hook.On(
            func(next ent.Mutator) ent.Mutator {
                return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (ent.Value, error) {
                    // Skip soft-delete, means delete the entity permanently.
                    if skip, _ := ctx.Value(softDeleteKey{}).(bool); skip {
                        return next.Mutate(ctx, m)
                    }
                    mx, ok := m.(interface {
                        SetOp(ent.Op)
                        Client() *gen.Client
                        SetDeleteTime(time.Time)
                        WhereP(...func(*sql.Selector))
                    })
                    if !ok {
                        return nil, fmt.Errorf("unexpected mutation type %T", m)
                    }
                    d.P(mx)
                    mx.SetOp(ent.OpUpdate)
                    mx.SetDeleteTime(time.Now())
                    return mx.Client().Mutate(ctx, m)
                })
            },
            ent.OpDeleteOne|ent.OpDelete,
        ),
    }
}

// P adds a storage-level predicate to the queries and mutations.
func (d SoftDeleteMixin) P(w interface{ WhereP(...func(*sql.Selector)) }) {
    w.WhereP(
        sql.FieldIsNull(d.Fields()[0].Descriptor().Name),
    )
}

如果使用为Goctls可以通过执行命令 goctls extra ent mixin -a soft_delete 来添加Mixin

修改 Service Context

service_context.go 中添加本地的 ent/runtime

路径: internal/svc/service_context.go

引用 Mixin

在需要添加软删除的 schema 引用 mixins

// 例子
func (Task) Mixin() []ent.Mixin {
	return []ent.Mixin{
		mixins.IDMixin{},
		mixins.StatusMixin{},
		mixins2.SoftDeleteMixin{},
	}
}

最后更新Ent代码生成即可

注意事项

由于添加软删除后会添加 deleted_at 字段,所以需要重新初始化数据库,