Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
X
xpkg
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
屠思豪
xpkg
Commits
0963312f
提交
0963312f
authored
12月 11, 2025
作者:
mooncake
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
sort 排序问题
上级
8a7e23c9
显示空白字符变更
内嵌
并排
正在显示
4 个修改的文件
包含
253 行增加
和
89 行删除
+253
-89
readme.md
rpc/readme.md
+14
-0
rpc.go
rpc/rpc.go
+1
-1
gormutils.go
xutils/gormutils/gormutils.go
+162
-84
gormutils_test.go
xutils/gormutils/gormutils_test.go
+76
-4
没有找到文件。
rpc/readme.md
浏览文件 @
0963312f
# rpc
调用局域网内其他服务
目前仅实现http协议
已实现
-
重试机制
-
打印接口信息、耗时记录
TODO
-
支持grpc
-
支持加解密
-
支持appID appSecret
\ No newline at end of file
rpc/rpc.go
浏览文件 @
0963312f
...
...
@@ -16,4 +16,4 @@ const (
API_EXPORT_CSV
=
"exportCSV"
)
var
ErrApiNotExist
=
errors
.
New
(
"theapi not exist"
)
var
ErrApiNotExist
=
errors
.
New
(
"the
api not exist"
)
xutils/gormutils/gormutils.go
浏览文件 @
0963312f
...
...
@@ -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
:=
&
G
ormCondition
{}
func
makeScopes
(
db
*
gorm
.
DB
,
where
,
resultType
any
)
*
gorm
.
DB
{
condition
:=
&
g
ormCondition
{}
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
G
ormCondition
struct
{
type
g
ormCondition
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
*
G
ormCondition
)
SetJoinOn
(
t
,
on
string
)
Condition
{
func
(
e
*
g
ormCondition
)
SetJoinOn
(
t
,
on
string
)
Condition
{
return
nil
}
func
(
e
*
G
ormCondition
)
SetWhere
(
k
string
,
v
[]
any
)
{
func
(
e
*
g
ormCondition
)
SetWhere
(
k
string
,
v
[]
any
)
{
if
e
.
Where
==
nil
{
e
.
Where
=
make
(
map
[
string
][]
any
)
}
e
.
Where
[
k
]
=
v
}
func
(
e
*
G
ormCondition
)
SetOr
(
k
string
,
v
[]
any
)
{
func
(
e
*
g
ormCondition
)
SetOr
(
k
string
,
v
[]
any
)
{
if
e
.
Or
==
nil
{
e
.
Or
=
make
(
map
[
string
][]
any
)
}
e
.
Or
[
k
]
=
v
}
func
(
e
*
G
ormCondition
)
SetOrder
(
k
string
)
{
func
(
e
*
g
ormCondition
)
SetOrder
(
k
string
)
{
if
e
.
Order
==
nil
{
e
.
Order
=
make
([]
string
,
0
)
}
e
.
Order
=
append
(
e
.
Order
,
k
)
}
type
J
oinCondition
struct
{
type
j
oinCondition
struct
{
Type
string
JoinOn
string
G
ormCondition
g
ormCondition
}
//
R
esolveSearchQuery 解析
//
r
esolveSearchQuery 解析
/**
* 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
:=
Query
Tag
(
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
Query
Tag
string
type
Tag
string
const
(
EQ
Query
Tag
=
"eq"
LIKE
Query
Tag
=
"like"
ILIKE
Query
Tag
=
"ilike"
LEFT
Query
Tag
=
"left"
ILEFT
Query
Tag
=
"ileft"
RIGHT
Query
Tag
=
"right"
IRIGHT
Query
Tag
=
"iright"
GT
Query
Tag
=
"gt"
GTE
Query
Tag
=
"gte"
LT
Query
Tag
=
"lt"
LTE
Query
Tag
=
"lte"
IN
Query
Tag
=
"in"
ISNULL
Query
Tag
=
"isnull"
ISNOTNULL
Query
Tag
=
"isnotnull"
ORDER
Query
Tag
=
"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
}
columns
:=
strings
.
Split
(
order
,
","
)
orderColumns
:=
make
([]
string
,
0
,
len
(
columns
))
for
_
,
column
:=
range
columns
{
column
=
strings
.
TrimSpace
(
column
)
if
len
(
column
)
==
0
{
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
}
order
:=
"asc"
if
column
[
:
1
]
==
"-"
{
order
=
"desc"
column
=
column
[
1
:
]
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
}
orderColumns
=
append
(
orderColumns
,
column
+
" "
+
order
)
if
isDesc
{
result
=
append
(
result
,
mappedValue
+
" desc"
)
}
else
{
result
=
append
(
result
,
mappedValue
+
" asc"
)
}
if
len
(
orderColumns
)
==
0
{
return
""
,
false
}
return
strings
.
Join
(
orderColumns
,
","
),
true
return
strings
.
Join
(
result
,
","
)
}
func
detectSQLInjection
(
input
string
)
bool
{
return
detectSQLInjectionRe
.
MatchString
(
input
)
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
var
(
orderReg
=
regexp
.
MustCompile
(
`order\[([^\]]+)\]=([^=]+)`
)
detectSQLInjectionRe
=
regexp
.
MustCompile
(
`['";]+|UNION|SELECT|INSERT|UPDATE|DELETE|DROP|GRANT|EXEC|CREATE|ALTER|TRUNCATE|COUNT|\*|--|\/\*|;|\+|\/`
)
)
if
t
.
Kind
()
==
reflect
.
Ptr
{
t
=
t
.
Elem
()
}
if
t
.
Kind
()
!=
reflect
.
Struct
{
return
}
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
}
if
sortValue
,
ok
:=
parseGQTag
(
gqTag
);
ok
{
result
[
currentKey
]
=
sortValue
}
}
}
func
parseGQTag
(
gqTag
string
)
(
string
,
bool
)
{
if
gqTag
==
"-"
{
return
""
,
false
}
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
}
xutils/gormutils/gormutils_test.go
浏览文件 @
0963312f
...
...
@@ -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
(
"
\n
Expected Mapping:"
)
for
key
,
value
:=
range
expected
{
fmt
.
Printf
(
" %s: %s
\n
"
,
key
,
value
)
}
if
len
(
mapping
)
!=
len
(
expected
)
{
fmt
.
Println
(
"
\n
Error: Length mismatch!"
)
return
}
for
key
,
expectedValue
:=
range
expected
{
if
actualValue
,
ok
:=
mapping
[
key
];
!
ok
||
actualValue
!=
expectedValue
{
fmt
.
Printf
(
"
\n
Error: Mismatch for key %s: expected %s, got %s
\n
"
,
key
,
expectedValue
,
actualValue
)
return
}
}
fmt
.
Println
(
"
\n
Success: All mappings are correct!"
)
}
func
Test_sort2
(
t
*
testing
.
T
)
{
user
:=
EnhancedUser
{}
mapping
:=
gormutils
.
GetSortMapping
(
user
)
fmt
.
Println
(
"
\n
Nested 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
(
"
\n
Nested 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
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论