提交 0963312f authored 作者: mooncake's avatar mooncake

sort 排序问题

上级 8a7e23c9
# rpc
调用局域网内其他服务
目前仅实现http协议
已实现
- 重试机制
- 打印接口信息、耗时记录
TODO
- 支持grpc
- 支持加解密
- 支持appID appSecret
\ No newline at end of file
......@@ -16,4 +16,4 @@ const (
API_EXPORT_CSV = "exportCSV"
)
var ErrApiNotExist = errors.New("theapi not exist")
var ErrApiNotExist = errors.New("the api not exist")
......@@ -3,7 +3,6 @@ package gormutils
import (
"fmt"
"reflect"
"regexp"
"strings"
"gorm.io/gorm"
......@@ -14,16 +13,16 @@ const (
Tag2 = "query"
)
func MakeScopes(where any) func(db *gorm.DB) *gorm.DB {
func MakeScopes(where, resultType any) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return makeScopes(db, where)
return makeScopes(db, where, resultType)
}
}
func makeScopes(db *gorm.DB, where any) *gorm.DB {
condition := &GormCondition{}
func makeScopes(db *gorm.DB, where, resultType any) *gorm.DB {
condition := &gormCondition{}
ResolveSearchQuery(where, condition)
resolveSearchQuery(where, condition, resultType)
for k, v := range condition.Where {
db = db.Where(k, v...)
......@@ -38,7 +37,7 @@ func makeScopes(db *gorm.DB, where any) *gorm.DB {
return db
}
type GormCondition struct {
type gormCondition struct {
Where map[string][]any
Order []string
Or map[string][]any
......@@ -51,38 +50,38 @@ type Condition interface {
SetJoinOn(t, on string) Condition
}
func (e *GormCondition) SetJoinOn(t, on string) Condition {
func (e *gormCondition) SetJoinOn(t, on string) Condition {
return nil
}
func (e *GormCondition) SetWhere(k string, v []any) {
func (e *gormCondition) SetWhere(k string, v []any) {
if e.Where == nil {
e.Where = make(map[string][]any)
}
e.Where[k] = v
}
func (e *GormCondition) SetOr(k string, v []any) {
func (e *gormCondition) SetOr(k string, v []any) {
if e.Or == nil {
e.Or = make(map[string][]any)
}
e.Or[k] = v
}
func (e *GormCondition) SetOrder(k string) {
func (e *gormCondition) SetOrder(k string) {
if e.Order == nil {
e.Order = make([]string, 0)
}
e.Order = append(e.Order, k)
}
type JoinCondition struct {
type joinCondition struct {
Type string
JoinOn string
GormCondition
gormCondition
}
// ResolveSearchQuery 解析
// resolveSearchQuery 解析
/**
* eq 等于(默认不填都可以)
* like 包含
......@@ -94,7 +93,7 @@ type JoinCondition struct {
* isnull
* order 排序 e.g. order[key]=desc order[key]=asc
*/
func ResolveSearchQuery(q any, condition Condition) {
func resolveSearchQuery(q any, condition Condition, resultType any) {
qType := reflect.TypeOf(q)
qValue := reflect.ValueOf(q)
......@@ -122,7 +121,7 @@ func ResolveSearchQuery(q any, condition Condition) {
if !ok {
tag, ok = field.Tag.Lookup(Tag2)
if !ok {
ResolveSearchQuery(fieldValue.Interface(), condition)
resolveSearchQuery(fieldValue.Interface(), condition, resultType)
continue
}
}
......@@ -165,17 +164,16 @@ func ResolveSearchQuery(q any, condition Condition) {
t.Table = tname
}
//解析 Postgres `语法不支持,单独适配
otherSql(t, condition, qValue, i)
mysql(t, condition, qValue, i, resultType)
}
}
func otherSql(t *resolveSearchTag, condition Condition, qValue reflect.Value, i int) {
func mysql(t *resolveSearchTag, condition Condition, qValue reflect.Value, i int, resultType any) {
if t.Type == "" {
condition.SetWhere(fmt.Sprintf("`%s`.`%s` = ?", t.Table, t.Column), []any{qValue.Field(i).Interface()})
return
}
qtag := QueryTag(t.Type)
qtag := Tag(t.Type)
switch qtag {
case EQ:
condition.SetWhere(fmt.Sprintf("`%s`.`%s` = ?", t.Table, t.Column), []any{qValue.Field(i).Interface()})
......@@ -215,27 +213,9 @@ func otherSql(t *resolveSearchTag, condition Condition, qValue reflect.Value, i
}
return
case ORDER:
val := strings.TrimSpace(qValue.Field(i).String())
if val != "" {
order, success := parseOrder(val)
if success {
if !strings.Contains(order, ",") && !strings.Contains(order, ".") {
condition.SetOrder(fmt.Sprintf("%s.%s", t.Table, order))
} else {
condition.SetOrder(order)
}
} else {
switch strings.ToLower(qValue.Field(i).String()) {
case "desc", "asc":
condition.SetOrder(fmt.Sprintf("`%s`.`%s` %s", t.Table, t.Column, qValue.Field(i).String()))
}
}
} else {
switch strings.ToLower(qValue.Field(i).String()) {
case "desc", "asc":
condition.SetOrder(fmt.Sprintf("`%s`.`%s` %s", t.Table, t.Column, qValue.Field(i).String()))
}
}
orderStr := qValue.Field(i).String()
sortStr := Sort(orderStr, resultType)
condition.SetOrder(sortStr)
return
default:
condition.SetWhere(fmt.Sprintf("`%s`.`%s` = ?", t.Table, t.Column), []any{qValue.Field(i).Interface()})
......@@ -286,24 +266,24 @@ func parseTag(tag string) *resolveSearchTag {
return r
}
type QueryTag string
type Tag string
const (
EQ QueryTag = "eq"
LIKE QueryTag = "like"
ILIKE QueryTag = "ilike"
LEFT QueryTag = "left"
ILEFT QueryTag = "ileft"
RIGHT QueryTag = "right"
IRIGHT QueryTag = "iright"
GT QueryTag = "gt"
GTE QueryTag = "gte"
LT QueryTag = "lt"
LTE QueryTag = "lte"
IN QueryTag = "in"
ISNULL QueryTag = "isnull"
ISNOTNULL QueryTag = "isnotnull"
ORDER QueryTag = "order"
EQ Tag = "eq"
LIKE Tag = "like"
ILIKE Tag = "ilike"
LEFT Tag = "left"
ILEFT Tag = "ileft"
RIGHT Tag = "right"
IRIGHT Tag = "iright"
GT Tag = "gt"
GTE Tag = "gte"
LT Tag = "lt"
LTE Tag = "lte"
IN Tag = "in"
ISNULL Tag = "isnull"
ISNOTNULL Tag = "isnotnull"
ORDER Tag = "order"
)
/**
......@@ -314,9 +294,6 @@ func snakeCase(s string, allMode bool) string {
data := make([]byte, 0, num*2)
for i := 0; i < num; i++ {
d := s[i]
// or通过ASCII码进行大小写的转化
// 65-90(A-Z),97-122(a-z)
//判断如果字母为大写的A-Z就在前面拼接一个_
if d >= 'A' && d <= 'Z' {
if i > 0 {
if allMode {
......@@ -335,38 +312,139 @@ func snakeCase(s string, allMode bool) string {
data = append(data, d)
}
}
//ToLower把大写字母统一转小写
return string(data[:])
}
func parseOrder(order string) (string, bool) {
if detectSQLInjection(order) {
return "", false
func GetSortMapping(v any) map[string]string {
return GetFieldMappingV2(v)
}
func ParseSortStringExplicitWithSpace(a string, b map[string]string) string {
var result []string
parts := strings.Split(a, ",")
for _, part := range parts {
part = strings.TrimSpace(part)
if part == "" {
continue
}
isDesc := strings.HasPrefix(part, "-")
fieldName := strings.TrimPrefix(part, "-")
fieldName = strings.TrimSpace(fieldName)
if strings.Contains(fieldName, " ") {
fieldName = strings.Fields(fieldName)[0]
}
mappedValue, ok := b[fieldName]
if !ok {
mappedValue = fieldName
}
if isDesc {
result = append(result, mappedValue+" desc")
} else {
result = append(result, mappedValue+" asc")
}
}
return strings.Join(result, ",")
}
func Sort(sort string, resultType any) string {
m := GetFieldMappingV2(resultType)
return ParseSortStringExplicitWithSpace(sort, m)
}
func GetFieldMappingV2(v interface{}) map[string]string {
result := make(map[string]string)
visited := make(map[reflect.Type]bool)
t := reflect.TypeOf(v)
extractFieldMappingsV2(t, result, "", visited)
return result
}
func extractFieldMappingsV2(t reflect.Type, result map[string]string, prefix string, visited map[reflect.Type]bool) {
if visited[t] {
return
}
visited[t] = true
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.Kind() != reflect.Struct {
return
}
columns := strings.Split(order, ",")
orderColumns := make([]string, 0, len(columns))
for _, column := range columns {
column = strings.TrimSpace(column)
if len(column) == 0 {
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fieldType := field.Type
jsonTag := field.Tag.Get("json")
if field.Anonymous {
if fieldType.Kind() == reflect.Ptr {
fieldType = fieldType.Elem()
}
extractFieldMappingsV2(fieldType, result, prefix, visited)
continue
}
if jsonTag == "" || jsonTag == "-" {
continue
}
currentKey := jsonTag
if prefix != "" {
currentKey = prefix + "." + jsonTag
}
gqTag := field.Tag.Get(Tag1)
if gqTag == "" {
gqTag = field.Tag.Get(Tag2)
}
if fieldType.Kind() == reflect.Struct || (fieldType.Kind() == reflect.Ptr && fieldType.Elem().Kind() == reflect.Struct) {
shouldProcessNested := true
if gqTag != "" && gqTag != "-" {
if sortValue, ok := parseGQTag(gqTag); ok {
result[currentKey] = sortValue
shouldProcessNested = false
}
}
if shouldProcessNested && gqTag != "-" {
extractFieldMappingsV2(field.Type, result, currentKey, visited)
}
continue
}
if gqTag == "-" {
continue
}
order := "asc"
if column[:1] == "-" {
order = "desc"
column = column[1:]
if sortValue, ok := parseGQTag(gqTag); ok {
result[currentKey] = sortValue
}
orderColumns = append(orderColumns, column+" "+order)
}
if len(orderColumns) == 0 {
}
func parseGQTag(gqTag string) (string, bool) {
if gqTag == "-" {
return "", false
}
return strings.Join(orderColumns, ","), true
}
func detectSQLInjection(input string) bool {
return detectSQLInjectionRe.MatchString(input)
}
var (
orderReg = regexp.MustCompile(`order\[([^\]]+)\]=([^=]+)`)
detectSQLInjectionRe = regexp.MustCompile(`['";]+|UNION|SELECT|INSERT|UPDATE|DELETE|DROP|GRANT|EXEC|CREATE|ALTER|TRUNCATE|COUNT|\*|--|\/\*|;|\+|\/`)
)
sortPrefix := "sort:"
sortIndex := strings.Index(gqTag, sortPrefix)
if sortIndex == -1 {
return "", false
}
sortPart := gqTag[sortIndex+len(sortPrefix):]
semicolonIndex := strings.Index(sortPart, ";")
var sortValue string
if semicolonIndex != -1 {
sortValue = sortPart[:semicolonIndex]
} else {
sortValue = sortPart
}
return strings.TrimSpace(sortValue), true
}
......@@ -24,7 +24,8 @@ func TestMakeWhere(t *testing.T) {
in.IDs = []xsf.ID{10, 2, 3}
in.UserID = 10
in.SiteName = "优盟"
scopes := gormutils.MakeScopes(in)
in.Sort = "-id,siteID,-siteName"
scopes := gormutils.MakeScopes(in, User{})
conn := getConn()
var rs []*User
if e := conn.WithContext(context.TODO()).
......@@ -72,7 +73,78 @@ type PageReq2 struct {
}
type User struct {
ID xsf.ID `json:"id" gorm:"id"`
SiteID xsf.ID `json:"siteID" gorm:"site_id"`
SiteName string `json:"siteName" gorm:"site_name"`
ID xsf.ID `json:"id" gorm:"id" gq:"sort:op.id"`
SiteID xsf.ID `json:"siteID" gorm:"site_id" gq:"sort:s.id;table:s;column:id"`
SiteName string `json:"siteName" gorm:"site_name" gq:"sort:s.name;table:s;column:name"`
}
func Test_sort(t *testing.T) {
user := User{}
mapping := gormutils.GetSortMapping(user)
fmt.Println("Field Mapping:")
for key, value := range mapping {
fmt.Printf(" %s: %s\n", key, value)
}
expected := map[string]string{
"id": "u.id",
"siteID": "s.id",
"siteName": "s.name",
}
fmt.Println("\nExpected Mapping:")
for key, value := range expected {
fmt.Printf(" %s: %s\n", key, value)
}
if len(mapping) != len(expected) {
fmt.Println("\nError: Length mismatch!")
return
}
for key, expectedValue := range expected {
if actualValue, ok := mapping[key]; !ok || actualValue != expectedValue {
fmt.Printf("\nError: Mismatch for key %s: expected %s, got %s\n",
key, expectedValue, actualValue)
return
}
}
fmt.Println("\nSuccess: All mappings are correct!")
}
func Test_sort2(t *testing.T) {
user := EnhancedUser{}
mapping := gormutils.GetSortMapping(user)
fmt.Println("\nNested Struct Mapping:")
for key, value := range mapping {
fmt.Printf(" %s: %s\n", key, value)
}
}
func Test_sort3(t *testing.T) {
user := EnhancedUser{}
mapping := gormutils.GetSortMapping(user)
fmt.Println("\nNested Struct Mapping:")
for key, value := range mapping {
fmt.Printf(" %s: %s\n", key, value)
}
sort := gormutils.ParseSortStringExplicitWithSpace("-address.city,-siteID,simple", mapping)
fmt.Println(sort)
}
func Test_sort4(t *testing.T) {
sort := gormutils.Sort("-address.city,-siteID,simple,xid", EnhancedUser{})
fmt.Println(sort)
}
// 嵌套结构体测试
type Address struct {
City string `json:"city" gq:"sort:a.city"`
Country string `json:"country" gq:"sort:a.country"`
}
type EnhancedUser struct {
User
Address Address `json:"address"`
AddrPtr *Address `json:"addrPtr"`
Simple string `json:"simple" gq:"sort:u.simple"`
Ignored string `json:"ignored" gq:"-"`
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论