Store 包设计
概述
Store 包是 Bingo 数据访问层的核心,通过泛型和组合模式实现了一个灵活、可扩展的数据访问框架,减少重复代码并提高代码复用率。
所有服务(apiserver、admserver 等)的数据访问层已统一使用 internal/pkg/store 中的泛型 Store[T] 模式。
包结构
pkg/store/
├── store.go # 通用泛型 Store[T] 实现
├── logger/ # Logger 接口定义
└── where/
└── where.go # 查询条件构建器
internal/pkg/store/ # 推荐:使用泛型 Store[T]
├── store.go # IStore 接口(含 TX 事务方法)
├── logger.go # 日志实现
└── user.go # 用户 Store(组合泛型 Store[T])注意:
internal/pkg/store中所有文件必须平铺在同一目录中,避免循环引用。使用命名规范而非目录结构来组织模块。
命名规范
internal/pkg/store 中所有文件必须平铺(避免循环引用),使用命名规范来组织代码。
规范
文件名:
<prefix>_<model>.go(如sys_admin.go,bot_channel.go,user.go)- 系统模块:
sys_前缀 - 其他模块: 模块名前缀(如
bot_,api_) - 无冲突时可省略前缀(如
user.go)
- 系统模块:
Store 接口:
<Prefix><Model>Store(如BotChannelStore,AdminStore)- 需要与同名模型区分时才加模块前缀
实现结构体: 小写
<prefix><model>Store(如botChannelStore)扩展接口:
<Prefix><Model>Expansion(如BotChannelExpansion)创建函数:
New<Prefix><Model>Store()IStore 方法: 复数或单数,保持简洁(如
Users(),Bot(),BotChannel())
核心设计
通用的 Store[T] 实现所有 CRUD 操作,业务特定的 Store 通过组合来扩展:
// pkg/store/store.go - 泛型实现
type Store[T any] struct {
logger Logger
storage DBProvider
}
// internal/pkg/store/user.go - 业务扩展
type userStore struct {
*genericstore.Store[model.UserM] // 组合泛型 Store
}
type UserStore interface {
Create(ctx context.Context, obj *model.UserM) error
Update(ctx context.Context, obj *model.UserM, fields ...string) error
Delete(ctx context.Context, opts *where.Options) error
Get(ctx context.Context, opts *where.Options) (*model.UserM, error)
List(ctx context.Context, opts *where.Options) (int64, []*model.UserM, error)
UserExpansion // 业务特定的扩展接口
}
func NewUserStore(store *datastore) *userStore {
return &userStore{
Store: genericstore.NewStore[model.UserM](store, NewLogger()),
}
}条件构建器
使用 pkg/store/where 包提供链式 API 构建查询条件:
import "bingo/pkg/store/where"
// 分页查询
opts := where.F("status", "active").P(1, 10)
// 复杂条件
opts := where.NewWhere().
F("status", "active").
Q("created_at > ?", time.Now().AddDate(0, 0, -7)).
Load("User", "Tags").
P(1, 20)
// 便捷函数
opts := where.P(1, 10) // 分页
opts := where.F("field", value) // 过滤
opts := where.Load("Association") // 预加载支持的操作:
F(kvs...)- 过滤条件Q(query, args...)- 自定义 SQLP(page, pageSize)- 分页O(offset)/L(limit)- 偏移和限制C(clauses...)- GORM 子句Load(associations...)- 预加载关联
事务处理
internal/pkg/store 的 IStore 支持通过 context 自动处理事务:
// internal/pkg/store/store.go
type IStore interface {
DB(ctx context.Context, wheres ...where.Where) *gorm.DB
TX(ctx context.Context, fn func(ctx context.Context) error) error
// ...
}
// 事务会自动从 context 中提取
func (ds *datastore) DB(ctx context.Context, wheres ...where.Where) *gorm.DB {
db := ds.core
// 自动从上下文提取事务
if tx, ok := ctx.Value(transactionKey{}).(*gorm.DB); ok {
db = tx
}
// 应用查询条件
for _, whr := range wheres {
db = whr.Where(db)
}
return db
}
// 事务 API
func (ds *datastore) TX(ctx context.Context, fn func(ctx context.Context) error) error {
return ds.core.WithContext(ctx).Transaction(
func(tx *gorm.DB) error {
ctx = context.WithValue(ctx, transactionKey{}, tx)
return fn(ctx)
},
)
}API 说明
Store[T] 方法
// CRUD 操作
Create(ctx context.Context, obj *T) error
Update(ctx context.Context, obj *T, fields ...string) error
Delete(ctx context.Context, opts *where.Options) error
Get(ctx context.Context, opts *where.Options) (*T, error)
// 查询操作
List(ctx context.Context, opts *where.Options) (count int64, ret []*T, err error)
Find(ctx context.Context, opts *where.Options) (ret []*T, err error)
Last(ctx context.Context, opts *where.Options) (*T, error)
// 批量和条件操作
CreateInBatch(ctx context.Context, objs []*T, batchSize int) error
CreateIfNotExist(ctx context.Context, obj *T) error
FirstOrCreate(ctx context.Context, where any, obj *T) error
UpdateOrCreate(ctx context.Context, where any, obj *T) error
Upsert(ctx context.Context, obj *T, fields ...string) error
DeleteInBatch(ctx context.Context, ids []uint) error
// 原始数据库访问
DB(ctx context.Context, wheres ...where.Where) *gorm.DB方法说明:
- CreateIfNotExist: 创建对象,如果已存在则忽略(使用 OnConflict DoNothing)
- FirstOrCreate: 根据条件查找对象,不存在则创建
- UpdateOrCreate: 根据条件在事务中更新或创建对象,支持乐观锁
- DeleteInBatch: 按 ID 批量删除对象
IStore 接口
IStore 是应用级的统一数据访问接口,负责返回各个 Store 实现。接口采用模块化设计,通过方法返回相应的 Store:
type IStore interface {
// 事务和数据库
DB(ctx context.Context, wheres ...where.Where) *gorm.DB
TX(ctx context.Context, fn func(ctx context.Context) error) error
// 业务 Store 方法(按模块组织)
// 例如: Users() UserStore, Admin() AdminStore 等
}使用示例
本章通过一个简单示例展示 Store 的基本用法。
基本 CRUD 操作
假设有一个 User 模型,通过 Store 实现基本操作:
// 创建
user := &User{Name: "John", Email: "[email protected]"}
err := store.Users().Create(ctx, user)
// 读取
user, err := store.Users().Get(ctx, where.F("id", 1))
// 更新(仅更新指定字段)
user.Email = "[email protected]"
err := store.Users().Update(ctx, user, "email")
// 删除
err := store.Users().Delete(ctx, where.F("id", 1))查询和分页
// 构建查询条件
opts := where.F("status", "active").
P(1, 10) // 第1页,每页10条
// 执行查询
count, users, err := store.Users().List(ctx, opts)事务处理
多个操作需要原子性保证时,使用 TX() 方法:
err := store.TX(ctx, func(ctx context.Context) error {
// Store 会自动使用事务
if err := store.Users().Create(ctx, user1); err != nil {
return err // 自动回滚
}
if err := store.Users().Create(ctx, user2); err != nil {
return err // 自动回滚
}
return nil // 自动提交
})扩展操作
特定业务 Store 可以通过扩展接口添加自定义操作:
// internal/pkg/store/user.go
type UserExpansion interface {
FindByEmail(ctx context.Context, email string) (*User, error)
}
func (s *userStore) FindByEmail(ctx context.Context, email string) (*User, error) {
return s.Get(ctx, where.F("email", email))
}添加新的业务 Store
添加新的 Store 需要遵循以下步骤和命名规范(参考"命名规范"一节):
1. 创建 Store 接口和实现
// internal/pkg/store/user.go
package store
// Store 接口定义 CRUD 操作
type UserStore interface {
Create(ctx context.Context, obj *User) error
Update(ctx context.Context, obj *User, fields ...string) error
Delete(ctx context.Context, opts *where.Options) error
Get(ctx context.Context, opts *where.Options) (*User, error)
List(ctx context.Context, opts *where.Options) (int64, []*User, error)
UserExpansion // 扩展接口
}
// 扩展接口定义业务特定操作
type UserExpansion interface {
FindByEmail(ctx context.Context, email string) (*User, error)
}
// 实现类
type userStore struct {
*genericstore.Store[User]
}
// 工厂函数
func NewUserStore(store *datastore) *userStore {
return &userStore{
Store: genericstore.NewStore[User](store, NewLogger()),
}
}
// 实现扩展方法
func (s *userStore) FindByEmail(ctx context.Context, email string) (*User, error) {
return s.Get(ctx, where.F("email", email))
}2. 注册到 IStore
在 internal/pkg/store/store.go 中添加方法:
type IStore interface {
Users() UserStore // 新增
// ...
}
func (ds *datastore) Users() UserStore {
return NewUserStore(ds)
}下一步
- API Server - 了解核心 API 服务的设计和使用