res_field.go/res_field_option.go(字段定义)、res_query_field.go/res_query_field_option.go(查询字段)、res_change_row.go(变更行)、res_mask_field.go(字段脱敏标记)及对应测试文件,配合此前已提交的 resource.go/res_interceptor.go 组成完整的 resx 资源实现包。
169 lines
4.9 KiB
Go
169 lines
4.9 KiB
Go
package resx
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"git.fsdpf.net/go/db"
|
|
"git.fsdpf.net/go/db/exp"
|
|
"git.fsdpf.net/go/req"
|
|
)
|
|
|
|
// QueryField 是 req.QueryField 的具体实现。原来放在 orm-v2,这里搬过来跟 ResField 是同一个原因:
|
|
// ToSql() 需要用 MaskField 按字段的 Roles 做脱敏标记,放在 resx 里可以直接调用,不用 orm 包再反过来
|
|
// import resx。orm-v2/contracts-v2 base 的 QueryField 现在都是指向这里的类型别名,兼容原有引用。
|
|
//
|
|
// 嵌入的是 req.ResField 接口而不是具体的 ResField 结构体:调用方经常手上已经有一个真实的
|
|
// req.ResField(比如从 resource.GetField(code) 查出来的),这时候直接传它本身即可,不用先转换成
|
|
// 具体类型(也不用为了绕开"没有的字段"再拼一个大部分为空的 ResField 出来)。QueryField 自己的方法
|
|
// 全程只通过 req.ResField 接口访问底层字段,不需要具体类型独有的能力。
|
|
type QueryField struct {
|
|
req.ResField
|
|
typ req.RouteParamType `db:"dataType"`
|
|
alias string `db:"alias"`
|
|
isExpr bool `db:"isExpr"`
|
|
isOmitempty bool
|
|
ignored bool
|
|
}
|
|
|
|
// Alias 返回字段的别名。
|
|
func (this QueryField) Alias() string {
|
|
return this.alias
|
|
}
|
|
|
|
// Ignored 返回字段是否被忽略。
|
|
func (this QueryField) Ignored() bool {
|
|
return this.ignored
|
|
}
|
|
|
|
// IsExpr 返回字段是否为表达式。
|
|
func (this QueryField) IsExpr() bool {
|
|
return this.isExpr
|
|
}
|
|
|
|
// IsOmitempty 返回字段是否在为空时忽略。
|
|
func (this QueryField) IsOmitempty() bool {
|
|
return this.isOmitempty
|
|
}
|
|
|
|
// Type 返回字段的数据类型。
|
|
func (this QueryField) Type() req.RouteParamType {
|
|
return this.typ
|
|
}
|
|
|
|
// SetOptions 设置字段选项(表达式、忽略空值、忽略等)。
|
|
func (this *QueryField) SetOptions(options byte) req.QueryField {
|
|
if options&req.IsExpr != 0 {
|
|
this.isExpr = true
|
|
}
|
|
|
|
if options&req.IsOmitempty != 0 {
|
|
this.isOmitempty = true
|
|
}
|
|
|
|
if options&req.Ignored != 0 {
|
|
this.ignored = true
|
|
}
|
|
|
|
return this
|
|
}
|
|
|
|
// GetCodeOrAlias 返回字段的别名(如果有),否则返回字段代码。
|
|
func (this QueryField) GetCodeOrAlias() string {
|
|
if this.Alias() != "" {
|
|
return this.Alias()
|
|
}
|
|
|
|
return this.GetCode()
|
|
}
|
|
|
|
// ToSql 将查询字段转换为SQL表达式。
|
|
func (this *QueryField) ToSql() db.Expression {
|
|
if this.Alias() != "" {
|
|
if this.IsExpr() {
|
|
if this.GetCode() == "" {
|
|
return db.V(nil).As(this.Alias())
|
|
}
|
|
return db.L("(?)", db.L(this.GetCode())).As(this.Alias())
|
|
}
|
|
return this.col().As(this.Alias())
|
|
}
|
|
return this.col()
|
|
}
|
|
|
|
// col 构造这个字段对应的列引用,一律用 MaskField(...) 标记。这里不能靠 this.GetRoles() 提前判断
|
|
// 要不要标记——构造 QueryField 的这个 ResField 经常是从 DataListField/ResApiField 之类的配置记录
|
|
// 现造出来的,根本不带 Roles(这些配置结构体压根没有 Roles 这一列),如果在这里用"当前手上这个
|
|
// ResField 有没有 Roles"来决定要不要包 MaskField,等于默认永远不脱敏,绕过了字段级权限。
|
|
// 真正的判断交给 dataProcessor.maskSelect:查询真正执行前,从 SELECT 列表所在的 FROM/JOIN 子句里
|
|
// 把 GetCodeResource() 这个别名解析成真实表名,再查真实资源上对应字段的真实 Roles——这一步不需要
|
|
// 构造 QueryField 时就知道 Roles。
|
|
func (this *QueryField) col() exp.IdentifierExpression {
|
|
return MaskField(this.GetCodeResource() + "." + this.GetCode())
|
|
}
|
|
|
|
// ToStructField 将查询字段转换为反射结构体字段定义。
|
|
func (this *QueryField) ToStructField(tags ...string) reflect.StructField {
|
|
var typ reflect.Type
|
|
|
|
fCode := this.GetCodeOrAlias()
|
|
|
|
// 字段规则
|
|
if !unicode.IsLetter(rune(fCode[0])) {
|
|
panic("struct field name invalid. " + fCode)
|
|
}
|
|
|
|
fTag := `db:"` + fCode
|
|
|
|
if this.ignored {
|
|
fTag = fTag + `" json:"-"`
|
|
} else if this.isOmitempty {
|
|
fTag = fTag + `" json:"` + fCode + `,omitempty"`
|
|
} else {
|
|
fTag = fTag + `" json:"` + fCode + `"`
|
|
}
|
|
|
|
if len(tags) > 0 {
|
|
fTag = strings.Join(tags, " ") + " " + fTag
|
|
}
|
|
|
|
switch this.typ {
|
|
case req.ReqString:
|
|
typ = reflect.TypeOf("")
|
|
case req.ReqNumber:
|
|
typ = reflect.TypeOf(float64(0))
|
|
case req.ReqInteger:
|
|
typ = reflect.TypeOf(int64(0))
|
|
case req.ReqFloat:
|
|
typ = reflect.TypeOf(float64(0))
|
|
case req.ReqBool:
|
|
typ = reflect.TypeOf(true)
|
|
case req.ReqArray:
|
|
typ = reflect.TypeOf([]any{})
|
|
case req.ReqJson:
|
|
typ = reflect.TypeOf(map[string]any{})
|
|
}
|
|
|
|
return reflect.StructField{
|
|
Name: strings.ToUpper(fCode[:1]) + fCode[1:],
|
|
Tag: reflect.StructTag(fTag),
|
|
Type: typ,
|
|
}
|
|
}
|
|
|
|
// NewQueryField 创建并返回一个新的查询字段实例。
|
|
// 参数 rField 是资源字段,t 是字段类型,opts 是可选的配置选项。
|
|
func NewQueryField(rField req.ResField, t req.RouteParamType, opts ...QueryFieldOption) req.QueryField {
|
|
qf := &QueryField{
|
|
ResField: rField,
|
|
typ: t,
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
opt(qf)
|
|
}
|
|
|
|
return qf
|
|
}
|