# 3.3 DAO 数据层 :::tip 概述 DAO 四件(dao/{m}.go、dao/internal/{m}.go、entity、do)由 `gf gen dao` 或代码生成器产出,无需手写。但 `dao.{X}.Ctx(ctx)` 是全系统**唯一查询入口**(架构红线),理解它的两行自定义模板逻辑至关重要。 ::: ## 四件产出与职责 | 文件 | 产出方式 | 职责 | |------|---------|------| | `internal/dao/{m}.go` | 自定义模板 hack/tpl/dao_index.tpl | 挂全局钩子 + Ctx 覆写(红线执行点) | | `internal/dao/internal/{m}.go` | gf gen dao | 内部实现(列常量/表名/基础查询),勿手改 | | `internal/model/entity/{m}.go` | gf gen dao | 实体结构体(对齐表列) | | `internal/model/do/{m}.go` | gf gen dao | 数据对象 DO(Insert/Update 入参形态) | ## 真实代码(internal/dao/example.go,摘自项目) ```go // ================================================================================= // This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. // 自定义模板:hack/tpl/dao_index.tpl // - 构造时挂载全局模型钩子(审计字段 / 软删除改写;主键交数据库自增) // - Ctx 覆写注入软删除与租户隔离条件(架构红线执行点) // ================================================================================= package dao import ( "context" "github.com/gogf/gf/v2/database/gdb" "easygoadmin/internal/dao/internal" "easygoadmin/internal/model/hooks" ) // exampleDao is the data access object for the table goframe_example. // You can define custom methods on it to extend its functionality as needed. type exampleDao struct { *internal.ExampleDao } var ( // Example is a globally accessible object for table goframe_example operations. Example = exampleDao{ internal.NewExampleDao(func(m *gdb.Model) *gdb.Model { // 全局钩子:Insert 补齐审计字段/租户ID(主键自增);Update 盖章;Delete 改写为软删除 return m.Hook(hooks.Handler()) }), } ) // Ctx 覆写:在 SQL 构建之前注入架构红线条件 —— // 所有表追加 del_flag=0;租户级表对租户上下文追加 tenant_id IN (0, 当前租户)。 // 业务代码必须经由本方法访问模型,禁止绕过 dao 直连 DB。 func (d *exampleDao) Ctx(ctx context.Context) *gdb.Model { return hooks.ApplyCtx(ctx, d.ExampleDao.Ctx(ctx), "goframe_example") } // Add your custom methods and functionality below. ``` ## Ctx 覆写做了什么(hooks.ApplyCtx) ```text dao.Example.Ctx(ctx) │ ├─ 所有表: WHERE del_flag = 0 └─ 租户级表且非平台上下文:WHERE tenant_id IN (0, 当前租户) (租户用户看自己 + 平台共享行;超管 ctx tenant_id=0 不加过滤,全量可见) ``` ## 全局钩子行为(hooks.Handler) | 钩子 | 行为 | |------|------| | Insert | 主键交数据库自增(不填 id);补 del_flag=0、create_time、create_user(ctx 用户名,回退真实姓名);租户级表自动携带 tenant_id(租户用户零值→填自身;超管未指定→`tenant.defaultId`;显式 0 可建平台共享行) | | Update | 自动盖章 update_time / update_user(仅 map 数据生效) | | Delete | 物理删除一律改写为 `UPDATE del_flag=1`(软删) | ## 生成方式 ```sh # 方式一:表结构变更后刷新已实现模块的 dao gf gen dao -c hack/config.yaml # 方式二:新模块随生成器一次性产出(dao 四件与 gf gen dao 同构,生成后统一 gofmt) go run ./cmd/generator -tables "goframe_example|案例演示表" ``` ::: warning 三条红线 1. **禁止绕过 dao 直连 `g.DB()`**——软删/租户/审计全部失效。 2. **禁止手写 del_flag/tenant_id 条件**——`Ctx` 已自动注入,手写必错(漏平台行/漏软删行)。 3. **禁止手改 dao/internal/**——重新生成即被覆盖;自定义方法写在 `dao/{m}.go` 尾部。 ::: ## 事务中的 dao `WithTrans(ctx, fn)` 包裹的事务上下文内,`dao.{X}.Ctx(ctx)` 自动走事务连接: ```go // internal/logic/base.go 的事务包裹(摘自项目) // WithTrans 事务包裹(错误自动回滚) func WithTrans(ctx context.Context, fn func(ctx context.Context) error) error { // g.DB().Begin → fn(gdb.WithTX(ctx, tx)) → 出错 Rollback,否则 Commit } ``` 纪律:事务内禁止 HTTP/文件 IO;bcrypt 等 CPU 密集操作在事务外预计算(典型:租户开户、用户新增)。