Files
assets/dao/base/catch_sql.go
2026-03-18 10:18:03 +08:00

326 lines
9.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package base
import (
"context"
"fmt"
"gitea.com/red-future/common/utils"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
)
// ==================== CatchSQL 全局SQL条件拼接控制 ====================
// SQLConditionBuilder SQL条件构建器
type SQLConditionBuilder struct {
// 是否自动添加租户ID条件
EnableTenantId bool
// 是否自动添加创建人条件
EnableCreator bool
// 是否自动添加修改人条件
EnableUpdater bool
// 是否自动添加删除标记条件(只查询未删除数据)
EnableDeletedFilter bool
// 自定义额外条件
ExtraConditions map[string]interface{}
}
// DefaultSQLConditionBuilder 默认SQL条件构建器配置
var DefaultSQLConditionBuilder = SQLConditionBuilder{
EnableTenantId: true,
EnableCreator: false,
EnableUpdater: false,
EnableDeletedFilter: true,
ExtraConditions: make(map[string]interface{}),
}
// ctxKeyCatchSQL 上下文键
type ctxKeyCatchSQL string
const (
// ctxKeySQLBuilder SQL构建器上下文键
ctxKeySQLBuilder ctxKeyCatchSQL = "catch_sql_builder"
// ctxKeySkipCatchSQL 跳过CatchSQL的上下文键
ctxKeySkipCatchSQL ctxKeyCatchSQL = "catch_sql_skip"
)
// WithSQLBuilder 设置自定义SQL条件构建器到上下文
func WithSQLBuilder(ctx context.Context, builder *SQLConditionBuilder) context.Context {
return context.WithValue(ctx, ctxKeySQLBuilder, builder)
}
// GetSQLBuilder 从上下文获取SQL条件构建器
func GetSQLBuilder(ctx context.Context) *SQLConditionBuilder {
if ctx == nil {
return &DefaultSQLConditionBuilder
}
if builder, ok := ctx.Value(ctxKeySQLBuilder).(*SQLConditionBuilder); ok && builder != nil {
return builder
}
return &DefaultSQLConditionBuilder
}
// SkipCatchSQL 跳过CatchSQL条件拼接
func SkipCatchSQL(ctx context.Context) context.Context {
return context.WithValue(ctx, ctxKeySkipCatchSQL, true)
}
// IsSkipCatchSQL 检查是否跳过CatchSQL
func IsSkipCatchSQL(ctx context.Context) bool {
if ctx == nil {
return false
}
v, ok := ctx.Value(ctxKeySkipCatchSQL).(bool)
return ok && v
}
// ==================== CatchSQL 核心方法 ====================
// CatchSQL 全局统一控制SQL条件拼接
// 根据上下文自动添加租户ID、创建人、修改人、删除标记等条件
// 使用示例:
//
// // 基础使用(自动添加默认条件)
// m := base.CatchSQL(ctx, g.DB().Model("asset"))
// m.Where("status", 1).Scan(&result)
//
// // 自定义条件构建器
// builder := &base.SQLConditionBuilder{
// EnableTenantId: true,
// EnableCreator: true,
// }
// ctx = base.WithSQLBuilder(ctx, builder)
// m := base.CatchSQL(ctx, g.DB().Model("asset"))
//
// // 跳过CatchSQL
// ctx = base.SkipCatchSQL(ctx)
// m := base.CatchSQL(ctx, g.DB().Model("asset"))
func CatchSQL(ctx context.Context, model *gdb.Model) *gdb.Model {
if ctx == nil || model == nil {
return model
}
// 检查是否跳过
if IsSkipCatchSQL(ctx) {
return model
}
builder := GetSQLBuilder(ctx)
userInfo, _ := utils.GetUserInfo(ctx)
// 1. 自动添加租户ID条件
if builder.EnableTenantId && !g.IsEmpty(userInfo.TenantId) {
model = model.Where("tenant_id", userInfo.TenantId)
}
// 2. 自动添加创建人条件
if builder.EnableCreator && !g.IsEmpty(userInfo.UserName) {
model = model.Where("creator", userInfo.UserName)
}
// 3. 自动添加修改人条件
if builder.EnableUpdater && !g.IsEmpty(userInfo.UserName) {
model = model.Where("updater", userInfo.UserName)
}
// 4. 自动添加删除标记条件(只查询未删除数据)
if builder.EnableDeletedFilter {
model = model.Where("is_deleted", 0)
}
// 5. 添加自定义额外条件
for field, value := range builder.ExtraConditions {
if field != "" && value != nil {
model = model.Where(field, value)
}
}
return model
}
// CatchSQLWithTable 指定表名创建带CatchSQL条件的Model
// 使用示例:
//
// m := base.CatchSQLWithTable(ctx, "asset")
// m.Where("status", 1).Scan(&result)
func CatchSQLWithTable(ctx context.Context, table string) *gdb.Model {
if ctx == nil {
return g.DB().Model(table).Safe()
}
model := g.DB().Model(table).Safe().Ctx(ctx)
return CatchSQL(ctx, model)
}
// CatchSQLWithSchema 指定Schema和表名创建带CatchSQL条件的Model
// 使用示例:
//
// m := base.CatchSQLWithSchema(ctx, "public", "asset")
// m.Where("status", 1).Scan(&result)
func CatchSQLWithSchema(ctx context.Context, schema, table string) *gdb.Model {
if ctx == nil {
return g.DB().Schema(schema).Model(table).Safe()
}
model := g.DB().Schema(schema).Model(table).Safe().Ctx(ctx)
return CatchSQL(ctx, model)
}
// ==================== 快捷条件构建器 ====================
// NewSQLBuilder 创建新的SQL条件构建器
func NewSQLBuilder() *SQLConditionBuilder {
return &SQLConditionBuilder{
EnableTenantId: true,
EnableCreator: false,
EnableUpdater: false,
EnableDeletedFilter: true,
ExtraConditions: make(map[string]interface{}),
}
}
// WithTenantId 启用/禁用租户ID条件
func (b *SQLConditionBuilder) WithTenantId(enable bool) *SQLConditionBuilder {
b.EnableTenantId = enable
return b
}
// WithCreator 启用/禁用创建人条件
func (b *SQLConditionBuilder) WithCreator(enable bool) *SQLConditionBuilder {
b.EnableCreator = enable
return b
}
// WithUpdater 启用/禁用修改人条件
func (b *SQLConditionBuilder) WithUpdater(enable bool) *SQLConditionBuilder {
b.EnableUpdater = enable
return b
}
// WithDeletedFilter 启用/禁用删除标记过滤
func (b *SQLConditionBuilder) WithDeletedFilter(enable bool) *SQLConditionBuilder {
b.EnableDeletedFilter = enable
return b
}
// WithExtraCondition 添加自定义条件
func (b *SQLConditionBuilder) WithExtraCondition(field string, value interface{}) *SQLConditionBuilder {
if b.ExtraConditions == nil {
b.ExtraConditions = make(map[string]interface{})
}
b.ExtraConditions[field] = value
return b
}
// Build 构建并返回Model
func (b *SQLConditionBuilder) Build(ctx context.Context, model *gdb.Model) *gdb.Model {
ctx = WithSQLBuilder(ctx, b)
return CatchSQL(ctx, model)
}
// ==================== 常用场景快捷方法 ====================
// CatchSQLForTenant 只添加租户ID条件的快捷方法
func CatchSQLForTenant(ctx context.Context, model *gdb.Model) *gdb.Model {
builder := NewSQLBuilder().
WithTenantId(true).
WithDeletedFilter(false)
ctx = WithSQLBuilder(ctx, builder)
return CatchSQL(ctx, model)
}
// CatchSQLForCreator 只添加创建人条件的快捷方法
func CatchSQLForCreator(ctx context.Context, model *gdb.Model) *gdb.Model {
builder := NewSQLBuilder().
WithTenantId(false).
WithCreator(true).
WithDeletedFilter(false)
ctx = WithSQLBuilder(ctx, builder)
return CatchSQL(ctx, model)
}
// CatchSQLForList 列表查询的快捷方法租户ID + 删除标记)
func CatchSQLForList(ctx context.Context, model *gdb.Model) *gdb.Model {
builder := NewSQLBuilder().
WithTenantId(true).
WithDeletedFilter(true)
ctx = WithSQLBuilder(ctx, builder)
return CatchSQL(ctx, model)
}
// CatchSQLForAdmin 管理员查询的快捷方法(只过滤删除标记,不过滤租户)
func CatchSQLForAdmin(ctx context.Context, model *gdb.Model) *gdb.Model {
builder := NewSQLBuilder().
WithTenantId(false).
WithDeletedFilter(true)
ctx = WithSQLBuilder(ctx, builder)
return CatchSQL(ctx, model)
}
// ==================== DAO层无感知集成 ====================
// CtxModel 创建带 CatchSQL 条件的 ModelDAO层无感知使用
// 这是推荐的无感知使用方式,直接在 DAO 的 Ctx() 方法中调用
//
// 使用示例DAO层
//
// func (d *assetDao) Ctx(ctx context.Context) *gdb.Model {
// return base.CtxModel(ctx, entity.Asset{})
// }
//
// func (d *assetDao) GetById(ctx context.Context, id uint64) (*entity.Asset, error) {
// var result entity.Asset
// err := d.Ctx(ctx).Where("id", id).Scan(&result) // 自动带 tenant_id 和 is_deleted=0 条件
// return &result, err
// }
func CtxModel(ctx context.Context, table interface{}) *gdb.Model {
if ctx == nil {
return g.DB().Model(table).Safe()
}
model := g.DB().Model(table).Safe().Ctx(ctx)
return CatchSQL(ctx, model)
}
// CtxModelWithHook 创建带 CatchSQL 条件和 Hook 的 Model完整版
// 同时启用 CatchSQL 条件拼接和 CatchSQLHook 自动字段赋值
//
// 使用示例DAO层
//
// func (d *assetDao) Ctx(ctx context.Context) *gdb.Model {
// return base.CtxModelWithHook(ctx, entity.Asset{})
// }
//
// func (d *assetDao) Insert(ctx context.Context, data g.Map) (int64, error) {
// return d.Ctx(ctx).Data(data).InsertAndGetId() // 自动赋值 tenant_id, creator, updater
// }
func CtxModelWithHook(ctx context.Context, table interface{}) *gdb.Model {
if ctx == nil {
return g.DB().Model(table).Safe()
}
model := g.DB().Model(table).Safe().Ctx(ctx)
// 先应用 CatchSQL 条件
model = CatchSQL(ctx, model)
// 再应用 Hook用于 Insert/Update 自动字段赋值)
model = model.Hook(CatchSQLHook())
return model
}
// ==================== 调试工具 ====================
// GetCatchSQLInfo 获取当前CatchSQL的配置信息用于调试
func GetCatchSQLInfo(ctx context.Context) string {
if IsSkipCatchSQL(ctx) {
return "CatchSQL: skipped"
}
builder := GetSQLBuilder(ctx)
userInfo, _ := utils.GetUserInfo(ctx)
return fmt.Sprintf(
"CatchSQL{TenantId:%v(%v), Creator:%v(%v), Updater:%v(%v), DeletedFilter:%v, Extra:%v}",
builder.EnableTenantId, userInfo.TenantId,
builder.EnableCreator, userInfo.UserName,
builder.EnableUpdater, userInfo.UserName,
builder.EnableDeletedFilter,
len(builder.ExtraConditions),
)
}