3.2 KiB
3.2 KiB
3.4 数据访问层(repository)
:::tip 概述
仓储层做一件事:给 service 一个安全的数据库入口。铁律:禁止裸用 *gorm.DB——统一经 repository.NewXxxRepo().Ctx(ctx) 访问,软删过滤与租户隔离自动注入。
:::
标准结构
// internal/repository/example.go
package repository
import (
"context"
"gorm.io/gorm"
)
// ExampleRepo 案例演示仓储
type ExampleRepo struct {
Base
}
// NewExampleRepo 构造案例演示仓储(表名 = gin_ + 模块名)
func NewExampleRepo() *ExampleRepo {
return &ExampleRepo{Base: NewBase("gin_example")}
}
// Ctx 带 ctx 的查询入口
func (r *ExampleRepo) Ctx(ctx context.Context) *gorm.DB {
return r.Base.Ctx(ctx)
}
就这么短——结构、构造、Ctx 三段是每个仓储的固定模板,自定义查询方法按需追加在同文件内。关联表(user_role / role_menu)同样建仓储。
Base 提供的能力
Ctx(ctx) 返回的 *gorm.DB 已自动注入:
| 注入项 | 条件 |
|---|---|
del_flag = 0 |
所有表——已软删行对查询不可见 |
tenant_id IN (0, 自身租户) |
仅租户白名单表且当前身份非平台级(超管)时 |
Base 附带的便捷方法:
// 软删除(业务删除统一入口,UPDATE del_flag=1)
repo.SoftDelete(ctx, "id", id)
repo.SoftDeleteByID(ctx, id)
// 存在性判断(软删/租户已过滤)
ok, err := repo.ExistsByCond(ctx, "name", name)
GORM 查询写法要点
| 场景 | 写法 |
|---|---|
| 等值条件 | .Where("col = ?", v) |
| 非等值 | .Where("id <> ?", id) |
| IN 条件 | .Where("col IN ?", slice) |
| LIKE | .Where(col+" LIKE ?", "%"+pat+"%") |
| 指定列 | .Select("a,b") |
| 单行读 map | .Take(&one)(one 为 map[string]any,判空用 gorm.ErrRecordNotFound) |
| 多行读 | .Find(&rows)([]map[string]any 或 *[]*T) |
| 计数 | .Count(&n)(n 为 int64) |
| map 插入 | .Create(&d)(d 为 map[string]any) |
| map 更新 | .Where(..).Updates(d) |
| 分页 | .Offset((p-1)*size).Limit(size).Find(&list) |
| 排序 | .Order("sort, id") |
| 结构体扫描 | Find 直接扫结构体(无 gorm 标签时按字段名蛇形映射;非列字段加 gorm:"-") |
Raw 入口(受限场景)
repo.Raw(ctx) // 不注入软删/租户 Scope
Raw() 仅限导入、字典管理、登录校验等明确场景使用——已软删行会进入结果集、跨租户数据可见,条件完整性由使用者自行保证。常规业务查询一律走 Ctx。
事务
// db.WithTrans:事务内所有 Ctx 查询自动复用事务连接
err := db.WithTrans(ctx, func(ctx context.Context) error {
if err := repoA.Ctx(ctx).Create(&d1).Error; err != nil {
return err // 返回错误自动回滚
}
return repoB.Ctx(ctx).Create(&d2).Error
})
事务内务必使用回调入参的 ctx(而非外层 ctx),否则拿不到事务连接。
编写检查清单
- 表名
gin_前缀 + 模块名 - 只有 Ctx / Raw 两个查询入口,无裸
*gorm.DB字段外泄 - 删除走 SoftDelete 系列,不写物理 DELETE
- Raw 使用处有注释说明场景正当性