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 }