WithVirtual(bool) 只是给 New() 打一个标记位,虚拟资源需要的表达式(子查询)没有 承载的地方,之前借用 table string 字段硬拼字符串。改成 NewVirtualResource(res, code, table, opts...) 专门构造:table 参数直接接收 exp.SQLExpression, GetTableExpr 对虚拟资源直接用它生成子查询表达式,不用再手工拼括号和字符串; container/conn/historyRoles 从传入的父资源上直接复用。 GetTableExpr 返回值类型从 exp.LiteralExpression 放宽成 exp.Aliaseable, 兼容 db.T(...)(IdentifierExpression)和 db.L(...)(LiteralExpression)两种 返回值。
159 lines
5.7 KiB
Go
Executable File
159 lines
5.7 KiB
Go
Executable File
package req
|
||
|
||
import (
|
||
"reflect"
|
||
|
||
"git.fsdpf.net/go/db"
|
||
"git.fsdpf.net/go/db/exp"
|
||
"git.fsdpf.net/go/db/schema"
|
||
"git.fsdpf.net/go/reflux"
|
||
)
|
||
|
||
type ResDataType string
|
||
|
||
// ResFlags 是一组可以按位组合的权限检查开关,零值表示什么检查都不做。行级过滤
|
||
// (ResRow)和字段脱敏(ResMask)是两个独立维度,各自的 xxxRelations 位表示"连关联
|
||
// 资源一起处理",目前只保留位定义,还没接入判断逻辑。
|
||
type ResFlags int
|
||
|
||
const (
|
||
// ResRow 行级权限过滤:按角色过滤能看到/操作的行,只处理当前资源自己
|
||
ResRow ResFlags = 1 << iota
|
||
// ResRowRelations 行级权限过滤扩展到关联资源(依附于 ResRow,单独设置无效果,尚未实现)
|
||
ResRowRelations
|
||
// ResMask 字段级脱敏:按 ResField.GetRoles() 脱敏无权限查看/写入的字段,只处理当前资源自己
|
||
ResMask
|
||
// ResMaskRelations 字段级脱敏扩展到关联资源(依附于 ResMask,单独设置无效果,尚未实现)
|
||
ResMaskRelations
|
||
|
||
// ResAll 常用组合:行级过滤 + 字段脱敏都开启(不含关联资源)
|
||
ResAll = ResRow | ResMask
|
||
)
|
||
|
||
const (
|
||
ResString ResDataType = "string"
|
||
ResText ResDataType = "text"
|
||
ResInteger ResDataType = "integer"
|
||
ResSmallInteger ResDataType = "smallInteger"
|
||
ResDecimal ResDataType = "decimal"
|
||
ResBoolean ResDataType = "boolean"
|
||
ResJson ResDataType = "json"
|
||
ResEnum ResDataType = "enum"
|
||
ResTimestamp ResDataType = "timestamp"
|
||
ResDate ResDataType = "date"
|
||
ResDatetime ResDataType = "dateTime"
|
||
ResVector ResDataType = "vector"
|
||
)
|
||
|
||
const (
|
||
IsExpr byte = 1 << iota
|
||
IsOmitempty
|
||
Ignored
|
||
)
|
||
|
||
type Resource interface {
|
||
GetUuid() string
|
||
GetName() string
|
||
GetCode() string
|
||
GetDescription() string
|
||
GetPrimarykey() string
|
||
GetConn() string
|
||
GetFields() []ResField
|
||
GetField(string) (ResField, bool)
|
||
HasField(string) bool
|
||
// GetTable 原始表名
|
||
GetTable() string
|
||
// GetTableExpr 可用于 FROM/JOIN 的表引用表达式
|
||
GetTableExpr() exp.Aliaseable
|
||
|
||
DB() *db.Database
|
||
BeginTransaction() (*db.TxDatabase, error)
|
||
|
||
GetDBTable(User, ...ResOption) *db.SelectDataset
|
||
|
||
GetStruct(extends ...reflect.StructField) any
|
||
GetSliceStruct(extends ...reflect.StructField) any
|
||
|
||
// GetHistoryRoles 配置了变更留痕的角色列表,为空表示不留痕
|
||
GetHistoryRoles() []string
|
||
|
||
// 是否虚拟资源
|
||
IsVirtual() bool
|
||
// 是否系统资源
|
||
IsSystem() bool
|
||
}
|
||
|
||
// ResVirtualTable 虚拟表接口,由业务层实现,框架通过 SQLite vtab 机制调用。
|
||
// Select 提供列表查询,Fetch 提供单条完整查询(含 Detail 补充字段),
|
||
// Insert/Update/Delete 支持写操作并自动同步缓存。
|
||
type ResVirtualTable interface {
|
||
GetResource() Resource
|
||
|
||
// Fetch 按主键获取单条完整数据,内部负责调用远端接口并补充 Detail 专属字段。
|
||
// 框架在 item cache miss 时调用,结果写入 item cache 供后续查询复用。
|
||
Fetch(pk any) (map[string]any, error)
|
||
|
||
// Detail 对已有的 item 数据进行补充,填充列表接口不返回的详情字段。
|
||
// changed=true 时框架会将修改后的数据回写 item cache。
|
||
Detail(item reflux.R) (changed bool, err error)
|
||
|
||
// Select 查询列表数据,filter 为过滤条件,pagesize/page 控制分页。
|
||
// 返回的 items 每项为 map[string]any,total 为总记录数。
|
||
Select(filter reflux.R, pagesize, page int) (items []any, total int64, err error)
|
||
|
||
// Insert 新增一条记录,返回新记录的 rowid。
|
||
Insert(item reflux.R) (rowid int64, err error)
|
||
|
||
// Update 按 rowid 更新记录。
|
||
Update(rowid any, item reflux.R) error
|
||
|
||
// Delete 按 rowid 删除记录。
|
||
Delete(rowid any) error
|
||
}
|
||
|
||
// ResVirtualTableSetup 由 framework 在 sqlite_vtable build tag 下注册到容器,负责向 SQLite
|
||
// 注册虚拟表模块({res.GetTable()}_mod)。当前由上层在资源缓存构建时提前对所有 vtable 连接的资源
|
||
type ResVirtualTableSetup func(res Resource) error
|
||
|
||
// GetResource 按 code 或 uuid 查找已注册的资源,找不到返回 ok=false。放在 req 而不是 contracts,
|
||
// 是为了让 req 自己的子包(比如 resource 包)也能通过 DI 拿到查找能力,不用反过来 import
|
||
// contracts(contracts 依赖 req,import 反过来会成环)。contracts.GetResource 是它的类型别名,
|
||
// 兼容原有引用。
|
||
type GetResource func(code string) (Resource, bool)
|
||
|
||
// MustResource 按 code 或 uuid 查找已注册的资源,找不到时由具体实现负责报错(通常是 panic)。
|
||
type MustResource func(code string) Resource
|
||
|
||
type ResField interface {
|
||
GetName() string
|
||
GetCode() string
|
||
GetCodeResource() string
|
||
GetDataType() ResDataType
|
||
GetQueryDataType() RouteParamType
|
||
GetRawDefault() db.Expression
|
||
ToStructField(tags ...string) reflect.StructField
|
||
ToValue(any) any
|
||
ToBlueprint(table *schema.Blueprint) *schema.ColumnDefinition
|
||
ToQueryField(t RouteParamType, alias string, options byte) QueryField
|
||
IsVirtual() bool
|
||
// GetRoles 配置了"能读写该字段真实值"的角色列表,为空表示不限制(所有角色都能读写真实值);
|
||
// 非空时,用户所属角色只要有一个在列表里就有权限,否则:查询时这个字段会被替换成脱敏哨兵值
|
||
// (见 resource 包的 MaskFunc),写入时这个字段会被静默丢弃。注意这跟 GetHistoryRoles 等
|
||
// "空=没人有权限"的既有约定相反:这是后加的限制能力,必须保证未配置时不影响任何现有字段的
|
||
// 读写行为。
|
||
GetRoles() []string
|
||
}
|
||
|
||
type QueryField interface {
|
||
ResField
|
||
Type() RouteParamType
|
||
Alias() string
|
||
GetCodeOrAlias() string
|
||
IsExpr() bool
|
||
IsOmitempty() bool
|
||
Ignored() bool
|
||
SetOptions(byte) QueryField
|
||
ToSql() db.Expression
|
||
ToStructField(tags ...string) reflect.StructField
|
||
}
|