Files
req/resource.go
T
what 5cdf1cc00d 重构: 虚拟资源改用 NewVirtualResource 显式构造,移除 WithVirtual
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)两种
返回值。
2026-08-20 15:58:03 +08:00

159 lines
5.7 KiB
Go
Executable File
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 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]anytotal 为总记录数。
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
// contractscontracts 依赖 reqimport 反过来会成环)。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
}