Buffalo框架无内置软删除支持,需手动添加deleted_at字段、重写Destroy方法、所有查询显式过滤deleted_at IS NULL,并统一封装Active查询器避免遗漏。

Buffalo 框架里没有内置软删除支持
Buffalo 本身不提供 SoftDelete 或类似 ActiveRecord 的钩子机制,它只是基于标准 database/sql 封装了简单 CRUD 和迁移工具。你不能靠 buffalo db generate model 自动生成带软删除字段的结构体或自动拦截 Delete 调用——所有逻辑得自己写。
常见错误是直接在 Destroy 方法里改 SQL,结果漏掉关联查询、列表页、API 返回值等地方,导致“删了还显示”或“删不干净”。软删除不是加个字段就完事,关键在统一拦截读写行为。
手动添加 deleted_at 字段并覆盖 Model 方法
以用户模型为例,先在 migration 中添加字段:
func AddDeletedAtToUsers(tx *pop.Connection) error {
return tx.RawQuery("ALTER TABLE users ADD COLUMN deleted_at TIMESTAMP NULL").Exec()
}
然后在 models/user.go 中扩展结构体和方法:
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 结构体里加
DeletedAt time.Time `json:"deleted_at" db:"deleted_at"`字段(注意 tag 里db:"deleted_at"必须显式声明,否则 pop 不会映射) - 重写
Destroy方法:不调tx.Destroy(u),而是执行tx.RawQuery("UPDATE users SET deleted_at = ? WHERE id = ?", time.Now(), u.ID).Exec() - 定义新方法如
HardDestroy供真正需要物理删除时调用
Pop 不会自动识别 deleted_at 为软删除字段,所以 Find、All 等默认仍返回已“删除”的记录——这点极易被忽略。
所有查询都必须显式过滤 deleted_at
Buffalo 的 Find、All、Where 都不自动排除 deleted_at IS NOT NULL。你必须在每个业务查询中手动加条件:
- 列表页:
tx.Where("deleted_at IS NULL").All(&users) - 详情页:
tx.Where("id = ? AND deleted_at IS NULL", id).First(&user) - 关联预加载(如
user.Posts)也要在HasMany定义里加Where("deleted_at IS NULL"),否则软删除的帖子仍会被加载
如果项目里有几十个查询点,靠人工补容易漏。更稳妥的做法是封装一个 Active 查询器,比如 tx.Q().Where("deleted_at IS NULL"),并在所有业务层统一使用它,而不是直接用 tx。
迁移、种子数据和 API 响应要同步处理
软删除上线后,老数据的 deleted_at 全是 NULL,看起来像全都没删过——这没问题;但如果你后续要导入历史数据,得确保新插入记录的 deleted_at 默认为 NULL(即未删除),而不是意外设成零值时间。
- 数据库层面建议加约束:
DEFAULT NULL+ 允许为空,避免 ORM 插入时填错零时间 - API 返回 JSON 时,别把
deleted_at: "0001-01-01T00:00:00Z"暴露给前端,用omitempty或自定义 JSON marshaler 过滤掉空值 - 迁移脚本里若需恢复某条记录,别只清空
deleted_at,还得检查是否触发了其他业务状态(比如订单恢复后库存要不要回滚)
最麻烦的是搜索和统计类接口:比如 “总用户数” 是算全部还是只算活跃?这个语义必须在每处明确,不能依赖全局开关——Buffalo 没有作用域级别的软删除开关。

















