基于 Go 泛型的业务模型基础能力封装,实现充血模型架构,助力高效、规范的业务开发。
base 是一个面向企业级应用开发的 Go 基础库,采用**充血模型(Rich Domain Model)**设计思想,通过泛型封装为业务实体提供开箱即用的基础能力。
- 重复代码:不同业务模型重复编写 CRUD、校验、审计等基础代码
- 过程式编程:业务复杂时代码难以维护,缺乏面向对象的设计思维
- 代码质量:通过标准化流程和封装,提升代码一致性和可维护性
- 开发效率:沉淀通用能力,让开发者专注于业务逻辑
- 企业级 Web 应用开发
- 需要复杂业务逻辑编排的领域
- 追求代码规范和长期维护的项目
- 基于 Gin + GORM 的 Go 项目
- 泛型支持:基于 Go 1.18+ 泛型实现类型安全的基类封装
- 标准 CRUD:内置增删改查、分页查询、批量操作等 40+ 基础方法
- 数据校验:Validate / Repair / Complete 三段式数据处理流程
- 状态机:内置有限状态机(FSM)支持,轻松实现工作流状态流转
- 审计追踪:自动维护创建人、更新人、时间戳等审计字段
- 事务管理:支持跨实体的事务编排,防止重复开启事务
- 搜索 DSL:基于结构体标签的声明式查询条件构建
- SQL 注入防护:字段名校验机制,防止非法 SQL 注入
- 本地缓存:实体级缓存,支持 TTL 过期策略
go get github.com/jianyuezhexue/basepackage main
import (
"github.com/gin-gonic/gin"
"github.com/jianyuezhexue/base"
"gorm.io/gorm"
)
// 1. 定义业务实体
type Product struct {
base.BaseModel[Product]
Name string `json:"name" gorm:"column:name"`
Price float64 `json:"price" gorm:"column:price"`
Code string `json:"code" gorm:"column:code"`
}
func (p *Product) TableName() string {
return "products"
}
// 2. 实现业务接口
type ProductInterface interface {
base.BaseModelInterface[Product]
}
// 3. 实现必要钩子
func (p *Product) Validate() error {
if p.Name == "" {
return fmt.Errorf("商品名称不能为空")
}
if p.Price <= 0 {
return fmt.Errorf("商品价格必须大于0")
}
return nil
}
func (p *Product) Repair() error {
// 自动修复逻辑,如设置默认值
if p.Code == "" {
p.Code = generateCode()
}
return nil
}
func (p *Product) Complete() error {
// 数据补全逻辑,如查询关联数据
return nil
}
// 4. 实体构造函数
func NewProduct(ctx *base.BaseContext, db *gorm.DB) ProductInterface {
entity := &Product{}
entity.BaseModel = base.NewBaseModel(ctx, db, entity.TableName(), entity)
return entity
}┌─────────────────────────────────────────┐
│ Logic 层(业务逻辑层) │
│ - 编排多个实体能力 │
│ - 处理事务边界 │
│ - 实现复杂业务流程 │
├─────────────────────────────────────────┤
│ Entity 层(领域实体层) │
│ - 业务数据 + 业务行为 │
│ - 继承 BaseModel 基础能力 │
│ - 实现领域特定方法 │
├─────────────────────────────────────────┤
│ Base 层(基础能力层) │
│ - CRUD 操作 │
│ - 数据校验 & 修复 │
│ - 状态机 & 审计追踪 │
└─────────────────────────────────────────┘
- 领域划分清晰:按业务领域组织代码,每个领域包含多个业务实体
- 充血模型:实体封装数据和行为,避免贫血模型的过程式代码
-
组合优于继承:通过组合
BaseModel获得能力,而非深继承链 - 标准化流程:定义统一的 Input/Output/Complex 业务处理流程
接收参数 → 实例化实体 → 填充数据 → 数据校验 → 数据修复 →
查询旧数据 → 业务校验 → 保存/更新 → 记录日志
接收参数 → 实例化实体 → 构造条件 → 查询数据 → 数据完善 → 返回结果
实例化多实体 → 设置/加载数据 → 调用实体能力A → 开启事务 →
调用实体能力B → 事务提交/回滚
// BaseModelInterface 定义了 40+ 基础能力
type BaseModelInterface[T any] interface {
// 数据操作
Create() (*T, error) // 创建
CreateWithData(data *T) (*T, error) // 使用指定数据创建
Update() (*T, error) // 更新
UpdateWithData(data *T) (*T, error) // 使用指定数据更新
Del(ids ...uint64) error // 删除
// 数据加载(加载到当前实体)
LoadById(id uint64, preloads ...PreloadsType) (*T, error)
LoadByBusinessCode(field, value string, preloads ...PreloadsType) (*T, error)
// 数据查询(返回新数据,不修改当前实体)
GetById(id uint64, preloads ...PreloadsType) (*T, error)
GetByIds(ids []uint64, preloads ...PreloadsType) ([]*T, error)
List(conds ...SearchCondition) ([]*T, error)
Count(conds ...SearchCondition) (int64, error)
// 数据处理流程
SetData(data any) (*T, error) // 填充数据
Validate() error // 数据校验(需实现)
Repair() error // 数据修复(需实现)
Complete() error // 数据完善(需实现)
// 事务
Transaction(fc func(tx *gorm.DB) error) error
// 状态机
InitStateMachine(status string, events []fsm.EventDesc, callback fsm.Callback) error
EventExecution(status, event, eventName string, args ...any) error
// 唯一性检查
CheckBusinessCodeExist(field, code string) (bool, error)
CheckUniqueKeysExist(fields []string, values []string) (bool, error)
// 查询条件构建
MakeCondition(data any) SearchCondition
}基于结构体标签的声明式查询:
type ProductSearch struct {
// 基础字段查询
Name string `search:"type:icontains;column:name;table:products" form:"name"`
Status []int `search:"type:in;column:status;table:products" form:"status[]"`
MinPrice float64 `search:"type:gte;column:price;table:products" form:"min_price"`
MaxPrice float64 `search:"type:lte;column:price;table:products" form:"max_price"`
// 排序和分页
Sort string `search:"type:order;column:id;table:products" form:"sort"`
Page int `search:"type:page" form:"page"`
PageSize int `search:"type:pageSize" form:"page_size"`
}
// 使用
search := ProductSearch{Name: "iPhone", Status: []int{1, 2}, Page: 1, PageSize: 20}
condition := entity.MakeCondition(search)
list, _ := entity.List(condition)支持的查询类型:
| 类型 | 说明 | 示例值 |
|---|---|---|
exact / iexact
|
精确匹配(大小写敏感/不敏感) | status=1 |
contains / icontains
|
模糊匹配 | name=phone |
gt / gte
|
大于 / 大于等于 | price=100 |
lt / lte
|
小于 / 小于等于 | price=1000 |
startswith / istartswith
|
前缀匹配 | code=PRD |
endswith / iendswith
|
后缀匹配 | code=001 |
in |
IN 查询 | status[]=1&status[]=2 |
isnull |
NULL 检查 |
deleted=1 (is not null) |
order |
排序 | sort=desc |
type Order struct {
base.BaseModel[Order] // 嵌入基础模型
OrderId string `json:"orderId" gorm:"column:order_id"`
Status int `json:"status" gorm:"column:status"`
CustomerId string `json:"customerId" gorm:"column:customer_id"`
Amount float64 `json:"amount" gorm:"column:amount"`
OrderItems []*OrderItem `json:"orderItems" gorm:"foreignKey:OrderId;references:OrderId"`
}
func (o *Order) TableName() string {
return "orders"
}type OrderInterface interface {
base.BaseModelInterface[Order]
// 自定义业务能力
CalculateTotal() error
Cancel() error
}// Validate 数据校验 - 校验外部输入合法性
func (o *Order) Validate() error {
if o.CustomerId == "" {
return fmt.Errorf("客户ID不能为空")
}
if o.Amount <= 0 {
return fmt.Errorf("订单金额必须大于0")
}
// 检查客户是否存在...
return nil
}
// Repair 数据修复 - 设置默认值、补偿缺失数据
func (o *Order) Repair() error {
if o.OrderId == "" {
o.OrderId = generateOrderId()
}
if o.Status == 0 {
o.Status = OrderStatusDraft
}
return nil
}
// Complete 数据完善 - 补全关联数据、字典翻译
func (o *Order) Complete() error {
// 查询客户信息
// 翻译状态码为中文
// 格式化金额...
return nil
}func NewOrder(ctx *base.BaseContext, db *gorm.DB, opts ...base.Option[Order]) OrderInterface {
entity := &Order{}
entity.BaseModel = base.NewBaseModel(ctx, db, entity.TableName(), entity)
// 应用自定义选项
for _, opt := range opts {
opt(&entity.BaseModel)
}
return entity
}// 预加载配置
preloads := map[string][]any{"OrderItems": {}}
withPreloads := base.WithPreloads[Order](preloads)
// 权限条件配置
permissionConds := []base.SearchCondition{
func(db *gorm.DB) *gorm.DB {
return db.Where("tenant_id = ?", tenantId)
},
}
withPermissions := base.WithPermissionConditons[Order](permissionConds...)
// 创建实体
order := NewOrder(ctx, db, withPreloads, withPermissions)// 方式一:使用 SetData + Create(推荐)
req := CreateOrderRequest{CustomerId: "C001", Amount: 100}
order.SetData(req)
order.Validate()
order.Repair()
order.Create()
// 方式二:使用 CreateWithData(简洁)
data := &Order{CustomerId: "C001", Amount: 100}
order.CreateWithData(data)// 标准更新流程
order.LoadById(1) // 加载现有数据
order.SetData(updateReq) // 应用更新
order.Validate() // 校验
order.Repair() // 修复
order.Update() // 保存
// 或使用 UpdateWithData
order.UpdateWithData(&Order{Id: 1, Status: 2})// 单条查询
order.LoadById(1)
order.LoadByBusinessCode("order_id", "ORD2024001")
// 条件查询
search := OrderSearch{Status: []int{1, 2}}
cond := order.MakeCondition(search)
list, _ := order.List(cond)
total, _ := order.Count(cond)
// 批量查询
orders, _ := order.GetByIds([]uint64{1, 2, 3})order := NewOrder(ctx, db)
product := NewProduct(ctx, db)
err := order.Transaction(func(tx *gorm.DB) error {
// 在事务中操作
order.Create()
product.Update()
// 返回错误会触发回滚
return nil
})// 定义状态流转
var events = []fsm.EventDesc{
{Src: []string{"draft"}, Name: "submit", Dst: "pending"},
{Src: []string{"pending"}, Name: "approve", Dst: "approved"},
{Src: []string{"pending"}, Name: "reject", Dst: "rejected"},
}
// 初始化状态机
order.InitStateMachine("draft", events, order.OnStateChange)
// 执行状态变更
order.EventExecution("pending", "approve", "审批通过")详见 exampleLogic/order_test.go:
// 创建订单
func TestCreateOrder(t *testing.T) {
ctx := createTestContext()
req := &CreateOrderRequest{
CustomerName: "张三",
Address: "北京市",
Items: []*OrderItem{{SkuCode: "SKU001", Qty: 2}},
}
entity := NewOrder(ctx, db)
entity.SetData(req)
entity.Validate()
entity.Repair()
err := entity.Transaction(func(tx *gorm.DB) error {
_, err := entity.Create()
return err
})
assert.Nil(t, err)
}
// 更新订单
func TestUpdateOrder(t *testing.T) {
ctx := createTestContext()
req := &UpdateOrderRequest{Id: 1, CustomerName: "李四"}
entity := NewOrder(ctx, db)
entity.LoadById(req.Id)
entity.SetData(req)
entity.Validate()
entity.Repair()
entity.Update()
}
// 查询列表
func TestListOrders(t *testing.T) {
ctx := createTestContext()
search := OrderSearch{CustomerName: "张", Page: 1, PageSize: 20}
entity := NewOrder(ctx, db, base.WithPreloads[Order](
map[string][]any{"OrderItems": {}}
))
cond := entity.MakeCondition(search)
total, _ := entity.Count(cond)
list, _ := entity.List(cond)
for _, item := range list {
item.Complete()
}
fmt.Printf("Total: %d, List: %v\n", total, list)
}- 单一职责:一个实体只负责一个业务概念
- 自包含:实体应包含自身的验证、修复逻辑
- 接口隔离:通过接口暴露能力,隐藏内部实现
- 在 Logic 层控制事务,而非 Entity 层
- 使用
Transaction()方法开启事务,库会自动防止重复开启 - 在事务中操作多个实体时,确保它们共享同一个 Context
确保 Gin Context 中包含必要的信息:
c.Set("currUserId", userId) // 当前用户ID
c.Set("currUserName", userName) // 当前用户名称对于关联数据查询,使用预加载避免 N+1 问题:
preloads := map[string][]any{
"OrderItems": {},
"Customer": {func(db *gorm.DB) *gorm.DB {
return db.Select("id", "name")
}},
}
entity := NewOrder(ctx, db, base.WithPreloads[Order](preloads))库会返回详细的错误信息,建议按以下方式处理:
if err != nil {
// 区分业务错误和系统错误
if strings.Contains(err.Error(), "查询的数据不存在") {
// 业务错误 - 返回 404
} else {
// 系统错误 - 返回 500
}
}- Repository: https://github.com/jianyuezhexue/base
- Issues: https://github.com/jianyuezhexue/base/issues
- License: MIT