Compare commits
66 Commits
gorm/v1.1.
...
query_pars
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bed43b3576 | ||
|
|
b9665d4f9b | ||
|
|
1d53d3af4c | ||
|
|
a502539417 | ||
|
|
0cd2eb95f2 | ||
|
|
2e38fe77d7 | ||
|
|
55c80024c2 | ||
|
|
de36ab695d | ||
|
|
a1b8326783 | ||
|
|
38f3cb8a3b | ||
|
|
be5aa063df | ||
|
|
3ca4745bac | ||
|
|
83c2ec5048 | ||
|
|
f48c7373d7 | ||
|
|
41c8b6d9dd | ||
|
|
b2efe51e40 | ||
|
|
9c450415a2 | ||
|
|
99c8f6e4c1 | ||
|
|
3f713e489b | ||
|
|
ace6bd4237 | ||
|
|
3ec9bbbd27 | ||
|
|
faa5817857 | ||
|
|
89a69a9d4d | ||
|
|
63789d090d | ||
|
|
06c29c57d2 | ||
|
|
5faa4e06f7 | ||
|
|
5abe88a0d9 | ||
|
|
028dd8dd19 | ||
|
|
50161f8c8a | ||
|
|
e686e7189c | ||
|
|
d1aa8e36ad | ||
|
|
2fb73a0a1f | ||
|
|
c03ac708f0 | ||
|
|
9035e79520 | ||
|
|
3b2678de10 | ||
|
|
982e18a991 | ||
|
|
f2f5388906 | ||
|
|
9eca340c7e | ||
|
|
376746f4db | ||
|
|
efc24c452f | ||
|
|
78cef077e5 | ||
|
|
0420e35a30 | ||
|
|
0b18560901 | ||
|
|
7b29f09e37 | ||
|
|
363a18b1c8 | ||
|
|
e90588b9ca | ||
|
|
713975e7f1 | ||
|
|
2ab920982a | ||
|
|
3153ff149f | ||
|
|
578cf26ee8 | ||
|
|
ef08927a50 | ||
|
|
6d209d3612 | ||
|
|
8f957a7d29 | ||
|
|
c83b10ca22 | ||
|
|
15b6b012c2 | ||
|
|
fdcb900700 | ||
|
|
ce04468424 | ||
|
|
d1418caf84 | ||
|
|
f73f016ec5 | ||
|
|
d549d305ae | ||
|
|
23dcad60a3 | ||
|
|
fa7ae4f876 | ||
|
|
9f6a4eba80 | ||
|
|
801deb98cd | ||
|
|
c9a0909d46 | ||
|
|
9be498db69 |
@@ -3,8 +3,8 @@ module github.com/tx7do/go-utils/bank_card
|
|||||||
go 1.20
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/mattn/go-sqlite3 v1.14.24
|
github.com/mattn/go-sqlite3 v1.14.28
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.10.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
|
||||||
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
|||||||
128
copierutil/converters.go
Normal file
128
copierutil/converters.go
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
package copierutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jinzhu/copier"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
|
"github.com/tx7do/go-utils/timeutil"
|
||||||
|
"github.com/tx7do/go-utils/trans"
|
||||||
|
)
|
||||||
|
|
||||||
|
var TimeToStringConverter = copier.TypeConverter{
|
||||||
|
SrcType: &time.Time{}, // 源类型
|
||||||
|
DstType: trans.Ptr(""), // 目标类型
|
||||||
|
Fn: func(src interface{}) (interface{}, error) {
|
||||||
|
return timeutil.TimeToTimeString(src.(*time.Time)), nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var StringToTimeConverter = copier.TypeConverter{
|
||||||
|
SrcType: trans.Ptr(""),
|
||||||
|
DstType: &time.Time{},
|
||||||
|
Fn: func(src interface{}) (interface{}, error) {
|
||||||
|
return timeutil.StringTimeToTime(src.(*string)), nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var TimeToTimestamppbConverter = copier.TypeConverter{
|
||||||
|
SrcType: &time.Time{},
|
||||||
|
DstType: ×tamppb.Timestamp{},
|
||||||
|
Fn: func(src interface{}) (interface{}, error) {
|
||||||
|
return timeutil.TimeToTimestamppb(src.(*time.Time)), nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var TimestamppbToTimeConverter = copier.TypeConverter{
|
||||||
|
SrcType: ×tamppb.Timestamp{},
|
||||||
|
DstType: &time.Time{},
|
||||||
|
Fn: func(src interface{}) (interface{}, error) {
|
||||||
|
return timeutil.TimestamppbToTime(src.(*timestamppb.Timestamp)), nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TimeToString(tm *time.Time) *string {
|
||||||
|
return timeutil.TimeToString(tm, timeutil.ISO8601)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTimeStringConverterPair() []copier.TypeConverter {
|
||||||
|
srcType := &time.Time{}
|
||||||
|
dstType := trans.Ptr("")
|
||||||
|
|
||||||
|
fromFn := TimeToString
|
||||||
|
toFn := timeutil.StringTimeToTime
|
||||||
|
|
||||||
|
return NewGenericTypeConverterPair(srcType, dstType, fromFn, toFn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTimeTimestamppbConverterPair() []copier.TypeConverter {
|
||||||
|
srcType := &time.Time{}
|
||||||
|
dstType := ×tamppb.Timestamp{}
|
||||||
|
|
||||||
|
fromFn := timeutil.TimeToTimestamppb
|
||||||
|
toFn := timeutil.TimestamppbToTime
|
||||||
|
|
||||||
|
return NewGenericTypeConverterPair(srcType, dstType, fromFn, toFn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTypeConverter(srcType, dstType interface{}, fn func(src interface{}) (interface{}, error)) copier.TypeConverter {
|
||||||
|
return copier.TypeConverter{
|
||||||
|
SrcType: srcType,
|
||||||
|
DstType: dstType,
|
||||||
|
Fn: fn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTypeConverterPair(srcType, dstType interface{}, fromFn, toFn func(src interface{}) (interface{}, error)) []copier.TypeConverter {
|
||||||
|
return []copier.TypeConverter{
|
||||||
|
{
|
||||||
|
SrcType: srcType,
|
||||||
|
DstType: dstType,
|
||||||
|
Fn: fromFn,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SrcType: dstType,
|
||||||
|
DstType: srcType,
|
||||||
|
Fn: toFn,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGenericTypeConverterPair[A interface{}, B interface{}](srcType A, dstType B, fromFn func(src A) B, toFn func(src B) A) []copier.TypeConverter {
|
||||||
|
return []copier.TypeConverter{
|
||||||
|
{
|
||||||
|
SrcType: srcType,
|
||||||
|
DstType: dstType,
|
||||||
|
Fn: func(src interface{}) (interface{}, error) {
|
||||||
|
return fromFn(src.(A)), nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SrcType: dstType,
|
||||||
|
DstType: srcType,
|
||||||
|
Fn: func(src interface{}) (interface{}, error) {
|
||||||
|
return toFn(src.(B)), nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewErrorHandlingGenericTypeConverterPair[A interface{}, B interface{}](srcType A, dstType B, fromFn func(src A) (B, error), toFn func(src B) (A, error)) []copier.TypeConverter {
|
||||||
|
return []copier.TypeConverter{
|
||||||
|
{
|
||||||
|
SrcType: srcType,
|
||||||
|
DstType: dstType,
|
||||||
|
Fn: func(src interface{}) (interface{}, error) {
|
||||||
|
return fromFn(src.(A))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SrcType: dstType,
|
||||||
|
DstType: srcType,
|
||||||
|
Fn: func(src interface{}) (interface{}, error) {
|
||||||
|
return toFn(src.(B))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
153
copierutil/converters_test.go
Normal file
153
copierutil/converters_test.go
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
package copierutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
|
"github.com/tx7do/go-utils/timeutil"
|
||||||
|
"github.com/tx7do/go-utils/trans"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewTypeConverter(t *testing.T) {
|
||||||
|
srcType := &time.Time{}
|
||||||
|
dstType := trans.Ptr("")
|
||||||
|
fn := func(src interface{}) (interface{}, error) {
|
||||||
|
return timeutil.TimeToTimeString(src.(*time.Time)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
converter := NewTypeConverter(srcType, dstType, fn)
|
||||||
|
|
||||||
|
// 验证转换器的类型
|
||||||
|
assert.IsType(t, srcType, converter.SrcType)
|
||||||
|
assert.IsType(t, dstType, converter.DstType)
|
||||||
|
|
||||||
|
// 验证转换器的功能
|
||||||
|
result, err := converter.Fn(&time.Time{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.IsType(t, dstType, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewTypeConverterPair(t *testing.T) {
|
||||||
|
srcType := &time.Time{}
|
||||||
|
dstType := trans.Ptr("")
|
||||||
|
fromFn := func(src interface{}) (interface{}, error) {
|
||||||
|
return timeutil.TimeToTimeString(src.(*time.Time)), nil
|
||||||
|
}
|
||||||
|
toFn := func(src interface{}) (interface{}, error) {
|
||||||
|
return timeutil.StringTimeToTime(src.(*string)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
converters := NewTypeConverterPair(srcType, dstType, fromFn, toFn)
|
||||||
|
assert.Len(t, converters, 2, "expected 2 converters")
|
||||||
|
|
||||||
|
// 验证第一个转换器
|
||||||
|
assert.IsType(t, srcType, converters[0].SrcType)
|
||||||
|
assert.IsType(t, dstType, converters[0].DstType)
|
||||||
|
result, err := converters[0].Fn(&time.Time{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.IsType(t, dstType, result)
|
||||||
|
|
||||||
|
// 验证第二个转换器
|
||||||
|
assert.IsType(t, dstType, converters[1].SrcType)
|
||||||
|
assert.IsType(t, srcType, converters[1].DstType)
|
||||||
|
result, err = converters[1].Fn(trans.Ptr(""))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.IsType(t, srcType, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewGenericTypeConverterPair(t *testing.T) {
|
||||||
|
srcType := &time.Time{}
|
||||||
|
dstType := trans.Ptr("")
|
||||||
|
fromFn := timeutil.TimeToTimeString
|
||||||
|
toFn := timeutil.StringTimeToTime
|
||||||
|
|
||||||
|
converters := NewGenericTypeConverterPair(srcType, dstType, fromFn, toFn)
|
||||||
|
assert.Len(t, converters, 2, "expected 2 converters")
|
||||||
|
|
||||||
|
// 验证第一个转换器
|
||||||
|
assert.IsType(t, srcType, converters[0].SrcType)
|
||||||
|
assert.IsType(t, dstType, converters[0].DstType)
|
||||||
|
result, err := converters[0].Fn(&time.Time{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.IsType(t, dstType, result)
|
||||||
|
|
||||||
|
// 验证第二个转换器
|
||||||
|
assert.IsType(t, dstType, converters[1].SrcType)
|
||||||
|
assert.IsType(t, srcType, converters[1].DstType)
|
||||||
|
result, err = converters[1].Fn(trans.Ptr(""))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.IsType(t, srcType, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewErrorHandlingGenericTypeConverterPair(t *testing.T) {
|
||||||
|
srcType := &time.Time{}
|
||||||
|
dstType := trans.Ptr("")
|
||||||
|
fromFn := func(src *time.Time) (*string, error) {
|
||||||
|
return timeutil.TimeToTimeString(src), nil
|
||||||
|
}
|
||||||
|
toFn := func(src *string) (*time.Time, error) {
|
||||||
|
return timeutil.StringTimeToTime(src), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
converters := NewErrorHandlingGenericTypeConverterPair(srcType, dstType, fromFn, toFn)
|
||||||
|
assert.Len(t, converters, 2, "expected 2 converters")
|
||||||
|
|
||||||
|
// 验证第一个转换器
|
||||||
|
assert.IsType(t, srcType, converters[0].SrcType)
|
||||||
|
assert.IsType(t, dstType, converters[0].DstType)
|
||||||
|
result, err := converters[0].Fn(&time.Time{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.IsType(t, dstType, result)
|
||||||
|
|
||||||
|
// 验证第二个转换器
|
||||||
|
assert.IsType(t, dstType, converters[1].SrcType)
|
||||||
|
assert.IsType(t, srcType, converters[1].DstType)
|
||||||
|
result, err = converters[1].Fn(trans.Ptr(""))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.IsType(t, srcType, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewTimeStringConverterPair(t *testing.T) {
|
||||||
|
converters := NewTimeStringConverterPair()
|
||||||
|
assert.Len(t, converters, 2, "expected 2 converters")
|
||||||
|
|
||||||
|
// 验证第一个转换器
|
||||||
|
srcType := &time.Time{}
|
||||||
|
dstType := trans.Ptr("")
|
||||||
|
assert.IsType(t, srcType, converters[0].SrcType)
|
||||||
|
assert.IsType(t, dstType, converters[0].DstType)
|
||||||
|
result, err := converters[0].Fn(&time.Time{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.IsType(t, dstType, result)
|
||||||
|
|
||||||
|
// 验证第二个转换器
|
||||||
|
assert.IsType(t, dstType, converters[1].SrcType)
|
||||||
|
assert.IsType(t, srcType, converters[1].DstType)
|
||||||
|
result, err = converters[1].Fn(trans.Ptr(""))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.IsType(t, srcType, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewTimeTimestamppbConverterPair(t *testing.T) {
|
||||||
|
converters := NewTimeTimestamppbConverterPair()
|
||||||
|
assert.Len(t, converters, 2, "expected 2 converters")
|
||||||
|
|
||||||
|
// 验证第一个转换器
|
||||||
|
srcType := &time.Time{}
|
||||||
|
dstType := ×tamppb.Timestamp{}
|
||||||
|
assert.IsType(t, srcType, converters[0].SrcType)
|
||||||
|
assert.IsType(t, dstType, converters[0].DstType)
|
||||||
|
result, err := converters[0].Fn(&time.Time{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.IsType(t, dstType, result)
|
||||||
|
|
||||||
|
// 验证第二个转换器
|
||||||
|
assert.IsType(t, dstType, converters[1].SrcType)
|
||||||
|
assert.IsType(t, srcType, converters[1].DstType)
|
||||||
|
result, err = converters[1].Fn(×tamppb.Timestamp{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.IsType(t, srcType, result)
|
||||||
|
}
|
||||||
24
copierutil/go.mod
Normal file
24
copierutil/go.mod
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
module github.com/tx7do/go-utils/copierutil
|
||||||
|
|
||||||
|
go 1.23.0
|
||||||
|
|
||||||
|
toolchain go1.24.3
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/jinzhu/copier v0.4.0
|
||||||
|
github.com/tx7do/go-utils v1.1.27
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/stretchr/testify v1.10.0
|
||||||
|
google.golang.org/protobuf v1.36.6
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/kr/text v0.2.0 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
replace github.com/tx7do/go-utils => ../
|
||||||
26
copierutil/go.sum
Normal file
26
copierutil/go.sum
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||||
|
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
|
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||||
|
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
21
crypto/README.md
Normal file
21
crypto/README.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# 加解密算法
|
||||||
|
|
||||||
|
## bcrypt
|
||||||
|
|
||||||
|
bcrypt是一个由美国计算机科学家尼尔斯·普罗沃斯(Niels Provos)以及大卫·马齐耶(David Mazières)根据Blowfish加密算法所设计的密码散列函数,于1999年在USENIX中展示[1]。实现中bcrypt会使用一个加盐的流程以防御彩虹表攻击,同时bcrypt还是适应性函数,它可以借由增加迭代之次数来抵御日益增进的电脑运算能力透过暴力法破解。
|
||||||
|
|
||||||
|
由bcrypt加密的文件可在所有支持的操作系统和处理器上进行转移。它的口令必须是8至56个字符,并将在内部被转化为448位的密钥。然而,所提供的所有字符都具有十分重要的意义。密码越强大,数据就越安全。
|
||||||
|
|
||||||
|
除了对数据进行加密,默认情况下,bcrypt在删除数据之前将使用随机数据三次覆盖原始输入文件,以阻挠可能会获得计算机数据的人恢复数据的尝试。如果您不想使用此功能,可设置禁用此功能。
|
||||||
|
|
||||||
|
具体来说,bcrypt使用美国密码学家保罗·柯切尔的算法实现。随bcrypt一起发布的源代码对原始版本作了略微改动。
|
||||||
|
|
||||||
|
bcrypt哈希由多个部分组成。这些部分用于确定创建哈希的设置,从而可以在不需要任何其他信息的情况下对其进行验证。
|
||||||
|
|
||||||
|
```text
|
||||||
|
$2a$10$ygWrRwHCzg2GUpz0UK40kuWAGva121VkScpcdMNsDCih2U/bL2qYy
|
||||||
|
```
|
||||||
|
|
||||||
|
- $2a$:Prefix 表示使用bcrypt的算法版本。
|
||||||
|
- 10$:Cost factor 表示加密的复杂度,值越大,计算时间越长。
|
||||||
|
- ygWrRwHCzg2GUpz0UK40kuWAGva121VkScpcdMNsDCih2U/bL2qYy:Salt 和 Hash,前22个字符是盐值,后面的字符是哈希值。
|
||||||
100
crypto/aes.go
Normal file
100
crypto/aes.go
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultAESKey 默认AES密钥(16字节)
|
||||||
|
var DefaultAESKey = []byte("f51d66a73d8a0927")
|
||||||
|
|
||||||
|
// GenerateAESKey 生成AES密钥
|
||||||
|
func GenerateAESKey(length int) ([]byte, error) {
|
||||||
|
if length != 16 && length != 24 && length != 32 {
|
||||||
|
return nil, fmt.Errorf("invalid key length: %d, must be 16, 24, or 32 bytes", length)
|
||||||
|
}
|
||||||
|
key := make([]byte, length)
|
||||||
|
_, err := rand.Read(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PKCS5Padding 填充明文
|
||||||
|
func PKCS5Padding(plaintext []byte, blockSize int) []byte {
|
||||||
|
padding := blockSize - len(plaintext)%blockSize
|
||||||
|
padText := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||||
|
return append(plaintext, padText...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PKCS5UnPadding 去除填充数据
|
||||||
|
func PKCS5UnPadding(origData []byte) []byte {
|
||||||
|
length := len(origData)
|
||||||
|
unpadding := int(origData[length-1])
|
||||||
|
return origData[:(length - unpadding)]
|
||||||
|
}
|
||||||
|
|
||||||
|
// AesEncrypt AES加密
|
||||||
|
func AesEncrypt(plainText, key, iv []byte) ([]byte, error) {
|
||||||
|
if plainText == nil {
|
||||||
|
return nil, fmt.Errorf("plain text is nil")
|
||||||
|
}
|
||||||
|
if key == nil {
|
||||||
|
return nil, fmt.Errorf("key is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
block, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// AES分组长度为128位,所以blockSize=16,单位字节
|
||||||
|
blockSize := block.BlockSize()
|
||||||
|
|
||||||
|
if iv == nil {
|
||||||
|
// 初始向量的长度必须等于块block的长度16字节
|
||||||
|
iv = key[:blockSize]
|
||||||
|
}
|
||||||
|
|
||||||
|
plainText = PKCS5Padding(plainText, blockSize)
|
||||||
|
|
||||||
|
blockMode := cipher.NewCBCEncrypter(block, iv)
|
||||||
|
cryptedText := make([]byte, len(plainText))
|
||||||
|
blockMode.CryptBlocks(cryptedText, plainText)
|
||||||
|
return cryptedText, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AesDecrypt AES解密
|
||||||
|
func AesDecrypt(cryptedText, key, iv []byte) ([]byte, error) {
|
||||||
|
if cryptedText == nil {
|
||||||
|
return nil, fmt.Errorf("crypted text is nil")
|
||||||
|
}
|
||||||
|
if key == nil {
|
||||||
|
return nil, fmt.Errorf("key is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
block, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//AES分组长度为128位,所以blockSize=16,单位字节
|
||||||
|
blockSize := block.BlockSize()
|
||||||
|
|
||||||
|
if iv == nil {
|
||||||
|
// 初始向量的长度必须等于块block的长度16字节
|
||||||
|
iv = key[:blockSize]
|
||||||
|
}
|
||||||
|
|
||||||
|
blockMode := cipher.NewCBCDecrypter(block, iv)
|
||||||
|
|
||||||
|
plainText := make([]byte, len(cryptedText))
|
||||||
|
blockMode.CryptBlocks(plainText, cryptedText)
|
||||||
|
plainText = PKCS5UnPadding(plainText)
|
||||||
|
return plainText, nil
|
||||||
|
}
|
||||||
50
crypto/aes_test.go
Normal file
50
crypto/aes_test.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"encoding/base64"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDecryptAES(t *testing.T) {
|
||||||
|
//key的长度必须是16、24或者32字节,分别用于选择AES-128, AES-192, or AES-256
|
||||||
|
aesKey, _ := GenerateAESKey(16)
|
||||||
|
aesKey = DefaultAESKey
|
||||||
|
|
||||||
|
plainText := []byte("cloud123456")
|
||||||
|
encryptText, err := AesEncrypt(plainText, aesKey, nil)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pass64 := base64.StdEncoding.EncodeToString(encryptText)
|
||||||
|
fmt.Printf("加密后:%v\n", pass64)
|
||||||
|
|
||||||
|
bytesPass, err := base64.StdEncoding.DecodeString(pass64)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
decryptText, err := AesDecrypt(bytesPass, aesKey, nil)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("解密后:%s\n", decryptText)
|
||||||
|
assert.Equal(t, plainText, decryptText)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateAESKey_ValidLengths(t *testing.T) {
|
||||||
|
lengths := []int{16, 24, 32}
|
||||||
|
for _, length := range lengths {
|
||||||
|
key, err := GenerateAESKey(length)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, length, len(key))
|
||||||
|
t.Logf("%d : %x\n", length, string(key))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,56 @@
|
|||||||
package crypto
|
package crypto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
|
||||||
|
"encoding/hex"
|
||||||
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DefaultCost 最小值=4 最大值=31 默认值=10
|
||||||
|
var DefaultCost = 10
|
||||||
|
|
||||||
// HashPassword 加密密码
|
// HashPassword 加密密码
|
||||||
func HashPassword(password string) (string, error) {
|
func HashPassword(password string) (string, error) {
|
||||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 10)
|
// Prefix + Cost + Salt + Hashed Text
|
||||||
|
bytes, err := bcrypt.GenerateFromPassword([]byte(password), DefaultCost)
|
||||||
return string(bytes), err
|
return string(bytes), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckPasswordHash 校验密码
|
// HashPasswordWithSalt 对密码进行加盐哈希处理
|
||||||
func CheckPasswordHash(password, hash string) bool {
|
func HashPasswordWithSalt(password, salt string) (string, error) {
|
||||||
|
// 将密码和盐组合
|
||||||
|
combined := []byte(password + salt)
|
||||||
|
|
||||||
|
// 计算哈希值
|
||||||
|
hash := sha256.Sum256(combined)
|
||||||
|
|
||||||
|
// 将哈希值转换为十六进制字符串
|
||||||
|
return hex.EncodeToString(hash[:]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyPassword 验证密码是否正确
|
||||||
|
func VerifyPassword(password, hash string) bool {
|
||||||
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VerifyPasswordWithSalt 验证密码是否正确
|
||||||
|
func VerifyPasswordWithSalt(password, salt, hashedPassword string) bool {
|
||||||
|
// 对输入的密码和盐进行哈希处理
|
||||||
|
newHash, _ := HashPasswordWithSalt(password, salt)
|
||||||
|
// 比较哈希值是否相同
|
||||||
|
return newHash == hashedPassword
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateSalt 生成指定长度的盐
|
||||||
|
func GenerateSalt(length int) (string, error) {
|
||||||
|
salt := make([]byte, length)
|
||||||
|
_, err := rand.Read(salt)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return hex.EncodeToString(salt), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ package crypto
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHashPassword(t *testing.T) {
|
func TestHashPassword(t *testing.T) {
|
||||||
@@ -13,16 +14,27 @@ func TestHashPassword(t *testing.T) {
|
|||||||
fmt.Println(hash)
|
fmt.Println(hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckPasswordHash(t *testing.T) {
|
func TestVerifyPassword(t *testing.T) {
|
||||||
text := "123456"
|
text := "123456"
|
||||||
|
|
||||||
|
// Prefix + Cost + Salt + Hashed Text
|
||||||
hash3 := "$2a$10$ygWrRwHCzg2GUpz0UK40kuWAGva121VkScpcdMNsDCih2U/bL2qYy"
|
hash3 := "$2a$10$ygWrRwHCzg2GUpz0UK40kuWAGva121VkScpcdMNsDCih2U/bL2qYy"
|
||||||
bMatched := CheckPasswordHash(text, hash3)
|
bMatched := VerifyPassword(text, hash3)
|
||||||
assert.True(t, bMatched)
|
assert.True(t, bMatched)
|
||||||
|
|
||||||
bMatched = CheckPasswordHash(text, hash3)
|
bMatched = VerifyPassword(text, hash3)
|
||||||
assert.True(t, bMatched)
|
assert.True(t, bMatched)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestVerifyPasswordWithSalt_CorrectPassword(t *testing.T) {
|
||||||
|
password := "securePassword"
|
||||||
|
salt, _ := GenerateSalt(16)
|
||||||
|
hashedPassword, _ := HashPasswordWithSalt(password, salt)
|
||||||
|
|
||||||
|
result := VerifyPasswordWithSalt(password, salt, hashedPassword)
|
||||||
|
assert.True(t, result, "Password verification should succeed with correct password and salt")
|
||||||
|
}
|
||||||
|
|
||||||
func TestJwtToken(t *testing.T) {
|
func TestJwtToken(t *testing.T) {
|
||||||
const bearerWord string = "Bearer"
|
const bearerWord string = "Bearer"
|
||||||
token := "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjowfQ.XgcKAAjHbA6o4sxxbEaMi05ingWvKdCNnyW9wowbJvs"
|
token := "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjowfQ.XgcKAAjHbA6o4sxxbEaMi05ingWvKdCNnyW9wowbJvs"
|
||||||
|
|||||||
@@ -15,16 +15,16 @@ import (
|
|||||||
entSql "entgo.io/ent/dialect/sql"
|
entSql "entgo.io/ent/dialect/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
type entClientInterface interface {
|
type EntClientInterface interface {
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
type EntClient[T entClientInterface] struct {
|
type EntClient[T EntClientInterface] struct {
|
||||||
db T
|
db T
|
||||||
drv *entSql.Driver
|
drv *entSql.Driver
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewEntClient[T entClientInterface](db T, drv *entSql.Driver) *EntClient[T] {
|
func NewEntClient[T EntClientInterface](db T, drv *entSql.Driver) *EntClient[T] {
|
||||||
return &EntClient[T]{
|
return &EntClient[T]{
|
||||||
db: db,
|
db: db,
|
||||||
drv: drv,
|
drv: drv,
|
||||||
|
|||||||
54
entgo/go.mod
54
entgo/go.mod
@@ -1,47 +1,57 @@
|
|||||||
module github.com/tx7do/go-utils/entgo
|
module github.com/tx7do/go-utils/entgo
|
||||||
|
|
||||||
go 1.23.0
|
go 1.24
|
||||||
|
|
||||||
toolchain go1.23.2
|
toolchain go1.24.4
|
||||||
|
|
||||||
|
replace github.com/tx7do/go-utils/id => ../id
|
||||||
|
|
||||||
require (
|
require (
|
||||||
entgo.io/contrib v0.6.0
|
entgo.io/contrib v0.6.0
|
||||||
entgo.io/ent v0.14.1
|
entgo.io/ent v0.14.4
|
||||||
github.com/XSAM/otelsql v0.35.0
|
github.com/XSAM/otelsql v0.39.0
|
||||||
github.com/go-kratos/kratos/v2 v2.8.1
|
github.com/go-kratos/kratos/v2 v2.8.4
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/tx7do/go-utils v1.1.12
|
github.com/tx7do/go-utils v1.1.29
|
||||||
go.opentelemetry.io/otel v1.31.0
|
github.com/tx7do/go-utils/id v0.0.2
|
||||||
|
go.opentelemetry.io/otel v1.36.0
|
||||||
|
google.golang.org/protobuf v1.36.6
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
ariga.io/atlas v0.28.1 // indirect
|
ariga.io/atlas v0.35.0 // indirect
|
||||||
github.com/agext/levenshtein v1.2.3 // indirect
|
github.com/agext/levenshtein v1.2.3 // indirect
|
||||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||||
github.com/bmatcuk/doublestar v1.3.4 // indirect
|
github.com/bmatcuk/doublestar v1.3.4 // indirect
|
||||||
github.com/bufbuild/protocompile v0.14.1 // indirect
|
github.com/bufbuild/protocompile v0.14.1 // indirect
|
||||||
|
github.com/bwmarrin/snowflake v0.3.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/go-logr/logr v1.4.2 // indirect
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-openapi/inflect v0.21.0 // indirect
|
github.com/go-openapi/inflect v0.21.2 // indirect
|
||||||
github.com/golang/protobuf v1.5.4 // indirect
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
github.com/google/go-cmp v0.6.0 // indirect
|
github.com/google/go-cmp v0.7.0 // indirect
|
||||||
github.com/hashicorp/hcl/v2 v2.22.0 // indirect
|
github.com/hashicorp/hcl/v2 v2.23.0 // indirect
|
||||||
github.com/jhump/protoreflect v1.17.0 // indirect
|
github.com/jhump/protoreflect v1.17.0 // indirect
|
||||||
|
github.com/lithammer/shortuuid/v4 v4.2.0 // indirect
|
||||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/sony/sonyflake v1.2.0 // indirect
|
github.com/rs/xid v1.6.0 // indirect
|
||||||
github.com/zclconf/go-cty v1.15.0 // indirect
|
github.com/segmentio/ksuid v1.0.4 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.31.0 // indirect
|
github.com/sony/sonyflake v1.2.1 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.31.0 // indirect
|
github.com/zclconf/go-cty v1.16.3 // indirect
|
||||||
|
github.com/zclconf/go-cty-yaml v1.1.0 // indirect
|
||||||
|
go.mongodb.org/mongo-driver v1.17.4 // indirect
|
||||||
|
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/metric v1.36.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v1.36.0 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
golang.org/x/mod v0.21.0 // indirect
|
golang.org/x/mod v0.25.0 // indirect
|
||||||
golang.org/x/sync v0.8.0 // indirect
|
golang.org/x/sync v0.15.0 // indirect
|
||||||
golang.org/x/text v0.19.0 // indirect
|
golang.org/x/text v0.26.0 // indirect
|
||||||
golang.org/x/tools v0.26.0 // indirect
|
golang.org/x/tools v0.34.0 // indirect
|
||||||
google.golang.org/protobuf v1.35.1 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
110
entgo/go.sum
110
entgo/go.sum
@@ -1,13 +1,13 @@
|
|||||||
ariga.io/atlas v0.28.1 h1:cNE0FYmoYs1u4KF+FGnp2on1srhM6FDpjaCgL7Rd8/c=
|
ariga.io/atlas v0.35.0 h1:tzco6CEZm1/jGD2ifHhKFlsQB7Bfsc/mty4zwm6Mlbc=
|
||||||
ariga.io/atlas v0.28.1/go.mod h1:LOOp18LCL9r+VifvVlJqgYJwYl271rrXD9/wIyzJ8sw=
|
ariga.io/atlas v0.35.0/go.mod h1:9ZAIr/V85596AVxmN8edyVHYKKpnNsDMdnHLsEliW7k=
|
||||||
entgo.io/contrib v0.6.0 h1:xfo4TbJE7sJZWx7BV7YrpSz7IPFvS8MzL3fnfzZjKvQ=
|
entgo.io/contrib v0.6.0 h1:xfo4TbJE7sJZWx7BV7YrpSz7IPFvS8MzL3fnfzZjKvQ=
|
||||||
entgo.io/contrib v0.6.0/go.mod h1:3qWIseJ/9Wx2Hu5zVh15FDzv7d/UvKNcYKdViywWCQg=
|
entgo.io/contrib v0.6.0/go.mod h1:3qWIseJ/9Wx2Hu5zVh15FDzv7d/UvKNcYKdViywWCQg=
|
||||||
entgo.io/ent v0.14.1 h1:fUERL506Pqr92EPHJqr8EYxbPioflJo6PudkrEA8a/s=
|
entgo.io/ent v0.14.4 h1:/DhDraSLXIkBhyiVoJeSshr4ZYi7femzhj6/TckzZuI=
|
||||||
entgo.io/ent v0.14.1/go.mod h1:MH6XLG0KXpkcDQhKiHfANZSzR55TJyPL5IGNpI8wpco=
|
entgo.io/ent v0.14.4/go.mod h1:aDPE/OziPEu8+OWbzy4UlvWmD2/kbRuWfK2A40hcxJM=
|
||||||
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
|
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
|
||||||
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||||
github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4=
|
github.com/XSAM/otelsql v0.39.0 h1:4o374mEIMweaeevL7fd8Q3C710Xi2Jh/c8G4Qy9bvCY=
|
||||||
github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc=
|
github.com/XSAM/otelsql v0.39.0/go.mod h1:uMOXLUX+wkuAuP0AR3B45NXX7E9lJS2mERa8gqdU8R0=
|
||||||
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
|
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
|
||||||
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
|
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
|
||||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
|
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
|
||||||
@@ -16,33 +16,37 @@ github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQ
|
|||||||
github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
|
github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
|
||||||
github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw=
|
github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw=
|
||||||
github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c=
|
github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c=
|
||||||
|
github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0=
|
||||||
|
github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/go-kratos/kratos/v2 v2.8.1 h1:nK+NRp8C+wQk7tr55K9Er7nBjmBLYGbnyNz7UIy37qw=
|
github.com/go-kratos/kratos/v2 v2.8.4 h1:eIJLE9Qq9WSoKx+Buy2uPyrahtF/lPh+Xf4MTpxhmjs=
|
||||||
github.com/go-kratos/kratos/v2 v2.8.1/go.mod h1:+Vfe3FzF0d+BfMdajA11jT0rAyJWublRE/seZQNZVxE=
|
github.com/go-kratos/kratos/v2 v2.8.4/go.mod h1:mq62W2101a5uYyRxe+7IdWubu7gZCGYqSNKwGFiiRcw=
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-openapi/inflect v0.21.0 h1:FoBjBTQEcbg2cJUWX6uwL9OyIW8eqc9k4KhN4lfbeYk=
|
github.com/go-openapi/inflect v0.21.2 h1:0gClGlGcxifcJR56zwvhaOulnNgnhc4qTAkob5ObnSM=
|
||||||
github.com/go-openapi/inflect v0.21.0/go.mod h1:INezMuUu7SJQc2AyR3WO0DqqYUJSj8Kb4hBd7WtjlAw=
|
github.com/go-openapi/inflect v0.21.2/go.mod h1:INezMuUu7SJQc2AyR3WO0DqqYUJSj8Kb4hBd7WtjlAw=
|
||||||
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
|
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
|
||||||
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/hashicorp/hcl/v2 v2.22.0 h1:hkZ3nCtqeJsDhPRFz5EA9iwcG1hNWGePOTw6oyul12M=
|
github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos=
|
||||||
github.com/hashicorp/hcl/v2 v2.22.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
|
github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
|
||||||
github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94=
|
github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94=
|
||||||
github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8=
|
github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/lithammer/shortuuid/v4 v4.2.0 h1:LMFOzVB3996a7b8aBuEXxqOBflbfPQAiVzkIcHO0h8c=
|
||||||
|
github.com/lithammer/shortuuid/v4 v4.2.0/go.mod h1:D5noHZ2oFw/YaKCfGy0YxyE7M0wMbezmMjPdhyEFe6Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
|
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
|
||||||
@@ -51,47 +55,57 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua
|
|||||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
github.com/sony/sonyflake v1.2.0 h1:Pfr3A+ejSg+0SPqpoAmQgEtNDAhc2G1SUYk205qVMLQ=
|
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||||
github.com/sony/sonyflake v1.2.0/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y=
|
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
|
||||||
github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ=
|
github.com/sony/sonyflake v1.2.1 h1:Jzo4abS84qVNbYamXZdrZF1/6TzNJjEogRfXv7TsG48=
|
||||||
github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
|
github.com/sony/sonyflake v1.2.1/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y=
|
||||||
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/zclconf/go-cty v1.16.3 h1:osr++gw2T61A8KVYHoQiFbFd1Lh3JOCXc/jFLJXKTxk=
|
||||||
|
github.com/zclconf/go-cty v1.16.3/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
|
||||||
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
|
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
|
||||||
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
|
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
|
||||||
go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
|
github.com/zclconf/go-cty-yaml v1.1.0 h1:nP+jp0qPHv2IhUVqmQSzjvqAWcObN0KBkUl2rWBdig0=
|
||||||
go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
|
github.com/zclconf/go-cty-yaml v1.1.0/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs=
|
||||||
go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
|
go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw=
|
||||||
go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
|
go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
||||||
go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk=
|
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||||
go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0=
|
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc=
|
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8=
|
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
|
||||||
go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys=
|
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
|
||||||
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
|
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
|
||||||
|
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
|
||||||
|
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
|
||||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||||
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||||
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||||
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
|
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||||
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
|
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
||||||
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos=
|
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU=
|
||||||
google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY=
|
google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY=
|
||||||
google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
|
google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
|
||||||
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||||
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"entgo.io/ent/schema/index"
|
"entgo.io/ent/schema/index"
|
||||||
"entgo.io/ent/schema/mixin"
|
"entgo.io/ent/schema/mixin"
|
||||||
|
|
||||||
"github.com/tx7do/go-utils/sonyflake"
|
"github.com/tx7do/go-utils/id"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SnowflackId struct {
|
type SnowflackId struct {
|
||||||
@@ -18,7 +18,7 @@ func (SnowflackId) Fields() []ent.Field {
|
|||||||
return []ent.Field{
|
return []ent.Field{
|
||||||
field.Uint64("id").
|
field.Uint64("id").
|
||||||
Comment("id").
|
Comment("id").
|
||||||
DefaultFunc(sonyflake.GenerateSonyflake).
|
DefaultFunc(id.GenerateSonyflakeID).
|
||||||
Positive().
|
Positive().
|
||||||
Immutable().
|
Immutable().
|
||||||
StructTag(`json:"id,omitempty"`).
|
StructTag(`json:"id,omitempty"`).
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
| query | `string` | `json object` 或 `json object array` | AND过滤条件 | json字符串: `{"field1":"val1","field2":"val2"}` 或者`[{"field1":"val1"},{"field1":"val2"},{"field2":"val2"}]` | `map`和`array`都支持,当需要同字段名,不同值的情况下,请使用`array`。具体规则请见:[过滤规则](#过滤规则) |
|
| query | `string` | `json object` 或 `json object array` | AND过滤条件 | json字符串: `{"field1":"val1","field2":"val2"}` 或者`[{"field1":"val1"},{"field1":"val2"},{"field2":"val2"}]` | `map`和`array`都支持,当需要同字段名,不同值的情况下,请使用`array`。具体规则请见:[过滤规则](#过滤规则) |
|
||||||
| or | `string` | `json object` 或 `json object array` | OR过滤条件 | 同 AND过滤条件 | |
|
| or | `string` | `json object` 或 `json object array` | OR过滤条件 | 同 AND过滤条件 | |
|
||||||
| orderBy | `string` | `json string array` | 排序条件 | json字符串:`["-create_time", "type"]` | json的`string array`,字段名前加`-`是为降序,不加为升序。具体规则请见:[排序规则](#排序规则) |
|
| orderBy | `string` | `json string array` | 排序条件 | json字符串:`["-create_time", "type"]` | json的`string array`,字段名前加`-`是为降序,不加为升序。具体规则请见:[排序规则](#排序规则) |
|
||||||
| nopaging | `boolean` | | 是否不分页 | | 此字段为`true`时,`page`、`pageSize`字段的传入将无效用。 |
|
| noPaging | `boolean` | | 是否不分页 | | 此字段为`true`时,`page`、`pageSize`字段的传入将无效用。 |
|
||||||
| fieldMask | `string` | `json string array` | 字段掩码 | | 此字段是`SELECT`条件,为空的时候是为`*`。 |
|
| fieldMask | `string` | 其语法为使用逗号分隔字段名 | 字段掩码 | 例如:id,realName,userName。 | 此字段是`SELECT`条件,为空的时候是为`*`。 |
|
||||||
|
|
||||||
## 排序规则
|
## 排序规则
|
||||||
|
|
||||||
@@ -36,6 +36,7 @@
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
{字段名}__{查找类型} : {值}
|
{字段名}__{查找类型} : {值}
|
||||||
|
{字段名}.{JSON字段名}__{查找类型} : {值}
|
||||||
```
|
```
|
||||||
|
|
||||||
| 查找类型 | 示例 | SQL | 备注 |
|
| 查找类型 | 示例 | SQL | 备注 |
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
package entgo
|
package entgo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
"entgo.io/ent/dialect"
|
"entgo.io/ent/dialect"
|
||||||
"entgo.io/ent/dialect/sql"
|
"entgo.io/ent/dialect/sql"
|
||||||
|
|
||||||
@@ -63,16 +64,6 @@ var ops = [...]string{
|
|||||||
FilterSearch: "search",
|
FilterSearch: "search",
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasOperations(str string) bool {
|
|
||||||
str = strings.ToLower(str)
|
|
||||||
for _, item := range ops {
|
|
||||||
if str == item {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
type DatePart int
|
type DatePart int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -109,6 +100,38 @@ var dateParts = [...]string{
|
|||||||
DatePartMicrosecond: "microsecond",
|
DatePartMicrosecond: "microsecond",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
QueryDelimiter = "__" // 分隔符
|
||||||
|
JsonFieldDelimiter = "." // JSONB字段分隔符
|
||||||
|
)
|
||||||
|
|
||||||
|
// splitQueryKey 分割查询键
|
||||||
|
func splitQueryKey(key string) []string {
|
||||||
|
return strings.Split(key, QueryDelimiter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitJsonFieldKey 分割JSON字段键
|
||||||
|
func splitJsonFieldKey(key string) []string {
|
||||||
|
return strings.Split(key, JsonFieldDelimiter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isJsonFieldKey 是否为JSON字段键
|
||||||
|
func isJsonFieldKey(key string) bool {
|
||||||
|
return strings.Contains(key, JsonFieldDelimiter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasOperations 是否有操作
|
||||||
|
func hasOperations(str string) bool {
|
||||||
|
str = strings.ToLower(str)
|
||||||
|
for _, item := range ops {
|
||||||
|
if str == item {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasDatePart 是否有日期部分
|
||||||
func hasDatePart(str string) bool {
|
func hasDatePart(str string) bool {
|
||||||
str = strings.ToLower(str)
|
str = strings.ToLower(str)
|
||||||
for _, item := range dateParts {
|
for _, item := range dateParts {
|
||||||
@@ -119,6 +142,32 @@ func hasDatePart(str string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BuildFilterSelector 构建过滤选择器
|
||||||
|
func BuildFilterSelector(andFilterJsonString, orFilterJsonString string) (error, []func(s *sql.Selector)) {
|
||||||
|
var err error
|
||||||
|
var queryConditions []func(s *sql.Selector)
|
||||||
|
|
||||||
|
var andSelector func(s *sql.Selector)
|
||||||
|
err, andSelector = QueryCommandToWhereConditions(andFilterJsonString, false)
|
||||||
|
if err != nil {
|
||||||
|
return err, nil
|
||||||
|
}
|
||||||
|
if andSelector != nil {
|
||||||
|
queryConditions = append(queryConditions, andSelector)
|
||||||
|
}
|
||||||
|
|
||||||
|
var orSelector func(s *sql.Selector)
|
||||||
|
err, orSelector = QueryCommandToWhereConditions(orFilterJsonString, true)
|
||||||
|
if err != nil {
|
||||||
|
return err, nil
|
||||||
|
}
|
||||||
|
if orSelector != nil {
|
||||||
|
queryConditions = append(queryConditions, orSelector)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, queryConditions
|
||||||
|
}
|
||||||
|
|
||||||
// QueryCommandToWhereConditions 查询命令转换为选择条件
|
// QueryCommandToWhereConditions 查询命令转换为选择条件
|
||||||
func QueryCommandToWhereConditions(strJson string, isOr bool) (error, func(s *sql.Selector)) {
|
func QueryCommandToWhereConditions(strJson string, isOr bool) (error, func(s *sql.Selector)) {
|
||||||
if len(strJson) == 0 {
|
if len(strJson) == 0 {
|
||||||
@@ -150,14 +199,13 @@ func QueryCommandToWhereConditions(strJson string, isOr bool) (error, func(s *sq
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// processQueryMap 处理查询映射表
|
||||||
func processQueryMap(s *sql.Selector, queryMap map[string]string) []*sql.Predicate {
|
func processQueryMap(s *sql.Selector, queryMap map[string]string) []*sql.Predicate {
|
||||||
var ps []*sql.Predicate
|
var ps []*sql.Predicate
|
||||||
for k, v := range queryMap {
|
for k, v := range queryMap {
|
||||||
key := stringcase.ToSnakeCase(k)
|
keys := splitQueryKey(k)
|
||||||
|
|
||||||
keys := strings.Split(key, "__")
|
if cond := makeFieldFilter(s, keys, v); cond != nil {
|
||||||
|
|
||||||
if cond := oneFieldFilter(s, keys, v); cond != nil {
|
|
||||||
ps = append(ps, cond)
|
ps = append(ps, cond)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -165,32 +213,8 @@ func processQueryMap(s *sql.Selector, queryMap map[string]string) []*sql.Predica
|
|||||||
return ps
|
return ps
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildFilterSelector(andFilterJsonString, orFilterJsonString string) (error, []func(s *sql.Selector)) {
|
// makeFieldFilter 构建一个字段过滤器
|
||||||
var err error
|
func makeFieldFilter(s *sql.Selector, keys []string, value string) *sql.Predicate {
|
||||||
var queryConditions []func(s *sql.Selector)
|
|
||||||
|
|
||||||
var andSelector func(s *sql.Selector)
|
|
||||||
err, andSelector = QueryCommandToWhereConditions(andFilterJsonString, false)
|
|
||||||
if err != nil {
|
|
||||||
return err, nil
|
|
||||||
}
|
|
||||||
if andSelector != nil {
|
|
||||||
queryConditions = append(queryConditions, andSelector)
|
|
||||||
}
|
|
||||||
|
|
||||||
var orSelector func(s *sql.Selector)
|
|
||||||
err, orSelector = QueryCommandToWhereConditions(orFilterJsonString, true)
|
|
||||||
if err != nil {
|
|
||||||
return err, nil
|
|
||||||
}
|
|
||||||
if orSelector != nil {
|
|
||||||
queryConditions = append(queryConditions, orSelector)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, queryConditions
|
|
||||||
}
|
|
||||||
|
|
||||||
func oneFieldFilter(s *sql.Selector, keys []string, value string) *sql.Predicate {
|
|
||||||
if len(keys) == 0 {
|
if len(keys) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -207,6 +231,21 @@ func oneFieldFilter(s *sql.Selector, keys []string, value string) *sql.Predicate
|
|||||||
|
|
||||||
switch len(keys) {
|
switch len(keys) {
|
||||||
case 1:
|
case 1:
|
||||||
|
if isJsonFieldKey(field) {
|
||||||
|
jsonFields := splitJsonFieldKey(field)
|
||||||
|
if len(jsonFields) != 2 {
|
||||||
|
field = stringcase.ToSnakeCase(field)
|
||||||
|
return filterEqual(s, p, field, value)
|
||||||
|
}
|
||||||
|
//value = "'" + value + "'"
|
||||||
|
return filterJsonb(
|
||||||
|
s, p,
|
||||||
|
stringcase.ToSnakeCase(jsonFields[1]),
|
||||||
|
stringcase.ToSnakeCase(jsonFields[0]),
|
||||||
|
).
|
||||||
|
EQ("", value)
|
||||||
|
}
|
||||||
|
field = stringcase.ToSnakeCase(field)
|
||||||
return filterEqual(s, p, field, value)
|
return filterEqual(s, p, field, value)
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
@@ -215,6 +254,19 @@ func oneFieldFilter(s *sql.Selector, keys []string, value string) *sql.Predicate
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isJsonFieldKey(field) {
|
||||||
|
jsonFields := splitJsonFieldKey(field)
|
||||||
|
if len(jsonFields) == 2 {
|
||||||
|
field = filterJsonbField(s,
|
||||||
|
stringcase.ToSnakeCase(jsonFields[1]),
|
||||||
|
stringcase.ToSnakeCase(jsonFields[0]),
|
||||||
|
)
|
||||||
|
//value = "'" + value + "'"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
field = stringcase.ToSnakeCase(field)
|
||||||
|
}
|
||||||
|
|
||||||
var cond *sql.Predicate
|
var cond *sql.Predicate
|
||||||
if hasOperations(op) {
|
if hasOperations(op) {
|
||||||
return processOp(s, p, op, field, value)
|
return processOp(s, p, op, field, value)
|
||||||
@@ -241,10 +293,22 @@ func oneFieldFilter(s *sql.Selector, keys []string, value string) *sql.Predicate
|
|||||||
|
|
||||||
//var cond *sql.Predicate
|
//var cond *sql.Predicate
|
||||||
if hasDatePart(op1) {
|
if hasDatePart(op1) {
|
||||||
|
if isJsonFieldKey(field) {
|
||||||
|
jsonFields := splitJsonFieldKey(field)
|
||||||
|
if len(jsonFields) == 2 {
|
||||||
|
field = filterJsonbField(s, jsonFields[1], jsonFields[0])
|
||||||
|
//value = "'" + value + "'"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
field = stringcase.ToSnakeCase(field)
|
||||||
|
}
|
||||||
|
|
||||||
str := filterDatePartField(s, op1, field)
|
str := filterDatePartField(s, op1, field)
|
||||||
|
|
||||||
if hasOperations(op2) {
|
if hasOperations(op2) {
|
||||||
return processOp(s, p, op2, str, value)
|
return processOp(s, p, op2, str, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
} else {
|
} else {
|
||||||
str := filterJsonbField(s, op1, field)
|
str := filterJsonbField(s, op1, field)
|
||||||
@@ -547,6 +611,7 @@ func filterDatePart(s *sql.Selector, p *sql.Predicate, datePart, field string) *
|
|||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// filterDatePartField 日期
|
||||||
func filterDatePartField(s *sql.Selector, datePart, field string) string {
|
func filterDatePartField(s *sql.Selector, datePart, field string) string {
|
||||||
p := sql.P()
|
p := sql.P()
|
||||||
switch s.Builder.Dialect() {
|
switch s.Builder.Dialect() {
|
||||||
@@ -565,12 +630,14 @@ func filterDatePartField(s *sql.Selector, datePart, field string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// filterJsonb 提取JSONB字段
|
// filterJsonb 提取JSONB字段
|
||||||
// Postgresql: WHERE ("app_profile"."preferences" -> daily_email) = 'true'
|
// Postgresql: WHERE ("app_profile"."preferences" ->> 'daily_email') = 'true'
|
||||||
func filterJsonb(s *sql.Selector, p *sql.Predicate, jsonbField, field string) *sql.Predicate {
|
func filterJsonb(s *sql.Selector, p *sql.Predicate, jsonbField, field string) *sql.Predicate {
|
||||||
|
field = stringcase.ToSnakeCase(field)
|
||||||
|
|
||||||
p.Append(func(b *sql.Builder) {
|
p.Append(func(b *sql.Builder) {
|
||||||
switch s.Builder.Dialect() {
|
switch s.Builder.Dialect() {
|
||||||
case dialect.Postgres:
|
case dialect.Postgres:
|
||||||
b.Ident(s.C(field)).WriteString(" -> ").WriteString(jsonbField)
|
b.Ident(s.C(field)).WriteString(" ->> ").WriteString("'" + jsonbField + "'")
|
||||||
//b.Arg(strings.ToLower(value))
|
//b.Arg(strings.ToLower(value))
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -584,11 +651,14 @@ func filterJsonb(s *sql.Selector, p *sql.Predicate, jsonbField, field string) *s
|
|||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// filterJsonbField JSONB字段
|
||||||
func filterJsonbField(s *sql.Selector, jsonbField, field string) string {
|
func filterJsonbField(s *sql.Selector, jsonbField, field string) string {
|
||||||
|
field = stringcase.ToSnakeCase(field)
|
||||||
|
|
||||||
p := sql.P()
|
p := sql.P()
|
||||||
switch s.Builder.Dialect() {
|
switch s.Builder.Dialect() {
|
||||||
case dialect.Postgres:
|
case dialect.Postgres:
|
||||||
p.Ident(s.C(field)).WriteString(" -> ").WriteString(jsonbField)
|
p.Ident(s.C(field)).WriteString(" ->> ").WriteString("'" + jsonbField + "'")
|
||||||
//b.Arg(strings.ToLower(value))
|
//b.Arg(strings.ToLower(value))
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|||||||
@@ -681,14 +681,104 @@ func TestFilter(t *testing.T) {
|
|||||||
s.Where(p)
|
s.Where(p)
|
||||||
|
|
||||||
query, args := s.Query()
|
query, args := s.Query()
|
||||||
require.Equal(t, "SELECT * FROM \"app_profile\" WHERE \"app_profile\".\"preferences\" -> daily_email = $1", query)
|
require.Equal(t, "SELECT * FROM \"app_profile\" WHERE \"app_profile\".\"preferences\" ->> 'daily_email' = $1", query)
|
||||||
require.NotEmpty(t, args)
|
require.NotEmpty(t, args)
|
||||||
require.Equal(t, args[0], "true")
|
require.Equal(t, args[0], "true")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFilterJsonbField(t *testing.T) {
|
func TestFilterJsonbField(t *testing.T) {
|
||||||
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("app_profile"))
|
t.Run("filterJsonbField", func(t *testing.T) {
|
||||||
str := filterJsonbField(s, "daily_email", "preferences")
|
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("app_profile"))
|
||||||
fmt.Println(str)
|
str := filterJsonbField(s, "daily_email", "preferences")
|
||||||
|
fmt.Println(str)
|
||||||
|
require.Equal(t, str, "\"app_profile\".\"preferences\" ->> 'daily_email'")
|
||||||
|
})
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
t.Run("MySQL_FilterEqual", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("menus"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = makeFieldFilter(s, []string{"meta.title"}, "tom")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM `menus` WHERE JSON_EXTRACT(`menus`.`meta`, '$.title') = ?", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "'tom'")
|
||||||
|
})
|
||||||
|
t.Run("PostgreSQL_FilterEqual", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("menus"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = makeFieldFilter(s, []string{"meta.title"}, "tom")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM \"menus\" WHERE \"menus\".\"meta\" ->> 'title' = $1", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "'tom'")
|
||||||
|
})
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
t.Run("MySQL_FilterNot", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = makeFieldFilter(s, []string{"meta.title", "not"}, "tom")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM `users` WHERE NOT JSON_EXTRACT(`users`.`meta`, '$.title') = ?", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "'tom'")
|
||||||
|
})
|
||||||
|
t.Run("PostgreSQL_FilterNot", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = makeFieldFilter(s, []string{"meta.title", "not"}, "tom")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM \"users\" WHERE NOT \"users\".\"meta\" ->> 'title' = $1", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "'tom'")
|
||||||
|
})
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
t.Run("MySQL_FilterNot_Date", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = makeFieldFilter(s, []string{"meta.title", "date", "not"}, "2023-01-01")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM `users` WHERE NOT DATE(JSON_EXTRACT(`users`.`meta`, '$.title')) = ?", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "'2023-01-01'")
|
||||||
|
})
|
||||||
|
t.Run("PostgreSQL_FilterNot_Date", func(t *testing.T) {
|
||||||
|
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
|
||||||
|
|
||||||
|
p := sql.P()
|
||||||
|
|
||||||
|
p = makeFieldFilter(s, []string{"meta.title", "date", "not"}, "2023-01-01")
|
||||||
|
s.Where(p)
|
||||||
|
|
||||||
|
query, args := s.Query()
|
||||||
|
require.Equal(t, "SELECT * FROM \"users\" WHERE NOT EXTRACT('DATE' FROM \"users\".\"meta\" ->> 'title') = $1", query)
|
||||||
|
require.NotEmpty(t, args)
|
||||||
|
require.Equal(t, args[0], "'2023-01-01'")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package entgo
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"entgo.io/ent/dialect"
|
"entgo.io/ent/dialect"
|
||||||
@@ -11,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/go-kratos/kratos/v2/encoding"
|
"github.com/go-kratos/kratos/v2/encoding"
|
||||||
_ "github.com/go-kratos/kratos/v2/encoding/json"
|
_ "github.com/go-kratos/kratos/v2/encoding/json"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -85,11 +85,11 @@ func TestJsonCodec(t *testing.T) {
|
|||||||
func TestSplitQuery(t *testing.T) {
|
func TestSplitQuery(t *testing.T) {
|
||||||
var keys []string
|
var keys []string
|
||||||
|
|
||||||
keys = strings.Split("id", "__")
|
keys = splitQueryKey("id")
|
||||||
assert.Equal(t, len(keys), 1)
|
assert.Equal(t, len(keys), 1)
|
||||||
assert.Equal(t, keys[0], "id")
|
assert.Equal(t, keys[0], "id")
|
||||||
|
|
||||||
keys = strings.Split("id__not", "__")
|
keys = splitQueryKey("id__not")
|
||||||
assert.Equal(t, len(keys), 2)
|
assert.Equal(t, len(keys), 2)
|
||||||
assert.Equal(t, keys[0], "id")
|
assert.Equal(t, keys[0], "id")
|
||||||
assert.Equal(t, keys[1], "not")
|
assert.Equal(t, keys[1], "not")
|
||||||
@@ -111,19 +111,22 @@ func TestBuildQuerySelectorDefault(t *testing.T) {
|
|||||||
{"PostgreSQL_NoPagination", dialect.Postgres, "", "", true, "SELECT * FROM \"users\" ORDER BY \"users\".\"created_at\" DESC"},
|
{"PostgreSQL_NoPagination", dialect.Postgres, "", "", true, "SELECT * FROM \"users\" ORDER BY \"users\".\"created_at\" DESC"},
|
||||||
|
|
||||||
{"MySQL_JsonbQuery", dialect.MySQL, "{\"preferences__daily_email\" : \"true\"}", "", true, "SELECT * FROM `users` WHERE JSON_EXTRACT(`users`.`preferences`, '$.daily_email') = ? ORDER BY `users`.`created_at` DESC"},
|
{"MySQL_JsonbQuery", dialect.MySQL, "{\"preferences__daily_email\" : \"true\"}", "", true, "SELECT * FROM `users` WHERE JSON_EXTRACT(`users`.`preferences`, '$.daily_email') = ? ORDER BY `users`.`created_at` DESC"},
|
||||||
{"PostgreSQL_JsonbQuery", dialect.Postgres, "{\"preferences__daily_email\" : \"true\"}", "", true, "SELECT * FROM \"users\" WHERE \"users\".\"preferences\" -> daily_email = $1 ORDER BY \"users\".\"created_at\" DESC"},
|
{"PostgreSQL_JsonbQuery", dialect.Postgres, "{\"preferences__daily_email\" : \"true\"}", "", true, "SELECT * FROM \"users\" WHERE \"users\".\"preferences\" ->> 'daily_email' = $1 ORDER BY \"users\".\"created_at\" DESC"},
|
||||||
|
|
||||||
{"MySQL_DatePartQuery", dialect.MySQL, "{\"created_at__date\" : \"2023-01-01\"}", "", true, "SELECT * FROM `users` WHERE DATE(`users`.`created_at`) = ? ORDER BY `users`.`created_at` DESC"},
|
{"MySQL_DatePartQuery", dialect.MySQL, "{\"created_at__date\" : \"2023-01-01\"}", "", true, "SELECT * FROM `users` WHERE DATE(`users`.`created_at`) = ? ORDER BY `users`.`created_at` DESC"},
|
||||||
{"PostgreSQL_DatePartQuery", dialect.Postgres, "{\"created_at__date\" : \"2023-01-01\"}", "", true, "SELECT * FROM \"users\" WHERE EXTRACT('DATE' FROM \"users\".\"created_at\") = $1 ORDER BY \"users\".\"created_at\" DESC"},
|
{"PostgreSQL_DatePartQuery", dialect.Postgres, "{\"created_at__date\" : \"2023-01-01\"}", "", true, "SELECT * FROM \"users\" WHERE EXTRACT('DATE' FROM \"users\".\"created_at\") = $1 ORDER BY \"users\".\"created_at\" DESC"},
|
||||||
|
|
||||||
{"MySQL_JsonbCombineQuery", dialect.MySQL, "{\"preferences__pub_date__not\" : \"true\"}", "", true, "SELECT * FROM `users` WHERE NOT JSON_EXTRACT(`users`.`preferences`, '$.pub_date') = ? ORDER BY `users`.`created_at` DESC"},
|
{"MySQL_JsonbCombineQuery", dialect.MySQL, "{\"preferences__pub_date__not\" : \"true\"}", "", true, "SELECT * FROM `users` WHERE NOT JSON_EXTRACT(`users`.`preferences`, '$.pub_date') = ? ORDER BY `users`.`created_at` DESC"},
|
||||||
{"PostgreSQL_JsonbCombineQuery", dialect.Postgres, "{\"preferences__pub_date__not\" : \"true\"}", "", true, "SELECT * FROM \"users\" WHERE NOT \"users\".\"preferences\" -> pub_date = $1 ORDER BY \"users\".\"created_at\" DESC"},
|
{"PostgreSQL_JsonbCombineQuery", dialect.Postgres, "{\"preferences__pub_date__not\" : \"true\"}", "", true, "SELECT * FROM \"users\" WHERE NOT \"users\".\"preferences\" ->> 'pub_date' = $1 ORDER BY \"users\".\"created_at\" DESC"},
|
||||||
|
|
||||||
{"MySQL_DatePartCombineQuery", dialect.MySQL, "{\"pub_date__date__not\" : \"true\"}", "", true, "SELECT * FROM `users` WHERE NOT DATE(`users`.`pub_date`) = ? ORDER BY `users`.`created_at` DESC"},
|
{"MySQL_DatePartCombineQuery", dialect.MySQL, "{\"pub_date__date__not\" : \"true\"}", "", true, "SELECT * FROM `users` WHERE NOT DATE(`users`.`pub_date`) = ? ORDER BY `users`.`created_at` DESC"},
|
||||||
{"PostgreSQL_DatePartCombineQuery", dialect.Postgres, "{\"pub_date__date__not\" : \"true\"}", "", true, "SELECT * FROM \"users\" WHERE NOT EXTRACT('DATE' FROM \"users\".\"pub_date\") = $1 ORDER BY \"users\".\"created_at\" DESC"},
|
{"PostgreSQL_DatePartCombineQuery", dialect.Postgres, "{\"pub_date__date__not\" : \"true\"}", "", true, "SELECT * FROM \"users\" WHERE NOT EXTRACT('DATE' FROM \"users\".\"pub_date\") = $1 ORDER BY \"users\".\"created_at\" DESC"},
|
||||||
|
|
||||||
{"MySQL_DatePartRangeQuery", dialect.MySQL, "{\"pub_date__date__range\" : \"[\\\"2023-10-25\\\", \\\"2024-10-25\\\"]\"}", "", true, "SELECT * FROM `users` WHERE DATE(`users`.`pub_date`) >= ? AND DATE(`users`.`pub_date`) <= ? ORDER BY `users`.`created_at` DESC"},
|
{"MySQL_DatePartRangeQuery", dialect.MySQL, "{\"pub_date__date__range\" : \"[\\\"2023-10-25\\\", \\\"2024-10-25\\\"]\"}", "", true, "SELECT * FROM `users` WHERE DATE(`users`.`pub_date`) >= ? AND DATE(`users`.`pub_date`) <= ? ORDER BY `users`.`created_at` DESC"},
|
||||||
{"PostgreSQL_DatePartRangeQuery", dialect.Postgres, "{\"pub_date__date__range\" : \"[\\\"2023-10-25\\\", \\\"2024-10-25\\\"]\"}", "", true, "SELECT * FROM \"users\" WHERE EXTRACT('DATE' FROM \"users\".\"pub_date\") >= $1 AND EXTRACT('DATE' FROM \"users\".\"pub_date\") <= $2 ORDER BY \"users\".\"created_at\" DESC"},
|
{"PostgreSQL_DatePartRangeQuery", dialect.Postgres, "{\"pub_date__date__range\" : \"[\\\"2023-10-25\\\", \\\"2024-10-25\\\"]\"}", "", true, "SELECT * FROM \"users\" WHERE EXTRACT('DATE' FROM \"users\".\"pub_date\") >= $1 AND EXTRACT('DATE' FROM \"users\".\"pub_date\") <= $2 ORDER BY \"users\".\"created_at\" DESC"},
|
||||||
|
|
||||||
|
{"MySQL_JsonQuery", dialect.MySQL, "{\"meta.title\" : \"preferences__daily_email\"}", "", true, "SELECT * FROM `users` WHERE JSON_EXTRACT(`users`.`meta`, '$.title') = ? ORDER BY `users`.`created_at` DESC"},
|
||||||
|
{"PostgreSQL_JsonQuery", dialect.Postgres, "{\"meta.title\" : \"preferences__daily_email\"}", "", true, "SELECT * FROM \"users\" WHERE \"users\".\"meta\" ->> 'title' = $1 ORDER BY \"users\".\"created_at\" DESC"},
|
||||||
}
|
}
|
||||||
for _, tc := range testcases {
|
for _, tc := range testcases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
package entgo
|
package entgo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"entgo.io/ent/dialect/sql"
|
"entgo.io/ent/dialect/sql"
|
||||||
|
|
||||||
|
"github.com/tx7do/go-utils/fieldmaskutil"
|
||||||
"github.com/tx7do/go-utils/stringcase"
|
"github.com/tx7do/go-utils/stringcase"
|
||||||
|
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
"google.golang.org/protobuf/reflect/protoreflect"
|
||||||
)
|
)
|
||||||
|
|
||||||
func BuildSetNullUpdate(u *sql.UpdateBuilder, fields []string) {
|
func BuildSetNullUpdate(u *sql.UpdateBuilder, fields []string) {
|
||||||
@@ -14,12 +22,79 @@ func BuildSetNullUpdate(u *sql.UpdateBuilder, fields []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildSetNullUpdater(fields []string) (error, func(u *sql.UpdateBuilder)) {
|
// BuildSetNullUpdater 构建一个UpdateBuilder,用于清空字段的值
|
||||||
if len(fields) > 0 {
|
func BuildSetNullUpdater(fields []string) func(u *sql.UpdateBuilder) {
|
||||||
return nil, func(u *sql.UpdateBuilder) {
|
if len(fields) == 0 {
|
||||||
BuildSetNullUpdate(u, fields)
|
return nil
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return nil, nil
|
return func(u *sql.UpdateBuilder) {
|
||||||
|
BuildSetNullUpdate(u, fields)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractJsonFieldKeyValues 提取json字段的键值对
|
||||||
|
func ExtractJsonFieldKeyValues(msg proto.Message, paths []string, needToSnakeCase bool) []string {
|
||||||
|
var keyValues []string
|
||||||
|
rft := msg.ProtoReflect()
|
||||||
|
for _, path := range paths {
|
||||||
|
fd := rft.Descriptor().Fields().ByName(protoreflect.Name(path))
|
||||||
|
if fd == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !rft.Has(fd) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var k string
|
||||||
|
if needToSnakeCase {
|
||||||
|
k = stringcase.ToSnakeCase(path)
|
||||||
|
} else {
|
||||||
|
k = path
|
||||||
|
}
|
||||||
|
|
||||||
|
keyValues = append(keyValues, fmt.Sprintf("'%s'", k))
|
||||||
|
|
||||||
|
v := rft.Get(fd)
|
||||||
|
switch v.Interface().(type) {
|
||||||
|
case int32, int64, uint32, uint64, float32, float64, bool:
|
||||||
|
keyValues = append(keyValues, fmt.Sprintf("%d", v.Interface()))
|
||||||
|
case string:
|
||||||
|
keyValues = append(keyValues, fmt.Sprintf("'%s'", v.Interface()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyValues
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetJsonNullFieldUpdateBuilder 设置json字段的空值
|
||||||
|
func SetJsonNullFieldUpdateBuilder(fieldName string, msg proto.Message, paths []string) func(u *sql.UpdateBuilder) {
|
||||||
|
nilPaths := fieldmaskutil.NilValuePaths(msg, paths)
|
||||||
|
if len(nilPaths) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(u *sql.UpdateBuilder) {
|
||||||
|
u.Set(fieldName,
|
||||||
|
sql.Expr(
|
||||||
|
fmt.Sprintf("\"%s\" - '{%s}'::text[]", fieldName, strings.Join(nilPaths, ",")),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetJsonFieldValueUpdateBuilder 设置json字段的值
|
||||||
|
func SetJsonFieldValueUpdateBuilder(fieldName string, msg proto.Message, paths []string, needToSnakeCase bool) func(u *sql.UpdateBuilder) {
|
||||||
|
keyValues := ExtractJsonFieldKeyValues(msg, paths, needToSnakeCase)
|
||||||
|
if len(keyValues) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(u *sql.UpdateBuilder) {
|
||||||
|
u.Set(fieldName,
|
||||||
|
sql.Expr(
|
||||||
|
fmt.Sprintf("\"%s\" || jsonb_build_object(%s)", fieldName, strings.Join(keyValues, ",")),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
12
geoip/go.mod
12
geoip/go.mod
@@ -1,14 +1,14 @@
|
|||||||
module github.com/tx7do/go-utils/geoip
|
module github.com/tx7do/go-utils/geoip
|
||||||
|
|
||||||
go 1.21
|
go 1.23.0
|
||||||
|
|
||||||
toolchain go1.23.2
|
toolchain go1.24.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/go-kratos/kratos/v2 v2.8.1
|
github.com/go-kratos/kratos/v2 v2.8.4
|
||||||
github.com/oschwald/geoip2-golang v1.11.0
|
github.com/oschwald/geoip2-golang v1.11.0
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.10.0
|
||||||
golang.org/x/text v0.19.0
|
golang.org/x/text v0.25.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -16,7 +16,7 @@ require (
|
|||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/kr/text v0.2.0 // indirect
|
||||||
github.com/oschwald/maxminddb-golang v1.13.1 // indirect
|
github.com/oschwald/maxminddb-golang v1.13.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
golang.org/x/sys v0.26.0 // indirect
|
golang.org/x/sys v0.33.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
20
geoip/go.sum
20
geoip/go.sum
@@ -1,8 +1,8 @@
|
|||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/go-kratos/kratos/v2 v2.8.1 h1:nK+NRp8C+wQk7tr55K9Er7nBjmBLYGbnyNz7UIy37qw=
|
github.com/go-kratos/kratos/v2 v2.8.4 h1:eIJLE9Qq9WSoKx+Buy2uPyrahtF/lPh+Xf4MTpxhmjs=
|
||||||
github.com/go-kratos/kratos/v2 v2.8.1/go.mod h1:+Vfe3FzF0d+BfMdajA11jT0rAyJWublRE/seZQNZVxE=
|
github.com/go-kratos/kratos/v2 v2.8.4/go.mod h1:mq62W2101a5uYyRxe+7IdWubu7gZCGYqSNKwGFiiRcw=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
@@ -15,14 +15,14 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
|||||||
15
go.mod
15
go.mod
@@ -1,23 +1,20 @@
|
|||||||
module github.com/tx7do/go-utils
|
module github.com/tx7do/go-utils
|
||||||
|
|
||||||
go 1.22.0
|
go 1.23.0
|
||||||
|
|
||||||
toolchain go1.23.2
|
toolchain go1.24.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gobwas/glob v0.2.3
|
github.com/gobwas/glob v0.2.3
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/gosimple/slug v1.14.0
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/sony/sonyflake v1.2.0
|
golang.org/x/crypto v0.39.0
|
||||||
github.com/stretchr/testify v1.9.0
|
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b
|
||||||
golang.org/x/crypto v0.28.0
|
google.golang.org/protobuf v1.36.6
|
||||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c
|
|
||||||
google.golang.org/protobuf v1.35.1
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/gosimple/unidecode v1.0.1 // indirect
|
|
||||||
github.com/kr/pretty v0.3.1 // indirect
|
github.com/kr/pretty v0.3.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
||||||
|
|||||||
22
go.sum
22
go.sum
@@ -7,10 +7,6 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
|||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gosimple/slug v1.14.0 h1:RtTL/71mJNDfpUbCOmnf/XFkzKRtD6wL6Uy+3akm4Es=
|
|
||||||
github.com/gosimple/slug v1.14.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ=
|
|
||||||
github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o=
|
|
||||||
github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc=
|
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
@@ -24,16 +20,14 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
|||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
github.com/sony/sonyflake v1.2.0 h1:Pfr3A+ejSg+0SPqpoAmQgEtNDAhc2G1SUYk205qVMLQ=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/sony/sonyflake v1.2.0/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
|
||||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
|
||||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
|
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
|
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||||
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
|
||||||
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
|||||||
47
gorm/go.mod
47
gorm/go.mod
@@ -1,55 +1,54 @@
|
|||||||
module github.com/tx7do/go-utils/gorm
|
module github.com/tx7do/go-utils/gorm
|
||||||
|
|
||||||
go 1.22
|
go 1.23.0
|
||||||
|
|
||||||
toolchain go1.23.2
|
toolchain go1.24.3
|
||||||
|
|
||||||
require (
|
require (
|
||||||
gorm.io/driver/clickhouse v0.6.1
|
gorm.io/driver/clickhouse v0.6.1
|
||||||
gorm.io/driver/mysql v1.5.7
|
gorm.io/driver/mysql v1.5.7
|
||||||
gorm.io/driver/postgres v1.5.9
|
gorm.io/driver/postgres v1.5.11
|
||||||
gorm.io/driver/sqlite v1.5.6
|
gorm.io/driver/sqlite v1.5.7
|
||||||
gorm.io/driver/sqlserver v1.5.3
|
gorm.io/driver/sqlserver v1.5.4
|
||||||
gorm.io/gorm v1.25.12
|
gorm.io/gorm v1.26.1
|
||||||
gorm.io/plugin/opentelemetry v0.1.7
|
gorm.io/plugin/opentelemetry v0.1.14
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
filippo.io/edwards25519 v1.1.0 // indirect
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
github.com/ClickHouse/ch-go v0.62.0 // indirect
|
github.com/ClickHouse/ch-go v0.66.0 // indirect
|
||||||
github.com/ClickHouse/clickhouse-go/v2 v2.30.0 // indirect
|
github.com/ClickHouse/clickhouse-go/v2 v2.34.0 // indirect
|
||||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||||
github.com/go-faster/city v1.0.1 // indirect
|
github.com/go-faster/city v1.0.1 // indirect
|
||||||
github.com/go-faster/errors v0.7.1 // indirect
|
github.com/go-faster/errors v0.7.1 // indirect
|
||||||
github.com/go-logr/logr v1.4.2 // indirect
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
github.com/go-sql-driver/mysql v1.9.2 // indirect
|
||||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/hashicorp/go-version v1.7.0 // indirect
|
github.com/hashicorp/go-version v1.7.0 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
github.com/jackc/pgx/v5 v5.7.1 // indirect
|
github.com/jackc/pgx/v5 v5.7.5 // indirect
|
||||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/klauspost/compress v1.17.11 // indirect
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.24 // indirect
|
github.com/mattn/go-sqlite3 v1.14.28 // indirect
|
||||||
github.com/microsoft/go-mssqldb v1.7.2 // indirect
|
github.com/microsoft/go-mssqldb v1.8.1 // indirect
|
||||||
github.com/paulmach/orb v0.11.1 // indirect
|
github.com/paulmach/orb v0.11.1 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
|
||||||
github.com/segmentio/asm v1.2.0 // indirect
|
github.com/segmentio/asm v1.2.0 // indirect
|
||||||
github.com/shopspring/decimal v1.4.0 // indirect
|
github.com/shopspring/decimal v1.4.0 // indirect
|
||||||
go.opentelemetry.io/otel v1.31.0 // indirect
|
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.31.0 // indirect
|
go.opentelemetry.io/otel v1.36.0 // indirect
|
||||||
go.opentelemetry.io/otel/sdk v1.25.0 // indirect
|
go.opentelemetry.io/otel/metric v1.36.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.31.0 // indirect
|
go.opentelemetry.io/otel/trace v1.36.0 // indirect
|
||||||
golang.org/x/crypto v0.28.0 // indirect
|
golang.org/x/crypto v0.38.0 // indirect
|
||||||
golang.org/x/sync v0.8.0 // indirect
|
golang.org/x/sync v0.14.0 // indirect
|
||||||
golang.org/x/sys v0.26.0 // indirect
|
golang.org/x/sys v0.33.0 // indirect
|
||||||
golang.org/x/text v0.19.0 // indirect
|
golang.org/x/text v0.25.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
159
gorm/go.sum
159
gorm/go.sum
@@ -1,34 +1,31 @@
|
|||||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
github.com/Azure/azure-sdk-for-go v56.3.0+incompatible h1:DmhwMrUIvpeoTDiWRDtNHqelNUd3Og8JCkrLHQK795c=
|
github.com/Azure/azure-sdk-for-go v56.3.0+incompatible h1:DmhwMrUIvpeoTDiWRDtNHqelNUd3Og8JCkrLHQK795c=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0/go.mod h1:ON4tFdPTwRcgWEaVDrN3584Ef+b7GgSJaXxe5fW9t4M=
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
|
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
|
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1 h1:lGlwhPtrX6EVml1hO0ivjkUxsSyl4dsiw9qcA1k/3IQ=
|
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1/go.mod h1:RKUqNu35KJYcVG/fqTRqmuXJZYNhYkBrnC/hX7yGbTA=
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1/go.mod h1:RKUqNu35KJYcVG/fqTRqmuXJZYNhYkBrnC/hX7yGbTA=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U=
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 h1:sO0/P7g68FrryJzljemN+6GTssUXdANk6aJ7T1ZxnsQ=
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1/go.mod h1:uE9zaUfEQT/nbQjVi2IblCG9iaLtZsuYZ8ne+PuQ02M=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1/go.mod h1:h8hyGFDsU5HMivxiS2iYFZsgDbU9OnnJ163x5UGVKYo=
|
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1/go.mod h1:h8hyGFDsU5HMivxiS2iYFZsgDbU9OnnJ163x5UGVKYo=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=
|
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 h1:U2rTu3Ef+7w9FHKIAXM6ZyqF3UOWJZ12zIm8zECAFfg=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.2.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=
|
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
|
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 h1:6oNBlSdi1QqM1PNW7FPA6xOGA5UNsXnkaYZz9vdPGhA=
|
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI=
|
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.0/go.mod h1:Q28U+75mpCaSCDowNEmhIo/rmgdkqmkmzI7N6TGR4UY=
|
github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 h1:jBQA3cKT4L2rWMpgE7Yt3Hwh2aUj8KXjIGLxjHeYNNo=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0/go.mod h1:4OG6tQ9EOP/MT0NMjDlRzWoVFxfu9rN9B2X+tlSVktg=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1 h1:MyVTgWR8qd/Jw1Le0NZebGBUCLbtak3bJ3z1OlqZBpw=
|
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1 h1:MyVTgWR8qd/Jw1Le0NZebGBUCLbtak3bJ3z1OlqZBpw=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1/go.mod h1:GpPjLhVR9dnUoJMyHWSPy71xY9/lcmpzIPZXmF0FCVY=
|
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1/go.mod h1:GpPjLhVR9dnUoJMyHWSPy71xY9/lcmpzIPZXmF0FCVY=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0/go.mod h1:cw4zVQgBby0Z5f2v0itn6se2dDP17nTjbZFXW5uPyHA=
|
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80=
|
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI=
|
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI=
|
||||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.1.0/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
|
||||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 h1:DzHpqpoJVaCgOUdVHxE8QB52S6NiVdDQvGlny1qvPqA=
|
|
||||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||||
github.com/ClickHouse/ch-go v0.62.0 h1:eXH0hytXeCEEZHgMvOX9IiW7wqBb4w1MJMp9rArbkrc=
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
|
||||||
github.com/ClickHouse/ch-go v0.62.0/go.mod h1:uzso52/PD9+gZj7tL6XAo8/EYDrx7CIwNF4c6PnO6S0=
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||||
github.com/ClickHouse/clickhouse-go/v2 v2.30.0 h1:AG4D/hW39qa58+JHQIFOSnxyL46H6h2lrmGGk17dhFo=
|
github.com/ClickHouse/ch-go v0.66.0 h1:hLslxxAVb2PHpbHr4n0d6aP8CEIpUYGMVT1Yj/Q5Img=
|
||||||
github.com/ClickHouse/clickhouse-go/v2 v2.30.0/go.mod h1:i9ZQAojcayW3RsdCb3YR+n+wC2h65eJsZCscZ1Z1wyo=
|
github.com/ClickHouse/ch-go v0.66.0/go.mod h1:noiHWyLMJAZ5wYuq3R/K0TcRhrNA8h7o1AqHX0klEhM=
|
||||||
|
github.com/ClickHouse/clickhouse-go/v2 v2.34.0 h1:Y4rqkdrRHgExvC4o/NTbLdY5LFQ3LHS77/RNFxFX3Co=
|
||||||
|
github.com/ClickHouse/clickhouse-go/v2 v2.34.0/go.mod h1:yioSINoRLVZkLyDzdMXPLRIqhDvel8iLBlwh6Iefso8=
|
||||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@@ -46,14 +43,13 @@ github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ4
|
|||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU=
|
||||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
|
||||||
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
|
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
|
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
|
||||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||||
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
||||||
@@ -62,9 +58,10 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS
|
|||||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||||
@@ -77,8 +74,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
|
|||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs=
|
github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs=
|
||||||
github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA=
|
github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
|
||||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
|
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
|
||||||
@@ -94,8 +91,8 @@ github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/
|
|||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
@@ -105,28 +102,27 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
|
||||||
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/microsoft/go-mssqldb v1.6.0/go.mod h1:00mDtPbeQCRGC1HwOOR5K/gr30P1NcEG0vx6Kbv2aJU=
|
|
||||||
github.com/microsoft/go-mssqldb v1.7.2 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP3b/PA=
|
|
||||||
github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA=
|
github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA=
|
||||||
|
github.com/microsoft/go-mssqldb v1.8.1 h1:/LPVjSb992vTa8CMVvliTMT//UAKj/jpe1xb/jJBjIk=
|
||||||
|
github.com/microsoft/go-mssqldb v1.8.1/go.mod h1:vp38dT33FGfVotRiTmDo3bFyaHq+p3LektQrjTULowo=
|
||||||
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
|
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
|
||||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||||
github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||||
github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU=
|
github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU=
|
||||||
github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
|
github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
|
||||||
github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
|
github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
|
||||||
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
|
||||||
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
|
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
||||||
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||||
@@ -141,10 +137,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||||
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
|
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
|
||||||
@@ -156,25 +151,30 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
|||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
|
go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
|
||||||
go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
|
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||||
go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
|
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||||
go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
|
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
|
||||||
go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
|
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
|
||||||
go.opentelemetry.io/otel/sdk v1.25.0 h1:PDryEJPC8YJZQSyLY5eqLeafHtG+X7FWnf3aXMtxbqo=
|
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
|
||||||
go.opentelemetry.io/otel/sdk v1.25.0/go.mod h1:oFgzCM2zdsxKzz6zwpTZYLLQsFwc+K0daArPdIhuxkw=
|
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
|
||||||
go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys=
|
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
|
||||||
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
|
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
|
||||||
|
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
|
||||||
|
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
|
||||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||||
|
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||||
|
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||||
|
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||||
|
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
@@ -183,7 +183,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
|||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
@@ -192,17 +191,21 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
|||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
|
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||||
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||||
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||||
|
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||||
|
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||||
|
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -212,18 +215,27 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||||
|
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
|
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
||||||
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||||
|
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||||
|
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||||
|
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
@@ -231,9 +243,12 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
|
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||||
|
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
@@ -261,15 +276,15 @@ gorm.io/driver/clickhouse v0.6.1 h1:t7JMB6sLBXxN8hEO6RdzCbJCwq/jAEVZdwXlmQs1Sd4=
|
|||||||
gorm.io/driver/clickhouse v0.6.1/go.mod h1:riMYpJcGZ3sJ/OAZZ1rEP1j/Y0H6cByOAnwz7fo2AyM=
|
gorm.io/driver/clickhouse v0.6.1/go.mod h1:riMYpJcGZ3sJ/OAZZ1rEP1j/Y0H6cByOAnwz7fo2AyM=
|
||||||
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
|
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
|
||||||
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
|
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
|
||||||
gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8=
|
gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314=
|
||||||
gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
|
gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
|
||||||
gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE=
|
gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
|
||||||
gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
||||||
gorm.io/driver/sqlserver v1.5.3 h1:rjupPS4PVw+rjJkfvr8jn2lJ8BMhT4UW5FwuJY0P3Z0=
|
gorm.io/driver/sqlserver v1.5.4 h1:xA+Y1KDNspv79q43bPyjDMUgHoYHLhXYmdFcYPobg8g=
|
||||||
gorm.io/driver/sqlserver v1.5.3/go.mod h1:B+CZ0/7oFJ6tAlefsKoyxdgDCXJKSgwS2bMOQZT0I00=
|
gorm.io/driver/sqlserver v1.5.4/go.mod h1:+frZ/qYmuna11zHPlh5oc2O6ZA/lS88Keb0XSH1Zh/g=
|
||||||
gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||||
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||||
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
gorm.io/gorm v1.26.1 h1:ghB2gUI9FkS46luZtn6DLZ0f6ooBJ5IbVej2ENFDjRw=
|
||||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
gorm.io/gorm v1.26.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||||
gorm.io/plugin/opentelemetry v0.1.7 h1:oKiwIOpNn0DlqZyHvpg7m8AT/YJ6XBBD1vp0F0rFtto=
|
gorm.io/plugin/opentelemetry v0.1.14 h1:xivP39t/0JgcceDl+BLwVAJHihjFEUj0ZocMSBwZ7ZY=
|
||||||
gorm.io/plugin/opentelemetry v0.1.7/go.mod h1:TYGUagk7h8WwuCsDDznEzznY31PP3+NRpfh6FH7Yqfs=
|
gorm.io/plugin/opentelemetry v0.1.14/go.mod h1:ZAp4v5vU1CCcK9Oo8/va5rl6NStrzpSU+a70evd+W/g=
|
||||||
|
|||||||
28
id/README.md
Normal file
28
id/README.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# ID生成器
|
||||||
|
|
||||||
|
## 订单ID
|
||||||
|
|
||||||
|
- 电商平台:202506041234567890(时间戳 + 随机数,19-20 位)。
|
||||||
|
- 支付系统:PAY20250604123456789(业务前缀 + 时间戳 + 序号)。
|
||||||
|
- 微信支付:1589123456789012345(类似 Snowflake 的纯数字 ID)。
|
||||||
|
- 美团订单:202506041234567890123(时间戳 + 商户 ID + 随机数)。
|
||||||
|
|
||||||
|
## UUID
|
||||||
|
|
||||||
|
| 特性 | GUID/UUID | KSUID | ShortUUID | XID | Snowflake |
|
||||||
|
|-------|--------------|------------|-----------|----------|----------------|
|
||||||
|
| 长度 | 36/32字符(不含-) | 27字符 | 22字符 | 20字符 | 19(数字位数) |
|
||||||
|
| 有序性 | 无序(UUIDv4) | 严格时序 | 无序 | 趋势有序 | 严格时序 |
|
||||||
|
| 时间精度 | 无(UUIDv4) | 毫秒级 | 无 | 秒级 | 毫秒级 |
|
||||||
|
| 分布式安全 | 高(随机数) | 高 | 高 | 高 | 高(需配置WorkerID) |
|
||||||
|
| 性能 | 中等 | 中等 | 较低(编码开销) | 极高 | 极高 |
|
||||||
|
| 时钟依赖 | 无 | 有(需处理时钟回拨) | 无 | 有(但影响较小) | 强依赖(需严格同步) |
|
||||||
|
| 适用场景 | 跨系统兼容 | 时序索引 | 短ID、URL | 高并发、短ID | 分布式时序ID |
|
||||||
|
|
||||||
|
## 选择建议
|
||||||
|
|
||||||
|
- **GUID/UUID**: 适用于需要跨系统兼容的场景,特别是当不需要有序性时。
|
||||||
|
- **KSUID**: 适合需要严格时序的应用,如事件日志、时间序列数据。
|
||||||
|
- **ShortUUID**: 当需要短ID且不关心有序性时的理想选择,适用于URL、短链接等。
|
||||||
|
- **XID**: 高并发场景下的短ID选择,适合需要一定有序性的应用。
|
||||||
|
- **Snowflake**: 适合分布式系统,特别是需要严格时序和高性能的场景,如大规模分布式应用。
|
||||||
27
id/go.mod
Normal file
27
id/go.mod
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
module github.com/tx7do/go-utils/id
|
||||||
|
|
||||||
|
go 1.23.0
|
||||||
|
|
||||||
|
toolchain go1.23.2
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/bwmarrin/snowflake v0.3.0
|
||||||
|
github.com/google/uuid v1.6.0
|
||||||
|
github.com/lithammer/shortuuid/v4 v4.2.0
|
||||||
|
github.com/rs/xid v1.6.0
|
||||||
|
github.com/segmentio/ksuid v1.0.4
|
||||||
|
github.com/stretchr/testify v1.10.0
|
||||||
|
github.com/tx7do/go-utils v1.1.27
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/kr/text v0.2.0 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||||
|
github.com/sony/sonyflake v1.2.1 // indirect
|
||||||
|
go.mongodb.org/mongo-driver v1.17.3 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
replace github.com/tx7do/go-utils => ../
|
||||||
32
id/go.sum
Normal file
32
id/go.sum
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0=
|
||||||
|
github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/lithammer/shortuuid/v4 v4.2.0 h1:LMFOzVB3996a7b8aBuEXxqOBflbfPQAiVzkIcHO0h8c=
|
||||||
|
github.com/lithammer/shortuuid/v4 v4.2.0/go.mod h1:D5noHZ2oFw/YaKCfGy0YxyE7M0wMbezmMjPdhyEFe6Y=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
|
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||||
|
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||||
|
github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=
|
||||||
|
github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
|
||||||
|
github.com/sony/sonyflake v1.2.1 h1:Jzo4abS84qVNbYamXZdrZF1/6TzNJjEogRfXv7TsG48=
|
||||||
|
github.com/sony/sonyflake v1.2.1/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y=
|
||||||
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ=
|
||||||
|
go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
84
id/order_id.go
Normal file
84
id/order_id.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package id
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/tx7do/go-utils/trans"
|
||||||
|
)
|
||||||
|
|
||||||
|
type idCounter uint32
|
||||||
|
|
||||||
|
func (c *idCounter) Increase() uint32 {
|
||||||
|
cur := *c
|
||||||
|
atomic.AddUint32((*uint32)(c), 1)
|
||||||
|
atomic.CompareAndSwapUint32((*uint32)(c), 1000, 0)
|
||||||
|
return uint32(cur)
|
||||||
|
}
|
||||||
|
|
||||||
|
var orderIdIndex idCounter
|
||||||
|
|
||||||
|
// GenerateOrderIdWithRandom 生成20位订单号,前缀 + 时间戳 + 随机数
|
||||||
|
func GenerateOrderIdWithRandom(prefix string, tm *time.Time) string {
|
||||||
|
// 前缀 + 时间戳(14位) + 随机数(4位)
|
||||||
|
|
||||||
|
if tm == nil {
|
||||||
|
tm = trans.Time(time.Now())
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp := tm.Format("20060102150405")
|
||||||
|
|
||||||
|
randNum := rand.Intn(10000) // 生成0-9999之间的随机数
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s%s%d", prefix, timestamp, randNum)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateOrderIdWithIncreaseIndex 生成20位订单号,前缀+时间+自增长索引
|
||||||
|
func GenerateOrderIdWithIncreaseIndex(prefix string, tm *time.Time) string {
|
||||||
|
if tm == nil {
|
||||||
|
tm = trans.Time(time.Now())
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp := tm.Format("20060102150405")
|
||||||
|
|
||||||
|
index := orderIdIndex.Increase()
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s%s%d", prefix, timestamp, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateOrderIdWithTenantId 带商户ID的订单ID生成器:202506041234567890123
|
||||||
|
func GenerateOrderIdWithTenantId(tenantID string) string {
|
||||||
|
// 时间戳(14位) + 商户ID(固定 5 位) + 随机数(4位)
|
||||||
|
|
||||||
|
// 时间戳部分(精确到毫秒)
|
||||||
|
now := time.Now()
|
||||||
|
timestamp := now.Format("20060102150405")
|
||||||
|
|
||||||
|
// 商户ID部分(截取或补零到5位)
|
||||||
|
tenantPart := tenantID
|
||||||
|
if len(tenantPart) > 5 {
|
||||||
|
tenantPart = tenantPart[:5]
|
||||||
|
} else {
|
||||||
|
tenantPart = fmt.Sprintf("%-5s", tenantPart)
|
||||||
|
tenantPart = strings.ReplaceAll(tenantPart, " ", "0")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 随机数部分(4位)
|
||||||
|
n := rand.Int31n(10000)
|
||||||
|
randomPart := fmt.Sprintf("%04d", n)
|
||||||
|
|
||||||
|
return timestamp + tenantPart + randomPart
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateOrderIdWithPrefixSonyflake(prefix string) string {
|
||||||
|
id, _ := NewSonyflakeID()
|
||||||
|
return fmt.Sprintf("%s%d", prefix, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateOrderIdWithPrefixSnowflake(workerId int64, prefix string) string {
|
||||||
|
id, _ := NewSnowflakeID(workerId)
|
||||||
|
return fmt.Sprintf("%s%d", prefix, id)
|
||||||
|
}
|
||||||
158
id/order_id_test.go
Normal file
158
id/order_id_test.go
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
package id
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGenerateOrderIdWithRandom(t *testing.T) {
|
||||||
|
prefix := "PT"
|
||||||
|
|
||||||
|
// 测试生成的订单号是否包含前缀
|
||||||
|
orderID := GenerateOrderIdWithRandom(prefix, nil)
|
||||||
|
assert.Contains(t, orderID, prefix, "订单号应包含前缀")
|
||||||
|
t.Logf("GenerateOrderIdWithRandom: %s", orderID)
|
||||||
|
|
||||||
|
// 测试生成的订单号长度是否正确
|
||||||
|
assert.Equal(t, len(prefix)+14+4, len(orderID), "订单号长度应为前缀+时间戳+随机数")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateOrderIdWithIndex(t *testing.T) {
|
||||||
|
prefix := "PT"
|
||||||
|
|
||||||
|
tm := time.Now()
|
||||||
|
|
||||||
|
fmt.Println(GenerateOrderIdWithIncreaseIndex(prefix, &(tm)))
|
||||||
|
|
||||||
|
ids := make(map[string]bool)
|
||||||
|
count := 100
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
ids[GenerateOrderIdWithIncreaseIndex(prefix, &(tm))] = true
|
||||||
|
}
|
||||||
|
assert.Equal(t, count, len(ids))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateOrderIdWithIndexThread(t *testing.T) {
|
||||||
|
tm := time.Now()
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
var ids sync.Map
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
id := GenerateOrderIdWithIncreaseIndex("PT", &(tm))
|
||||||
|
ids.Store(id, true)
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
aLen := 0
|
||||||
|
ids.Range(func(k, v interface{}) bool {
|
||||||
|
aLen++
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
assert.Equal(t, 1000, aLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateOrderIdWithTenantId(t *testing.T) {
|
||||||
|
tenantID := "M9876"
|
||||||
|
orderID := GenerateOrderIdWithTenantId(tenantID)
|
||||||
|
|
||||||
|
t.Logf(orderID)
|
||||||
|
|
||||||
|
// 验证订单号长度是否正确
|
||||||
|
assert.Equal(t, 14+5+4, len(orderID))
|
||||||
|
|
||||||
|
// 验证时间戳部分是否正确
|
||||||
|
timestamp := time.Now().Format("20060102150405")
|
||||||
|
assert.Contains(t, orderID, timestamp)
|
||||||
|
t.Logf("timestamp %d", len(timestamp))
|
||||||
|
|
||||||
|
// 验证商户ID部分是否正确
|
||||||
|
assert.Contains(t, orderID, tenantID)
|
||||||
|
|
||||||
|
// 验证随机数部分是否为4位数字
|
||||||
|
randomPart := orderID[len(orderID)-4:]
|
||||||
|
assert.Regexp(t, `^\d{4}$`, randomPart)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateOrderIdWithTenantIdCollision(t *testing.T) {
|
||||||
|
tenantID := "M9876"
|
||||||
|
count := 1000 // 生成订单号的数量
|
||||||
|
ids := make(map[string]bool)
|
||||||
|
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
orderID := GenerateOrderIdWithTenantId(tenantID)
|
||||||
|
if ids[orderID] {
|
||||||
|
t.Errorf("碰撞的订单号: %s", orderID)
|
||||||
|
}
|
||||||
|
ids[orderID] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("生成了 %d 个订单号,没有发生碰撞", count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateOrderIdWithPrefixSonyflake(t *testing.T) {
|
||||||
|
prefix := "ORD"
|
||||||
|
orderID := GenerateOrderIdWithPrefixSonyflake(prefix)
|
||||||
|
t.Logf("order id with prefix sonyflake: %s [%d]", orderID, len(orderID))
|
||||||
|
|
||||||
|
// 验证订单号是否包含前缀
|
||||||
|
assert.Contains(t, orderID, prefix, "订单号应包含前缀")
|
||||||
|
|
||||||
|
// 验证订单号是否为有效的数字字符串
|
||||||
|
assert.Regexp(t, `^ORD\d+$`, orderID, "订单号格式应为前缀加数字")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateOrderIdWithPrefixSonyflakeCollision(t *testing.T) {
|
||||||
|
prefix := "ORD"
|
||||||
|
count := 100000 // 生成订单号的数量
|
||||||
|
ids := make(map[string]bool)
|
||||||
|
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
orderID := GenerateOrderIdWithPrefixSonyflake(prefix)
|
||||||
|
if ids[orderID] {
|
||||||
|
t.Errorf("碰撞的订单号: %s", orderID)
|
||||||
|
}
|
||||||
|
ids[orderID] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("生成了 %d 个订单号,没有发生碰撞", count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateOrderIdWithPrefixSnowflake(t *testing.T) {
|
||||||
|
workerId := int64(1) // 假设使用的 workerId
|
||||||
|
prefix := "ORD"
|
||||||
|
orderID := GenerateOrderIdWithPrefixSnowflake(workerId, prefix)
|
||||||
|
t.Logf("order id with prefix snowflake: %s [%d]", orderID, len(orderID))
|
||||||
|
|
||||||
|
// 验证订单号是否包含前缀
|
||||||
|
assert.Contains(t, orderID, prefix, "订单号应包含前缀")
|
||||||
|
|
||||||
|
// 验证订单号是否为有效的数字字符串
|
||||||
|
assert.Regexp(t, `^ORD\d+$`, orderID, "订单号格式应为前缀加数字")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateOrderIdWithPrefixSnowflakeCollision(t *testing.T) {
|
||||||
|
workerId := int64(1) // 假设使用的 workerId
|
||||||
|
prefix := "ORD"
|
||||||
|
count := 1000000 // 生成订单号的数量
|
||||||
|
ids := make(map[string]bool)
|
||||||
|
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
orderID := GenerateOrderIdWithPrefixSnowflake(workerId, prefix)
|
||||||
|
if ids[orderID] {
|
||||||
|
t.Errorf("碰撞的订单号: %s", orderID)
|
||||||
|
}
|
||||||
|
ids[orderID] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("生成了 %d 个订单号,没有发生碰撞", count)
|
||||||
|
}
|
||||||
66
id/snowflake.go
Normal file
66
id/snowflake.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package id
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/bwmarrin/snowflake"
|
||||||
|
)
|
||||||
|
|
||||||
|
var snowflakeNodeMap = sync.Map{}
|
||||||
|
|
||||||
|
type SnowflakeNode struct {
|
||||||
|
workerId int64
|
||||||
|
node *snowflake.Node
|
||||||
|
sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSnowflakeNode(workerId int64) (*SnowflakeNode, error) {
|
||||||
|
node, err := snowflake.NewNode(workerId)
|
||||||
|
return &SnowflakeNode{
|
||||||
|
workerId: workerId,
|
||||||
|
node: node,
|
||||||
|
Mutex: sync.Mutex{},
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sfNode *SnowflakeNode) Generate() int64 {
|
||||||
|
sfNode.Lock()
|
||||||
|
defer sfNode.Unlock()
|
||||||
|
return sfNode.node.Generate().Int64()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sfNode *SnowflakeNode) GenerateString() string {
|
||||||
|
sfNode.Lock()
|
||||||
|
defer sfNode.Unlock()
|
||||||
|
return sfNode.node.Generate().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSnowflakeID(workerId int64) (int64, error) {
|
||||||
|
// 64 位 ID = 41 位时间戳 + 10 位工作节点 ID + 12 位序列号
|
||||||
|
|
||||||
|
var node *SnowflakeNode
|
||||||
|
var err error
|
||||||
|
find, ok := snowflakeNodeMap.Load(workerId)
|
||||||
|
if ok {
|
||||||
|
node = find.(*SnowflakeNode)
|
||||||
|
} else {
|
||||||
|
node, err = NewSnowflakeNode(workerId)
|
||||||
|
if err != nil {
|
||||||
|
//log.Println(err)
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
snowflakeNodeMap.Store(workerId, node)
|
||||||
|
}
|
||||||
|
if node == nil {
|
||||||
|
//log.Println("snowflake node is nil")
|
||||||
|
return 0, errors.New("snowflake node is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
return node.Generate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateSnowflakeID(workerId int64) int64 {
|
||||||
|
id, _ := NewSnowflakeID(workerId)
|
||||||
|
return id
|
||||||
|
}
|
||||||
30
id/sonyflake.go
Normal file
30
id/sonyflake.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package id
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sony/sonyflake"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
sf *sonyflake.Sonyflake
|
||||||
|
sfMu sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewSonyflakeID() (uint64, error) {
|
||||||
|
// 64 位 ID = 39 位时间戳 + 8 位机器 ID + 16 位序列号
|
||||||
|
|
||||||
|
sfMu.Lock()
|
||||||
|
defer sfMu.Unlock()
|
||||||
|
|
||||||
|
if sf == nil {
|
||||||
|
sf = sonyflake.NewSonyflake(sonyflake.Settings{})
|
||||||
|
}
|
||||||
|
|
||||||
|
return sf.NextID()
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateSonyflakeID() uint64 {
|
||||||
|
id, _ := NewSonyflakeID()
|
||||||
|
return id
|
||||||
|
}
|
||||||
36
id/uuid.go
Normal file
36
id/uuid.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package id
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/lithammer/shortuuid/v4"
|
||||||
|
"github.com/rs/xid"
|
||||||
|
"github.com/segmentio/ksuid"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewGUIDv4(withHyphen bool) string {
|
||||||
|
id := uuid.NewString()
|
||||||
|
if !withHyphen {
|
||||||
|
id = strings.ReplaceAll(id, "-", "")
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewShortUUID() string {
|
||||||
|
return shortuuid.New()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewKSUID() string {
|
||||||
|
return ksuid.New().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewXID() string {
|
||||||
|
return xid.New().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMongoObjectID() string {
|
||||||
|
objID := primitive.NewObjectID()
|
||||||
|
return objID.String()
|
||||||
|
}
|
||||||
267
id/uuid_test.go
Normal file
267
id/uuid_test.go
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
package id
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewGUIDv4(t *testing.T) {
|
||||||
|
// 测试带有连字符的 GUID
|
||||||
|
withHyphen := NewGUIDv4(true)
|
||||||
|
assert.NotEmpty(t, withHyphen)
|
||||||
|
assert.Equal(t, 4, strings.Count(withHyphen, "-"), "GUID 应包含 4 个连字符")
|
||||||
|
|
||||||
|
// 测试不带连字符的 GUID
|
||||||
|
withoutHyphen := NewGUIDv4(false)
|
||||||
|
assert.NotEmpty(t, withoutHyphen)
|
||||||
|
assert.Equal(t, 0, strings.Count(withoutHyphen, "-"), "GUID 不应包含连字符")
|
||||||
|
|
||||||
|
// 验证 GUID 的长度
|
||||||
|
assert.Equal(t, 36, len(withHyphen), "带连字符的 GUID 长度应为 36")
|
||||||
|
assert.Equal(t, 32, len(withoutHyphen), "不带连字符的 GUID 长度应为 32")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewGUIDv4CollisionRate(t *testing.T) {
|
||||||
|
const (
|
||||||
|
testCount = 100000 // 测试生成的GUID数量
|
||||||
|
withHyphen = true // 是否带连字符
|
||||||
|
)
|
||||||
|
|
||||||
|
ids := make(map[string]struct{})
|
||||||
|
for i := 0; i < testCount; i++ {
|
||||||
|
id := NewGUIDv4(withHyphen)
|
||||||
|
if _, exists := ids[id]; exists {
|
||||||
|
t.Errorf("碰撞发生: %s 已存在", id)
|
||||||
|
}
|
||||||
|
ids[id] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("生成了 %d 个GUID,无碰撞。", testCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewShortUUID(t *testing.T) {
|
||||||
|
// 测试生成的 ShortUUID 是否非空
|
||||||
|
id := NewShortUUID()
|
||||||
|
assert.NotEmpty(t, id, "生成的 ShortUUID 应该非空")
|
||||||
|
|
||||||
|
// 测试生成的 ShortUUID 的长度是否符合预期
|
||||||
|
assert.True(t, len(id) > 0, "生成的 ShortUUID 长度应该大于 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewShortUUIDCollisionRate(t *testing.T) {
|
||||||
|
const testCount = 100000 // 测试生成的ShortUUID数量
|
||||||
|
|
||||||
|
ids := make(map[string]struct{})
|
||||||
|
for i := 0; i < testCount; i++ {
|
||||||
|
id := NewShortUUID()
|
||||||
|
if _, exists := ids[id]; exists {
|
||||||
|
t.Errorf("碰撞发生: %s 已存在", id)
|
||||||
|
}
|
||||||
|
ids[id] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("生成了 %d 个ShortUUID,无碰撞。", testCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewKSUID(t *testing.T) {
|
||||||
|
// 测试生成的 KSUID 是否非空
|
||||||
|
id := NewKSUID()
|
||||||
|
assert.NotEmpty(t, id, "生成的 KSUID 应该非空")
|
||||||
|
|
||||||
|
// 测试生成的 KSUID 的长度是否符合预期
|
||||||
|
assert.Equal(t, 27, len(id), "生成的 KSUID 长度应该为 27")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewKSUIDCollisionRate(t *testing.T) {
|
||||||
|
const testCount = 100000 // 测试生成的KSUID数量
|
||||||
|
|
||||||
|
ids := make(map[string]struct{})
|
||||||
|
for i := 0; i < testCount; i++ {
|
||||||
|
id := NewKSUID()
|
||||||
|
if _, exists := ids[id]; exists {
|
||||||
|
t.Errorf("碰撞发生: %s 已存在", id)
|
||||||
|
}
|
||||||
|
ids[id] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("生成了 %d 个KSUID,无碰撞。", testCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewXID(t *testing.T) {
|
||||||
|
// 测试生成的 XID 是否非空
|
||||||
|
id := NewXID()
|
||||||
|
assert.NotEmpty(t, id, "生成的 XID 应该非空")
|
||||||
|
t.Logf("xid: %s", id)
|
||||||
|
|
||||||
|
// 测试生成的 XID 的长度是否符合预期
|
||||||
|
assert.Equal(t, 20, len(id), "生成的 XID 长度应该为 20")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewXIDCollisionRate(t *testing.T) {
|
||||||
|
const testCount = 100000 // 测试生成的XID数量
|
||||||
|
|
||||||
|
ids := make(map[string]struct{})
|
||||||
|
for i := 0; i < testCount; i++ {
|
||||||
|
id := NewXID()
|
||||||
|
if _, exists := ids[id]; exists {
|
||||||
|
t.Errorf("碰撞发生: %s 已存在", id)
|
||||||
|
}
|
||||||
|
ids[id] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("生成了 %d 个XID,无碰撞。", testCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewSnowflakeID(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
workerId int64
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{0, false}, // 有效的 workerId
|
||||||
|
{31, false}, // 有效的 workerId
|
||||||
|
{32, false}, // 有效的 workerId
|
||||||
|
|
||||||
|
{-1, true}, // 无效的 workerId
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
id, err := NewSnowflakeID(tt.workerId)
|
||||||
|
if (err != nil) != tt.expectErr {
|
||||||
|
t.Errorf("NewSnowflakeID(%d) 错误状态不符合预期: %v", tt.workerId, err)
|
||||||
|
}
|
||||||
|
if err == nil && id <= 0 {
|
||||||
|
t.Errorf("NewSnowflakeID(%d) 生成的ID无效: %d", tt.workerId, id)
|
||||||
|
}
|
||||||
|
t.Logf("NewSnowflakeID(%d) ID: %d", tt.workerId, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewSnowflakeIDCollisionRate(t *testing.T) {
|
||||||
|
const (
|
||||||
|
workerId = 0
|
||||||
|
testCount = 100000 // 测试生成的ID数量
|
||||||
|
)
|
||||||
|
|
||||||
|
ids := make(map[int64]struct{})
|
||||||
|
for i := 0; i < testCount; i++ {
|
||||||
|
id, err := NewSnowflakeID(workerId)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("生成ID时出现错误: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, exists := ids[id]; exists {
|
||||||
|
t.Errorf("碰撞发生: %d 已存在", id)
|
||||||
|
}
|
||||||
|
ids[id] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("生成了 %d 个Snowflake ID,无碰撞。", testCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConcurrentNewSnowflakeIDCollisionRate(t *testing.T) {
|
||||||
|
const (
|
||||||
|
workerId = 0 // Snowflake 工作节点 ID
|
||||||
|
testCount = 100000 // 测试生成的 ID 数量
|
||||||
|
workerCount = 10 // 并发工作线程数
|
||||||
|
)
|
||||||
|
|
||||||
|
var mu sync.Mutex
|
||||||
|
ids := make(map[int64]struct{})
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
for w := 0; w < workerCount; w++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for i := 0; i < testCount/workerCount; i++ {
|
||||||
|
id, err := NewSnowflakeID(workerId)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("生成 Snowflake ID 时出现错误: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mu.Lock()
|
||||||
|
if _, exists := ids[id]; exists {
|
||||||
|
t.Errorf("碰撞发生: %d 已存在", id)
|
||||||
|
}
|
||||||
|
ids[id] = struct{}{}
|
||||||
|
mu.Unlock()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
t.Logf("生成了 %d 个 Snowflake ID,无碰撞。", testCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewSonyflakeID(t *testing.T) {
|
||||||
|
// 测试生成的 Sonyflake ID 是否有效
|
||||||
|
id, err := NewSonyflakeID()
|
||||||
|
t.Logf("sonyflake id: %v", id)
|
||||||
|
assert.NoError(t, err, "生成 Sonyflake ID 时不应出现错误")
|
||||||
|
assert.True(t, id > 0, "生成的 Sonyflake ID 应该是正数")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewSonyflakeIDCollisionRate(t *testing.T) {
|
||||||
|
const testCount = 100000 // 测试生成的 Sonyflake ID 数量
|
||||||
|
|
||||||
|
ids := make(map[uint64]struct{})
|
||||||
|
for i := 0; i < testCount; i++ {
|
||||||
|
id, err := NewSonyflakeID()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("生成 Sonyflake ID 时出现错误: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, exists := ids[id]; exists {
|
||||||
|
t.Errorf("碰撞发生: %d 已存在", id)
|
||||||
|
}
|
||||||
|
ids[id] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("生成了 %d 个 Sonyflake ID,无碰撞。", testCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConcurrentNewSonyflakeIDCollisionRate(t *testing.T) {
|
||||||
|
const (
|
||||||
|
testCount = 100000 // 测试生成的 Sonyflake ID 数量
|
||||||
|
workerCount = 10 // 并发工作线程数
|
||||||
|
)
|
||||||
|
|
||||||
|
var mu sync.Mutex
|
||||||
|
ids := make(map[uint64]struct{})
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
for w := 0; w < workerCount; w++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for i := 0; i < testCount/workerCount; i++ {
|
||||||
|
id, err := NewSonyflakeID()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("生成 Sonyflake ID 时出现错误: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mu.Lock()
|
||||||
|
if _, exists := ids[id]; exists {
|
||||||
|
t.Errorf("碰撞发生: %d 已存在", id)
|
||||||
|
}
|
||||||
|
ids[id] = struct{}{}
|
||||||
|
mu.Unlock()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
t.Logf("生成了 %d 个 Sonyflake ID,无碰撞。", testCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewMongoObjectID(t *testing.T) {
|
||||||
|
// 测试生成的 ObjectID 是否非空
|
||||||
|
id := NewMongoObjectID()
|
||||||
|
assert.NotEmpty(t, id, "生成的 Mongo ObjectID 应该非空")
|
||||||
|
|
||||||
|
// 测试生成的 ObjectID 的长度是否符合预期
|
||||||
|
assert.Equal(t, 36, len(id), "生成的 Mongo ObjectID 长度应该为 36")
|
||||||
|
}
|
||||||
21
jwtutil/go.mod
Normal file
21
jwtutil/go.mod
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
module github.com/tx7do/go-utils/jwtutil
|
||||||
|
|
||||||
|
go 1.23.0
|
||||||
|
|
||||||
|
toolchain go1.24.1
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/go-kratos/kratos/v2 v2.8.4
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.1.0
|
||||||
|
github.com/stretchr/testify v1.10.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/kr/text v0.2.0 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.33.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
replace github.com/tx7do/go-utils => ../
|
||||||
28
jwtutil/go.sum
Normal file
28
jwtutil/go.sum
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/go-kratos/kratos/v2 v2.8.4 h1:eIJLE9Qq9WSoKx+Buy2uPyrahtF/lPh+Xf4MTpxhmjs=
|
||||||
|
github.com/go-kratos/kratos/v2 v2.8.4/go.mod h1:mq62W2101a5uYyRxe+7IdWubu7gZCGYqSNKwGFiiRcw=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.1.0 h1:UGKbA/IPjtS6zLcdB7i5TyACMgSbOTiR8qzXgw8HWQU=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.1.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
|
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||||
|
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||||
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||||
|
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
355
jwtutil/jwt.go
Normal file
355
jwtutil/jwt.go
Normal file
@@ -0,0 +1,355 @@
|
|||||||
|
package jwtutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-kratos/kratos/v2/encoding"
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseJWTPayload 使用 github.com/golang-jwt/jwt/v5 从 JWT 中解析出 payload
|
||||||
|
func ParseJWTPayload(tokenString string) (jwt.MapClaims, error) {
|
||||||
|
// 不验证签名,仅解析
|
||||||
|
parser := jwt.NewParser(jwt.WithoutClaimsValidation())
|
||||||
|
token, _, err := parser.ParseUnverified(tokenString, jwt.MapClaims{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, ok := token.Claims.(jwt.MapClaims)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid token claims")
|
||||||
|
}
|
||||||
|
|
||||||
|
return claims, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseJWTClaimsToStruct 解析 JWT 的负载部分,不验证签名,仅解析。
|
||||||
|
func ParseJWTClaimsToStruct[T any](tokenString string) (*T, error) {
|
||||||
|
// 不验证签名,仅解析
|
||||||
|
parser := jwt.NewParser(jwt.WithoutClaimsValidation())
|
||||||
|
token, _, err := parser.ParseUnverified(tokenString, jwt.MapClaims{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, ok := token.Claims.(jwt.MapClaims)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid token claims")
|
||||||
|
}
|
||||||
|
|
||||||
|
codec := encoding.GetCodec("json")
|
||||||
|
|
||||||
|
// 将 claims 转换为目标类型
|
||||||
|
claimsBytes, err := codec.Marshal(claims)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to marshal claims: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ret T
|
||||||
|
err = codec.Unmarshal(claimsBytes, &ret)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to unmarshal claims: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyJWT 验证 JWT 的签名
|
||||||
|
func VerifyJWT(tokenString string, secretKey []byte) (jwt.MapClaims, error) {
|
||||||
|
// 解析并验证 JWT
|
||||||
|
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||||
|
// 确保使用的是 HMAC 签名方法
|
||||||
|
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||||
|
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||||
|
}
|
||||||
|
return secretKey, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证 token 是否有效
|
||||||
|
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
|
||||||
|
return claims, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("invalid token")
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetJWTClaims(tokenString string) (map[string]interface{}, error) {
|
||||||
|
claims, err := ParseJWTPayload(tokenString)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return claims, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateJWT 生成 JWT
|
||||||
|
func GenerateJWT(mapClaims jwt.MapClaims, secretKey []byte, signingMethod jwt.SigningMethod) (string, error) {
|
||||||
|
// 检查密钥是否为空,密钥不能为空,否则会有安全隐患。
|
||||||
|
if len(secretKey) == 0 {
|
||||||
|
return "", fmt.Errorf("secret key cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建一个新的 JWT Token
|
||||||
|
token := jwt.NewWithClaims(signingMethod, mapClaims)
|
||||||
|
|
||||||
|
// 使用 HMAC 签名方法签名 token
|
||||||
|
signedToken, err := token.SignedString(secretKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return signedToken, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateGenericJWT 生成 JWT
|
||||||
|
func GenerateGenericJWT[T any](payload T, secretKey []byte, signingMethod jwt.SigningMethod) (string, error) {
|
||||||
|
// 检查密钥是否为空,密钥不能为空,否则会有安全隐患。
|
||||||
|
if len(secretKey) == 0 {
|
||||||
|
return "", fmt.Errorf("secret key cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将泛型 payload 转换为 jwt.MapClaims
|
||||||
|
claimsMap, err := ToMapClaims(payload)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to convert payload to claims: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建一个新的 JWT Token
|
||||||
|
token := jwt.NewWithClaims(signingMethod, claimsMap)
|
||||||
|
|
||||||
|
// 使用 HMAC 签名方法签名 token
|
||||||
|
signedToken, err := token.SignedString(secretKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return signedToken, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToMapClaims 将泛型 payload 转换为 jwt.MapClaims
|
||||||
|
func ToMapClaims[T any](payload T) (jwt.MapClaims, error) {
|
||||||
|
codec := encoding.GetCodec("json")
|
||||||
|
|
||||||
|
payloadBytes, err := codec.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var claims jwt.MapClaims
|
||||||
|
err = codec.Unmarshal(payloadBytes, &claims)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return claims, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RefreshJWT 刷新JWT
|
||||||
|
func RefreshJWT(tokenString string, secretKey []byte, newExpiration time.Time) (string, error) {
|
||||||
|
// 解析 JWT 的 payload
|
||||||
|
claims, err := ParseJWTPayload(tokenString)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新过期时间
|
||||||
|
claims["exp"] = newExpiration.Unix()
|
||||||
|
|
||||||
|
// 生成新的 JWT
|
||||||
|
newToken, err := GenerateJWT(claims, secretKey, jwt.SigningMethodHS256)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return newToken, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateJWTWithHeader 生成带自定义头部的JWT
|
||||||
|
func GenerateJWTWithHeader(payload jwt.MapClaims, secretKey []byte, signingMethod jwt.SigningMethod, customHeader map[string]interface{}) (string, error) {
|
||||||
|
// 检查密钥是否为空
|
||||||
|
if len(secretKey) == 0 {
|
||||||
|
return "", fmt.Errorf("secret key cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建一个新的 JWT Token
|
||||||
|
token := jwt.NewWithClaims(signingMethod, payload)
|
||||||
|
|
||||||
|
// 添加自定义头部
|
||||||
|
for key, value := range customHeader {
|
||||||
|
token.Header[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用密钥签名生成 JWT
|
||||||
|
signedToken, err := token.SignedString(secretKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return signedToken, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractJWTFromRequest 从请求中提取JWT
|
||||||
|
func ExtractJWTFromRequest(r *http.Request) (string, error) {
|
||||||
|
// 从 Authorization Header 中提取 JWT
|
||||||
|
authHeader := r.Header.Get("Authorization")
|
||||||
|
if authHeader == "" {
|
||||||
|
return "", fmt.Errorf("authorization header is missing")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否以 "Bearer " 开头
|
||||||
|
const bearerPrefix = "Bearer "
|
||||||
|
if len(authHeader) <= len(bearerPrefix) || authHeader[:len(bearerPrefix)] != bearerPrefix {
|
||||||
|
return "", fmt.Errorf("invalid authorization header format")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取 JWT 部分
|
||||||
|
token := authHeader[len(bearerPrefix):]
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateShortLivedJWT 生成短期有效的JWT
|
||||||
|
func GenerateShortLivedJWT(payload jwt.MapClaims, secretKey []byte, signingMethod jwt.SigningMethod, duration time.Duration) (string, error) {
|
||||||
|
// 检查密钥是否为空
|
||||||
|
if len(secretKey) == 0 {
|
||||||
|
return "", fmt.Errorf("secret key cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置过期时间
|
||||||
|
expirationTime := time.Now().Add(duration).Unix()
|
||||||
|
payload["exp"] = expirationTime
|
||||||
|
|
||||||
|
// 创建一个新的 JWT Token
|
||||||
|
token := jwt.NewWithClaims(signingMethod, payload)
|
||||||
|
|
||||||
|
// 使用密钥签名生成 JWT
|
||||||
|
signedToken, err := token.SignedString(secretKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return signedToken, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateJWTAudience 验证JWT受众
|
||||||
|
func ValidateJWTAudience(tokenString string, expectedAudience string) (bool, error) {
|
||||||
|
// 解析 JWT 的 payload
|
||||||
|
claims, err := ParseJWTPayload(tokenString)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 `aud` 字段
|
||||||
|
audience, err := claims.GetAudience()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证 `aud` 是否包含预期值
|
||||||
|
for _, aud := range audience {
|
||||||
|
if aud == expectedAudience {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateJWTAlgorithm 验证JWT算法
|
||||||
|
func ValidateJWTAlgorithm(tokenString string, expectedAlgorithm string) (bool, error) {
|
||||||
|
// 使用 jwt.Parser 解析 JWT,不验证签名
|
||||||
|
parser := jwt.NewParser(jwt.WithoutClaimsValidation())
|
||||||
|
token, _, err := parser.ParseUnverified(tokenString, jwt.MapClaims{})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 `alg` 字段
|
||||||
|
alg, ok := token.Header["alg"].(string)
|
||||||
|
if !ok {
|
||||||
|
return false, fmt.Errorf("invalid or missing algorithm in token header")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证算法是否符合预期
|
||||||
|
if alg != expectedAlgorithm {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsJWTExpired 检查JWT是否过期
|
||||||
|
func IsJWTExpired(tokenString string) (bool, error) {
|
||||||
|
// 解析 JWT 的 payload
|
||||||
|
claims, err := ParseJWTPayload(tokenString)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 `exp` 字段
|
||||||
|
exp, err := claims.GetExpirationTime()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查当前时间是否超过 `exp`
|
||||||
|
if time.Now().After(exp.Time) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetJWTHeader 获取JWT头部
|
||||||
|
func GetJWTHeader(tokenString string) (map[string]interface{}, error) {
|
||||||
|
// 使用 jwt.Parser 解析 JWT,不验证签名
|
||||||
|
parser := jwt.NewParser(jwt.WithoutClaimsValidation())
|
||||||
|
token, _, err := parser.ParseUnverified(tokenString, jwt.MapClaims{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return token.Header, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateJWTIssuer 验证JWT发行者
|
||||||
|
func ValidateJWTIssuer(tokenString string, expectedIssuer string) (bool, error) {
|
||||||
|
// 解析 JWT 的 payload
|
||||||
|
claims, err := ParseJWTPayload(tokenString)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 `iss` 字段
|
||||||
|
issuer, err := claims.GetIssuer()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证 `iss` 是否符合预期
|
||||||
|
if issuer != expectedIssuer {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetJWTIssuedAt 获取JWT签发时间
|
||||||
|
func GetJWTIssuedAt(tokenString string) (*time.Time, error) {
|
||||||
|
claims, err := ParseJWTPayload(tokenString)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
iat, err := claims.GetIssuedAt()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &iat.Time, nil
|
||||||
|
}
|
||||||
519
jwtutil/jwt_test.go
Normal file
519
jwtutil/jwt_test.go
Normal file
@@ -0,0 +1,519 @@
|
|||||||
|
package jwtutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
_ "github.com/go-kratos/kratos/v2/encoding/json"
|
||||||
|
_ "github.com/go-kratos/kratos/v2/encoding/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseJWTPayload(t *testing.T) {
|
||||||
|
var secretKey = []byte("secret")
|
||||||
|
|
||||||
|
// 测试有效的 JWT 负载解析
|
||||||
|
token, err := GenerateJWT(jwt.MapClaims{
|
||||||
|
"sub": "userId",
|
||||||
|
"exp": 1672728000,
|
||||||
|
}, secretKey, jwt.SigningMethodHS256)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create token: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := ParseJWTPayload(token)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, result)
|
||||||
|
assert.Equal(t, "userId", result["sub"])
|
||||||
|
assert.Equal(t, float64(1672728000), result["exp"]) // 注意:JSON 解码后数字为 float64
|
||||||
|
|
||||||
|
// 测试无效的 JWT
|
||||||
|
invalidToken := "invalid.token.string"
|
||||||
|
result, err = ParseJWTPayload(invalidToken)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, result)
|
||||||
|
|
||||||
|
// 测试空字符串
|
||||||
|
result, err = ParseJWTPayload("")
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseJWTClaimsToStruct(t *testing.T) {
|
||||||
|
// 测试有效的 JWT 负载解析
|
||||||
|
type Payload struct {
|
||||||
|
Sub string `json:"sub"`
|
||||||
|
Exp int64 `json:"exp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var secretKey = []byte("secret")
|
||||||
|
|
||||||
|
// 验证签名以生成有效的 token
|
||||||
|
token, err := GenerateJWT(jwt.MapClaims{
|
||||||
|
"sub": "userId",
|
||||||
|
"exp": 1672728000,
|
||||||
|
}, secretKey, jwt.SigningMethodHS256)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create token: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := ParseJWTClaimsToStruct[Payload](token)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, result)
|
||||||
|
assert.Equal(t, "userId", result.Sub)
|
||||||
|
assert.Equal(t, int64(1672728000), result.Exp)
|
||||||
|
|
||||||
|
// 测试无效的 JWT
|
||||||
|
invalidToken := "invalid.token.string"
|
||||||
|
result, err = ParseJWTClaimsToStruct[Payload](invalidToken)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, result)
|
||||||
|
|
||||||
|
// 测试空字符串
|
||||||
|
result, err = ParseJWTClaimsToStruct[Payload]("")
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyJWT(t *testing.T) {
|
||||||
|
// 测试有效的 JWT 验证
|
||||||
|
secretKey := []byte("secret")
|
||||||
|
|
||||||
|
token, err := GenerateJWT(jwt.MapClaims{
|
||||||
|
"sub": "userId",
|
||||||
|
}, secretKey, jwt.SigningMethodHS256)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create token: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := VerifyJWT(token, secretKey)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, result)
|
||||||
|
assert.Equal(t, "userId", result["sub"])
|
||||||
|
|
||||||
|
// 测试无效的签名
|
||||||
|
invalidSecretKey := []byte("invalid_secret")
|
||||||
|
result, err = VerifyJWT(token, invalidSecretKey)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, result)
|
||||||
|
|
||||||
|
// 测试无效的 JWT 格式
|
||||||
|
invalidToken := "invalid.token.string"
|
||||||
|
result, err = VerifyJWT(invalidToken, secretKey)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, result)
|
||||||
|
|
||||||
|
// 测试空字符串
|
||||||
|
result, err = VerifyJWT("", secretKey)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateGenericJWT(t *testing.T) {
|
||||||
|
// 定义测试用的 payload 和密钥
|
||||||
|
type Payload struct {
|
||||||
|
Sub string `json:"sub"`
|
||||||
|
Exp int64 `json:"exp"`
|
||||||
|
}
|
||||||
|
secretKey := []byte("secret")
|
||||||
|
payload := Payload{
|
||||||
|
Sub: "userId",
|
||||||
|
Exp: 1672728000,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试生成有效的 JWT
|
||||||
|
token, err := GenerateGenericJWT(payload, secretKey, jwt.SigningMethodHS256)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, token)
|
||||||
|
|
||||||
|
// 验证生成的 JWT 是否正确
|
||||||
|
parsedPayload, err := ParseJWTClaimsToStruct[Payload](token)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, parsedPayload)
|
||||||
|
assert.Equal(t, payload.Sub, parsedPayload.Sub)
|
||||||
|
assert.Equal(t, payload.Exp, parsedPayload.Exp)
|
||||||
|
|
||||||
|
// 测试使用空密钥生成 JWT
|
||||||
|
emptySecretKey := []byte("")
|
||||||
|
token, err = GenerateGenericJWT(payload, emptySecretKey, jwt.SigningMethodHS256)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Empty(t, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateJWT(t *testing.T) {
|
||||||
|
// 定义测试用的 payload 和密钥
|
||||||
|
payload := jwt.MapClaims{
|
||||||
|
"sub": "userId",
|
||||||
|
"exp": 1672728000,
|
||||||
|
}
|
||||||
|
secretKey := []byte("secret")
|
||||||
|
|
||||||
|
// 测试生成有效的 JWT
|
||||||
|
token, err := GenerateJWT(payload, secretKey, jwt.SigningMethodHS256)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, token)
|
||||||
|
|
||||||
|
// 验证生成的 JWT 是否正确
|
||||||
|
parsedPayload, err := ParseJWTPayload(token)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, parsedPayload)
|
||||||
|
assert.Equal(t, payload["sub"], parsedPayload["sub"])
|
||||||
|
assert.Equal(t, float64(payload["exp"].(int)), parsedPayload["exp"].(float64)) // 注意:JSON 解码后数字为 float64
|
||||||
|
|
||||||
|
// 测试使用空密钥生成 JWT
|
||||||
|
emptySecretKey := []byte("")
|
||||||
|
token, err = GenerateJWT(payload, emptySecretKey, jwt.SigningMethodHS256)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Empty(t, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRefreshJWT(t *testing.T) {
|
||||||
|
secretKey := []byte("secret")
|
||||||
|
|
||||||
|
// 创建一个初始的 JWT
|
||||||
|
originalToken, err := GenerateJWT(jwt.MapClaims{
|
||||||
|
"sub": "userId",
|
||||||
|
"exp": time.Now().Add(1 * time.Hour).Unix(),
|
||||||
|
}, secretKey, jwt.SigningMethodHS256)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create token: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新 JWT,设置新的过期时间
|
||||||
|
newExpiration := time.Now().Add(2 * time.Hour)
|
||||||
|
refreshedToken, err := RefreshJWT(originalToken, secretKey, newExpiration)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, refreshedToken)
|
||||||
|
|
||||||
|
// 验证刷新后的 JWT 是否包含新的过期时间
|
||||||
|
claims, err := ParseJWTPayload(refreshedToken)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, claims)
|
||||||
|
|
||||||
|
exp, ok := claims["exp"].(float64)
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, newExpiration.Unix(), int64(exp))
|
||||||
|
|
||||||
|
// 测试无效的 JWT
|
||||||
|
invalidToken := "invalid.token.string"
|
||||||
|
_, err = RefreshJWT(invalidToken, secretKey, newExpiration)
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
// 测试空字符串
|
||||||
|
_, err = RefreshJWT("", secretKey, newExpiration)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateJWTWithHeader(t *testing.T) {
|
||||||
|
// 定义测试用的 payload、密钥和自定义头部
|
||||||
|
payload := jwt.MapClaims{
|
||||||
|
"sub": "userId",
|
||||||
|
"exp": time.Now().Add(1 * time.Hour).Unix(),
|
||||||
|
}
|
||||||
|
secretKey := []byte("secret")
|
||||||
|
customHeader := map[string]interface{}{
|
||||||
|
"kid": "key-id-123",
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试生成有效的 JWT
|
||||||
|
token, err := GenerateJWTWithHeader(payload, secretKey, jwt.SigningMethodHS256, customHeader)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, token)
|
||||||
|
|
||||||
|
// 验证生成的 JWT 是否包含自定义头部
|
||||||
|
parser := jwt.NewParser(jwt.WithoutClaimsValidation())
|
||||||
|
parsedToken, _, err := parser.ParseUnverified(token, jwt.MapClaims{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, parsedToken)
|
||||||
|
|
||||||
|
// 检查头部是否包含自定义字段
|
||||||
|
assert.Equal(t, "key-id-123", parsedToken.Header["kid"])
|
||||||
|
|
||||||
|
// 测试使用空密钥生成 JWT
|
||||||
|
emptySecretKey := []byte("")
|
||||||
|
token, err = GenerateJWTWithHeader(payload, emptySecretKey, jwt.SigningMethodHS256, customHeader)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Empty(t, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExtractJWTFromRequest(t *testing.T) {
|
||||||
|
// 测试有效的 Authorization Header
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
req.Header.Set("Authorization", "Bearer valid.jwt.token")
|
||||||
|
token, err := ExtractJWTFromRequest(req)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "valid.jwt.token", token)
|
||||||
|
|
||||||
|
// 测试缺少 Authorization Header
|
||||||
|
req = httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
token, err = ExtractJWTFromRequest(req)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, "authorization header is missing", err.Error())
|
||||||
|
assert.Empty(t, token)
|
||||||
|
|
||||||
|
// 测试无效的 Authorization Header 格式
|
||||||
|
req = httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
req.Header.Set("Authorization", "InvalidFormat")
|
||||||
|
token, err = ExtractJWTFromRequest(req)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, "invalid authorization header format", err.Error())
|
||||||
|
assert.Empty(t, token)
|
||||||
|
|
||||||
|
// 测试空的 Bearer Token
|
||||||
|
req = httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
req.Header.Set("Authorization", "Bearer ")
|
||||||
|
token, err = ExtractJWTFromRequest(req)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Empty(t, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateShortLivedJWT(t *testing.T) {
|
||||||
|
secretKey := []byte("secret")
|
||||||
|
payload := jwt.MapClaims{
|
||||||
|
"sub": "userId",
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试生成短期有效的 JWT
|
||||||
|
duration := 1 * time.Hour
|
||||||
|
token, err := GenerateShortLivedJWT(payload, secretKey, jwt.SigningMethodHS256, duration)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, token)
|
||||||
|
|
||||||
|
// 验证生成的 JWT 是否包含正确的过期时间
|
||||||
|
claims, err := ParseJWTPayload(token)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, claims)
|
||||||
|
|
||||||
|
exp, ok := claims["exp"].(float64)
|
||||||
|
assert.True(t, ok)
|
||||||
|
expectedExp := time.Now().Add(duration).Unix()
|
||||||
|
assert.InDelta(t, expectedExp, int64(exp), 5) // 允许 5 秒的时间误差
|
||||||
|
|
||||||
|
// 测试空密钥
|
||||||
|
emptySecretKey := []byte("")
|
||||||
|
token, err = GenerateShortLivedJWT(payload, emptySecretKey, jwt.SigningMethodHS256, duration)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Empty(t, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateJWTAudience(t *testing.T) {
|
||||||
|
secretKey := []byte("secret")
|
||||||
|
|
||||||
|
// 创建一个包含受众的 JWT
|
||||||
|
token, err := GenerateJWT(jwt.MapClaims{
|
||||||
|
"aud": []string{"audience1", "audience2"},
|
||||||
|
}, secretKey, jwt.SigningMethodHS256)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, token)
|
||||||
|
|
||||||
|
// 测试受众验证成功
|
||||||
|
valid, err := ValidateJWTAudience(token, "audience1")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, valid)
|
||||||
|
|
||||||
|
// 测试受众验证失败
|
||||||
|
valid, err = ValidateJWTAudience(token, "audience3")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.False(t, valid)
|
||||||
|
|
||||||
|
// 测试无效的 JWT
|
||||||
|
invalidToken := "invalid.token.string"
|
||||||
|
valid, err = ValidateJWTAudience(invalidToken, "audience1")
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.False(t, valid)
|
||||||
|
|
||||||
|
// 测试空字符串
|
||||||
|
valid, err = ValidateJWTAudience("", "audience1")
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.False(t, valid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateJWTAlgorithm(t *testing.T) {
|
||||||
|
secretKey := []byte("secret")
|
||||||
|
|
||||||
|
// 测试有效的 JWT 算法验证
|
||||||
|
token, err := GenerateJWT(jwt.MapClaims{
|
||||||
|
"sub": "userId",
|
||||||
|
}, secretKey, jwt.SigningMethodHS256)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, token)
|
||||||
|
|
||||||
|
valid, err := ValidateJWTAlgorithm(token, "HS256")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, valid)
|
||||||
|
|
||||||
|
// 测试无效的算法
|
||||||
|
valid, err = ValidateJWTAlgorithm(token, "RS256")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.False(t, valid)
|
||||||
|
|
||||||
|
// 测试无效的 JWT
|
||||||
|
invalidToken := "invalid.token.string"
|
||||||
|
valid, err = ValidateJWTAlgorithm(invalidToken, "HS256")
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.False(t, valid)
|
||||||
|
|
||||||
|
// 测试空字符串
|
||||||
|
valid, err = ValidateJWTAlgorithm("", "HS256")
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.False(t, valid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsJWTExpired(t *testing.T) {
|
||||||
|
secretKey := []byte("secret")
|
||||||
|
|
||||||
|
// 测试未过期的 JWT
|
||||||
|
notExpiredToken, err := GenerateJWT(jwt.MapClaims{
|
||||||
|
"sub": "userId",
|
||||||
|
"exp": time.Now().Add(1 * time.Hour).Unix(),
|
||||||
|
}, secretKey, jwt.SigningMethodHS256)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, notExpiredToken)
|
||||||
|
|
||||||
|
isExpired, err := IsJWTExpired(notExpiredToken)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.False(t, isExpired)
|
||||||
|
|
||||||
|
// 测试已过期的 JWT
|
||||||
|
expiredToken, err := GenerateJWT(jwt.MapClaims{
|
||||||
|
"sub": "userId",
|
||||||
|
"exp": time.Now().Add(-1 * time.Hour).Unix(),
|
||||||
|
}, secretKey, jwt.SigningMethodHS256)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, expiredToken)
|
||||||
|
|
||||||
|
isExpired, err = IsJWTExpired(expiredToken)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, isExpired)
|
||||||
|
|
||||||
|
// 测试无效的 JWT
|
||||||
|
invalidToken := "invalid.token.string"
|
||||||
|
isExpired, err = IsJWTExpired(invalidToken)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.False(t, isExpired)
|
||||||
|
|
||||||
|
// 测试空字符串
|
||||||
|
isExpired, err = IsJWTExpired("")
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.False(t, isExpired)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetJWTHeader(t *testing.T) {
|
||||||
|
secretKey := []byte("secret")
|
||||||
|
|
||||||
|
// 测试有效的 JWT 头部解析
|
||||||
|
token, err := GenerateJWT(jwt.MapClaims{
|
||||||
|
"sub": "userId",
|
||||||
|
}, secretKey, jwt.SigningMethodHS256)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, token)
|
||||||
|
|
||||||
|
header, err := GetJWTHeader(token)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, header)
|
||||||
|
assert.Equal(t, "JWT", header["typ"])
|
||||||
|
assert.Equal(t, "HS256", header["alg"])
|
||||||
|
|
||||||
|
// 测试无效的 JWT
|
||||||
|
invalidToken := "invalid.token.string"
|
||||||
|
header, err = GetJWTHeader(invalidToken)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, header)
|
||||||
|
|
||||||
|
// 测试空字符串
|
||||||
|
header, err = GetJWTHeader("")
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, header)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateJWTIssuer(t *testing.T) {
|
||||||
|
secretKey := []byte("secret")
|
||||||
|
|
||||||
|
// 测试有效的发行者验证
|
||||||
|
token, err := GenerateJWT(jwt.MapClaims{
|
||||||
|
"iss": "trusted-issuer",
|
||||||
|
}, secretKey, jwt.SigningMethodHS256)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, token)
|
||||||
|
|
||||||
|
valid, err := ValidateJWTIssuer(token, "trusted-issuer")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, valid)
|
||||||
|
|
||||||
|
// 测试无效的发行者
|
||||||
|
valid, err = ValidateJWTIssuer(token, "untrusted-issuer")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.False(t, valid)
|
||||||
|
|
||||||
|
// 测试无效的 JWT
|
||||||
|
invalidToken := "invalid.token.string"
|
||||||
|
valid, err = ValidateJWTIssuer(invalidToken, "trusted-issuer")
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.False(t, valid)
|
||||||
|
|
||||||
|
// 测试空字符串
|
||||||
|
valid, err = ValidateJWTIssuer("", "trusted-issuer")
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.False(t, valid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetJWTIssuedAt(t *testing.T) {
|
||||||
|
secretKey := []byte("secret")
|
||||||
|
|
||||||
|
// 测试有效的 JWT 签发时间
|
||||||
|
issuedAt := time.Now().Add(-1 * time.Hour).Unix()
|
||||||
|
token, err := GenerateJWT(jwt.MapClaims{
|
||||||
|
"sub": "userId",
|
||||||
|
"iat": issuedAt,
|
||||||
|
}, secretKey, jwt.SigningMethodHS256)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, token)
|
||||||
|
|
||||||
|
iat, err := GetJWTIssuedAt(token)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, iat)
|
||||||
|
assert.Equal(t, issuedAt, iat.Unix())
|
||||||
|
|
||||||
|
// 测试无效的 JWT
|
||||||
|
invalidToken := "invalid.token.string"
|
||||||
|
iat, err = GetJWTIssuedAt(invalidToken)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, iat)
|
||||||
|
|
||||||
|
// 测试空字符串
|
||||||
|
iat, err = GetJWTIssuedAt("")
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, iat)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetJWTClaims(t *testing.T) {
|
||||||
|
secretKey := []byte("secret")
|
||||||
|
|
||||||
|
// 测试有效的 JWT
|
||||||
|
token, err := GenerateJWT(jwt.MapClaims{
|
||||||
|
"sub": "userId",
|
||||||
|
"exp": time.Now().Add(1 * time.Hour).Unix(),
|
||||||
|
}, secretKey, jwt.SigningMethodHS256)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, token)
|
||||||
|
|
||||||
|
claims, err := GetJWTClaims(token)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, claims)
|
||||||
|
assert.Equal(t, "userId", claims["sub"])
|
||||||
|
|
||||||
|
// 测试无效的 JWT
|
||||||
|
invalidToken := "invalid.token.string"
|
||||||
|
claims, err = GetJWTClaims(invalidToken)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, claims)
|
||||||
|
|
||||||
|
// 测试空字符串
|
||||||
|
claims, err = GetJWTClaims("")
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, claims)
|
||||||
|
}
|
||||||
95
mapper/README.md
Normal file
95
mapper/README.md
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
# 类型映射器
|
||||||
|
|
||||||
|
类型映射器的作用是将一个数据结构的字段映射到另一个数据结构的字段,通常用于对象之间的数据转换。它可以简化不同类型或结构之间的数据传递,减少手动赋值的代码量,提高开发效率。例如,在处理数据库实体与业务模型或 API 请求/响应模型之间的转换时,类型映射器非常有用。
|
||||||
|
|
||||||
|
## 基于Copier的类型映射器
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tx7do/go-utils/mapper"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
type DtoType struct {
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
}
|
||||||
|
|
||||||
|
type EntityType struct {
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
}
|
||||||
|
|
||||||
|
mapper := mapper.NewCopierMapper[DtoType, EntityType]()
|
||||||
|
|
||||||
|
// 测试 ToEntity 方法
|
||||||
|
dto := &DtoType{Name: "Alice", Age: 25}
|
||||||
|
entity := mapper.ToEntity(dto)
|
||||||
|
|
||||||
|
// 测试 ToEntity 方法,传入 nil
|
||||||
|
entityNil := mapper.ToEntity(nil)
|
||||||
|
|
||||||
|
// 测试 ToDTO 方法
|
||||||
|
entity = &EntityType{Name: "Bob", Age: 30}
|
||||||
|
dtoResult := mapper.ToDTO(entity)
|
||||||
|
|
||||||
|
// 测试 ToDTO 方法,传入 nil
|
||||||
|
dtoNil := mapper.ToDTO(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## ent 与 protobuf 的枚举类型映射器
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/tx7do/go-utils/mapper"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
type DtoType int32
|
||||||
|
type EntityType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
DtoTypeOne DtoType = 1
|
||||||
|
DtoTypeTwo DtoType = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
EntityTypeOne EntityType = "One"
|
||||||
|
EntityTypeTwo EntityType = "Two"
|
||||||
|
)
|
||||||
|
|
||||||
|
nameMap := map[int32]string{
|
||||||
|
1: "One",
|
||||||
|
2: "Two",
|
||||||
|
}
|
||||||
|
valueMap := map[string]int32{
|
||||||
|
"One": 1,
|
||||||
|
"Two": 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
converter := mapper.NewEnumTypeConverter[DtoType, EntityType](nameMap, valueMap)
|
||||||
|
|
||||||
|
// 测试 ToEntity 方法
|
||||||
|
dto := DtoTypeOne
|
||||||
|
entity := converter.ToEntity(&dto)
|
||||||
|
|
||||||
|
// 测试 ToEntity 方法,传入不存在的值
|
||||||
|
dtoInvalid := DtoType(3)
|
||||||
|
entityInvalid := converter.ToEntity(&dtoInvalid)
|
||||||
|
|
||||||
|
// 测试 ToDTO 方法
|
||||||
|
tmpEntityTwo := EntityTypeTwo
|
||||||
|
entity = &tmpEntityTwo
|
||||||
|
dtoResult := converter.ToDTO(entity)
|
||||||
|
|
||||||
|
// 测试 ToDTO 方法,传入不存在的值
|
||||||
|
tmpEntityThree := EntityType("Three")
|
||||||
|
entityInvalid = &tmpEntityThree
|
||||||
|
dtoInvalidResult := converter.ToDTO(entityInvalid)
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
82
mapper/enum_converter.go
Normal file
82
mapper/enum_converter.go
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
package mapper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jinzhu/copier"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EnumTypeConverter[DTO ~int32, ENTITY ~string] struct {
|
||||||
|
nameMap map[int32]string
|
||||||
|
valueMap map[string]int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEnumTypeConverter[DTO ~int32, ENTITY ~string](
|
||||||
|
nameMap map[int32]string,
|
||||||
|
valueMap map[string]int32,
|
||||||
|
) *EnumTypeConverter[DTO, ENTITY] {
|
||||||
|
return &EnumTypeConverter[DTO, ENTITY]{
|
||||||
|
valueMap: valueMap,
|
||||||
|
nameMap: nameMap,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *EnumTypeConverter[DTO, ENTITY]) ToEntity(dto *DTO) *ENTITY {
|
||||||
|
if dto == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
find, ok := m.nameMap[int32(*dto)]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
entity := ENTITY(find)
|
||||||
|
return &entity
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *EnumTypeConverter[DTO, ENTITY]) ToDTO(entity *ENTITY) *DTO {
|
||||||
|
if entity == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
find, ok := m.valueMap[string(*entity)]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dto := DTO(find)
|
||||||
|
return &dto
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *EnumTypeConverter[DTO, ENTITY]) NewConverterPair() []copier.TypeConverter {
|
||||||
|
srcType := ENTITY("")
|
||||||
|
dstType := DTO(0)
|
||||||
|
|
||||||
|
fromFn := m.ToDTO
|
||||||
|
toFn := m.ToEntity
|
||||||
|
|
||||||
|
return NewGenericTypeConverterPair(&srcType, &dstType, fromFn, toFn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGenericTypeConverterPair[A interface{}, B interface{}](
|
||||||
|
srcType A,
|
||||||
|
dstType B,
|
||||||
|
fromFn func(src A) B,
|
||||||
|
toFn func(src B) A,
|
||||||
|
) []copier.TypeConverter {
|
||||||
|
return []copier.TypeConverter{
|
||||||
|
{
|
||||||
|
SrcType: srcType,
|
||||||
|
DstType: dstType,
|
||||||
|
Fn: func(src interface{}) (interface{}, error) {
|
||||||
|
return fromFn(src.(A)), nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SrcType: dstType,
|
||||||
|
DstType: srcType,
|
||||||
|
Fn: func(src interface{}) (interface{}, error) {
|
||||||
|
return toFn(src.(B)), nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
20
mapper/go.mod
Normal file
20
mapper/go.mod
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
module github.com/tx7do/go-utils/mapper
|
||||||
|
|
||||||
|
go 1.23.0
|
||||||
|
|
||||||
|
toolchain go1.24.3
|
||||||
|
|
||||||
|
require github.com/jinzhu/copier v0.4.0
|
||||||
|
|
||||||
|
require github.com/stretchr/testify v1.10.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/kr/pretty v0.3.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
replace github.com/tx7do/go-utils => ../
|
||||||
25
mapper/go.sum
Normal file
25
mapper/go.sum
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||||
|
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||||
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
|
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
10
mapper/interface.go
Normal file
10
mapper/interface.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package mapper
|
||||||
|
|
||||||
|
// Mapper defines the interface for converting between Data Transfer Objects (DTOs) and Database Entities.
|
||||||
|
type Mapper[DTO any, ENTITY any] interface {
|
||||||
|
// ToEntity converts a DTO to a Database Entity.
|
||||||
|
ToEntity(*DTO) *ENTITY
|
||||||
|
|
||||||
|
// ToDTO converts a Database Entity to a DTO.
|
||||||
|
ToDTO(*ENTITY) *DTO
|
||||||
|
}
|
||||||
51
mapper/mapper.go
Normal file
51
mapper/mapper.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package mapper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jinzhu/copier"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CopierMapper[DTO any, ENTITY any] struct {
|
||||||
|
copierOption copier.Option
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCopierMapper[DTO any, ENTITY any]() *CopierMapper[DTO, ENTITY] {
|
||||||
|
return &CopierMapper[DTO, ENTITY]{
|
||||||
|
copierOption: copier.Option{
|
||||||
|
Converters: []copier.TypeConverter{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *CopierMapper[DTO, ENTITY]) AppendConverter(converter copier.TypeConverter) {
|
||||||
|
m.copierOption.Converters = append(m.copierOption.Converters, converter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *CopierMapper[DTO, ENTITY]) AppendConverters(converters []copier.TypeConverter) {
|
||||||
|
m.copierOption.Converters = append(m.copierOption.Converters, converters...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *CopierMapper[DTO, ENTITY]) ToEntity(dto *DTO) *ENTITY {
|
||||||
|
if dto == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var entity ENTITY
|
||||||
|
if err := copier.CopyWithOption(&entity, dto, m.copierOption); err != nil {
|
||||||
|
panic(err) // Handle error appropriately in production code
|
||||||
|
}
|
||||||
|
|
||||||
|
return &entity
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *CopierMapper[DTO, ENTITY]) ToDTO(entity *ENTITY) *DTO {
|
||||||
|
if entity == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var dto DTO
|
||||||
|
if err := copier.CopyWithOption(&dto, entity, m.copierOption); err != nil {
|
||||||
|
panic(err) // Handle error appropriately in production code
|
||||||
|
}
|
||||||
|
|
||||||
|
return &dto
|
||||||
|
}
|
||||||
93
mapper/mapper_test.go
Normal file
93
mapper/mapper_test.go
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
package mapper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCopierMapper(t *testing.T) {
|
||||||
|
type DtoType struct {
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
}
|
||||||
|
|
||||||
|
type EntityType struct {
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
}
|
||||||
|
|
||||||
|
mapper := NewCopierMapper[DtoType, EntityType]()
|
||||||
|
|
||||||
|
// 测试 ToEntity 方法
|
||||||
|
dto := &DtoType{Name: "Alice", Age: 25}
|
||||||
|
entity := mapper.ToEntity(dto)
|
||||||
|
assert.NotNil(t, entity)
|
||||||
|
assert.Equal(t, "Alice", entity.Name)
|
||||||
|
assert.Equal(t, 25, entity.Age)
|
||||||
|
|
||||||
|
// 测试 ToEntity 方法,传入 nil
|
||||||
|
entityNil := mapper.ToEntity(nil)
|
||||||
|
assert.Nil(t, entityNil)
|
||||||
|
|
||||||
|
// 测试 ToDTO 方法
|
||||||
|
entity = &EntityType{Name: "Bob", Age: 30}
|
||||||
|
dtoResult := mapper.ToDTO(entity)
|
||||||
|
assert.NotNil(t, dtoResult)
|
||||||
|
assert.Equal(t, "Bob", dtoResult.Name)
|
||||||
|
assert.Equal(t, 30, dtoResult.Age)
|
||||||
|
|
||||||
|
// 测试 ToDTO 方法,传入 nil
|
||||||
|
dtoNil := mapper.ToDTO(nil)
|
||||||
|
assert.Nil(t, dtoNil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnumTypeConverter(t *testing.T) {
|
||||||
|
type DtoType int32
|
||||||
|
type EntityType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
DtoTypeOne DtoType = 1
|
||||||
|
DtoTypeTwo DtoType = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
EntityTypeOne EntityType = "One"
|
||||||
|
EntityTypeTwo EntityType = "Two"
|
||||||
|
)
|
||||||
|
|
||||||
|
nameMap := map[int32]string{
|
||||||
|
1: "One",
|
||||||
|
2: "Two",
|
||||||
|
}
|
||||||
|
valueMap := map[string]int32{
|
||||||
|
"One": 1,
|
||||||
|
"Two": 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
converter := NewEnumTypeConverter[DtoType, EntityType](nameMap, valueMap)
|
||||||
|
|
||||||
|
// 测试 ToEntity 方法
|
||||||
|
dto := DtoTypeOne
|
||||||
|
entity := converter.ToEntity(&dto)
|
||||||
|
assert.NotNil(t, entity)
|
||||||
|
assert.Equal(t, "One", string(*entity))
|
||||||
|
|
||||||
|
// 测试 ToEntity 方法,传入不存在的值
|
||||||
|
dtoInvalid := DtoType(3)
|
||||||
|
entityInvalid := converter.ToEntity(&dtoInvalid)
|
||||||
|
assert.Nil(t, entityInvalid)
|
||||||
|
|
||||||
|
// 测试 ToDTO 方法
|
||||||
|
tmpEntityTwo := EntityTypeTwo
|
||||||
|
entity = &tmpEntityTwo
|
||||||
|
dtoResult := converter.ToDTO(entity)
|
||||||
|
assert.NotNil(t, dtoResult)
|
||||||
|
assert.Equal(t, DtoType(2), *dtoResult)
|
||||||
|
|
||||||
|
// 测试 ToDTO 方法,传入不存在的值
|
||||||
|
tmpEntityThree := EntityType("Three")
|
||||||
|
entityInvalid = &tmpEntityThree
|
||||||
|
dtoInvalidResult := converter.ToDTO(entityInvalid)
|
||||||
|
assert.Nil(t, dtoInvalidResult)
|
||||||
|
}
|
||||||
158
name_generator/README.md
Normal file
158
name_generator/README.md
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
# 名字生成器
|
||||||
|
|
||||||
|
这是一个简单的名字生成器。它可以生成中文名、英文名和日文名。以及游戏的昵称,道具名等。
|
||||||
|
|
||||||
|
## 生成中文名
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/tx7do/go-utils/name_generator"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
g := name_generator.New()
|
||||||
|
|
||||||
|
result := g.GenerateChineseName(1, true, false)
|
||||||
|
if result == "" {
|
||||||
|
log.Errorf("result is empty, please check the dictionary data")
|
||||||
|
} else {
|
||||||
|
log.Logf("Generated single surname single name (female): %s", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
result = g.GenerateChineseName(2, false, true)
|
||||||
|
if result == "" {
|
||||||
|
log.Errorf("result is empty, please check the dictionary data")
|
||||||
|
} else {
|
||||||
|
log.Logf("Generated compound surname double name (male): %s", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
输出效果:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
Generated single surname single name (female): 候影
|
||||||
|
Generated compound surname double name (male): 宗政辰宁
|
||||||
|
```
|
||||||
|
|
||||||
|
## 生成英文名
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/tx7do/go-utils/name_generator"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
g := name_generator.New()
|
||||||
|
|
||||||
|
result := g.GenerateEnglishName(1, 0, 1, true)
|
||||||
|
if result == "" {
|
||||||
|
log.Errorf("result is empty, please check the dictionary data")
|
||||||
|
} else {
|
||||||
|
log.Logf("Generated female English name: %s", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
result = g.GenerateEnglishName(2, 0, 1, false)
|
||||||
|
if result == "" {
|
||||||
|
log.Errorf("result is empty, please check the dictionary data")
|
||||||
|
} else {
|
||||||
|
log.Logf("Generated male English name: %s", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
输出效果:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
Generated female English name: Magical Alexander
|
||||||
|
Generated male English name: Valentine Roderick Hayes
|
||||||
|
```
|
||||||
|
|
||||||
|
## 生成日文名
|
||||||
|
|
||||||
|
汉字版
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/tx7do/go-utils/name_generator"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
g := name_generator.New()
|
||||||
|
|
||||||
|
result := g.GenerateJapaneseNameCN()
|
||||||
|
if result == "" {
|
||||||
|
log.Errorf("result is empty, please check the dictionary data")
|
||||||
|
} else {
|
||||||
|
log.Logf("Generated Japanese name (CN): %s", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
输出效果:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
Generated Japanese name (CN): 瀬尾和子
|
||||||
|
```
|
||||||
|
|
||||||
|
日文版
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/tx7do/go-utils/name_generator"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
g := name_generator.New()
|
||||||
|
|
||||||
|
result := g.GenerateJapaneseName()
|
||||||
|
if result == "" {
|
||||||
|
log.Errorf("result is empty, please check the dictionary data")
|
||||||
|
} else {
|
||||||
|
log.Logf("Generated Japanese name: %s", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
输出效果:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
Generated Japanese name: 渋沢洋
|
||||||
|
```
|
||||||
|
|
||||||
|
## 生成游戏昵称
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/tx7do/go-utils/name_generator"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
g := name_generator.New()
|
||||||
|
|
||||||
|
dictTypes := name_generator.Scheme5
|
||||||
|
|
||||||
|
result := g.Generate(dictTypes)
|
||||||
|
|
||||||
|
if result == "" {
|
||||||
|
log.Errorf("result is empty, please check the dictionary data")
|
||||||
|
} else {
|
||||||
|
log.Logf("generate`s nickname: %s", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
输出效果:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
generate`s nickname: 谦逊之讲笑话呼保义
|
||||||
|
```
|
||||||
|
|
||||||
|
## 感谢
|
||||||
|
|
||||||
|
- [中文人名语料库(Chinese-Names-Corpus)](https://github.com/wainshine/Chinese-Names-Corpus)
|
||||||
577
name_generator/assets/adjective.txt
Normal file
577
name_generator/assets/adjective.txt
Normal file
@@ -0,0 +1,577 @@
|
|||||||
|
老
|
||||||
|
小
|
||||||
|
大
|
||||||
|
幼年
|
||||||
|
少年
|
||||||
|
中年
|
||||||
|
老年
|
||||||
|
萌
|
||||||
|
乖
|
||||||
|
傻
|
||||||
|
呆
|
||||||
|
懒
|
||||||
|
神族
|
||||||
|
妖怪
|
||||||
|
亡灵
|
||||||
|
魔族
|
||||||
|
血族
|
||||||
|
变化
|
||||||
|
地狱
|
||||||
|
山洞
|
||||||
|
矿洞
|
||||||
|
远古
|
||||||
|
太古
|
||||||
|
上古
|
||||||
|
化石
|
||||||
|
新生
|
||||||
|
重生
|
||||||
|
不败
|
||||||
|
混沌
|
||||||
|
极品
|
||||||
|
奥术
|
||||||
|
魔法
|
||||||
|
深渊
|
||||||
|
异世
|
||||||
|
异界
|
||||||
|
变异
|
||||||
|
颜控
|
||||||
|
腿控
|
||||||
|
御宅
|
||||||
|
极大
|
||||||
|
极巨
|
||||||
|
压制
|
||||||
|
火焰
|
||||||
|
炽焰
|
||||||
|
烈焰
|
||||||
|
爆燃
|
||||||
|
废柴
|
||||||
|
暗黑
|
||||||
|
死宅
|
||||||
|
肥宅
|
||||||
|
帝都
|
||||||
|
魔都
|
||||||
|
妖都
|
||||||
|
旧都
|
||||||
|
伪都
|
||||||
|
古都
|
||||||
|
神都
|
||||||
|
腐都
|
||||||
|
邪都
|
||||||
|
陪都
|
||||||
|
雾都
|
||||||
|
萌豚
|
||||||
|
声豚
|
||||||
|
军宅
|
||||||
|
天朝
|
||||||
|
高丽
|
||||||
|
11区
|
||||||
|
岛国
|
||||||
|
米帝
|
||||||
|
米国
|
||||||
|
白学
|
||||||
|
哲学
|
||||||
|
废萌
|
||||||
|
崩坏
|
||||||
|
绅士
|
||||||
|
奇行种
|
||||||
|
超大
|
||||||
|
中二
|
||||||
|
厨二
|
||||||
|
巨大
|
||||||
|
傲娇
|
||||||
|
平原
|
||||||
|
高原
|
||||||
|
丘陵
|
||||||
|
盆地
|
||||||
|
山地
|
||||||
|
极地
|
||||||
|
水星
|
||||||
|
火星
|
||||||
|
金星
|
||||||
|
木星
|
||||||
|
地球
|
||||||
|
土星
|
||||||
|
月球
|
||||||
|
非洲
|
||||||
|
欧洲
|
||||||
|
热带
|
||||||
|
寒带
|
||||||
|
温带
|
||||||
|
腹黑
|
||||||
|
毒舌
|
||||||
|
家养
|
||||||
|
野生
|
||||||
|
火山
|
||||||
|
冰原
|
||||||
|
冰川
|
||||||
|
小学
|
||||||
|
中学
|
||||||
|
大学
|
||||||
|
博士
|
||||||
|
脑洞
|
||||||
|
蠢萌
|
||||||
|
呆萌
|
||||||
|
老牌
|
||||||
|
大触
|
||||||
|
纯种
|
||||||
|
纯粹
|
||||||
|
伦敦
|
||||||
|
东京
|
||||||
|
巴黎
|
||||||
|
雅典
|
||||||
|
埃及
|
||||||
|
首尔
|
||||||
|
平壤
|
||||||
|
朝鲜
|
||||||
|
京都
|
||||||
|
纽约
|
||||||
|
费城
|
||||||
|
乡下
|
||||||
|
城市
|
||||||
|
魔术
|
||||||
|
闪电
|
||||||
|
机械
|
||||||
|
木头
|
||||||
|
石头
|
||||||
|
黑铁
|
||||||
|
青铜
|
||||||
|
钻石
|
||||||
|
铂金
|
||||||
|
黄金
|
||||||
|
白银
|
||||||
|
王者
|
||||||
|
幽灵
|
||||||
|
鬼魂
|
||||||
|
丧尸
|
||||||
|
僵尸
|
||||||
|
北京
|
||||||
|
广州
|
||||||
|
香港
|
||||||
|
挪威
|
||||||
|
大阪
|
||||||
|
希腊
|
||||||
|
澳门
|
||||||
|
上海
|
||||||
|
深圳
|
||||||
|
芬兰
|
||||||
|
德州
|
||||||
|
襄阳
|
||||||
|
西安
|
||||||
|
绵阳
|
||||||
|
南昌
|
||||||
|
江苏
|
||||||
|
福建
|
||||||
|
福州
|
||||||
|
厦门
|
||||||
|
欧气
|
||||||
|
多兰
|
||||||
|
银河
|
||||||
|
太阳
|
||||||
|
仙女
|
||||||
|
水瓶
|
||||||
|
天秤
|
||||||
|
巨蟹
|
||||||
|
白羊
|
||||||
|
金牛
|
||||||
|
双子
|
||||||
|
风暴
|
||||||
|
狮子
|
||||||
|
天蝎
|
||||||
|
射手
|
||||||
|
双鱼
|
||||||
|
魔蝎
|
||||||
|
强壮
|
||||||
|
城堡
|
||||||
|
结界
|
||||||
|
长生
|
||||||
|
摧毁
|
||||||
|
破坏
|
||||||
|
精确
|
||||||
|
主宰
|
||||||
|
巫术
|
||||||
|
兼备
|
||||||
|
惩戒
|
||||||
|
相位
|
||||||
|
湮灭
|
||||||
|
制裁
|
||||||
|
古老
|
||||||
|
传说
|
||||||
|
坚强
|
||||||
|
史诗
|
||||||
|
普通
|
||||||
|
破损
|
||||||
|
虚弱
|
||||||
|
迅捷
|
||||||
|
超级
|
||||||
|
终结
|
||||||
|
坚韧
|
||||||
|
圣佑
|
||||||
|
狂暴
|
||||||
|
愤怒
|
||||||
|
豪华
|
||||||
|
一流
|
||||||
|
活泼
|
||||||
|
颤抖
|
||||||
|
北风
|
||||||
|
寒冬
|
||||||
|
烈火
|
||||||
|
燃烧
|
||||||
|
烟雾
|
||||||
|
静态
|
||||||
|
发光
|
||||||
|
弯曲
|
||||||
|
震撼
|
||||||
|
锯齿
|
||||||
|
邪恶
|
||||||
|
残酷
|
||||||
|
残忍
|
||||||
|
阴险
|
||||||
|
悔恨
|
||||||
|
野蛮
|
||||||
|
绝情
|
||||||
|
凶暴
|
||||||
|
冷酷
|
||||||
|
幻影
|
||||||
|
幻想
|
||||||
|
冷冽
|
||||||
|
冰冷
|
||||||
|
雷心
|
||||||
|
雷霆
|
||||||
|
霜冻
|
||||||
|
霜裔
|
||||||
|
神邪
|
||||||
|
穿甲
|
||||||
|
破甲
|
||||||
|
破败
|
||||||
|
蛮族
|
||||||
|
蛮力
|
||||||
|
雷击
|
||||||
|
恶魔
|
||||||
|
暴怒
|
||||||
|
巨力
|
||||||
|
力量
|
||||||
|
敏捷
|
||||||
|
智力
|
||||||
|
审判
|
||||||
|
抵御
|
||||||
|
保卫
|
||||||
|
耐热
|
||||||
|
耐磨
|
||||||
|
耐用
|
||||||
|
虚触
|
||||||
|
防水
|
||||||
|
防腐
|
||||||
|
防火
|
||||||
|
防护
|
||||||
|
防偷
|
||||||
|
防骗
|
||||||
|
防炸
|
||||||
|
防盗
|
||||||
|
磐石
|
||||||
|
大地
|
||||||
|
止血
|
||||||
|
武力
|
||||||
|
抵抗
|
||||||
|
灼热
|
||||||
|
抗性
|
||||||
|
守护
|
||||||
|
庇护
|
||||||
|
引流
|
||||||
|
优质
|
||||||
|
侵害
|
||||||
|
侵扰
|
||||||
|
贪婪
|
||||||
|
野火
|
||||||
|
破碎
|
||||||
|
破裂
|
||||||
|
怨恨
|
||||||
|
虚空
|
||||||
|
虚无
|
||||||
|
爆炎
|
||||||
|
无上
|
||||||
|
真实
|
||||||
|
真正
|
||||||
|
疯狂
|
||||||
|
破暗
|
||||||
|
污染
|
||||||
|
憎恶
|
||||||
|
无情
|
||||||
|
密谋
|
||||||
|
枯萎
|
||||||
|
怒火
|
||||||
|
治愈
|
||||||
|
愈合
|
||||||
|
隔绝
|
||||||
|
追踪
|
||||||
|
操纵
|
||||||
|
圣贤
|
||||||
|
强硬
|
||||||
|
强悍
|
||||||
|
强劲
|
||||||
|
坚决
|
||||||
|
坚信
|
||||||
|
坚定
|
||||||
|
坚韧
|
||||||
|
坚实
|
||||||
|
坚贞
|
||||||
|
勇敢
|
||||||
|
勇猛
|
||||||
|
刚毅
|
||||||
|
决断
|
||||||
|
果敢
|
||||||
|
果决
|
||||||
|
坚强
|
||||||
|
坚忍
|
||||||
|
决然
|
||||||
|
毅然
|
||||||
|
断然
|
||||||
|
泼辣
|
||||||
|
断腕
|
||||||
|
发誓
|
||||||
|
干脆
|
||||||
|
爽快
|
||||||
|
果断
|
||||||
|
真诚
|
||||||
|
热诚
|
||||||
|
至诚
|
||||||
|
赤诚
|
||||||
|
诚挚
|
||||||
|
恳切
|
||||||
|
纯真
|
||||||
|
率直
|
||||||
|
坦率
|
||||||
|
笃实
|
||||||
|
热忱
|
||||||
|
热心
|
||||||
|
好客
|
||||||
|
客气
|
||||||
|
殷勤
|
||||||
|
和气
|
||||||
|
和蔼
|
||||||
|
和善
|
||||||
|
亲切
|
||||||
|
过谦
|
||||||
|
谦卑
|
||||||
|
谦恭
|
||||||
|
谦和
|
||||||
|
谦让
|
||||||
|
谦虚
|
||||||
|
谦逊
|
||||||
|
虚心
|
||||||
|
外向
|
||||||
|
开朗
|
||||||
|
大方
|
||||||
|
主动
|
||||||
|
俏皮
|
||||||
|
敏捷
|
||||||
|
乐观
|
||||||
|
调皮
|
||||||
|
爽脆
|
||||||
|
爽朗
|
||||||
|
豪爽
|
||||||
|
正直
|
||||||
|
直率
|
||||||
|
直爽
|
||||||
|
直言
|
||||||
|
爽直
|
||||||
|
刚直
|
||||||
|
憨直
|
||||||
|
耿直
|
||||||
|
公正
|
||||||
|
公道
|
||||||
|
公平
|
||||||
|
公允
|
||||||
|
正派
|
||||||
|
开阔
|
||||||
|
豁达
|
||||||
|
明朗
|
||||||
|
率真
|
||||||
|
怒吼
|
||||||
|
恐惧
|
||||||
|
胆怯
|
||||||
|
畏缩
|
||||||
|
发慌
|
||||||
|
心慌
|
||||||
|
恐慌
|
||||||
|
激怒
|
||||||
|
恼火
|
||||||
|
欢乐
|
||||||
|
快慰
|
||||||
|
开心
|
||||||
|
高兴
|
||||||
|
愉悦
|
||||||
|
微笑
|
||||||
|
舒畅
|
||||||
|
笑噱
|
||||||
|
欢闹
|
||||||
|
欢心
|
||||||
|
欢欣
|
||||||
|
欢悦
|
||||||
|
宽慰
|
||||||
|
欢舒
|
||||||
|
狂欢
|
||||||
|
震怒
|
||||||
|
气愤
|
||||||
|
担忧
|
||||||
|
发愁
|
||||||
|
犯愁
|
||||||
|
忧伤
|
||||||
|
忧愁
|
||||||
|
忧心
|
||||||
|
愁闷
|
||||||
|
悲痛
|
||||||
|
悲惨
|
||||||
|
悲凉
|
||||||
|
哀伤
|
||||||
|
哀怨
|
||||||
|
伤感
|
||||||
|
瘦削
|
||||||
|
憔悴
|
||||||
|
快乐
|
||||||
|
喜悦
|
||||||
|
愉快
|
||||||
|
畅快
|
||||||
|
欢畅
|
||||||
|
欢喜
|
||||||
|
欢腾
|
||||||
|
欢快
|
||||||
|
欣喜
|
||||||
|
今天
|
||||||
|
昨天
|
||||||
|
明天
|
||||||
|
后天
|
||||||
|
上午
|
||||||
|
下午
|
||||||
|
过去
|
||||||
|
未来
|
||||||
|
去年
|
||||||
|
前年
|
||||||
|
散步
|
||||||
|
漫步
|
||||||
|
踏步
|
||||||
|
信步
|
||||||
|
转悠
|
||||||
|
闲逛
|
||||||
|
徜徉
|
||||||
|
踉跄
|
||||||
|
蹒跚
|
||||||
|
小跑
|
||||||
|
慢跑
|
||||||
|
飞跑
|
||||||
|
飞奔
|
||||||
|
飞翔
|
||||||
|
啜泣
|
||||||
|
抽泣
|
||||||
|
呜咽
|
||||||
|
哀号
|
||||||
|
号哭
|
||||||
|
痛哭
|
||||||
|
大笑
|
||||||
|
欢笑
|
||||||
|
嬉笑
|
||||||
|
狂笑
|
||||||
|
嗤笑
|
||||||
|
憨笑
|
||||||
|
傻笑
|
||||||
|
哄笑
|
||||||
|
苦笑
|
||||||
|
阴笑
|
||||||
|
狞笑
|
||||||
|
奸笑
|
||||||
|
嘲笑
|
||||||
|
冷笑
|
||||||
|
哈腰
|
||||||
|
猫腰
|
||||||
|
挺身
|
||||||
|
挺胸
|
||||||
|
俯身
|
||||||
|
躬身
|
||||||
|
仰卧
|
||||||
|
蜷曲
|
||||||
|
倒立
|
||||||
|
转体
|
||||||
|
屈体
|
||||||
|
屈身
|
||||||
|
欠身
|
||||||
|
纵身
|
||||||
|
蹲身
|
||||||
|
鞠躬
|
||||||
|
曲背
|
||||||
|
匍匐
|
||||||
|
笔挺
|
||||||
|
腾跃
|
||||||
|
直立
|
||||||
|
翻腾
|
||||||
|
前倾
|
||||||
|
摇摆
|
||||||
|
翻跃
|
||||||
|
扭动
|
||||||
|
扭转
|
||||||
|
旋转
|
||||||
|
好吃
|
||||||
|
好看
|
||||||
|
好玩
|
||||||
|
清白
|
||||||
|
凛然
|
||||||
|
无私
|
||||||
|
刚正
|
||||||
|
负重
|
||||||
|
忠心
|
||||||
|
忠贞
|
||||||
|
谨慎
|
||||||
|
廉洁
|
||||||
|
大度
|
||||||
|
坦白
|
||||||
|
勤奋
|
||||||
|
刻苦
|
||||||
|
认真
|
||||||
|
专注
|
||||||
|
踏实
|
||||||
|
勤恳
|
||||||
|
好学
|
||||||
|
高尚
|
||||||
|
杰出
|
||||||
|
超伦
|
||||||
|
自爱
|
||||||
|
自尊
|
||||||
|
自强
|
||||||
|
宽容
|
||||||
|
宽宏
|
||||||
|
律己
|
||||||
|
朴素
|
||||||
|
憨厚
|
||||||
|
诚实
|
||||||
|
忠诚
|
||||||
|
诚恳
|
||||||
|
天真
|
||||||
|
幼稚
|
||||||
|
活泼
|
||||||
|
聪明
|
||||||
|
圆滑
|
||||||
|
狡猾
|
||||||
|
虚伪
|
||||||
|
自私
|
||||||
|
任性
|
||||||
|
骄傲
|
||||||
|
贪婪
|
||||||
|
愚蠢
|
||||||
|
奸诈
|
||||||
|
高傲
|
||||||
|
害羞
|
||||||
|
内向
|
||||||
|
孤僻
|
||||||
|
可爱
|
||||||
|
招烦
|
||||||
|
阴险
|
||||||
|
双重
|
||||||
|
刻薄
|
||||||
|
宽厚
|
||||||
|
仁慈
|
||||||
|
仁厚
|
||||||
|
尖酸
|
||||||
|
阴郁
|
||||||
|
肤浅
|
||||||
|
浅薄
|
||||||
|
胆小
|
||||||
|
乐天
|
||||||
|
达观
|
||||||
54
name_generator/assets/assets.go
Normal file
54
name_generator/assets/assets.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package assets
|
||||||
|
|
||||||
|
import _ "embed"
|
||||||
|
|
||||||
|
//go:embed adjective.txt
|
||||||
|
var Adjective []byte
|
||||||
|
|
||||||
|
//go:embed goods.txt
|
||||||
|
var Goods []byte
|
||||||
|
|
||||||
|
//go:embed name.txt
|
||||||
|
var Name []byte
|
||||||
|
|
||||||
|
//go:embed prefix.txt
|
||||||
|
var Prefix []byte
|
||||||
|
|
||||||
|
//go:embed role.txt
|
||||||
|
var Role []byte
|
||||||
|
|
||||||
|
//go:embed verb.txt
|
||||||
|
var Verb []byte
|
||||||
|
|
||||||
|
//go:embed sensitive.txt
|
||||||
|
var Sensitive []byte
|
||||||
|
|
||||||
|
//go:embed chinese_single_surnames.txt
|
||||||
|
var ChineseSingleSurnames []byte
|
||||||
|
|
||||||
|
//go:embed chinese_compound_surnames.txt
|
||||||
|
var ChineseCompoundSurnames []byte
|
||||||
|
|
||||||
|
//go:embed chinese_first_name_female.txt
|
||||||
|
var ChineseFirstNameFemale []byte
|
||||||
|
|
||||||
|
//go:embed chinese_first_name_male.txt
|
||||||
|
var ChineseFirstNameMale []byte
|
||||||
|
|
||||||
|
//go:embed english_first_name_female.txt
|
||||||
|
var EnglishFirstNameFemale []byte
|
||||||
|
|
||||||
|
//go:embed english_first_name_male.txt
|
||||||
|
var EnglishFirstNameMale []byte
|
||||||
|
|
||||||
|
//go:embed english_last_name.txt
|
||||||
|
var EnglishLastName []byte
|
||||||
|
|
||||||
|
//go:embed japanese_names_corpus.txt
|
||||||
|
var JapaneseNamesCorpus []byte
|
||||||
|
|
||||||
|
//go:embed japanese_surnames.txt
|
||||||
|
var JapaneseSurnames []byte
|
||||||
|
|
||||||
|
//go:embed japanese_last_name.txt
|
||||||
|
var JapaneseLastName []byte
|
||||||
126
name_generator/assets/chinese_compound_surnames.txt
Normal file
126
name_generator/assets/chinese_compound_surnames.txt
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
万俟
|
||||||
|
司马
|
||||||
|
上官
|
||||||
|
欧阳
|
||||||
|
夏侯
|
||||||
|
诸葛
|
||||||
|
闻人
|
||||||
|
东方
|
||||||
|
赫连
|
||||||
|
皇甫
|
||||||
|
尉迟
|
||||||
|
公羊
|
||||||
|
澹台
|
||||||
|
公冶
|
||||||
|
宗政
|
||||||
|
濮阳
|
||||||
|
淳于
|
||||||
|
单于
|
||||||
|
太叔
|
||||||
|
申屠
|
||||||
|
公孙
|
||||||
|
仲孙
|
||||||
|
轩辕
|
||||||
|
令狐
|
||||||
|
钟离
|
||||||
|
宇文
|
||||||
|
长孙
|
||||||
|
慕容
|
||||||
|
鲜于
|
||||||
|
闾丘
|
||||||
|
司徒
|
||||||
|
司空
|
||||||
|
亓官
|
||||||
|
司寇
|
||||||
|
子车
|
||||||
|
颛孙
|
||||||
|
端木
|
||||||
|
巫马
|
||||||
|
公西
|
||||||
|
漆雕
|
||||||
|
乐正
|
||||||
|
壤驷
|
||||||
|
公良
|
||||||
|
拓跋
|
||||||
|
夹谷
|
||||||
|
宰父
|
||||||
|
谷梁
|
||||||
|
段干
|
||||||
|
百里
|
||||||
|
东郭
|
||||||
|
南门
|
||||||
|
呼延
|
||||||
|
羊舌
|
||||||
|
微生
|
||||||
|
梁丘
|
||||||
|
左丘
|
||||||
|
东门
|
||||||
|
西门
|
||||||
|
南宫
|
||||||
|
第五
|
||||||
|
步都
|
||||||
|
东欧
|
||||||
|
聂晁
|
||||||
|
空曾
|
||||||
|
相查
|
||||||
|
万俟
|
||||||
|
司马
|
||||||
|
上官
|
||||||
|
欧阳
|
||||||
|
夏侯
|
||||||
|
诸葛
|
||||||
|
闻人
|
||||||
|
东方
|
||||||
|
赫连
|
||||||
|
皇甫
|
||||||
|
尉迟
|
||||||
|
公羊
|
||||||
|
澹台
|
||||||
|
公冶
|
||||||
|
宗政
|
||||||
|
濮阳
|
||||||
|
淳于
|
||||||
|
单于
|
||||||
|
太叔
|
||||||
|
申屠
|
||||||
|
公孙
|
||||||
|
仲孙
|
||||||
|
轩辕
|
||||||
|
令狐
|
||||||
|
钟离
|
||||||
|
宇文
|
||||||
|
长孙
|
||||||
|
慕容
|
||||||
|
司徒
|
||||||
|
司空
|
||||||
|
凃肖
|
||||||
|
第五
|
||||||
|
南宫
|
||||||
|
左丘
|
||||||
|
梁丘
|
||||||
|
微生
|
||||||
|
羊舌
|
||||||
|
呼延
|
||||||
|
东郭
|
||||||
|
百里
|
||||||
|
谷梁
|
||||||
|
宰父
|
||||||
|
夹谷
|
||||||
|
拓跋
|
||||||
|
壤驷
|
||||||
|
乐正
|
||||||
|
漆雕
|
||||||
|
公西
|
||||||
|
巫马
|
||||||
|
端木
|
||||||
|
颛孙
|
||||||
|
子车
|
||||||
|
司寇
|
||||||
|
亓官
|
||||||
|
鲜于
|
||||||
|
锺离
|
||||||
|
闾丘
|
||||||
|
公良
|
||||||
|
段干
|
||||||
|
荔菲
|
||||||
|
辗迟
|
||||||
149
name_generator/assets/chinese_first_name_female.txt
Normal file
149
name_generator/assets/chinese_first_name_female.txt
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
嘉
|
||||||
|
琼
|
||||||
|
桂
|
||||||
|
娣
|
||||||
|
叶
|
||||||
|
璧
|
||||||
|
璐
|
||||||
|
娅
|
||||||
|
琦
|
||||||
|
晶
|
||||||
|
妍
|
||||||
|
茜
|
||||||
|
秋
|
||||||
|
珊
|
||||||
|
莎
|
||||||
|
锦
|
||||||
|
黛
|
||||||
|
青
|
||||||
|
倩
|
||||||
|
婷
|
||||||
|
姣
|
||||||
|
婉
|
||||||
|
娴
|
||||||
|
瑾
|
||||||
|
颖
|
||||||
|
露
|
||||||
|
瑶
|
||||||
|
怡
|
||||||
|
婵
|
||||||
|
雁
|
||||||
|
蓓
|
||||||
|
纨
|
||||||
|
仪
|
||||||
|
荷
|
||||||
|
丹
|
||||||
|
蓉
|
||||||
|
眉
|
||||||
|
君
|
||||||
|
琴
|
||||||
|
蕊
|
||||||
|
薇
|
||||||
|
菁
|
||||||
|
梦
|
||||||
|
岚
|
||||||
|
苑
|
||||||
|
婕
|
||||||
|
馨
|
||||||
|
瑗
|
||||||
|
琰
|
||||||
|
韵
|
||||||
|
融
|
||||||
|
园
|
||||||
|
艺
|
||||||
|
咏
|
||||||
|
卿
|
||||||
|
聪
|
||||||
|
澜
|
||||||
|
纯
|
||||||
|
毓
|
||||||
|
悦
|
||||||
|
昭
|
||||||
|
冰
|
||||||
|
爽
|
||||||
|
琬
|
||||||
|
茗
|
||||||
|
羽
|
||||||
|
希
|
||||||
|
宁
|
||||||
|
欣
|
||||||
|
飘
|
||||||
|
育
|
||||||
|
滢
|
||||||
|
馥
|
||||||
|
筠
|
||||||
|
柔
|
||||||
|
竹
|
||||||
|
霭
|
||||||
|
凝
|
||||||
|
晓
|
||||||
|
欢
|
||||||
|
霄
|
||||||
|
枫
|
||||||
|
芸
|
||||||
|
菲
|
||||||
|
寒
|
||||||
|
伊
|
||||||
|
亚
|
||||||
|
宜
|
||||||
|
可
|
||||||
|
姬
|
||||||
|
舒
|
||||||
|
影
|
||||||
|
荔
|
||||||
|
枝
|
||||||
|
思
|
||||||
|
丽
|
||||||
|
秀
|
||||||
|
娟
|
||||||
|
英
|
||||||
|
华
|
||||||
|
慧
|
||||||
|
巧
|
||||||
|
美
|
||||||
|
娜
|
||||||
|
静
|
||||||
|
淑
|
||||||
|
惠
|
||||||
|
珠
|
||||||
|
翠
|
||||||
|
雅
|
||||||
|
芝
|
||||||
|
玉
|
||||||
|
萍
|
||||||
|
红
|
||||||
|
娥
|
||||||
|
玲
|
||||||
|
芬
|
||||||
|
芳
|
||||||
|
燕
|
||||||
|
彩
|
||||||
|
春
|
||||||
|
菊
|
||||||
|
勤
|
||||||
|
珍
|
||||||
|
贞
|
||||||
|
莉
|
||||||
|
兰
|
||||||
|
凤
|
||||||
|
洁
|
||||||
|
梅
|
||||||
|
琳
|
||||||
|
素
|
||||||
|
云
|
||||||
|
莲
|
||||||
|
真
|
||||||
|
环
|
||||||
|
雪
|
||||||
|
荣
|
||||||
|
爱
|
||||||
|
妹
|
||||||
|
霞
|
||||||
|
香
|
||||||
|
月
|
||||||
|
莺
|
||||||
|
媛
|
||||||
|
艳
|
||||||
|
瑞
|
||||||
|
凡
|
||||||
|
佳
|
||||||
150
name_generator/assets/chinese_first_name_male.txt
Normal file
150
name_generator/assets/chinese_first_name_male.txt
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
涛
|
||||||
|
昌
|
||||||
|
进
|
||||||
|
林
|
||||||
|
有
|
||||||
|
坚
|
||||||
|
和
|
||||||
|
彪
|
||||||
|
博
|
||||||
|
诚
|
||||||
|
先
|
||||||
|
敬
|
||||||
|
震
|
||||||
|
振
|
||||||
|
壮
|
||||||
|
会
|
||||||
|
群
|
||||||
|
豪
|
||||||
|
心
|
||||||
|
邦
|
||||||
|
承
|
||||||
|
乐
|
||||||
|
绍
|
||||||
|
功
|
||||||
|
松
|
||||||
|
善
|
||||||
|
厚
|
||||||
|
庆
|
||||||
|
磊
|
||||||
|
民
|
||||||
|
友
|
||||||
|
裕
|
||||||
|
河
|
||||||
|
哲
|
||||||
|
江
|
||||||
|
超
|
||||||
|
浩
|
||||||
|
亮
|
||||||
|
政
|
||||||
|
谦
|
||||||
|
亨
|
||||||
|
奇
|
||||||
|
固
|
||||||
|
之
|
||||||
|
轮
|
||||||
|
翰
|
||||||
|
朗
|
||||||
|
伯
|
||||||
|
宏
|
||||||
|
言
|
||||||
|
若
|
||||||
|
鸣
|
||||||
|
朋
|
||||||
|
斌
|
||||||
|
梁
|
||||||
|
栋
|
||||||
|
维
|
||||||
|
启
|
||||||
|
克
|
||||||
|
伦
|
||||||
|
翔
|
||||||
|
旭
|
||||||
|
鹏
|
||||||
|
泽
|
||||||
|
晨
|
||||||
|
辰
|
||||||
|
士
|
||||||
|
以
|
||||||
|
建
|
||||||
|
家
|
||||||
|
致
|
||||||
|
树
|
||||||
|
炎
|
||||||
|
德
|
||||||
|
行
|
||||||
|
时
|
||||||
|
泰
|
||||||
|
盛
|
||||||
|
雄
|
||||||
|
琛
|
||||||
|
钧
|
||||||
|
冠
|
||||||
|
策
|
||||||
|
腾
|
||||||
|
伟
|
||||||
|
刚
|
||||||
|
勇
|
||||||
|
毅
|
||||||
|
俊
|
||||||
|
峰
|
||||||
|
强
|
||||||
|
军
|
||||||
|
平
|
||||||
|
保
|
||||||
|
东
|
||||||
|
文
|
||||||
|
辉
|
||||||
|
力
|
||||||
|
明
|
||||||
|
永
|
||||||
|
健
|
||||||
|
世
|
||||||
|
广
|
||||||
|
志
|
||||||
|
义
|
||||||
|
兴
|
||||||
|
良
|
||||||
|
海
|
||||||
|
山
|
||||||
|
仁
|
||||||
|
波
|
||||||
|
宁
|
||||||
|
贵
|
||||||
|
福
|
||||||
|
生
|
||||||
|
龙
|
||||||
|
元
|
||||||
|
全
|
||||||
|
国
|
||||||
|
胜
|
||||||
|
学
|
||||||
|
祥
|
||||||
|
才
|
||||||
|
发
|
||||||
|
成
|
||||||
|
康
|
||||||
|
星
|
||||||
|
光
|
||||||
|
天
|
||||||
|
达
|
||||||
|
安
|
||||||
|
岩
|
||||||
|
中
|
||||||
|
茂
|
||||||
|
武
|
||||||
|
新
|
||||||
|
利
|
||||||
|
清
|
||||||
|
飞
|
||||||
|
彬
|
||||||
|
富
|
||||||
|
顺
|
||||||
|
信
|
||||||
|
子
|
||||||
|
杰
|
||||||
|
楠
|
||||||
|
榕
|
||||||
|
风
|
||||||
|
航
|
||||||
|
弘
|
||||||
1119
name_generator/assets/chinese_single_surnames.txt
Normal file
1119
name_generator/assets/chinese_single_surnames.txt
Normal file
File diff suppressed because it is too large
Load Diff
267
name_generator/assets/english_first_name_female.txt
Normal file
267
name_generator/assets/english_first_name_female.txt
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
Abby
|
||||||
|
Adalheid
|
||||||
|
Adelaide
|
||||||
|
Ailsa
|
||||||
|
Aimee
|
||||||
|
Al
|
||||||
|
Alexandra
|
||||||
|
Alexis
|
||||||
|
Alice
|
||||||
|
Alina
|
||||||
|
Alisa
|
||||||
|
Allison
|
||||||
|
Amanda
|
||||||
|
Amber
|
||||||
|
Amy
|
||||||
|
Anastasia
|
||||||
|
Andrea
|
||||||
|
Angela
|
||||||
|
Angelia
|
||||||
|
Angelina
|
||||||
|
Angle
|
||||||
|
Anita
|
||||||
|
Ann
|
||||||
|
Annabelle
|
||||||
|
Anne
|
||||||
|
Annie
|
||||||
|
April
|
||||||
|
Ariel
|
||||||
|
Ashley
|
||||||
|
Aviva
|
||||||
|
Avivahc
|
||||||
|
Avivi
|
||||||
|
Barbara
|
||||||
|
Barbie
|
||||||
|
Beata
|
||||||
|
Beatrice
|
||||||
|
Beatrix
|
||||||
|
Becky
|
||||||
|
Beenle
|
||||||
|
Belle
|
||||||
|
Betty
|
||||||
|
Blanche
|
||||||
|
Bonnie
|
||||||
|
Brandon
|
||||||
|
Brenda
|
||||||
|
Brendan
|
||||||
|
Brianna
|
||||||
|
Bubles
|
||||||
|
Bunny
|
||||||
|
Camille
|
||||||
|
Candice
|
||||||
|
Carina
|
||||||
|
Carmen
|
||||||
|
Carol
|
||||||
|
Caroline
|
||||||
|
Carrie
|
||||||
|
Carry
|
||||||
|
Cassandra
|
||||||
|
Cassie
|
||||||
|
Catherine
|
||||||
|
Cathy
|
||||||
|
Charlene
|
||||||
|
Charlotte
|
||||||
|
Cher
|
||||||
|
Cherie
|
||||||
|
Cherry
|
||||||
|
Cheryl
|
||||||
|
Chris
|
||||||
|
Christal
|
||||||
|
Christina
|
||||||
|
Christine
|
||||||
|
Christy
|
||||||
|
Cinderella
|
||||||
|
Cindy
|
||||||
|
Claudia
|
||||||
|
Clement
|
||||||
|
Cloris
|
||||||
|
Colorfully
|
||||||
|
Connie
|
||||||
|
Constance
|
||||||
|
Cora
|
||||||
|
Corrine
|
||||||
|
Croesus
|
||||||
|
Crystal
|
||||||
|
Cynthia
|
||||||
|
Daisy
|
||||||
|
Daphne
|
||||||
|
Darcy
|
||||||
|
Davis
|
||||||
|
Debbie
|
||||||
|
Deborah
|
||||||
|
Debra
|
||||||
|
Demi
|
||||||
|
Diana
|
||||||
|
Dolores
|
||||||
|
Donna
|
||||||
|
Doris
|
||||||
|
Dreamy
|
||||||
|
Edith
|
||||||
|
Editha
|
||||||
|
El
|
||||||
|
Elaine
|
||||||
|
Eleanor
|
||||||
|
Elizabeth
|
||||||
|
Ella
|
||||||
|
Ellen
|
||||||
|
Ellie
|
||||||
|
Emily
|
||||||
|
Emma
|
||||||
|
Estelle
|
||||||
|
Esther
|
||||||
|
Eudora
|
||||||
|
Eva
|
||||||
|
Eve
|
||||||
|
Fairy
|
||||||
|
Fannie
|
||||||
|
Fanny
|
||||||
|
Fern
|
||||||
|
Fiona
|
||||||
|
Flower
|
||||||
|
Frances
|
||||||
|
Francis
|
||||||
|
Frederica
|
||||||
|
Frieda
|
||||||
|
Gillian
|
||||||
|
Gina
|
||||||
|
Girl
|
||||||
|
Gladys
|
||||||
|
Gloria
|
||||||
|
Grace
|
||||||
|
Greenle
|
||||||
|
Greta
|
||||||
|
Gwendolyn
|
||||||
|
Hailey
|
||||||
|
Hannah
|
||||||
|
Hebbe
|
||||||
|
Hebe
|
||||||
|
Heidi
|
||||||
|
Helen
|
||||||
|
Helena
|
||||||
|
Hellen
|
||||||
|
Icey
|
||||||
|
Ingrid
|
||||||
|
Irene
|
||||||
|
Iris
|
||||||
|
Ishara
|
||||||
|
Ivy
|
||||||
|
Jacqueline
|
||||||
|
James
|
||||||
|
Jamie
|
||||||
|
Jane
|
||||||
|
Janet
|
||||||
|
Janice
|
||||||
|
Jasmine
|
||||||
|
Jean
|
||||||
|
Jennie
|
||||||
|
Jennifer
|
||||||
|
Jenny
|
||||||
|
Jessee
|
||||||
|
Jessica
|
||||||
|
Jessie
|
||||||
|
Jill
|
||||||
|
Joan
|
||||||
|
Joanna
|
||||||
|
Jocelyn
|
||||||
|
Jodie
|
||||||
|
John
|
||||||
|
Josephine
|
||||||
|
Josie
|
||||||
|
Joy
|
||||||
|
Joyce
|
||||||
|
Jr
|
||||||
|
Judith
|
||||||
|
Judy
|
||||||
|
Julia
|
||||||
|
Juliana
|
||||||
|
Julie
|
||||||
|
June
|
||||||
|
Kaitlyn
|
||||||
|
Kerry
|
||||||
|
Kitty
|
||||||
|
Kris
|
||||||
|
Kristine
|
||||||
|
Krystal
|
||||||
|
Lareina
|
||||||
|
Laura
|
||||||
|
Lawrence
|
||||||
|
Lena
|
||||||
|
Liddy
|
||||||
|
Lilian
|
||||||
|
Lillian
|
||||||
|
Linda
|
||||||
|
Lisa
|
||||||
|
Liz
|
||||||
|
Lovely
|
||||||
|
Lucinda
|
||||||
|
Lydia
|
||||||
|
Madison
|
||||||
|
Maggie
|
||||||
|
Magical
|
||||||
|
Manda
|
||||||
|
Mandy
|
||||||
|
Margaret
|
||||||
|
Maria
|
||||||
|
Melinda
|
||||||
|
Miranda
|
||||||
|
Moon
|
||||||
|
Names
|
||||||
|
Nancy
|
||||||
|
Nicole
|
||||||
|
Nina
|
||||||
|
Purplegrape
|
||||||
|
Qearl
|
||||||
|
Rebecca
|
||||||
|
Regina
|
||||||
|
Rose
|
||||||
|
S
|
||||||
|
Sabrina
|
||||||
|
Samantha
|
||||||
|
Sammy
|
||||||
|
Samson
|
||||||
|
Samuel
|
||||||
|
Sandra
|
||||||
|
Sandy
|
||||||
|
Sarah
|
||||||
|
Sbrina
|
||||||
|
Scalett
|
||||||
|
Selina
|
||||||
|
Selma
|
||||||
|
Serena
|
||||||
|
Sharon
|
||||||
|
Sheila
|
||||||
|
Shelby
|
||||||
|
Shelley
|
||||||
|
Sherry
|
||||||
|
Sheryl
|
||||||
|
Shirley
|
||||||
|
Silverdew
|
||||||
|
Silvia
|
||||||
|
Snowhite
|
||||||
|
Snowy
|
||||||
|
Sonia
|
||||||
|
Stacey
|
||||||
|
Stacy
|
||||||
|
Star
|
||||||
|
Stella
|
||||||
|
Stephanie
|
||||||
|
Stephen
|
||||||
|
Sue
|
||||||
|
Sunny
|
||||||
|
Susan
|
||||||
|
Sweety
|
||||||
|
Temple
|
||||||
|
Vanessa
|
||||||
|
Vicky
|
||||||
|
Victoria
|
||||||
|
Vivian
|
||||||
|
Wanda
|
||||||
|
Wendy
|
||||||
|
Winnie
|
||||||
|
Y
|
||||||
|
Yilia
|
||||||
|
Yolanda
|
||||||
|
Yvette
|
||||||
|
Yvonne
|
||||||
|
Zoe
|
||||||
|
Zoey
|
||||||
320
name_generator/assets/english_first_name_male.txt
Normal file
320
name_generator/assets/english_first_name_male.txt
Normal file
@@ -0,0 +1,320 @@
|
|||||||
|
Derrick
|
||||||
|
Devin
|
||||||
|
Dick
|
||||||
|
Dominic
|
||||||
|
Don
|
||||||
|
Donahue
|
||||||
|
Donald
|
||||||
|
Douglas
|
||||||
|
Drew
|
||||||
|
Duke
|
||||||
|
Duncan
|
||||||
|
Dunn
|
||||||
|
Dwight
|
||||||
|
Dylan
|
||||||
|
Earl
|
||||||
|
Ed
|
||||||
|
Eden
|
||||||
|
Edgar
|
||||||
|
Edison
|
||||||
|
Edmund
|
||||||
|
Edward
|
||||||
|
Edwiin
|
||||||
|
Egbert
|
||||||
|
Eli
|
||||||
|
Elijah
|
||||||
|
Elliot
|
||||||
|
Ellis
|
||||||
|
Elmer
|
||||||
|
Elroy
|
||||||
|
Elton
|
||||||
|
Elvis
|
||||||
|
Emmanuel
|
||||||
|
Enoch
|
||||||
|
Eric
|
||||||
|
Ernest
|
||||||
|
Eugene
|
||||||
|
Evan
|
||||||
|
Everley
|
||||||
|
Fabian
|
||||||
|
Felix
|
||||||
|
Ferdinand
|
||||||
|
Fitch
|
||||||
|
Fitzgerald
|
||||||
|
Ford
|
||||||
|
Francis
|
||||||
|
Frank
|
||||||
|
Franklin
|
||||||
|
Frederic
|
||||||
|
Gabriel
|
||||||
|
Gale
|
||||||
|
Gary
|
||||||
|
Gavin
|
||||||
|
Gene
|
||||||
|
Geoff
|
||||||
|
Geoffrey
|
||||||
|
George
|
||||||
|
Gerald
|
||||||
|
Gilbert
|
||||||
|
Giles
|
||||||
|
Glenn
|
||||||
|
Goddard
|
||||||
|
Godfery
|
||||||
|
Gordon
|
||||||
|
Greg
|
||||||
|
Gregary
|
||||||
|
Griffith
|
||||||
|
Grover
|
||||||
|
Gustave
|
||||||
|
Guy
|
||||||
|
Hale
|
||||||
|
Haley
|
||||||
|
Hamiltion
|
||||||
|
Hardy
|
||||||
|
Harlan
|
||||||
|
Harley
|
||||||
|
Harold
|
||||||
|
Harriet
|
||||||
|
Harry
|
||||||
|
Harvey
|
||||||
|
Hayden
|
||||||
|
Heather
|
||||||
|
Henry
|
||||||
|
Herbert
|
||||||
|
Herman
|
||||||
|
Hilary
|
||||||
|
Hiram
|
||||||
|
Hobart
|
||||||
|
Hogan
|
||||||
|
Horace
|
||||||
|
Howar
|
||||||
|
Hubery
|
||||||
|
Hugh
|
||||||
|
Hugo
|
||||||
|
Humphrey
|
||||||
|
Hunter
|
||||||
|
Hyman
|
||||||
|
Ian
|
||||||
|
Ingemar
|
||||||
|
Ingram
|
||||||
|
Ira
|
||||||
|
Isaac
|
||||||
|
Isidore
|
||||||
|
Ivan
|
||||||
|
Ives
|
||||||
|
Jack
|
||||||
|
Jacob
|
||||||
|
James
|
||||||
|
Jared
|
||||||
|
Jason
|
||||||
|
Jay
|
||||||
|
Jeff
|
||||||
|
Jeffrey
|
||||||
|
Jeremy
|
||||||
|
Jerome
|
||||||
|
Jerry
|
||||||
|
Jesse
|
||||||
|
Jim
|
||||||
|
Jo
|
||||||
|
John
|
||||||
|
Jonas
|
||||||
|
Jonathan
|
||||||
|
Joseph
|
||||||
|
Joshua
|
||||||
|
Joyce
|
||||||
|
Julian
|
||||||
|
Julius
|
||||||
|
Justin
|
||||||
|
Keith
|
||||||
|
Kelly
|
||||||
|
Ken
|
||||||
|
Kennedy
|
||||||
|
Kenneth
|
||||||
|
Kent
|
||||||
|
Kerr
|
||||||
|
Kerwin
|
||||||
|
Kevin
|
||||||
|
Kim
|
||||||
|
King
|
||||||
|
Kirk
|
||||||
|
Kyle
|
||||||
|
Lambert
|
||||||
|
Lance
|
||||||
|
Larry
|
||||||
|
Lawrence
|
||||||
|
Leif
|
||||||
|
Len
|
||||||
|
Lennon
|
||||||
|
Leo
|
||||||
|
Leonard
|
||||||
|
Leopold
|
||||||
|
Les
|
||||||
|
Lester
|
||||||
|
Levi
|
||||||
|
Lewis
|
||||||
|
Lionel
|
||||||
|
Lou
|
||||||
|
Louis
|
||||||
|
Lucien
|
||||||
|
Luther
|
||||||
|
Lyle
|
||||||
|
Lyndon
|
||||||
|
Lynn
|
||||||
|
Magee
|
||||||
|
Malcolm
|
||||||
|
Mandel
|
||||||
|
Marcus
|
||||||
|
Marico
|
||||||
|
Mark
|
||||||
|
Marlon
|
||||||
|
Marsh
|
||||||
|
Marshall
|
||||||
|
Martin
|
||||||
|
Marvin
|
||||||
|
Matt
|
||||||
|
Matthew
|
||||||
|
Maurice
|
||||||
|
Max
|
||||||
|
Maximilian
|
||||||
|
Maxwell
|
||||||
|
Meredith
|
||||||
|
Merle
|
||||||
|
Merlin
|
||||||
|
Michael
|
||||||
|
Michell
|
||||||
|
Mick
|
||||||
|
Mike
|
||||||
|
Miles
|
||||||
|
Milo
|
||||||
|
Monroe
|
||||||
|
Montague
|
||||||
|
Moore
|
||||||
|
Morgan
|
||||||
|
Mortimer
|
||||||
|
Morton
|
||||||
|
Moses
|
||||||
|
Murphy
|
||||||
|
Murray
|
||||||
|
Myron
|
||||||
|
Nat
|
||||||
|
Nathan
|
||||||
|
Nathaniel
|
||||||
|
Neil
|
||||||
|
Nelson
|
||||||
|
Newman
|
||||||
|
Nicholas
|
||||||
|
Nick
|
||||||
|
Nigel
|
||||||
|
Noah
|
||||||
|
Noel
|
||||||
|
Norman
|
||||||
|
Norton
|
||||||
|
Ogden
|
||||||
|
Oliver
|
||||||
|
Omar
|
||||||
|
Orville
|
||||||
|
Osborn
|
||||||
|
Oscar
|
||||||
|
Osmond
|
||||||
|
Oswald
|
||||||
|
Otis
|
||||||
|
Otto
|
||||||
|
Owen
|
||||||
|
Paddy
|
||||||
|
Page
|
||||||
|
Parker
|
||||||
|
Patrick
|
||||||
|
Paul
|
||||||
|
Philip
|
||||||
|
Porter
|
||||||
|
Prescott
|
||||||
|
Primo
|
||||||
|
Quennel
|
||||||
|
Quentin
|
||||||
|
Quincy
|
||||||
|
Quinn
|
||||||
|
Quintion
|
||||||
|
Rachel
|
||||||
|
Ralap
|
||||||
|
Reuben
|
||||||
|
Rex
|
||||||
|
Richard
|
||||||
|
Robert
|
||||||
|
Robin
|
||||||
|
Rock
|
||||||
|
Rod
|
||||||
|
Roderick
|
||||||
|
Rodney
|
||||||
|
Rudolf
|
||||||
|
Rupert
|
||||||
|
Ryan
|
||||||
|
Sam
|
||||||
|
Sampson
|
||||||
|
Sandy
|
||||||
|
Saxon
|
||||||
|
Scott
|
||||||
|
Sean
|
||||||
|
Sebastian
|
||||||
|
Sid
|
||||||
|
Sidney
|
||||||
|
Sidon
|
||||||
|
Sidon
|
||||||
|
Silvester
|
||||||
|
Simon
|
||||||
|
Spencer
|
||||||
|
Stan
|
||||||
|
Stanford
|
||||||
|
Stanley
|
||||||
|
Stev
|
||||||
|
Steven
|
||||||
|
Steward
|
||||||
|
Tab
|
||||||
|
Taylor
|
||||||
|
Ted
|
||||||
|
Ternence
|
||||||
|
Theobald
|
||||||
|
Theodore
|
||||||
|
Thomas
|
||||||
|
Tiffany
|
||||||
|
Tim
|
||||||
|
Timothy
|
||||||
|
Tobias
|
||||||
|
Toby
|
||||||
|
Todd
|
||||||
|
Tom
|
||||||
|
Tony
|
||||||
|
Troy
|
||||||
|
Tyler
|
||||||
|
Tyrone
|
||||||
|
Ulysses
|
||||||
|
Upton
|
||||||
|
Uriah
|
||||||
|
Valentine
|
||||||
|
Valentine
|
||||||
|
Vic
|
||||||
|
Victor
|
||||||
|
Vincent
|
||||||
|
Virgil
|
||||||
|
Vito
|
||||||
|
Vivian
|
||||||
|
Walter
|
||||||
|
Ward
|
||||||
|
Warner
|
||||||
|
Wayne
|
||||||
|
Webb
|
||||||
|
Webster
|
||||||
|
Wendell
|
||||||
|
Werner
|
||||||
|
Wilbur
|
||||||
|
Will
|
||||||
|
William
|
||||||
|
Willie
|
||||||
|
Winfred
|
||||||
|
Winston
|
||||||
|
Wright
|
||||||
|
Wythe
|
||||||
|
Yale
|
||||||
|
York
|
||||||
|
Yves
|
||||||
|
Zachary
|
||||||
|
Ziv
|
||||||
100
name_generator/assets/english_last_name.txt
Normal file
100
name_generator/assets/english_last_name.txt
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
Adams
|
||||||
|
Alexander
|
||||||
|
Allen
|
||||||
|
Anderson
|
||||||
|
Bailey
|
||||||
|
Baker
|
||||||
|
Barnes
|
||||||
|
Bell
|
||||||
|
Bennett
|
||||||
|
Brooks
|
||||||
|
Brown
|
||||||
|
Bryant
|
||||||
|
Butler
|
||||||
|
Campbell
|
||||||
|
Carter
|
||||||
|
Clark
|
||||||
|
Coleman
|
||||||
|
Collins
|
||||||
|
Cook
|
||||||
|
Cooper
|
||||||
|
Cox
|
||||||
|
Davis
|
||||||
|
Diaz
|
||||||
|
Edwards
|
||||||
|
Evans
|
||||||
|
Flores
|
||||||
|
Foster
|
||||||
|
Garcia
|
||||||
|
Gonzales
|
||||||
|
Gonzalez
|
||||||
|
Gray
|
||||||
|
Green
|
||||||
|
Griffin
|
||||||
|
Hall
|
||||||
|
Harris
|
||||||
|
Hayes
|
||||||
|
Henderson
|
||||||
|
Hernandez
|
||||||
|
Hill
|
||||||
|
Howard
|
||||||
|
Hughes
|
||||||
|
Jackson
|
||||||
|
James
|
||||||
|
Jenkins
|
||||||
|
Johnson
|
||||||
|
Jones
|
||||||
|
Kelly
|
||||||
|
King
|
||||||
|
Lee
|
||||||
|
Lewis
|
||||||
|
Long
|
||||||
|
Lopez
|
||||||
|
Martin
|
||||||
|
Martinez
|
||||||
|
Miller
|
||||||
|
Mitchell
|
||||||
|
Moore
|
||||||
|
Morgan
|
||||||
|
Morris
|
||||||
|
Murphy
|
||||||
|
Nelson
|
||||||
|
Parker
|
||||||
|
Patterson
|
||||||
|
Perez
|
||||||
|
Perry
|
||||||
|
Peterson
|
||||||
|
Phillips
|
||||||
|
Powell
|
||||||
|
Price
|
||||||
|
Ramirez
|
||||||
|
Reed
|
||||||
|
Richardson
|
||||||
|
Rivera
|
||||||
|
Roberts
|
||||||
|
Robinson
|
||||||
|
Rodriguez
|
||||||
|
Rogers
|
||||||
|
Ross
|
||||||
|
Russell
|
||||||
|
Sanchez
|
||||||
|
Sanders
|
||||||
|
Scott
|
||||||
|
Simmons
|
||||||
|
Smith
|
||||||
|
Stewart
|
||||||
|
Taylor
|
||||||
|
Thomas
|
||||||
|
Thompson
|
||||||
|
Torres
|
||||||
|
Turner
|
||||||
|
Walker
|
||||||
|
Ward
|
||||||
|
Washington
|
||||||
|
Watson
|
||||||
|
White
|
||||||
|
Williams
|
||||||
|
Wilson
|
||||||
|
Wood
|
||||||
|
Wright
|
||||||
|
Young
|
||||||
421
name_generator/assets/goods.txt
Normal file
421
name_generator/assets/goods.txt
Normal file
@@ -0,0 +1,421 @@
|
|||||||
|
空气
|
||||||
|
石头
|
||||||
|
草方块
|
||||||
|
泥土
|
||||||
|
圆石
|
||||||
|
木板
|
||||||
|
树苗
|
||||||
|
基岩
|
||||||
|
水
|
||||||
|
岩浆
|
||||||
|
沙子
|
||||||
|
沙砾
|
||||||
|
金矿
|
||||||
|
铁矿
|
||||||
|
煤矿
|
||||||
|
木头
|
||||||
|
树叶
|
||||||
|
海绵
|
||||||
|
玻璃
|
||||||
|
青金石
|
||||||
|
发射器
|
||||||
|
沙石
|
||||||
|
音符盒
|
||||||
|
床
|
||||||
|
铁轨
|
||||||
|
活塞
|
||||||
|
蜘蛛网
|
||||||
|
草丛
|
||||||
|
灌木
|
||||||
|
活塞臂
|
||||||
|
羊毛
|
||||||
|
蒲公英
|
||||||
|
高粱酒
|
||||||
|
棕蘑菇
|
||||||
|
红蘑菇
|
||||||
|
金块
|
||||||
|
铁块
|
||||||
|
石台阶
|
||||||
|
砖块
|
||||||
|
TNT
|
||||||
|
书架
|
||||||
|
苔石
|
||||||
|
黑曜石
|
||||||
|
火把
|
||||||
|
火
|
||||||
|
刷怪箱
|
||||||
|
橡木梯
|
||||||
|
箱子
|
||||||
|
红石线
|
||||||
|
钻石矿
|
||||||
|
钻石块
|
||||||
|
工作台
|
||||||
|
耕地
|
||||||
|
梯子
|
||||||
|
圆石梯
|
||||||
|
拉杆
|
||||||
|
红石矿
|
||||||
|
雪
|
||||||
|
冰
|
||||||
|
雪块
|
||||||
|
仙人掌
|
||||||
|
唱片机
|
||||||
|
栅栏
|
||||||
|
南瓜
|
||||||
|
地狱岩
|
||||||
|
灵魂沙
|
||||||
|
萤石
|
||||||
|
下界门
|
||||||
|
南瓜灯
|
||||||
|
怪物蛋
|
||||||
|
石砖
|
||||||
|
巨蘑菇
|
||||||
|
毒蘑菇
|
||||||
|
铁栏杆
|
||||||
|
玻璃板
|
||||||
|
南瓜梗
|
||||||
|
西瓜梗
|
||||||
|
藤蔓
|
||||||
|
砖楼梯
|
||||||
|
石楼梯
|
||||||
|
菌丝
|
||||||
|
睡莲
|
||||||
|
地狱砖
|
||||||
|
附魔台
|
||||||
|
末地门
|
||||||
|
末地石
|
||||||
|
龙蛋
|
||||||
|
红石灯
|
||||||
|
木台阶
|
||||||
|
可可果
|
||||||
|
末影箱
|
||||||
|
绊线钩
|
||||||
|
绊线
|
||||||
|
绿宝石块
|
||||||
|
命令方块
|
||||||
|
信标
|
||||||
|
圆石墙
|
||||||
|
花盆
|
||||||
|
胡萝卜
|
||||||
|
马铃薯
|
||||||
|
木按钮
|
||||||
|
铁砧
|
||||||
|
陷阱箱
|
||||||
|
压力板
|
||||||
|
比较器
|
||||||
|
传感器
|
||||||
|
红石块
|
||||||
|
石英矿
|
||||||
|
漏斗
|
||||||
|
石英块
|
||||||
|
投掷器
|
||||||
|
粘液块
|
||||||
|
屏障
|
||||||
|
活板门
|
||||||
|
海晶石
|
||||||
|
海晶灯
|
||||||
|
干草块
|
||||||
|
地毯
|
||||||
|
硬粘土
|
||||||
|
煤炭块
|
||||||
|
浮冰
|
||||||
|
大型花
|
||||||
|
旗帜
|
||||||
|
红沙石
|
||||||
|
栅栏门
|
||||||
|
铁栅栏
|
||||||
|
木门
|
||||||
|
末地烛
|
||||||
|
紫影花
|
||||||
|
紫珀块
|
||||||
|
甜菜种
|
||||||
|
草径
|
||||||
|
霜冰
|
||||||
|
打火石
|
||||||
|
苹果
|
||||||
|
弓
|
||||||
|
箭
|
||||||
|
煤炭
|
||||||
|
钻石
|
||||||
|
铁锭
|
||||||
|
金锭
|
||||||
|
铁剑
|
||||||
|
铁锹
|
||||||
|
铁镐
|
||||||
|
铁斧
|
||||||
|
木剑
|
||||||
|
木锹
|
||||||
|
木镐
|
||||||
|
木斧
|
||||||
|
石剑
|
||||||
|
石锹
|
||||||
|
石镐
|
||||||
|
石斧
|
||||||
|
钻石剑
|
||||||
|
钻石锹
|
||||||
|
钻石镐
|
||||||
|
钻石斧
|
||||||
|
木棍
|
||||||
|
碗
|
||||||
|
蘑菇煲
|
||||||
|
金剑
|
||||||
|
金锹
|
||||||
|
金镐
|
||||||
|
金斧
|
||||||
|
线
|
||||||
|
羽毛
|
||||||
|
火药
|
||||||
|
木锄
|
||||||
|
石锄
|
||||||
|
铁锄
|
||||||
|
钻石锄
|
||||||
|
金锄
|
||||||
|
小麦种子
|
||||||
|
小麦
|
||||||
|
面包
|
||||||
|
皮革帽
|
||||||
|
皮革甲
|
||||||
|
皮革裤
|
||||||
|
皮革靴
|
||||||
|
铁头盔
|
||||||
|
铁胸甲
|
||||||
|
铁护腿
|
||||||
|
铁靴子
|
||||||
|
钻石盔
|
||||||
|
钻石甲
|
||||||
|
钻石腿
|
||||||
|
钻石靴
|
||||||
|
金头盔
|
||||||
|
金胸甲
|
||||||
|
金护腿
|
||||||
|
金靴子
|
||||||
|
燧石
|
||||||
|
生猪排
|
||||||
|
熟猪排
|
||||||
|
画
|
||||||
|
金苹果
|
||||||
|
告示牌
|
||||||
|
桶
|
||||||
|
水桶
|
||||||
|
岩浆桶
|
||||||
|
矿车
|
||||||
|
鞍
|
||||||
|
铁门
|
||||||
|
红石粉
|
||||||
|
雪球
|
||||||
|
船
|
||||||
|
皮革
|
||||||
|
牛奶
|
||||||
|
红砖
|
||||||
|
粘土
|
||||||
|
甘蔗
|
||||||
|
纸
|
||||||
|
书
|
||||||
|
粘液球
|
||||||
|
鸡蛋
|
||||||
|
指南针
|
||||||
|
钓鱼竿
|
||||||
|
钟
|
||||||
|
萤石粉
|
||||||
|
生鱼
|
||||||
|
熟鱼
|
||||||
|
染料
|
||||||
|
骨头
|
||||||
|
糖
|
||||||
|
蛋糕
|
||||||
|
中继器
|
||||||
|
曲奇
|
||||||
|
地图
|
||||||
|
剪刀
|
||||||
|
西瓜片
|
||||||
|
种子
|
||||||
|
西瓜种
|
||||||
|
生牛肉
|
||||||
|
牛排
|
||||||
|
生鸡肉
|
||||||
|
熟鸡肉
|
||||||
|
腐肉
|
||||||
|
末影珠
|
||||||
|
烈焰棒
|
||||||
|
恶魂之泪
|
||||||
|
金粒
|
||||||
|
地狱疣
|
||||||
|
药水
|
||||||
|
玻璃瓶
|
||||||
|
蜘蛛眼
|
||||||
|
烈焰粉
|
||||||
|
岩浆膏
|
||||||
|
酿造台
|
||||||
|
炼药锅
|
||||||
|
末影眼
|
||||||
|
西瓜
|
||||||
|
刷怪蛋
|
||||||
|
附魔瓶
|
||||||
|
火焰弹
|
||||||
|
书与笔
|
||||||
|
成书
|
||||||
|
绿宝石
|
||||||
|
展示框
|
||||||
|
烤马铃薯
|
||||||
|
毒马铃薯
|
||||||
|
空地图
|
||||||
|
金萝卜
|
||||||
|
下界星
|
||||||
|
南瓜派
|
||||||
|
火箭
|
||||||
|
烟火星
|
||||||
|
附魔书
|
||||||
|
生兔肉
|
||||||
|
熟兔肉
|
||||||
|
兔肉煲
|
||||||
|
兔子脚
|
||||||
|
兔子皮
|
||||||
|
盔甲架
|
||||||
|
铁马铠
|
||||||
|
金马铠
|
||||||
|
拴绳
|
||||||
|
命名牌
|
||||||
|
生羊肉
|
||||||
|
熟羊肉
|
||||||
|
云杉门
|
||||||
|
白桦门
|
||||||
|
丛林门
|
||||||
|
橡木门
|
||||||
|
紫影果
|
||||||
|
爆裂果
|
||||||
|
甜菜根
|
||||||
|
甜菜汤
|
||||||
|
龙息
|
||||||
|
光灵箭
|
||||||
|
药箭
|
||||||
|
盾牌
|
||||||
|
鞘翅
|
||||||
|
云杉船
|
||||||
|
白桦船
|
||||||
|
丛林船
|
||||||
|
橡木船
|
||||||
|
唱片
|
||||||
|
经验球
|
||||||
|
拴绳结
|
||||||
|
凋灵头
|
||||||
|
烟花
|
||||||
|
爬行者
|
||||||
|
骷髅
|
||||||
|
蜘蛛
|
||||||
|
巨人
|
||||||
|
僵尸
|
||||||
|
史莱姆
|
||||||
|
恶魂
|
||||||
|
僵尸猪
|
||||||
|
末影人
|
||||||
|
蠹虫
|
||||||
|
烈焰人
|
||||||
|
岩浆怪
|
||||||
|
末影龙
|
||||||
|
凋灵
|
||||||
|
女巫
|
||||||
|
末影螨
|
||||||
|
守卫者
|
||||||
|
潜影贝
|
||||||
|
长耳兔
|
||||||
|
蝙蝠
|
||||||
|
猪
|
||||||
|
羊
|
||||||
|
牛
|
||||||
|
鸡
|
||||||
|
鱿鱼
|
||||||
|
狼
|
||||||
|
哞菇
|
||||||
|
雪傀儡
|
||||||
|
豹猫
|
||||||
|
铁傀儡
|
||||||
|
马
|
||||||
|
兔子
|
||||||
|
村民
|
||||||
|
刷怪笼
|
||||||
|
刷怪塔
|
||||||
|
发电机
|
||||||
|
永动机
|
||||||
|
铁炉
|
||||||
|
粉碎机
|
||||||
|
电炉
|
||||||
|
反应堆
|
||||||
|
采矿机
|
||||||
|
工业炉
|
||||||
|
合成机
|
||||||
|
变压器
|
||||||
|
电池
|
||||||
|
青铜粉
|
||||||
|
电路板
|
||||||
|
传送带
|
||||||
|
枪械
|
||||||
|
手枪
|
||||||
|
冲锋枪
|
||||||
|
机枪
|
||||||
|
特种枪
|
||||||
|
步枪
|
||||||
|
大炮
|
||||||
|
火炮
|
||||||
|
加农炮
|
||||||
|
榴弹炮
|
||||||
|
迫击炮
|
||||||
|
高射炮
|
||||||
|
航炮
|
||||||
|
舰炮
|
||||||
|
海岸炮
|
||||||
|
火箭炮
|
||||||
|
坦克炮
|
||||||
|
航母
|
||||||
|
战列舰
|
||||||
|
驱逐舰
|
||||||
|
巡洋舰
|
||||||
|
护卫舰
|
||||||
|
潜艇
|
||||||
|
原子弹
|
||||||
|
氢弹
|
||||||
|
中子弹
|
||||||
|
飞机
|
||||||
|
轰炸机
|
||||||
|
运输机
|
||||||
|
侦察机
|
||||||
|
歼击机
|
||||||
|
导弹
|
||||||
|
核导弹
|
||||||
|
瓦斯弹
|
||||||
|
鱼雷
|
||||||
|
来复枪
|
||||||
|
神机炮
|
||||||
|
散弹枪
|
||||||
|
机炮
|
||||||
|
狙击枪
|
||||||
|
内燃机
|
||||||
|
锅炉
|
||||||
|
轮船
|
||||||
|
游艇
|
||||||
|
激光枪
|
||||||
|
机车
|
||||||
|
机床
|
||||||
|
电灯
|
||||||
|
木桌
|
||||||
|
木椅
|
||||||
|
木床
|
||||||
|
木柜
|
||||||
|
铁桌
|
||||||
|
铁椅
|
||||||
|
铁床
|
||||||
|
铁柜
|
||||||
|
酒柜
|
||||||
|
书橱
|
||||||
|
电脑桌
|
||||||
|
梳妆台
|
||||||
|
梳妆凳
|
||||||
|
梳子
|
||||||
|
木梳
|
||||||
|
骨梳
|
||||||
|
毛巾
|
||||||
|
餐车
|
||||||
|
茶几
|
||||||
|
玄关
|
||||||
|
衣架
|
||||||
|
沙发
|
||||||
|
屏风
|
||||||
10000
name_generator/assets/japanese_last_name.txt
Normal file
10000
name_generator/assets/japanese_last_name.txt
Normal file
File diff suppressed because it is too large
Load Diff
172413
name_generator/assets/japanese_names_corpus.txt
Normal file
172413
name_generator/assets/japanese_names_corpus.txt
Normal file
File diff suppressed because it is too large
Load Diff
3000
name_generator/assets/japanese_surnames.txt
Normal file
3000
name_generator/assets/japanese_surnames.txt
Normal file
File diff suppressed because it is too large
Load Diff
1109
name_generator/assets/name.txt
Normal file
1109
name_generator/assets/name.txt
Normal file
File diff suppressed because it is too large
Load Diff
626
name_generator/assets/prefix.txt
Normal file
626
name_generator/assets/prefix.txt
Normal file
@@ -0,0 +1,626 @@
|
|||||||
|
强硬的
|
||||||
|
强悍的
|
||||||
|
强劲的
|
||||||
|
坚决的
|
||||||
|
坚信的
|
||||||
|
坚定的
|
||||||
|
坚韧的
|
||||||
|
坚实的
|
||||||
|
坚贞的
|
||||||
|
勇敢的
|
||||||
|
勇猛的
|
||||||
|
刚毅的
|
||||||
|
决断的
|
||||||
|
果敢的
|
||||||
|
果决的
|
||||||
|
坚强的
|
||||||
|
坚忍的
|
||||||
|
决然的
|
||||||
|
毅然的
|
||||||
|
断然的
|
||||||
|
泼辣的
|
||||||
|
断腕的
|
||||||
|
发誓的
|
||||||
|
干脆的
|
||||||
|
爽快的
|
||||||
|
果断的
|
||||||
|
真诚的
|
||||||
|
热诚的
|
||||||
|
至诚的
|
||||||
|
赤诚的
|
||||||
|
诚挚的
|
||||||
|
恳切的
|
||||||
|
纯真的
|
||||||
|
率直的
|
||||||
|
坦率的
|
||||||
|
笃实的
|
||||||
|
热忱的
|
||||||
|
热心的
|
||||||
|
好客的
|
||||||
|
客气的
|
||||||
|
殷勤的
|
||||||
|
和气的
|
||||||
|
和蔼的
|
||||||
|
和善的
|
||||||
|
亲切的
|
||||||
|
过谦的
|
||||||
|
谦卑的
|
||||||
|
谦恭的
|
||||||
|
谦和的
|
||||||
|
谦让的
|
||||||
|
谦虚的
|
||||||
|
谦逊的
|
||||||
|
虚心的
|
||||||
|
外向的
|
||||||
|
开朗的
|
||||||
|
大方的
|
||||||
|
主动的
|
||||||
|
俏皮的
|
||||||
|
敏捷的
|
||||||
|
乐观的
|
||||||
|
调皮的
|
||||||
|
爽脆的
|
||||||
|
爽朗的
|
||||||
|
豪爽的
|
||||||
|
正直的
|
||||||
|
直率的
|
||||||
|
直爽的
|
||||||
|
直言的
|
||||||
|
爽直的
|
||||||
|
刚直的
|
||||||
|
憨直的
|
||||||
|
耿直的
|
||||||
|
公正的
|
||||||
|
公道的
|
||||||
|
公平的
|
||||||
|
公允的
|
||||||
|
正派的
|
||||||
|
开阔的
|
||||||
|
豁达的
|
||||||
|
明朗的
|
||||||
|
率真的
|
||||||
|
怒吼的
|
||||||
|
恐惧的
|
||||||
|
胆怯的
|
||||||
|
畏缩的
|
||||||
|
发慌的
|
||||||
|
心慌的
|
||||||
|
恐慌的
|
||||||
|
激怒的
|
||||||
|
恼火的
|
||||||
|
欢乐的
|
||||||
|
快慰的
|
||||||
|
开心的
|
||||||
|
高兴的
|
||||||
|
愉悦的
|
||||||
|
微笑的
|
||||||
|
舒畅的
|
||||||
|
不适的
|
||||||
|
欢闹的
|
||||||
|
欢心的
|
||||||
|
欢欣的
|
||||||
|
欢悦的
|
||||||
|
宽慰的
|
||||||
|
欢舒的
|
||||||
|
狂欢的
|
||||||
|
震怒的
|
||||||
|
气愤的
|
||||||
|
担忧的
|
||||||
|
发愁的
|
||||||
|
犯愁的
|
||||||
|
忧伤的
|
||||||
|
忧愁的
|
||||||
|
忧心的
|
||||||
|
愁闷的
|
||||||
|
悲痛的
|
||||||
|
悲惨的
|
||||||
|
悲凉的
|
||||||
|
哀伤的
|
||||||
|
哀怨的
|
||||||
|
伤感的
|
||||||
|
瘦削的
|
||||||
|
憔悴的
|
||||||
|
快乐的
|
||||||
|
喜悦的
|
||||||
|
愉快的
|
||||||
|
畅快的
|
||||||
|
欢畅的
|
||||||
|
欢喜的
|
||||||
|
欢腾的
|
||||||
|
欢快的
|
||||||
|
欣喜的
|
||||||
|
今天的
|
||||||
|
昨天的
|
||||||
|
明天的
|
||||||
|
后天的
|
||||||
|
上午的
|
||||||
|
下午的
|
||||||
|
过去的
|
||||||
|
未来的
|
||||||
|
去年的
|
||||||
|
前年的
|
||||||
|
散步的
|
||||||
|
漫步的
|
||||||
|
踏步的
|
||||||
|
信步的
|
||||||
|
转悠的
|
||||||
|
闲逛的
|
||||||
|
徜徉的
|
||||||
|
踉跄的
|
||||||
|
蹒跚的
|
||||||
|
小跑的
|
||||||
|
慢跑的
|
||||||
|
飞跑的
|
||||||
|
飞奔的
|
||||||
|
飞翔的
|
||||||
|
啜泣的
|
||||||
|
抽泣的
|
||||||
|
呜咽的
|
||||||
|
哀号的
|
||||||
|
号哭的
|
||||||
|
痛哭的
|
||||||
|
大笑的
|
||||||
|
欢笑的
|
||||||
|
嬉笑的
|
||||||
|
狂笑的
|
||||||
|
嗤笑的
|
||||||
|
憨笑的
|
||||||
|
傻笑的
|
||||||
|
哄笑的
|
||||||
|
苦笑的
|
||||||
|
阴笑的
|
||||||
|
狞笑的
|
||||||
|
奸笑的
|
||||||
|
嘲笑的
|
||||||
|
冷笑的
|
||||||
|
哈腰的
|
||||||
|
猫腰的
|
||||||
|
挺身的
|
||||||
|
挺胸的
|
||||||
|
俯身的
|
||||||
|
躬身的
|
||||||
|
仰卧的
|
||||||
|
蜷曲的
|
||||||
|
倒立的
|
||||||
|
转体的
|
||||||
|
屈体的
|
||||||
|
屈身的
|
||||||
|
欠身的
|
||||||
|
纵身的
|
||||||
|
蹲身的
|
||||||
|
鞠躬的
|
||||||
|
曲背的
|
||||||
|
匍匐的
|
||||||
|
笔挺的
|
||||||
|
腾跃的
|
||||||
|
直立的
|
||||||
|
翻腾的
|
||||||
|
前倾的
|
||||||
|
摇摆的
|
||||||
|
翻跃的
|
||||||
|
扭动的
|
||||||
|
扭转的
|
||||||
|
旋转的
|
||||||
|
好吃的
|
||||||
|
好看的
|
||||||
|
好玩的
|
||||||
|
清白的
|
||||||
|
凛然的
|
||||||
|
无私的
|
||||||
|
刚正的
|
||||||
|
负重的
|
||||||
|
忠心的
|
||||||
|
忠贞的
|
||||||
|
谨慎的
|
||||||
|
廉洁的
|
||||||
|
大度的
|
||||||
|
坦白的
|
||||||
|
勤奋的
|
||||||
|
刻苦的
|
||||||
|
认真的
|
||||||
|
专注的
|
||||||
|
踏实的
|
||||||
|
勤恳的
|
||||||
|
好学的
|
||||||
|
高尚的
|
||||||
|
杰出的
|
||||||
|
超伦的
|
||||||
|
自爱的
|
||||||
|
自尊的
|
||||||
|
自强的
|
||||||
|
宽容的
|
||||||
|
宽宏的
|
||||||
|
律己的
|
||||||
|
朴素的
|
||||||
|
憨厚的
|
||||||
|
诚实的
|
||||||
|
忠诚的
|
||||||
|
诚恳的
|
||||||
|
天真的
|
||||||
|
幼稚的
|
||||||
|
活泼的
|
||||||
|
聪明的
|
||||||
|
圆滑的
|
||||||
|
狡猾的
|
||||||
|
虚伪的
|
||||||
|
自私的
|
||||||
|
任性的
|
||||||
|
骄傲的
|
||||||
|
贪婪的
|
||||||
|
愚蠢的
|
||||||
|
奸诈的
|
||||||
|
高傲的
|
||||||
|
害羞的
|
||||||
|
内向的
|
||||||
|
孤僻的
|
||||||
|
可爱的
|
||||||
|
招烦的
|
||||||
|
阴险的
|
||||||
|
双重的
|
||||||
|
刻薄的
|
||||||
|
宽厚的
|
||||||
|
仁慈的
|
||||||
|
仁厚的
|
||||||
|
尖酸的
|
||||||
|
阴郁的
|
||||||
|
肤浅的
|
||||||
|
浅薄的
|
||||||
|
胆小的
|
||||||
|
乐天的
|
||||||
|
达观的
|
||||||
|
成熟的
|
||||||
|
稳重的
|
||||||
|
淘气的
|
||||||
|
温柔的
|
||||||
|
体贴的
|
||||||
|
强硬之
|
||||||
|
强悍之
|
||||||
|
强劲之
|
||||||
|
坚决之
|
||||||
|
坚信之
|
||||||
|
坚定之
|
||||||
|
坚韧之
|
||||||
|
坚实之
|
||||||
|
坚贞之
|
||||||
|
勇敢之
|
||||||
|
勇猛之
|
||||||
|
刚毅之
|
||||||
|
决断之
|
||||||
|
果敢之
|
||||||
|
果决之
|
||||||
|
坚强之
|
||||||
|
坚忍之
|
||||||
|
决然之
|
||||||
|
毅然之
|
||||||
|
断然之
|
||||||
|
泼辣之
|
||||||
|
断腕之
|
||||||
|
发誓之
|
||||||
|
干脆之
|
||||||
|
爽快之
|
||||||
|
果断之
|
||||||
|
真诚之
|
||||||
|
热诚之
|
||||||
|
至诚之
|
||||||
|
赤诚之
|
||||||
|
诚挚之
|
||||||
|
恳切之
|
||||||
|
纯真之
|
||||||
|
率直之
|
||||||
|
坦率之
|
||||||
|
笃实之
|
||||||
|
热忱之
|
||||||
|
热心之
|
||||||
|
好客之
|
||||||
|
客气之
|
||||||
|
殷勤之
|
||||||
|
和气之
|
||||||
|
和蔼之
|
||||||
|
和善之
|
||||||
|
亲切之
|
||||||
|
过谦之
|
||||||
|
谦卑之
|
||||||
|
谦恭之
|
||||||
|
谦和之
|
||||||
|
谦让之
|
||||||
|
谦虚之
|
||||||
|
谦逊之
|
||||||
|
虚心之
|
||||||
|
外向之
|
||||||
|
开朗之
|
||||||
|
大方之
|
||||||
|
主动之
|
||||||
|
俏皮之
|
||||||
|
敏捷之
|
||||||
|
乐观之
|
||||||
|
调皮之
|
||||||
|
爽脆之
|
||||||
|
爽朗之
|
||||||
|
豪爽之
|
||||||
|
正直之
|
||||||
|
直率之
|
||||||
|
直爽之
|
||||||
|
直言之
|
||||||
|
爽直之
|
||||||
|
刚直之
|
||||||
|
憨直之
|
||||||
|
耿直之
|
||||||
|
公正之
|
||||||
|
公道之
|
||||||
|
公平之
|
||||||
|
公允之
|
||||||
|
正派之
|
||||||
|
简捷之
|
||||||
|
开阔之
|
||||||
|
豁达之
|
||||||
|
明朗之
|
||||||
|
率真之
|
||||||
|
怒吼之
|
||||||
|
恐惧之
|
||||||
|
胆怯之
|
||||||
|
畏缩之
|
||||||
|
发慌之
|
||||||
|
心慌之
|
||||||
|
恐慌之
|
||||||
|
激怒之
|
||||||
|
恼火之
|
||||||
|
欢乐之
|
||||||
|
快慰之
|
||||||
|
开心之
|
||||||
|
高兴之
|
||||||
|
愉悦之
|
||||||
|
微笑之
|
||||||
|
舒畅之
|
||||||
|
笑噱之
|
||||||
|
欢闹之
|
||||||
|
欢心之
|
||||||
|
欢欣之
|
||||||
|
欢悦之
|
||||||
|
宽慰之
|
||||||
|
欢舒之
|
||||||
|
狂欢之
|
||||||
|
震怒之
|
||||||
|
气愤之
|
||||||
|
担忧之
|
||||||
|
发愁之
|
||||||
|
犯愁之
|
||||||
|
忧伤之
|
||||||
|
忧愁之
|
||||||
|
忧心之
|
||||||
|
愁闷之
|
||||||
|
悲痛之
|
||||||
|
悲惨之
|
||||||
|
悲凉之
|
||||||
|
哀伤之
|
||||||
|
哀怨之
|
||||||
|
伤感之
|
||||||
|
瘦削之
|
||||||
|
憔悴之
|
||||||
|
快乐之
|
||||||
|
喜悦之
|
||||||
|
愉快之
|
||||||
|
畅快之
|
||||||
|
欢畅之
|
||||||
|
欢喜之
|
||||||
|
欢腾之
|
||||||
|
欢快之
|
||||||
|
欣喜之
|
||||||
|
今天之
|
||||||
|
昨天之
|
||||||
|
明天之
|
||||||
|
后天之
|
||||||
|
上午之
|
||||||
|
下午之
|
||||||
|
过去之
|
||||||
|
未来之
|
||||||
|
去年之
|
||||||
|
前年之
|
||||||
|
散步之
|
||||||
|
漫步之
|
||||||
|
踏步之
|
||||||
|
信步之
|
||||||
|
转悠之
|
||||||
|
闲逛之
|
||||||
|
徜徉之
|
||||||
|
踉跄之
|
||||||
|
蹒跚之
|
||||||
|
小跑之
|
||||||
|
慢跑之
|
||||||
|
飞跑之
|
||||||
|
飞奔之
|
||||||
|
飞翔之
|
||||||
|
啜泣之
|
||||||
|
抽泣之
|
||||||
|
呜咽之
|
||||||
|
哀号之
|
||||||
|
号哭之
|
||||||
|
痛哭之
|
||||||
|
大笑之
|
||||||
|
欢笑之
|
||||||
|
嬉笑之
|
||||||
|
狂笑之
|
||||||
|
嗤笑之
|
||||||
|
憨笑之
|
||||||
|
傻笑之
|
||||||
|
哄笑之
|
||||||
|
苦笑之
|
||||||
|
阴笑之
|
||||||
|
狞笑之
|
||||||
|
奸笑之
|
||||||
|
嘲笑之
|
||||||
|
冷笑之
|
||||||
|
哈腰之
|
||||||
|
猫腰之
|
||||||
|
挺身之
|
||||||
|
挺胸之
|
||||||
|
俯身之
|
||||||
|
躬身之
|
||||||
|
仰卧之
|
||||||
|
蜷曲之
|
||||||
|
倒立之
|
||||||
|
转体之
|
||||||
|
屈体之
|
||||||
|
屈身之
|
||||||
|
欠身之
|
||||||
|
纵身之
|
||||||
|
蹲身之
|
||||||
|
鞠躬之
|
||||||
|
曲背之
|
||||||
|
匍匐之
|
||||||
|
笔挺之
|
||||||
|
腾跃之
|
||||||
|
直立之
|
||||||
|
翻腾之
|
||||||
|
前倾之
|
||||||
|
摇摆之
|
||||||
|
翻跃之
|
||||||
|
扭动之
|
||||||
|
扭转之
|
||||||
|
旋转之
|
||||||
|
好吃之
|
||||||
|
好看之
|
||||||
|
好玩之
|
||||||
|
清白之
|
||||||
|
凛然之
|
||||||
|
无私之
|
||||||
|
刚正之
|
||||||
|
负重之
|
||||||
|
忠心之
|
||||||
|
忠贞之
|
||||||
|
谨慎之
|
||||||
|
廉洁之
|
||||||
|
大度之
|
||||||
|
坦白之
|
||||||
|
勤奋之
|
||||||
|
刻苦之
|
||||||
|
认真之
|
||||||
|
专注之
|
||||||
|
踏实之
|
||||||
|
勤恳之
|
||||||
|
好学之
|
||||||
|
高尚之
|
||||||
|
杰出之
|
||||||
|
超伦之
|
||||||
|
自爱之
|
||||||
|
自尊之
|
||||||
|
自强之
|
||||||
|
宽容之
|
||||||
|
宽宏之
|
||||||
|
律己之
|
||||||
|
朴素之
|
||||||
|
憨厚之
|
||||||
|
诚实之
|
||||||
|
忠诚之
|
||||||
|
诚恳之
|
||||||
|
天真之
|
||||||
|
幼稚之
|
||||||
|
活泼之
|
||||||
|
聪明之
|
||||||
|
圆滑之
|
||||||
|
狡猾之
|
||||||
|
虚伪之
|
||||||
|
自私之
|
||||||
|
任性之
|
||||||
|
骄傲之
|
||||||
|
贪婪之
|
||||||
|
愚蠢之
|
||||||
|
奸诈之
|
||||||
|
高傲之
|
||||||
|
害羞之
|
||||||
|
内向之
|
||||||
|
孤僻之
|
||||||
|
可爱之
|
||||||
|
招烦之
|
||||||
|
阴险之
|
||||||
|
双重之
|
||||||
|
刻薄之
|
||||||
|
宽厚之
|
||||||
|
仁慈之
|
||||||
|
仁厚之
|
||||||
|
尖酸之
|
||||||
|
阴郁之
|
||||||
|
肤浅之
|
||||||
|
浅薄之
|
||||||
|
胆小之
|
||||||
|
乐天之
|
||||||
|
达观之
|
||||||
|
成熟之
|
||||||
|
稳重之
|
||||||
|
淘气之
|
||||||
|
温柔之
|
||||||
|
体贴之
|
||||||
|
苹果味
|
||||||
|
香蕉味
|
||||||
|
橘子味
|
||||||
|
桃子味
|
||||||
|
荔枝味
|
||||||
|
龙眼味
|
||||||
|
桔子味
|
||||||
|
李子味
|
||||||
|
葡萄味
|
||||||
|
青梅味
|
||||||
|
椰子味
|
||||||
|
石榴味
|
||||||
|
草莓味
|
||||||
|
栗子味
|
||||||
|
梨子味
|
||||||
|
樱桃味
|
||||||
|
苹果味
|
||||||
|
木瓜味
|
||||||
|
芒果味
|
||||||
|
菠萝味
|
||||||
|
柠檬味
|
||||||
|
柿子味
|
||||||
|
柚子味
|
||||||
|
西瓜味
|
||||||
|
南瓜味
|
||||||
|
甘蔗味
|
||||||
|
小麦味
|
||||||
|
蜂蜜味
|
||||||
|
白菜味
|
||||||
|
黄瓜味
|
||||||
|
豌豆味
|
||||||
|
苦瓜味
|
||||||
|
菠菜味
|
||||||
|
冬瓜味
|
||||||
|
茄子味
|
||||||
|
竹笋味
|
||||||
|
蚕豆味
|
||||||
|
萝卜味
|
||||||
|
辣椒味
|
||||||
|
鸡肉味
|
||||||
|
牛肉味
|
||||||
|
烤肉味
|
||||||
|
炸鸡味
|
||||||
|
番茄味
|
||||||
|
芝士味
|
||||||
|
榴莲味
|
||||||
|
山楂味
|
||||||
|
水果味
|
||||||
|
陈皮味
|
||||||
|
花椒味
|
||||||
|
莲雾味
|
||||||
|
杨梅味
|
||||||
|
泥土味
|
||||||
|
枇杷味
|
||||||
|
杨桃味
|
||||||
|
板栗味
|
||||||
|
瓜子味
|
||||||
|
桑葚味
|
||||||
|
猪蹄味
|
||||||
|
香瓜味
|
||||||
|
怪味的
|
||||||
|
塑料味
|
||||||
|
简单的
|
||||||
|
枯燥的
|
||||||
|
仙气的
|
||||||
|
酸臭味
|
||||||
|
蜜柑味
|
||||||
|
火锅味
|
||||||
|
泡菜味
|
||||||
|
抹茶味
|
||||||
|
蓝莓味
|
||||||
|
无味
|
||||||
|
甜味
|
||||||
|
苦味
|
||||||
|
酸甜味
|
||||||
449
name_generator/assets/role.txt
Normal file
449
name_generator/assets/role.txt
Normal file
@@ -0,0 +1,449 @@
|
|||||||
|
蝙蝠
|
||||||
|
烈焰人
|
||||||
|
蜘蛛
|
||||||
|
鸡
|
||||||
|
鸡骑士
|
||||||
|
牛
|
||||||
|
爬行者
|
||||||
|
驴
|
||||||
|
守卫者
|
||||||
|
末影龙
|
||||||
|
末影人
|
||||||
|
末影螨
|
||||||
|
唤魔者
|
||||||
|
恶魂
|
||||||
|
巨人
|
||||||
|
马
|
||||||
|
尸壳
|
||||||
|
幻术师
|
||||||
|
铁傀儡
|
||||||
|
兔子
|
||||||
|
羊驼
|
||||||
|
吉祥物
|
||||||
|
岩浆怪
|
||||||
|
哞菇
|
||||||
|
骡
|
||||||
|
豹猫
|
||||||
|
鹦鹉
|
||||||
|
猪
|
||||||
|
北极熊
|
||||||
|
羊
|
||||||
|
潜影贝
|
||||||
|
蠹虫
|
||||||
|
骷髅
|
||||||
|
骷髅马
|
||||||
|
史莱姆
|
||||||
|
雪傀儡
|
||||||
|
守卫
|
||||||
|
鱿鱼
|
||||||
|
流髑
|
||||||
|
恼鬼
|
||||||
|
卫道士
|
||||||
|
村民
|
||||||
|
女巫
|
||||||
|
凋灵
|
||||||
|
狼
|
||||||
|
僵尸
|
||||||
|
僵尸马
|
||||||
|
鲑鱼
|
||||||
|
河豚
|
||||||
|
金枪鱼
|
||||||
|
鲤鱼
|
||||||
|
黄鳝
|
||||||
|
电鳗
|
||||||
|
泥鳅
|
||||||
|
巫师
|
||||||
|
弓手
|
||||||
|
公主
|
||||||
|
土豪
|
||||||
|
工程师
|
||||||
|
程序员
|
||||||
|
服主
|
||||||
|
阿婆主
|
||||||
|
妹子
|
||||||
|
精灵
|
||||||
|
兽人
|
||||||
|
矮人
|
||||||
|
龙
|
||||||
|
龙骑士
|
||||||
|
天使
|
||||||
|
恶魔
|
||||||
|
地狱疣
|
||||||
|
蘑菇
|
||||||
|
程序
|
||||||
|
美术
|
||||||
|
策划
|
||||||
|
开发
|
||||||
|
客服
|
||||||
|
侍卫
|
||||||
|
侍从
|
||||||
|
仆人
|
||||||
|
宅男
|
||||||
|
炮姐
|
||||||
|
侏儒
|
||||||
|
泰坦
|
||||||
|
血精灵
|
||||||
|
牛头人
|
||||||
|
牧师
|
||||||
|
圣骑士
|
||||||
|
猎人
|
||||||
|
德鲁伊
|
||||||
|
法师
|
||||||
|
术士
|
||||||
|
战士
|
||||||
|
盗贼
|
||||||
|
蜗牛
|
||||||
|
黑猪
|
||||||
|
国王
|
||||||
|
王子
|
||||||
|
女王
|
||||||
|
阴阳师
|
||||||
|
宗师
|
||||||
|
建筑师
|
||||||
|
特种兵
|
||||||
|
专家
|
||||||
|
猪骑士
|
||||||
|
骑士
|
||||||
|
狼骑士
|
||||||
|
君主
|
||||||
|
郡主
|
||||||
|
君王
|
||||||
|
骷髅兵
|
||||||
|
吉吉怪
|
||||||
|
苦力怕
|
||||||
|
蜘蛛娘
|
||||||
|
苦力娘
|
||||||
|
僵尸娘
|
||||||
|
末影娘
|
||||||
|
哞菇娘
|
||||||
|
凋零娘
|
||||||
|
搬运工
|
||||||
|
版主
|
||||||
|
汉化组
|
||||||
|
字幕君
|
||||||
|
画师
|
||||||
|
漫画家
|
||||||
|
动画师
|
||||||
|
声优
|
||||||
|
歌手
|
||||||
|
唱见
|
||||||
|
舞见
|
||||||
|
人偶师
|
||||||
|
主播
|
||||||
|
编剧
|
||||||
|
导演
|
||||||
|
吉他手
|
||||||
|
监督
|
||||||
|
贝斯手
|
||||||
|
主唱
|
||||||
|
鼓手
|
||||||
|
房管
|
||||||
|
贝斯
|
||||||
|
苹果
|
||||||
|
香蕉
|
||||||
|
橘子
|
||||||
|
桃子
|
||||||
|
荔枝
|
||||||
|
龙眼
|
||||||
|
桔子
|
||||||
|
李子
|
||||||
|
葡萄
|
||||||
|
青梅
|
||||||
|
椰子
|
||||||
|
石榴
|
||||||
|
草莓
|
||||||
|
栗子
|
||||||
|
梨子
|
||||||
|
樱桃
|
||||||
|
梨
|
||||||
|
木瓜
|
||||||
|
芒果
|
||||||
|
菠萝
|
||||||
|
柠檬
|
||||||
|
柿子
|
||||||
|
柚子
|
||||||
|
无花果
|
||||||
|
猕猴桃
|
||||||
|
西红柿
|
||||||
|
水蜜桃
|
||||||
|
西瓜
|
||||||
|
南瓜
|
||||||
|
甘蔗
|
||||||
|
小麦
|
||||||
|
高粱
|
||||||
|
胡萝卜
|
||||||
|
马铃薯
|
||||||
|
可可豆
|
||||||
|
仙人掌
|
||||||
|
白菜
|
||||||
|
黄瓜
|
||||||
|
豌豆
|
||||||
|
苦瓜
|
||||||
|
菠菜
|
||||||
|
冬瓜
|
||||||
|
茄子
|
||||||
|
竹笋
|
||||||
|
蚕豆
|
||||||
|
萝卜
|
||||||
|
辣椒
|
||||||
|
火龙
|
||||||
|
冰龙
|
||||||
|
野狼
|
||||||
|
野猪
|
||||||
|
双头龙
|
||||||
|
猴子
|
||||||
|
猎豹
|
||||||
|
企鹅
|
||||||
|
青蛙
|
||||||
|
蝌蚪
|
||||||
|
猛犸
|
||||||
|
半兽人
|
||||||
|
亚龙人
|
||||||
|
半人马
|
||||||
|
牛头
|
||||||
|
牛头怪
|
||||||
|
食人魔
|
||||||
|
仙女
|
||||||
|
小仙女
|
||||||
|
蛇妖
|
||||||
|
女妖
|
||||||
|
妖怪
|
||||||
|
地精
|
||||||
|
霍比特
|
||||||
|
半身人
|
||||||
|
巫女
|
||||||
|
萨满
|
||||||
|
魔王
|
||||||
|
魔女
|
||||||
|
地狱犬
|
||||||
|
甲虫
|
||||||
|
罗刹
|
||||||
|
石像
|
||||||
|
雕塑
|
||||||
|
飞马
|
||||||
|
树精
|
||||||
|
娜迦
|
||||||
|
狼人
|
||||||
|
猫人
|
||||||
|
猫女
|
||||||
|
巨魔
|
||||||
|
海豹
|
||||||
|
蜥蜴
|
||||||
|
三文鱼
|
||||||
|
希鲮鱼
|
||||||
|
纸巾
|
||||||
|
豹子
|
||||||
|
狮子
|
||||||
|
狮子王
|
||||||
|
妖精
|
||||||
|
英雄
|
||||||
|
侠客
|
||||||
|
老鼠
|
||||||
|
猫咪
|
||||||
|
狼狗
|
||||||
|
哈士奇
|
||||||
|
金毛
|
||||||
|
萨摩
|
||||||
|
斗牛犬
|
||||||
|
牧羊犬
|
||||||
|
猎犬
|
||||||
|
吉娃娃
|
||||||
|
八哥
|
||||||
|
腊肠犬
|
||||||
|
柯基
|
||||||
|
约克夏
|
||||||
|
松狮
|
||||||
|
秋田犬
|
||||||
|
柴犬
|
||||||
|
博美
|
||||||
|
藏獒
|
||||||
|
牛头梗
|
||||||
|
比熊
|
||||||
|
二郎神
|
||||||
|
玉帝
|
||||||
|
弼马温
|
||||||
|
波斯猫
|
||||||
|
英短
|
||||||
|
布偶
|
||||||
|
美短
|
||||||
|
入殓师
|
||||||
|
清洁工
|
||||||
|
教师
|
||||||
|
清道夫
|
||||||
|
律师
|
||||||
|
医生
|
||||||
|
码农
|
||||||
|
猛男
|
||||||
|
学姐
|
||||||
|
学长
|
||||||
|
师兄
|
||||||
|
大兵
|
||||||
|
网红
|
||||||
|
帅哥
|
||||||
|
课代表
|
||||||
|
班长
|
||||||
|
组长
|
||||||
|
跳蛛
|
||||||
|
蜜柑
|
||||||
|
痒痒鼠
|
||||||
|
跳跳鼠
|
||||||
|
作家
|
||||||
|
维修工
|
||||||
|
快递员
|
||||||
|
蝴蝶
|
||||||
|
瓢虫
|
||||||
|
蚂蚱
|
||||||
|
蚂蚁
|
||||||
|
毛毛虫
|
||||||
|
屎壳郎
|
||||||
|
苍蝇
|
||||||
|
蜜蜂
|
||||||
|
独角仙
|
||||||
|
飞蛾
|
||||||
|
天牛
|
||||||
|
鼻涕虫
|
||||||
|
金龟子
|
||||||
|
红蚂蚁
|
||||||
|
蚜虫
|
||||||
|
甲壳虫
|
||||||
|
蛾子
|
||||||
|
跳蚤
|
||||||
|
兔狲
|
||||||
|
短毛猫
|
||||||
|
折耳猫
|
||||||
|
暹罗猫
|
||||||
|
无毛猫
|
||||||
|
卷毛猫
|
||||||
|
猞猁
|
||||||
|
云豹
|
||||||
|
花豹
|
||||||
|
雪豹
|
||||||
|
灰狼
|
||||||
|
鬃狼
|
||||||
|
沙狐
|
||||||
|
藏狐
|
||||||
|
北极狐
|
||||||
|
苍狐
|
||||||
|
赤狐
|
||||||
|
大耳狐
|
||||||
|
貂
|
||||||
|
画眉鸟
|
||||||
|
麻雀
|
||||||
|
鸽子
|
||||||
|
文鸟
|
||||||
|
珍珠鸟
|
||||||
|
蜂鸟
|
||||||
|
火烈鸟
|
||||||
|
海鸥
|
||||||
|
猫头鹰
|
||||||
|
苍鹰
|
||||||
|
秃鹫
|
||||||
|
布谷鸟
|
||||||
|
乌鸦
|
||||||
|
灰鹦鹉
|
||||||
|
蜡嘴鸟
|
||||||
|
园丁鸟
|
||||||
|
孔雀
|
||||||
|
喜鹊
|
||||||
|
杜鹃
|
||||||
|
翠鸟
|
||||||
|
啄木鸟
|
||||||
|
主管
|
||||||
|
经理
|
||||||
|
监工
|
||||||
|
规划师
|
||||||
|
药师
|
||||||
|
护理
|
||||||
|
护士
|
||||||
|
会计
|
||||||
|
咨询师
|
||||||
|
翻译
|
||||||
|
记者
|
||||||
|
兽医
|
||||||
|
测量员
|
||||||
|
面壁者
|
||||||
|
破壁人
|
||||||
|
中介
|
||||||
|
厨师
|
||||||
|
老板
|
||||||
|
掌勺
|
||||||
|
营养师
|
||||||
|
推销员
|
||||||
|
司机
|
||||||
|
售票员
|
||||||
|
管理
|
||||||
|
导游
|
||||||
|
调酒师
|
||||||
|
美容师
|
||||||
|
理发师
|
||||||
|
解说员
|
||||||
|
交易员
|
||||||
|
保姆
|
||||||
|
苗圃工
|
||||||
|
设计师
|
||||||
|
模特
|
||||||
|
售货员
|
||||||
|
保安
|
||||||
|
警察
|
||||||
|
消防员
|
||||||
|
花匠
|
||||||
|
水电工
|
||||||
|
建筑工
|
||||||
|
电工
|
||||||
|
钳工
|
||||||
|
修护工
|
||||||
|
铸造工
|
||||||
|
缝纫工
|
||||||
|
顾问
|
||||||
|
白领
|
||||||
|
公务员
|
||||||
|
文秘
|
||||||
|
天文学家
|
||||||
|
大法师
|
||||||
|
小法师
|
||||||
|
大牧师
|
||||||
|
小牧师
|
||||||
|
剑客
|
||||||
|
刀客
|
||||||
|
剑术师
|
||||||
|
蛇
|
||||||
|
森冠蛇
|
||||||
|
宽头蛇
|
||||||
|
眼镜蛇
|
||||||
|
蟒蛇
|
||||||
|
竹叶青
|
||||||
|
草蛇
|
||||||
|
角马
|
||||||
|
狒狒
|
||||||
|
斑马
|
||||||
|
大象
|
||||||
|
非洲象
|
||||||
|
巨像
|
||||||
|
小象
|
||||||
|
水牛
|
||||||
|
瞪羚
|
||||||
|
尼罗鳄
|
||||||
|
河马
|
||||||
|
犀牛
|
||||||
|
小河马
|
||||||
|
羚羊
|
||||||
|
山羊
|
||||||
|
母羊
|
||||||
|
公羊
|
||||||
|
野马
|
||||||
|
野驴
|
||||||
|
金猫
|
||||||
|
盘羊
|
||||||
|
丹顶鹤
|
||||||
|
白鹤
|
||||||
|
金雕
|
||||||
|
天鹅
|
||||||
|
扬子鳄
|
||||||
|
白唇鹿
|
||||||
|
乌龟
|
||||||
|
蟾蜍
|
||||||
|
蝎子
|
||||||
|
沙蝎
|
||||||
|
鱼人
|
||||||
|
鱼怪
|
||||||
|
蜘蛛精
|
||||||
37
name_generator/assets/sensitive.txt
Normal file
37
name_generator/assets/sensitive.txt
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
sabi
|
||||||
|
sb
|
||||||
|
shabi
|
||||||
|
dafeiji
|
||||||
|
sese
|
||||||
|
chuo
|
||||||
|
http
|
||||||
|
https
|
||||||
|
www
|
||||||
|
jiba
|
||||||
|
weiguang
|
||||||
|
zhilang
|
||||||
|
250
|
||||||
|
520
|
||||||
|
nima
|
||||||
|
niba
|
||||||
|
woba
|
||||||
|
woma
|
||||||
|
woshinima
|
||||||
|
nijia
|
||||||
|
giegie
|
||||||
|
daxiong
|
||||||
|
xiongda
|
||||||
|
zhanai
|
||||||
|
niuniu
|
||||||
|
jiji
|
||||||
|
penis
|
||||||
|
yindao
|
||||||
|
yingdao
|
||||||
|
yinhui
|
||||||
|
yinghui
|
||||||
|
dadoudou
|
||||||
|
胸
|
||||||
|
寄吧
|
||||||
|
傻
|
||||||
|
傻子
|
||||||
|
傻逼
|
||||||
324
name_generator/assets/verb.txt
Normal file
324
name_generator/assets/verb.txt
Normal file
@@ -0,0 +1,324 @@
|
|||||||
|
奔跑
|
||||||
|
爬行
|
||||||
|
蹦极
|
||||||
|
游行
|
||||||
|
吃土
|
||||||
|
剁手
|
||||||
|
飞行
|
||||||
|
滑翔
|
||||||
|
背书
|
||||||
|
学习
|
||||||
|
思考
|
||||||
|
度假
|
||||||
|
啃书
|
||||||
|
吃鲸
|
||||||
|
洗脸
|
||||||
|
刷牙
|
||||||
|
登基
|
||||||
|
诞生
|
||||||
|
挖矿
|
||||||
|
下矿
|
||||||
|
游蝶泳
|
||||||
|
蹦迪
|
||||||
|
吃糖
|
||||||
|
跳舞
|
||||||
|
吸猫
|
||||||
|
遛狗
|
||||||
|
遛娃
|
||||||
|
烧烤
|
||||||
|
拔线
|
||||||
|
狗吠
|
||||||
|
上学
|
||||||
|
飞升
|
||||||
|
探险
|
||||||
|
观光
|
||||||
|
抽卡
|
||||||
|
划水
|
||||||
|
潜水
|
||||||
|
赏花
|
||||||
|
赏月
|
||||||
|
品茶
|
||||||
|
化妆
|
||||||
|
煮饭
|
||||||
|
做饭
|
||||||
|
觉醒
|
||||||
|
长跑
|
||||||
|
吸气
|
||||||
|
呼气
|
||||||
|
练功
|
||||||
|
熬夜
|
||||||
|
听歌
|
||||||
|
开车
|
||||||
|
上车
|
||||||
|
下车
|
||||||
|
飘着
|
||||||
|
开学
|
||||||
|
放假
|
||||||
|
画画
|
||||||
|
弹琴
|
||||||
|
砍树
|
||||||
|
挖坑
|
||||||
|
寻宝
|
||||||
|
追寻
|
||||||
|
下坠
|
||||||
|
浮沉
|
||||||
|
劈叉
|
||||||
|
踏雪
|
||||||
|
睡觉
|
||||||
|
发梦
|
||||||
|
做梦
|
||||||
|
冲浪
|
||||||
|
跑酷
|
||||||
|
哭泣
|
||||||
|
咆哮
|
||||||
|
穿越
|
||||||
|
潜行
|
||||||
|
复习
|
||||||
|
追番
|
||||||
|
搁浅
|
||||||
|
吟诗
|
||||||
|
葬花
|
||||||
|
大笑
|
||||||
|
苦笑
|
||||||
|
尬笑
|
||||||
|
羽化
|
||||||
|
消亡
|
||||||
|
爆破
|
||||||
|
求佛
|
||||||
|
鸟瞰
|
||||||
|
发芽
|
||||||
|
开花
|
||||||
|
滑行
|
||||||
|
练发声
|
||||||
|
扮鬼
|
||||||
|
出击
|
||||||
|
闯关
|
||||||
|
解密
|
||||||
|
交易
|
||||||
|
听写
|
||||||
|
摘星
|
||||||
|
落泪
|
||||||
|
祈祷
|
||||||
|
冥想
|
||||||
|
吹牛
|
||||||
|
研究
|
||||||
|
购物
|
||||||
|
补牙
|
||||||
|
拔牙
|
||||||
|
舞剑
|
||||||
|
灌篮
|
||||||
|
预习
|
||||||
|
考试
|
||||||
|
卖萌
|
||||||
|
扮猪
|
||||||
|
吃狗粮
|
||||||
|
坐飞机
|
||||||
|
开赛车
|
||||||
|
吃橙子
|
||||||
|
吃苹果
|
||||||
|
吃菠萝
|
||||||
|
吃榴莲
|
||||||
|
吃香蕉
|
||||||
|
吃枇杷
|
||||||
|
喝圣水
|
||||||
|
跳热舞
|
||||||
|
吹喇叭
|
||||||
|
开飞机
|
||||||
|
水上漂
|
||||||
|
跳芭蕾
|
||||||
|
穿西装
|
||||||
|
穿裙子
|
||||||
|
看日出
|
||||||
|
建房子
|
||||||
|
说相声
|
||||||
|
吹短笛
|
||||||
|
练吉他
|
||||||
|
吹长笛
|
||||||
|
泡温泉
|
||||||
|
深呼吸
|
||||||
|
肝游戏
|
||||||
|
看涨潮
|
||||||
|
刷副本
|
||||||
|
喝阔咯
|
||||||
|
看日落
|
||||||
|
等吃饭
|
||||||
|
做作业
|
||||||
|
看直播
|
||||||
|
说谢谢
|
||||||
|
打豆豆
|
||||||
|
看视频
|
||||||
|
看大海
|
||||||
|
做自己
|
||||||
|
讲笑话
|
||||||
|
背古诗
|
||||||
|
写作文
|
||||||
|
写散文
|
||||||
|
斗蛐蛐
|
||||||
|
捉蛐蛐
|
||||||
|
捉昆虫
|
||||||
|
搞科研
|
||||||
|
种太阳
|
||||||
|
喝可乐
|
||||||
|
玩魔方
|
||||||
|
解方程
|
||||||
|
拿高分
|
||||||
|
影分身
|
||||||
|
捏泥人
|
||||||
|
做好事
|
||||||
|
喝咖啡
|
||||||
|
开班会
|
||||||
|
发通报
|
||||||
|
发牢骚
|
||||||
|
使性子
|
||||||
|
学音乐
|
||||||
|
学美术
|
||||||
|
吆喝
|
||||||
|
吃辣条
|
||||||
|
笑嘻嘻
|
||||||
|
扮可爱
|
||||||
|
吃钙片
|
||||||
|
吃麦片
|
||||||
|
吃披萨
|
||||||
|
上网
|
||||||
|
进观园
|
||||||
|
搬音响
|
||||||
|
解等式
|
||||||
|
玩卡牌
|
||||||
|
蹦跳跳
|
||||||
|
开汽车
|
||||||
|
作演讲
|
||||||
|
练书法
|
||||||
|
画漫画
|
||||||
|
开火车
|
||||||
|
做手工
|
||||||
|
读英语
|
||||||
|
吃牛肉
|
||||||
|
喝鸡汤
|
||||||
|
过马路
|
||||||
|
数绵羊
|
||||||
|
打电话
|
||||||
|
发短信
|
||||||
|
听广播
|
||||||
|
看漫画
|
||||||
|
变魔术
|
||||||
|
猜字谜
|
||||||
|
听音乐
|
||||||
|
学雷锋
|
||||||
|
切蔬菜
|
||||||
|
想问题
|
||||||
|
吃零食
|
||||||
|
想休息
|
||||||
|
拍气球
|
||||||
|
买玩具
|
||||||
|
开轿车
|
||||||
|
捏橘子
|
||||||
|
放鞭炮
|
||||||
|
吃水果
|
||||||
|
削水果
|
||||||
|
削苹果
|
||||||
|
削菠萝
|
||||||
|
削梨子
|
||||||
|
倒垃圾
|
||||||
|
改错误
|
||||||
|
吃醋
|
||||||
|
喝醋
|
||||||
|
上岸
|
||||||
|
吃面条
|
||||||
|
学数学
|
||||||
|
学语文
|
||||||
|
学地理
|
||||||
|
学英语
|
||||||
|
学历史
|
||||||
|
学生物
|
||||||
|
学物理
|
||||||
|
做装备
|
||||||
|
修钟表
|
||||||
|
修水管
|
||||||
|
修汽车
|
||||||
|
修桌子
|
||||||
|
修手机
|
||||||
|
修冰箱
|
||||||
|
吹口琴
|
||||||
|
弹琵琶
|
||||||
|
弹古筝
|
||||||
|
弹钢琴
|
||||||
|
吹口哨
|
||||||
|
弹吉他
|
||||||
|
吹笛子
|
||||||
|
弹三弦
|
||||||
|
敲排鼓
|
||||||
|
敲木鱼
|
||||||
|
敲渔鼓
|
||||||
|
撞铁钟
|
||||||
|
敲锣
|
||||||
|
拉二胡
|
||||||
|
拉马头琴
|
||||||
|
唱歌
|
||||||
|
唱山歌
|
||||||
|
唱高音
|
||||||
|
唱低音
|
||||||
|
听鸟叫
|
||||||
|
听摇滚
|
||||||
|
听爵士
|
||||||
|
听民谣
|
||||||
|
推箱子
|
||||||
|
看动漫
|
||||||
|
玩电脑
|
||||||
|
玩手机
|
||||||
|
逛街
|
||||||
|
拧瓶盖
|
||||||
|
打扫
|
||||||
|
擦椅子
|
||||||
|
削铅笔
|
||||||
|
背课文
|
||||||
|
背英语
|
||||||
|
念单词
|
||||||
|
念课文
|
||||||
|
打副本
|
||||||
|
放技能
|
||||||
|
躲技能
|
||||||
|
躲雨
|
||||||
|
跺脚
|
||||||
|
提水桶
|
||||||
|
涨工资
|
||||||
|
吃稀饭
|
||||||
|
喝冷饮
|
||||||
|
打篮球
|
||||||
|
踢足球
|
||||||
|
瞪眼睛
|
||||||
|
去砍树
|
||||||
|
闻花香
|
||||||
|
修管道
|
||||||
|
修电脑
|
||||||
|
擦桌子
|
||||||
|
咬铅笔
|
||||||
|
玩跳棋
|
||||||
|
看电视
|
||||||
|
看足球
|
||||||
|
看电影
|
||||||
|
看攻略
|
||||||
|
下象棋
|
||||||
|
下围棋
|
||||||
|
下棋
|
||||||
|
悔棋
|
||||||
|
上楼梯
|
||||||
|
坐电梯
|
||||||
|
下楼梯
|
||||||
|
看海报
|
||||||
|
喝稀饭
|
||||||
|
喝饮料
|
||||||
|
喝豆浆
|
||||||
|
喝果汁
|
||||||
|
喝豆奶
|
||||||
|
扔东西
|
||||||
|
扔垃圾
|
||||||
|
扔废纸
|
||||||
|
扔飞镖
|
||||||
|
丢飞机
|
||||||
|
开会
|
||||||
|
散步
|
||||||
|
游泳
|
||||||
|
健身
|
||||||
|
锻炼
|
||||||
|
迫降
|
||||||
|
变身
|
||||||
70
name_generator/consts.go
Normal file
70
name_generator/consts.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package name_generator
|
||||||
|
|
||||||
|
const (
|
||||||
|
DictionaryTypeAdjective DictionaryType = "adjective"
|
||||||
|
DictionaryTypeGoods DictionaryType = "goods"
|
||||||
|
DictionaryTypeName DictionaryType = "name"
|
||||||
|
DictionaryTypePrefix DictionaryType = "prefix"
|
||||||
|
DictionaryTypeRole DictionaryType = "role"
|
||||||
|
DictionaryTypeVerb DictionaryType = "verb"
|
||||||
|
DictionaryTypeSensitive DictionaryType = "sensitive"
|
||||||
|
|
||||||
|
DictionaryTypeSingleSurnames DictionaryType = "single_surnames"
|
||||||
|
DictionaryTypeCompoundSurnames DictionaryType = "compound_surnames"
|
||||||
|
DictionaryTypeChineseFirstNameFemale DictionaryType = "chinese_first_name_female"
|
||||||
|
DictionaryTypeChineseFirstNameMale DictionaryType = "chinese_first_name_male"
|
||||||
|
|
||||||
|
DictionaryTypeEnglishFirstNameFemale DictionaryType = "english_first_name_female"
|
||||||
|
DictionaryTypeEnglishFirstNameMale DictionaryType = "english_first_name_male"
|
||||||
|
DictionaryTypeEnglishLastName DictionaryType = "english_last_name"
|
||||||
|
|
||||||
|
DictionaryTypeJapaneseName DictionaryType = "japanese_name"
|
||||||
|
DictionaryTypeJapaneseSurnames DictionaryType = "japanese_surnames"
|
||||||
|
DictionaryTypeJapaneseLastName DictionaryType = "japanese_last_name"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Scheme1 = CombinedDictionaryType{
|
||||||
|
DictionaryTypePrefix,
|
||||||
|
DictionaryTypeName,
|
||||||
|
DictionaryTypeVerb,
|
||||||
|
}
|
||||||
|
|
||||||
|
var Scheme2 = CombinedDictionaryType{
|
||||||
|
DictionaryTypePrefix,
|
||||||
|
DictionaryTypeRole,
|
||||||
|
DictionaryTypeVerb,
|
||||||
|
}
|
||||||
|
|
||||||
|
var Scheme3 = CombinedDictionaryType{
|
||||||
|
DictionaryTypePrefix,
|
||||||
|
DictionaryTypeName,
|
||||||
|
DictionaryTypeAdjective,
|
||||||
|
}
|
||||||
|
|
||||||
|
var Scheme4 = CombinedDictionaryType{
|
||||||
|
DictionaryTypePrefix,
|
||||||
|
DictionaryTypeVerb,
|
||||||
|
DictionaryTypeRole,
|
||||||
|
}
|
||||||
|
|
||||||
|
var Scheme5 = CombinedDictionaryType{
|
||||||
|
DictionaryTypePrefix,
|
||||||
|
DictionaryTypeVerb,
|
||||||
|
DictionaryTypeName,
|
||||||
|
}
|
||||||
|
|
||||||
|
var Scheme6 = CombinedDictionaryType{
|
||||||
|
DictionaryTypeName,
|
||||||
|
DictionaryTypePrefix,
|
||||||
|
DictionaryTypeGoods,
|
||||||
|
}
|
||||||
|
|
||||||
|
var SchemeChineseNameFemale = CombinedDictionaryType{
|
||||||
|
DictionaryTypeSingleSurnames,
|
||||||
|
DictionaryTypeChineseFirstNameFemale,
|
||||||
|
}
|
||||||
|
|
||||||
|
var SchemeChineseNameMale = CombinedDictionaryType{
|
||||||
|
DictionaryTypeSingleSurnames,
|
||||||
|
DictionaryTypeChineseFirstNameMale,
|
||||||
|
}
|
||||||
321
name_generator/generator.go
Normal file
321
name_generator/generator.go
Normal file
@@ -0,0 +1,321 @@
|
|||||||
|
package name_generator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/tx7do/go-utils/name_generator/assets"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Generator struct {
|
||||||
|
dictionaries DictionaryMap
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *Generator {
|
||||||
|
g := &Generator{
|
||||||
|
dictionaries: make(DictionaryMap),
|
||||||
|
}
|
||||||
|
|
||||||
|
g.init()
|
||||||
|
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Generator) init() {
|
||||||
|
g.loadAllDict()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Generator) loadAllDict() {
|
||||||
|
_ = g.LoadDict(DictionaryTypeAdjective, assets.Adjective)
|
||||||
|
_ = g.LoadDict(DictionaryTypeGoods, assets.Goods)
|
||||||
|
_ = g.LoadDict(DictionaryTypeName, assets.Name)
|
||||||
|
_ = g.LoadDict(DictionaryTypePrefix, assets.Prefix)
|
||||||
|
_ = g.LoadDict(DictionaryTypeRole, assets.Role)
|
||||||
|
_ = g.LoadDict(DictionaryTypeVerb, assets.Verb)
|
||||||
|
|
||||||
|
_ = g.LoadDict(DictionaryTypeSensitive, assets.Sensitive)
|
||||||
|
|
||||||
|
_ = g.LoadDict(DictionaryTypeSingleSurnames, assets.ChineseSingleSurnames)
|
||||||
|
_ = g.LoadDict(DictionaryTypeCompoundSurnames, assets.ChineseCompoundSurnames)
|
||||||
|
_ = g.LoadDict(DictionaryTypeChineseFirstNameFemale, assets.ChineseFirstNameFemale)
|
||||||
|
_ = g.LoadDict(DictionaryTypeChineseFirstNameMale, assets.ChineseFirstNameMale)
|
||||||
|
|
||||||
|
//_ = g.LoadDict(DictionaryTypeEnglishFirstNameFemale, assets.EnglishFirstNameFemale)
|
||||||
|
//_ = g.LoadDict(DictionaryTypeEnglishFirstNameMale, assets.EnglishFirstNameMale)
|
||||||
|
//_ = g.LoadDict(DictionaryTypeEnglishLastName, assets.EnglishLastName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Generator) LoadDict(dictType DictionaryType, textData []byte) error {
|
||||||
|
if g.dictionaries == nil {
|
||||||
|
g.dictionaries = make(DictionaryMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := g.dictionaries[dictType]; ok {
|
||||||
|
return errors.New("dictionary already exists for type: " + string(dictType))
|
||||||
|
}
|
||||||
|
|
||||||
|
var dict Dictionary
|
||||||
|
|
||||||
|
reader := bytes.NewReader(textData)
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(reader)
|
||||||
|
for scanner.Scan() {
|
||||||
|
word := strings.TrimSpace(scanner.Text())
|
||||||
|
if word == "" {
|
||||||
|
continue // Skip empty lines
|
||||||
|
}
|
||||||
|
dict = append(dict, word)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
g.dictionaries[dictType] = dict
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Generator) LoadDictFromFile(dictType DictionaryType, filePath string) error {
|
||||||
|
if g.dictionaries == nil {
|
||||||
|
g.dictionaries = make(DictionaryMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := g.dictionaries[dictType]; ok {
|
||||||
|
return errors.New("dictionary already exists for type: " + string(dictType))
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Open(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func(file *os.File) {
|
||||||
|
err = file.Close()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error closing file:", err)
|
||||||
|
}
|
||||||
|
}(file)
|
||||||
|
|
||||||
|
var dict Dictionary
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
for scanner.Scan() {
|
||||||
|
word := strings.TrimSpace(scanner.Text())
|
||||||
|
if word == "" {
|
||||||
|
continue // Skip empty lines
|
||||||
|
}
|
||||||
|
dict = append(dict, strings.TrimSpace(word))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
g.dictionaries[dictType] = dict
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Generator) ExistDict(dictType DictionaryType) bool {
|
||||||
|
if g.dictionaries == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
_, exists := g.dictionaries[dictType]
|
||||||
|
return exists
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Generator) DictCount() int {
|
||||||
|
if g.dictionaries == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(g.dictionaries)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Generator) DictItemCount(dictType DictionaryType) int {
|
||||||
|
if g.dictionaries == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
dict, exists := g.dictionaries[dictType]
|
||||||
|
if !exists {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(dict)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Generator) randomWordFromDict(dictType DictionaryType) string {
|
||||||
|
dict, exists := g.dictionaries[dictType]
|
||||||
|
if !exists {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(dict) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
randomIndex := rand.Intn(len(dict))
|
||||||
|
return dict[randomIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Generator) Generate(dictTypes CombinedDictionaryType) string {
|
||||||
|
if len(dictTypes) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := g.GenerateParts(dictTypes)
|
||||||
|
if len(parts) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(parts, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Generator) GenerateParts(dictTypes CombinedDictionaryType) []string {
|
||||||
|
if len(dictTypes) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var parts []string
|
||||||
|
for _, dictType := range dictTypes {
|
||||||
|
word := g.randomWordFromDict(dictType)
|
||||||
|
if word != "" {
|
||||||
|
parts = append(parts, word)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Generator) GenerateChineseName(firstNameCount int, isFemale, isCompoundSurname bool) string {
|
||||||
|
if !g.ExistDict(DictionaryTypeSingleSurnames) {
|
||||||
|
_ = g.LoadDict(DictionaryTypeSingleSurnames, assets.ChineseSingleSurnames)
|
||||||
|
}
|
||||||
|
if !g.ExistDict(DictionaryTypeCompoundSurnames) {
|
||||||
|
_ = g.LoadDict(DictionaryTypeCompoundSurnames, assets.ChineseCompoundSurnames)
|
||||||
|
}
|
||||||
|
if !g.ExistDict(DictionaryTypeChineseFirstNameFemale) {
|
||||||
|
_ = g.LoadDict(DictionaryTypeChineseFirstNameFemale, assets.ChineseFirstNameFemale)
|
||||||
|
}
|
||||||
|
if !g.ExistDict(DictionaryTypeChineseFirstNameMale) {
|
||||||
|
_ = g.LoadDict(DictionaryTypeChineseFirstNameMale, assets.ChineseFirstNameMale)
|
||||||
|
}
|
||||||
|
|
||||||
|
if firstNameCount < 1 || firstNameCount > 2 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
dictTypes := make(CombinedDictionaryType, 0)
|
||||||
|
|
||||||
|
if isCompoundSurname {
|
||||||
|
dictTypes = append(dictTypes, DictionaryTypeCompoundSurnames)
|
||||||
|
} else {
|
||||||
|
dictTypes = append(dictTypes, DictionaryTypeSingleSurnames)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < firstNameCount; i++ {
|
||||||
|
if isFemale {
|
||||||
|
dictTypes = append(dictTypes, DictionaryTypeChineseFirstNameFemale)
|
||||||
|
} else {
|
||||||
|
dictTypes = append(dictTypes, DictionaryTypeChineseFirstNameMale)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := g.GenerateParts(dictTypes)
|
||||||
|
if len(parts) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(parts, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Generator) GenerateEnglishName(firstNameCount, middleNameCount, lastNameCount int, isFemale bool) string {
|
||||||
|
if !g.ExistDict(DictionaryTypeEnglishFirstNameFemale) {
|
||||||
|
_ = g.LoadDict(DictionaryTypeEnglishFirstNameFemale, assets.EnglishFirstNameFemale)
|
||||||
|
}
|
||||||
|
if !g.ExistDict(DictionaryTypeEnglishFirstNameMale) {
|
||||||
|
_ = g.LoadDict(DictionaryTypeEnglishFirstNameMale, assets.EnglishFirstNameMale)
|
||||||
|
}
|
||||||
|
if !g.ExistDict(DictionaryTypeEnglishLastName) {
|
||||||
|
_ = g.LoadDict(DictionaryTypeEnglishLastName, assets.EnglishLastName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if firstNameCount < 1 || firstNameCount > 2 ||
|
||||||
|
lastNameCount < 1 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
dictTypes := make(CombinedDictionaryType, 0)
|
||||||
|
|
||||||
|
for i := 0; i < firstNameCount; i++ {
|
||||||
|
if isFemale {
|
||||||
|
dictTypes = append(dictTypes, DictionaryTypeEnglishFirstNameFemale)
|
||||||
|
} else {
|
||||||
|
dictTypes = append(dictTypes, DictionaryTypeEnglishFirstNameMale)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < middleNameCount; i++ {
|
||||||
|
if isFemale {
|
||||||
|
dictTypes = append(dictTypes, DictionaryTypeEnglishFirstNameFemale)
|
||||||
|
} else {
|
||||||
|
dictTypes = append(dictTypes, DictionaryTypeEnglishFirstNameMale)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < lastNameCount; i++ {
|
||||||
|
dictTypes = append(dictTypes, DictionaryTypeEnglishLastName)
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := g.GenerateParts(dictTypes)
|
||||||
|
if len(parts) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(parts, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Generator) GenerateJapaneseNameCN() string {
|
||||||
|
if !g.ExistDict(DictionaryTypeJapaneseName) {
|
||||||
|
_ = g.LoadDict(DictionaryTypeJapaneseName, assets.JapaneseNamesCorpus)
|
||||||
|
}
|
||||||
|
|
||||||
|
dictTypes := CombinedDictionaryType{
|
||||||
|
DictionaryTypeJapaneseName,
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := g.GenerateParts(dictTypes)
|
||||||
|
if len(parts) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(parts, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Generator) GenerateJapaneseName() string {
|
||||||
|
if !g.ExistDict(DictionaryTypeJapaneseSurnames) {
|
||||||
|
_ = g.LoadDict(DictionaryTypeJapaneseSurnames, assets.JapaneseSurnames)
|
||||||
|
}
|
||||||
|
if !g.ExistDict(DictionaryTypeJapaneseLastName) {
|
||||||
|
_ = g.LoadDict(DictionaryTypeJapaneseLastName, assets.JapaneseLastName)
|
||||||
|
}
|
||||||
|
|
||||||
|
dictTypes := CombinedDictionaryType{
|
||||||
|
DictionaryTypeJapaneseSurnames,
|
||||||
|
DictionaryTypeJapaneseLastName,
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := g.GenerateParts(dictTypes)
|
||||||
|
if len(parts) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(parts, "")
|
||||||
|
}
|
||||||
97
name_generator/generator_test.go
Normal file
97
name_generator/generator_test.go
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
package name_generator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGenerate(t *testing.T) {
|
||||||
|
g := New()
|
||||||
|
|
||||||
|
dictTypes := Scheme5
|
||||||
|
|
||||||
|
result := g.Generate(dictTypes)
|
||||||
|
|
||||||
|
if result == "" {
|
||||||
|
t.Errorf("result is empty, please check the dictionary data")
|
||||||
|
} else {
|
||||||
|
t.Logf("generate`s nickname: %s", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateParts(t *testing.T) {
|
||||||
|
g := New()
|
||||||
|
|
||||||
|
dictTypes := Scheme6
|
||||||
|
|
||||||
|
parts := g.GenerateParts(dictTypes)
|
||||||
|
|
||||||
|
if len(parts) == 0 {
|
||||||
|
t.Errorf("result is empty, please check the dictionary data")
|
||||||
|
} else {
|
||||||
|
t.Logf("generate`s parts: %v", parts)
|
||||||
|
}
|
||||||
|
|
||||||
|
parts[0] = parts[0] + "#的"
|
||||||
|
|
||||||
|
result := strings.Join(parts, "")
|
||||||
|
t.Logf("generate`s nickname: %s", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateChineseName(t *testing.T) {
|
||||||
|
g := New()
|
||||||
|
|
||||||
|
result := g.GenerateChineseName(1, true, false)
|
||||||
|
if result == "" {
|
||||||
|
t.Errorf("result is empty, please check the dictionary data")
|
||||||
|
} else {
|
||||||
|
t.Logf("Generated single surname single name (female): %s", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
result = g.GenerateChineseName(2, false, true)
|
||||||
|
if result == "" {
|
||||||
|
t.Errorf("result is empty, please check the dictionary data")
|
||||||
|
} else {
|
||||||
|
t.Logf("Generated compound surname double name (male): %s", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateEnglishName(t *testing.T) {
|
||||||
|
g := New()
|
||||||
|
|
||||||
|
result := g.GenerateEnglishName(1, 0, 1, true)
|
||||||
|
if result == "" {
|
||||||
|
t.Errorf("result is empty, please check the dictionary data")
|
||||||
|
} else {
|
||||||
|
t.Logf("Generated female English name: %s", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
result = g.GenerateEnglishName(2, 0, 1, false)
|
||||||
|
if result == "" {
|
||||||
|
t.Errorf("result is empty, please check the dictionary data")
|
||||||
|
} else {
|
||||||
|
t.Logf("Generated male English name: %s", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateJapaneseNameCN(t *testing.T) {
|
||||||
|
g := New()
|
||||||
|
|
||||||
|
result := g.GenerateJapaneseNameCN()
|
||||||
|
if result == "" {
|
||||||
|
t.Errorf("result is empty, please check the dictionary data")
|
||||||
|
} else {
|
||||||
|
t.Logf("Generated Japanese name (CN): %s", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateJapaneseName(t *testing.T) {
|
||||||
|
g := New()
|
||||||
|
|
||||||
|
result := g.GenerateJapaneseName()
|
||||||
|
if result == "" {
|
||||||
|
t.Errorf("result is empty, please check the dictionary data")
|
||||||
|
} else {
|
||||||
|
t.Logf("Generated Japanese name: %s", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
7
name_generator/go.mod
Normal file
7
name_generator/go.mod
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
module github.com/tx7do/go-utils/name_generator
|
||||||
|
|
||||||
|
go 1.23.0
|
||||||
|
|
||||||
|
toolchain go1.23.2
|
||||||
|
|
||||||
|
replace github.com/tx7do/go-utils => ../
|
||||||
0
name_generator/go.sum
Normal file
0
name_generator/go.sum
Normal file
8
name_generator/options.go
Normal file
8
name_generator/options.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package name_generator
|
||||||
|
|
||||||
|
type DictionaryType string
|
||||||
|
|
||||||
|
type Dictionary []string
|
||||||
|
type DictionaryMap map[DictionaryType]Dictionary
|
||||||
|
|
||||||
|
type CombinedDictionaryType []DictionaryType
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
package order_id
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/tx7do/go-utils/trans"
|
|
||||||
)
|
|
||||||
|
|
||||||
type idCounter uint32
|
|
||||||
|
|
||||||
func (c *idCounter) Increase() uint32 {
|
|
||||||
cur := *c
|
|
||||||
atomic.AddUint32((*uint32)(c), 1)
|
|
||||||
atomic.CompareAndSwapUint32((*uint32)(c), 1000, 0)
|
|
||||||
return uint32(cur)
|
|
||||||
}
|
|
||||||
|
|
||||||
var orderIdIndex idCounter
|
|
||||||
|
|
||||||
// GenerateOrderIdWithRandom 生成20位订单号,前缀+时间+随机数
|
|
||||||
func GenerateOrderIdWithRandom(prefix string, split string, tm *time.Time) string {
|
|
||||||
if tm == nil {
|
|
||||||
tm = trans.Time(time.Now())
|
|
||||||
}
|
|
||||||
|
|
||||||
index := rand.Intn(1000)
|
|
||||||
|
|
||||||
return fmt.Sprintf("%s%s%.4d%s%.2d%s%.2d%s%.2d%s%.2d%s%.2d%s%.4d", prefix, split,
|
|
||||||
tm.Year(), split, tm.Month(), split, tm.Day(), split,
|
|
||||||
tm.Hour(), split, tm.Minute(), split, tm.Second(), split, index)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateOrderIdWithIncreaseIndex 生成20位订单号,前缀+时间+自增长索引
|
|
||||||
func GenerateOrderIdWithIncreaseIndex(prefix string, split string, tm *time.Time) string {
|
|
||||||
if tm == nil {
|
|
||||||
tm = trans.Time(time.Now())
|
|
||||||
}
|
|
||||||
|
|
||||||
index := orderIdIndex.Increase()
|
|
||||||
|
|
||||||
return fmt.Sprintf("%s%s%.4d%s%.2d%s%.2d%s%.2d%s%.2d%s%.2d%s%.4d", prefix, split,
|
|
||||||
tm.Year(), split, tm.Month(), split, tm.Day(), split,
|
|
||||||
tm.Hour(), split, tm.Minute(), split, tm.Second(), split, index)
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
package order_id
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
|
|
||||||
"github.com/tx7do/go-utils/trans"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGenerateOrderIdWithRandom(t *testing.T) {
|
|
||||||
fmt.Println(GenerateOrderIdWithRandom("PT", "-", trans.Time(time.Now())))
|
|
||||||
|
|
||||||
tm := time.Now()
|
|
||||||
var ids map[string]bool
|
|
||||||
ids = make(map[string]bool)
|
|
||||||
count := 100
|
|
||||||
for i := 0; i < count; i++ {
|
|
||||||
ids[GenerateOrderIdWithRandom("PT", "", trans.Time(tm))] = true
|
|
||||||
}
|
|
||||||
assert.Equal(t, count, len(ids))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGenerateOrderIdWithIndex(t *testing.T) {
|
|
||||||
tm := time.Now()
|
|
||||||
|
|
||||||
fmt.Println(GenerateOrderIdWithIncreaseIndex("PT", "", trans.Time(tm)))
|
|
||||||
|
|
||||||
ids := make(map[string]bool)
|
|
||||||
count := 100
|
|
||||||
for i := 0; i < count; i++ {
|
|
||||||
ids[GenerateOrderIdWithIncreaseIndex("PT", "", trans.Time(tm))] = true
|
|
||||||
}
|
|
||||||
assert.Equal(t, count, len(ids))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGenerateOrderIdWithIndexThread(t *testing.T) {
|
|
||||||
tm := time.Now()
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
var ids sync.Map
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
for i := 0; i < 100; i++ {
|
|
||||||
id := GenerateOrderIdWithIncreaseIndex("PT", "", trans.Time(tm))
|
|
||||||
ids.Store(id, true)
|
|
||||||
}
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
aLen := 0
|
|
||||||
ids.Range(func(k, v interface{}) bool {
|
|
||||||
aLen++
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
assert.Equal(t, 1000, aLen)
|
|
||||||
}
|
|
||||||
14
password/README.md
Normal file
14
password/README.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# 密码加解密
|
||||||
|
|
||||||
|
算法列表
|
||||||
|
|
||||||
|
| 算法名 | 特点 | 用途 |
|
||||||
|
|-----------------------------------------------|-------------------------------|------------------------|
|
||||||
|
| Bcrypt | 基于 Blowfish 算法,内置盐值,支持调整计算成本。 | 密码存储,防止暴力破解。 |
|
||||||
|
| Argon2 | 内存硬性哈希算法,支持多种参数调整(内存、时间、并行度)。 | 码存储,适合高安全性需求。 |
|
||||||
|
| PBKDF2 | 基于 HMAC 的密钥派生函数,支持多种哈希算法。 | 密码存储,兼容性好。 |
|
||||||
|
| SHA-256/SHA-512 | 快速单向哈希算法,无内置盐值。 | 数据完整性校验,需手动添加盐值用于密码存储。 |
|
||||||
|
| AES (Advanced Encryption Standard) | 对称加密算法,支持 128/192/256 位密钥。 | 数据加密传输或存储。 |
|
||||||
|
| RSA | 非对称加密算法,基于大整数分解难题。 | 数据加密、数字签名。 |
|
||||||
|
| ECDSA/ECDH (椭圆曲线算法) | 基于椭圆曲线的非对称加密,密钥更短但安全性高。 | 数据完整性和认证。 |
|
||||||
|
| HMAC (Hash-based Message Authentication Code) | 基于哈希算法的消息认证码。 | 数据完整性和认证。 |
|
||||||
111
password/argon2.go
Normal file
111
password/argon2.go
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/subtle"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/argon2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Argon2Crypto 实现 Argon2id 密码哈希算法
|
||||||
|
type Argon2Crypto struct {
|
||||||
|
// 参数可配置,默认使用推荐值
|
||||||
|
Memory uint32
|
||||||
|
Iterations uint32
|
||||||
|
Parallelism uint8
|
||||||
|
SaltLength uint32
|
||||||
|
KeyLength uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewArgon2Crypto 创建带默认参数的 Argon2 加密器
|
||||||
|
func NewArgon2Crypto() *Argon2Crypto {
|
||||||
|
return &Argon2Crypto{
|
||||||
|
Memory: 64 * 1024, // 64MB
|
||||||
|
Iterations: 3,
|
||||||
|
Parallelism: 2,
|
||||||
|
SaltLength: 16,
|
||||||
|
KeyLength: 32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt 实现密码加密
|
||||||
|
func (a *Argon2Crypto) Encrypt(password string) (string, error) {
|
||||||
|
// 生成随机盐值
|
||||||
|
salt := make([]byte, a.SaltLength)
|
||||||
|
if _, err := rand.Read(salt); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成哈希
|
||||||
|
hash := argon2.IDKey(
|
||||||
|
[]byte(password),
|
||||||
|
salt,
|
||||||
|
a.Iterations,
|
||||||
|
a.Memory,
|
||||||
|
a.Parallelism,
|
||||||
|
a.KeyLength,
|
||||||
|
)
|
||||||
|
|
||||||
|
// 格式化输出(兼容标准格式)
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s",
|
||||||
|
argon2.Version,
|
||||||
|
a.Memory,
|
||||||
|
a.Iterations,
|
||||||
|
a.Parallelism,
|
||||||
|
base64.RawStdEncoding.EncodeToString(salt),
|
||||||
|
base64.RawStdEncoding.EncodeToString(hash),
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify 验证密码
|
||||||
|
func (a *Argon2Crypto) Verify(password, encrypted string) (bool, error) {
|
||||||
|
// 解析哈希字符串
|
||||||
|
parts := strings.Split(encrypted, "$")
|
||||||
|
if len(parts) != 6 {
|
||||||
|
return false, errors.New("无效的 Argon2 哈希格式")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析参数
|
||||||
|
var version int
|
||||||
|
var memory, iterations uint32
|
||||||
|
var parallelism uint8
|
||||||
|
_, err := fmt.Sscanf(parts[2], "v=%d", &version)
|
||||||
|
if err != nil || version != argon2.Version {
|
||||||
|
return false, errors.New("不支持的 Argon2 版本")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = fmt.Sscanf(parts[3], "m=%d,t=%d,p=%d", &memory, &iterations, ¶llelism)
|
||||||
|
if err != nil {
|
||||||
|
return false, errors.New("无效的 Argon2 参数")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解码盐值和哈希
|
||||||
|
salt, err := base64.RawStdEncoding.DecodeString(parts[4])
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
decodedHash, err := base64.RawStdEncoding.DecodeString(parts[5])
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
keyLength := uint32(len(decodedHash))
|
||||||
|
|
||||||
|
// 使用相同参数生成新哈希
|
||||||
|
newHash := argon2.IDKey(
|
||||||
|
[]byte(password),
|
||||||
|
salt,
|
||||||
|
iterations,
|
||||||
|
memory,
|
||||||
|
parallelism,
|
||||||
|
keyLength,
|
||||||
|
)
|
||||||
|
|
||||||
|
// 安全比较
|
||||||
|
return subtle.ConstantTimeCompare(newHash, decodedHash) == 1, nil
|
||||||
|
}
|
||||||
41
password/argon2_test.go
Normal file
41
password/argon2_test.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestArgon2Crypto_EncryptAndVerify(t *testing.T) {
|
||||||
|
crypto := NewArgon2Crypto()
|
||||||
|
|
||||||
|
// 测试加密
|
||||||
|
password := "securepassword"
|
||||||
|
encrypted, err := crypto.Encrypt(password)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("加密失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if encrypted == "" {
|
||||||
|
t.Fatal("加密结果为空")
|
||||||
|
}
|
||||||
|
t.Log(encrypted)
|
||||||
|
|
||||||
|
// 测试验证成功
|
||||||
|
isValid, err := crypto.Verify(password, encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isValid {
|
||||||
|
t.Fatal("验证未通过,密码应匹配")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试验证失败
|
||||||
|
isValid, err = crypto.Verify("wrongpassword", encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isValid {
|
||||||
|
t.Fatal("验证通过,但密码不应匹配")
|
||||||
|
}
|
||||||
|
}
|
||||||
34
password/bcrypt.go
Normal file
34
password/bcrypt.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BCryptCrypto struct{}
|
||||||
|
|
||||||
|
func NewBCryptCrypto() *BCryptCrypto {
|
||||||
|
return &BCryptCrypto{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt 使用 bcrypt 加密密码,返回加密后的字符串和空盐值
|
||||||
|
func (b *BCryptCrypto) Encrypt(password string) (encrypted string, err error) {
|
||||||
|
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(hash), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify 验证密码是否匹配加密后的字符串
|
||||||
|
func (b *BCryptCrypto) Verify(password, encrypted string) (bool, error) {
|
||||||
|
err := bcrypt.CompareHashAndPassword([]byte(encrypted), []byte(password))
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
41
password/bcrypt_test.go
Normal file
41
password/bcrypt_test.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBCryptCrypto_EncryptAndVerify(t *testing.T) {
|
||||||
|
crypto := NewBCryptCrypto()
|
||||||
|
|
||||||
|
// 测试加密
|
||||||
|
password := "securepassword"
|
||||||
|
encrypted, err := crypto.Encrypt(password)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("加密失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if encrypted == "" {
|
||||||
|
t.Fatal("加密结果为空")
|
||||||
|
}
|
||||||
|
t.Log(encrypted)
|
||||||
|
|
||||||
|
// 测试验证成功
|
||||||
|
isValid, err := crypto.Verify(password, encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isValid {
|
||||||
|
t.Fatal("验证未通过,密码应匹配")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试验证失败
|
||||||
|
isValid, err = crypto.Verify("wrongpassword", encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isValid {
|
||||||
|
t.Fatal("验证通过,但密码不应匹配")
|
||||||
|
}
|
||||||
|
}
|
||||||
142
password/ecdsa_ecdh.go
Normal file
142
password/ecdsa_ecdh.go
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"math/big"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ECDSACrypto 实现基于 ECDSA 的加密和验证
|
||||||
|
type ECDSACrypto struct {
|
||||||
|
privateKey *ecdsa.PrivateKey
|
||||||
|
publicKey *ecdsa.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewECDSACrypto 创建一个新的 ECDSACrypto 实例
|
||||||
|
func NewECDSACrypto() (*ECDSACrypto, error) {
|
||||||
|
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &ECDSACrypto{
|
||||||
|
privateKey: privateKey,
|
||||||
|
publicKey: &privateKey.PublicKey,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt 使用 ECDSA 对消息进行签名
|
||||||
|
func (e *ECDSACrypto) Encrypt(plainPassword string) (string, error) {
|
||||||
|
if plainPassword == "" {
|
||||||
|
return "", errors.New("密码不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := sha256.Sum256([]byte(plainPassword))
|
||||||
|
r, s, err := ecdsa.Sign(rand.Reader, e.privateKey, hash[:])
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
signature := r.String() + "$" + s.String()
|
||||||
|
return "ecdsa$" + signature, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify 验证消息的签名是否有效
|
||||||
|
func (e *ECDSACrypto) Verify(plainPassword, encrypted string) (bool, error) {
|
||||||
|
if plainPassword == "" || encrypted == "" {
|
||||||
|
return false, errors.New("密码或加密字符串不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.SplitN(encrypted, "$", 3)
|
||||||
|
if len(parts) != 3 || parts[0] != "ecdsa" {
|
||||||
|
return false, errors.New("加密字符串格式无效")
|
||||||
|
}
|
||||||
|
|
||||||
|
r := new(big.Int)
|
||||||
|
s := new(big.Int)
|
||||||
|
r.SetString(parts[1], 10)
|
||||||
|
s.SetString(parts[2], 10)
|
||||||
|
|
||||||
|
hash := sha256.Sum256([]byte(plainPassword))
|
||||||
|
return ecdsa.Verify(e.publicKey, hash[:], r, s), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ECDHCrypto 实现基于 ECDH 的密钥交换
|
||||||
|
type ECDHCrypto struct {
|
||||||
|
privateKey *ecdsa.PrivateKey
|
||||||
|
publicKey *ecdsa.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewECDHCrypto 创建一个新的 ECDHCrypto 实例
|
||||||
|
func NewECDHCrypto() (*ECDHCrypto, error) {
|
||||||
|
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &ECDHCrypto{
|
||||||
|
privateKey: privateKey,
|
||||||
|
publicKey: &privateKey.PublicKey,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt 返回公钥作为加密结果
|
||||||
|
func (e *ECDHCrypto) Encrypt(plainPassword string) (string, error) {
|
||||||
|
if plainPassword == "" {
|
||||||
|
return "", errors.New("密码不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
publicKeyBytes := elliptic.Marshal(e.privateKey.Curve, e.publicKey.X, e.publicKey.Y)
|
||||||
|
return "ecdh$" + base64.StdEncoding.EncodeToString(publicKeyBytes), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify 验证共享密钥是否一致
|
||||||
|
func (e *ECDHCrypto) Verify(plainPassword, encrypted string) (bool, error) {
|
||||||
|
if plainPassword == "" || encrypted == "" {
|
||||||
|
return false, errors.New("密码或加密字符串不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.SplitN(encrypted, "$", 2)
|
||||||
|
if len(parts) != 2 || parts[0] != "ecdh" {
|
||||||
|
return false, errors.New("加密字符串格式无效")
|
||||||
|
}
|
||||||
|
|
||||||
|
publicKeyBytes, err := base64.StdEncoding.DecodeString(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
x, y := elliptic.Unmarshal(e.privateKey.Curve, publicKeyBytes)
|
||||||
|
if x == nil || y == nil {
|
||||||
|
return false, errors.New("无效的公钥")
|
||||||
|
}
|
||||||
|
|
||||||
|
sharedX, _ := e.privateKey.Curve.ScalarMult(x, y, e.privateKey.D.Bytes())
|
||||||
|
expectedHash := sha256.Sum256(sharedX.Bytes())
|
||||||
|
actualHash := sha256.Sum256([]byte(plainPassword))
|
||||||
|
|
||||||
|
return expectedHash == actualHash, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ECDHCrypto) DeriveSharedSecret(publicKey string) ([]byte, error) {
|
||||||
|
// 解码对方的公钥
|
||||||
|
publicKeyBytes, err := base64.StdEncoding.DecodeString(publicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 反序列化公钥
|
||||||
|
x, y := elliptic.Unmarshal(e.privateKey.Curve, publicKeyBytes)
|
||||||
|
if x == nil || y == nil {
|
||||||
|
return nil, errors.New("无效的公钥")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算共享密钥
|
||||||
|
sharedX, _ := e.privateKey.Curve.ScalarMult(x, y, e.privateKey.D.Bytes())
|
||||||
|
|
||||||
|
// 返回共享密钥的字节表示
|
||||||
|
return sharedX.Bytes(), nil
|
||||||
|
}
|
||||||
60
password/ecdsa_ecdh_test.go
Normal file
60
password/ecdsa_ecdh_test.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestECDSACrypto_EncryptAndVerify(t *testing.T) {
|
||||||
|
crypto, err := NewECDSACrypto()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("创建 ECDSACrypto 实例失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
message := "test message"
|
||||||
|
|
||||||
|
// 签名消息
|
||||||
|
encrypted, err := crypto.Encrypt(message)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("签名失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证签名
|
||||||
|
isValid, err := crypto.Verify(message, encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isValid {
|
||||||
|
t.Fatal("签名验证未通过")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestECDHCrypto_EncryptAndVerify(t *testing.T) {
|
||||||
|
crypto1, err := NewECDHCrypto()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("创建 ECDHCrypto 实例1失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
crypto2, err := NewECDHCrypto()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("创建 ECDHCrypto 实例2失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
message := "test message"
|
||||||
|
|
||||||
|
// 获取公钥
|
||||||
|
encrypted, err := crypto1.Encrypt(message)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("加密失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证共享密钥
|
||||||
|
isValid, err := crypto2.Verify(message, encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isValid {
|
||||||
|
t.Fatal("共享密钥验证未通过")
|
||||||
|
}
|
||||||
|
}
|
||||||
11
password/go.mod
Normal file
11
password/go.mod
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
module github.com/tx7do/go-utils/password
|
||||||
|
|
||||||
|
go 1.23.0
|
||||||
|
|
||||||
|
toolchain go1.23.2
|
||||||
|
|
||||||
|
require golang.org/x/crypto v0.39.0
|
||||||
|
|
||||||
|
require golang.org/x/sys v0.33.0 // indirect
|
||||||
|
|
||||||
|
replace github.com/tx7do/go-utils => ../
|
||||||
4
password/go.sum
Normal file
4
password/go.sum
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||||
|
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||||
|
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||||
|
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
50
password/hmac.go
Normal file
50
password/hmac.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HMACCrypto 实现基于 HMAC 的加密和验证
|
||||||
|
type HMACCrypto struct {
|
||||||
|
secretKey []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHMACCrypto 创建一个新的 HMACCrypto 实例
|
||||||
|
func NewHMACCrypto(secretKey string) *HMACCrypto {
|
||||||
|
return &HMACCrypto{
|
||||||
|
secretKey: []byte(secretKey),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt 使用 HMAC-SHA256 对数据进行加密
|
||||||
|
func (h *HMACCrypto) Encrypt(data string) (string, error) {
|
||||||
|
if data == "" {
|
||||||
|
return "", errors.New("数据不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
mac := hmac.New(sha256.New, h.secretKey)
|
||||||
|
_, err := mac.Write([]byte(data))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := mac.Sum(nil)
|
||||||
|
return hex.EncodeToString(hash), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify 验证数据的 HMAC 值是否匹配
|
||||||
|
func (h *HMACCrypto) Verify(data, encrypted string) (bool, error) {
|
||||||
|
if data == "" || encrypted == "" {
|
||||||
|
return false, errors.New("数据或加密字符串不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedHash, err := h.Encrypt(data)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return hmac.Equal([]byte(expectedHash), []byte(encrypted)), nil
|
||||||
|
}
|
||||||
43
password/hmac_test.go
Normal file
43
password/hmac_test.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHMACCrypto_EncryptAndVerify(t *testing.T) {
|
||||||
|
secretKey := "mysecretkey"
|
||||||
|
crypto := NewHMACCrypto(secretKey)
|
||||||
|
|
||||||
|
data := "testdata"
|
||||||
|
|
||||||
|
// 测试加密
|
||||||
|
encrypted, err := crypto.Encrypt(data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("加密失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if encrypted == "" {
|
||||||
|
t.Fatal("加密结果为空")
|
||||||
|
}
|
||||||
|
t.Log(encrypted)
|
||||||
|
|
||||||
|
// 测试验证
|
||||||
|
isValid, err := crypto.Verify(data, encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isValid {
|
||||||
|
t.Fatal("验证结果不匹配")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试验证失败的情况
|
||||||
|
isValid, err = crypto.Verify("wrongdata", encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isValid {
|
||||||
|
t.Fatal("验证结果错误,预期验证失败")
|
||||||
|
}
|
||||||
|
}
|
||||||
29
password/interface.go
Normal file
29
password/interface.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Crypto 密码加解密接口
|
||||||
|
type Crypto interface {
|
||||||
|
// Encrypt 加密密码,返回加密后的字符串(包含算法标识和盐值)
|
||||||
|
Encrypt(plainPassword string) (encrypted string, err error)
|
||||||
|
|
||||||
|
// Verify 验证密码是否匹配
|
||||||
|
Verify(plainPassword, encrypted string) (bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateCrypto(algorithm string) (Crypto, error) {
|
||||||
|
algorithm = strings.ToLower(algorithm)
|
||||||
|
switch algorithm {
|
||||||
|
case "bcrypt":
|
||||||
|
return NewBCryptCrypto(), nil
|
||||||
|
case "pbkdf2":
|
||||||
|
return NewPBKDF2Crypto(), nil
|
||||||
|
case "argon2":
|
||||||
|
return NewArgon2Crypto(), nil
|
||||||
|
default:
|
||||||
|
return nil, errors.New("不支持的加密算法")
|
||||||
|
}
|
||||||
|
}
|
||||||
150
password/pbkdf2.go
Normal file
150
password/pbkdf2.go
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"hash"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PBKDF2Crypto 实现 PBKDF2-HMAC 密码哈希算法
|
||||||
|
type PBKDF2Crypto struct {
|
||||||
|
// 可配置参数,默认使用推荐值
|
||||||
|
Iterations int
|
||||||
|
KeyLength int
|
||||||
|
Hash func() hash.Hash
|
||||||
|
HashName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPBKDF2Crypto 创建带默认参数的 PBKDF2 加密器 (SHA256)
|
||||||
|
func NewPBKDF2Crypto() *PBKDF2Crypto {
|
||||||
|
return &PBKDF2Crypto{
|
||||||
|
Iterations: 310000, // NIST 推荐最小值
|
||||||
|
KeyLength: 32, // 256-bit
|
||||||
|
Hash: sha256.New,
|
||||||
|
HashName: "sha256",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPBKDF2WithSHA512 创建使用 SHA512 的 PBKDF2 加密器
|
||||||
|
func NewPBKDF2WithSHA512() *PBKDF2Crypto {
|
||||||
|
return &PBKDF2Crypto{
|
||||||
|
Iterations: 600000, // SHA512 需要更多迭代
|
||||||
|
KeyLength: 64, // 512-bit
|
||||||
|
Hash: sha512.New,
|
||||||
|
HashName: "sha512",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt 实现密码加密
|
||||||
|
func (p *PBKDF2Crypto) Encrypt(password string) (string, error) {
|
||||||
|
// 生成随机盐值 (16 bytes 推荐最小值)
|
||||||
|
salt := make([]byte, 16)
|
||||||
|
if _, err := rand.Read(salt); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成密钥
|
||||||
|
key := pbkdf2Key([]byte(password), salt, p.Iterations, p.KeyLength, p.Hash)
|
||||||
|
|
||||||
|
// 格式: pbkdf2:<hash>:<iterations>:<base64-salt>:<base64-key>
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"pbkdf2:%s:%d:%s:%s",
|
||||||
|
p.HashName,
|
||||||
|
p.Iterations,
|
||||||
|
base64.RawStdEncoding.EncodeToString(salt),
|
||||||
|
base64.RawStdEncoding.EncodeToString(key),
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify 验证密码
|
||||||
|
func (p *PBKDF2Crypto) Verify(password, encrypted string) (bool, error) {
|
||||||
|
// 解析哈希字符串
|
||||||
|
parts := strings.Split(encrypted, ":")
|
||||||
|
if len(parts) != 5 || parts[0] != "pbkdf2" {
|
||||||
|
return false, errors.New("无效的 PBKDF2 哈希格式")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析参数
|
||||||
|
hashName := parts[1]
|
||||||
|
iterations, err := strconv.Atoi(parts[2])
|
||||||
|
if err != nil {
|
||||||
|
return false, errors.New("无效的迭代次数")
|
||||||
|
}
|
||||||
|
|
||||||
|
salt, err := base64.RawStdEncoding.DecodeString(parts[3])
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedKey, err := base64.RawStdEncoding.DecodeString(parts[4])
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据哈希名称选择哈希函数
|
||||||
|
hashFunc, ok := getHashFunction(hashName)
|
||||||
|
if !ok {
|
||||||
|
return false, fmt.Errorf("不支持的哈希算法: %s", hashName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成新密钥
|
||||||
|
keyLength := len(expectedKey)
|
||||||
|
newKey := pbkdf2Key([]byte(password), salt, iterations, keyLength, hashFunc)
|
||||||
|
|
||||||
|
// 安全比较
|
||||||
|
return hmac.Equal(newKey, expectedKey), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// pbkdf2Key 实现 PBKDF2 核心算法
|
||||||
|
func pbkdf2Key(password, salt []byte, iterations, keyLength int, hashFunc func() hash.Hash) []byte {
|
||||||
|
prf := hmac.New(hashFunc, password)
|
||||||
|
hashLength := prf.Size()
|
||||||
|
blockCount := (keyLength + hashLength - 1) / hashLength
|
||||||
|
|
||||||
|
output := make([]byte, 0, blockCount*hashLength)
|
||||||
|
for i := 1; i <= blockCount; i++ {
|
||||||
|
// U1 = PRF(password, salt || INT(i))
|
||||||
|
prf.Reset()
|
||||||
|
prf.Write(salt)
|
||||||
|
binary.BigEndian.PutUint32(make([]byte, 4), uint32(i))
|
||||||
|
prf.Write([]byte{byte(i >> 24), byte(i >> 16), byte(i >> 8), byte(i)})
|
||||||
|
u := prf.Sum(nil)
|
||||||
|
|
||||||
|
// F = U1 ⊕ U2 ⊕ ... ⊕ U_iterations
|
||||||
|
f := make([]byte, len(u))
|
||||||
|
copy(f, u)
|
||||||
|
|
||||||
|
for j := 1; j < iterations; j++ {
|
||||||
|
prf.Reset()
|
||||||
|
prf.Write(u)
|
||||||
|
u = prf.Sum(nil)
|
||||||
|
for k := 0; k < len(f); k++ {
|
||||||
|
f[k] ^= u[k]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output = append(output, f...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return output[:keyLength]
|
||||||
|
}
|
||||||
|
|
||||||
|
// getHashFunction 根据名称获取哈希函数
|
||||||
|
func getHashFunction(name string) (func() hash.Hash, bool) {
|
||||||
|
switch name {
|
||||||
|
case "sha256":
|
||||||
|
return sha256.New, true
|
||||||
|
case "sha512":
|
||||||
|
return sha512.New, true
|
||||||
|
default:
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
}
|
||||||
77
password/pbkdf2_test.go
Normal file
77
password/pbkdf2_test.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPBKDF2Crypto_EncryptAndVerify(t *testing.T) {
|
||||||
|
crypto := NewPBKDF2Crypto()
|
||||||
|
|
||||||
|
// 测试加密
|
||||||
|
password := "securepassword"
|
||||||
|
encrypted, err := crypto.Encrypt(password)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("加密失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if encrypted == "" {
|
||||||
|
t.Fatal("加密结果为空")
|
||||||
|
}
|
||||||
|
t.Log(encrypted)
|
||||||
|
|
||||||
|
// 测试验证成功
|
||||||
|
isValid, err := crypto.Verify(password, encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isValid {
|
||||||
|
t.Fatal("验证未通过,密码应匹配")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试验证失败
|
||||||
|
isValid, err = crypto.Verify("wrongpassword", encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isValid {
|
||||||
|
t.Fatal("验证通过,但密码不应匹配")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPBKDF2WithSHA512_EncryptAndVerify(t *testing.T) {
|
||||||
|
crypto := NewPBKDF2WithSHA512()
|
||||||
|
|
||||||
|
// 测试加密
|
||||||
|
password := "securepassword"
|
||||||
|
encrypted, err := crypto.Encrypt(password)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("加密失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if encrypted == "" {
|
||||||
|
t.Fatal("加密结果为空")
|
||||||
|
}
|
||||||
|
t.Log(encrypted)
|
||||||
|
|
||||||
|
// 测试验证成功
|
||||||
|
isValid, err := crypto.Verify(password, encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isValid {
|
||||||
|
t.Fatal("验证未通过,密码应匹配")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试验证失败
|
||||||
|
isValid, err = crypto.Verify("wrongpassword", encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isValid {
|
||||||
|
t.Fatal("验证通过,但密码不应匹配")
|
||||||
|
}
|
||||||
|
}
|
||||||
73
password/rsa.go
Normal file
73
password/rsa.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/pem"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RSACrypto 实现 RSA 加密和解密
|
||||||
|
type RSACrypto struct {
|
||||||
|
privateKey *rsa.PrivateKey
|
||||||
|
publicKey *rsa.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRSACrypto 创建一个新的 RSACrypto 实例
|
||||||
|
func NewRSACrypto(keySize int) (*RSACrypto, error) {
|
||||||
|
privateKey, err := rsa.GenerateKey(rand.Reader, keySize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &RSACrypto{
|
||||||
|
privateKey: privateKey,
|
||||||
|
publicKey: &privateKey.PublicKey,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt 使用公钥加密数据
|
||||||
|
func (r *RSACrypto) Encrypt(data string) (string, error) {
|
||||||
|
encryptedBytes, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, r.publicKey, []byte(data), nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return base64.StdEncoding.EncodeToString(encryptedBytes), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt 使用私钥解密数据
|
||||||
|
func (r *RSACrypto) Decrypt(encryptedData string) (string, error) {
|
||||||
|
decodedData, err := base64.StdEncoding.DecodeString(encryptedData)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
decryptedBytes, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, r.privateKey, decodedData, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(decryptedBytes), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportPrivateKey 导出私钥为 PEM 格式
|
||||||
|
func (r *RSACrypto) ExportPrivateKey() (string, error) {
|
||||||
|
privateKeyBytes := x509.MarshalPKCS1PrivateKey(r.privateKey)
|
||||||
|
privateKeyPEM := pem.EncodeToMemory(&pem.Block{
|
||||||
|
Type: "RSA PRIVATE KEY",
|
||||||
|
Bytes: privateKeyBytes,
|
||||||
|
})
|
||||||
|
return string(privateKeyPEM), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportPublicKey 导出公钥为 PEM 格式
|
||||||
|
func (r *RSACrypto) ExportPublicKey() (string, error) {
|
||||||
|
publicKeyBytes, err := x509.MarshalPKIXPublicKey(r.publicKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
publicKeyPEM := pem.EncodeToMemory(&pem.Block{
|
||||||
|
Type: "RSA PUBLIC KEY",
|
||||||
|
Bytes: publicKeyBytes,
|
||||||
|
})
|
||||||
|
return string(publicKeyPEM), nil
|
||||||
|
}
|
||||||
63
password/rsa_test.go
Normal file
63
password/rsa_test.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRSACrypto_EncryptAndDecrypt(t *testing.T) {
|
||||||
|
// 创建 RSACrypto 实例
|
||||||
|
crypto, err := NewRSACrypto(2048)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("创建 RSACrypto 实例失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试加密
|
||||||
|
originalData := "securedata"
|
||||||
|
encryptedData, err := crypto.Encrypt(originalData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("加密失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if encryptedData == "" {
|
||||||
|
t.Fatal("加密结果为空")
|
||||||
|
}
|
||||||
|
t.Log(encryptedData)
|
||||||
|
|
||||||
|
// 测试解密
|
||||||
|
decryptedData, err := crypto.Decrypt(encryptedData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("解密失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if decryptedData != originalData {
|
||||||
|
t.Fatalf("解密结果不匹配,期望: %s,实际: %s", originalData, decryptedData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRSACrypto_ExportKeys(t *testing.T) {
|
||||||
|
// 创建 RSACrypto 实例
|
||||||
|
crypto, err := NewRSACrypto(2048)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("创建 RSACrypto 实例失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试导出私钥
|
||||||
|
privateKey, err := crypto.ExportPrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("导出私钥失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if privateKey == "" {
|
||||||
|
t.Fatal("导出的私钥为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试导出公钥
|
||||||
|
publicKey, err := crypto.ExportPublicKey()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("导出公钥失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if publicKey == "" {
|
||||||
|
t.Fatal("导出的公钥为空")
|
||||||
|
}
|
||||||
|
}
|
||||||
110
password/sha.go
Normal file
110
password/sha.go
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"hash"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SHACrypto 实现 SHA-256/SHA-512 密码哈希算法(带盐值)
|
||||||
|
type SHACrypto struct {
|
||||||
|
Hash func() hash.Hash
|
||||||
|
HashName string
|
||||||
|
SaltLength int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSHA256Crypto 创建 SHA-256 加密器
|
||||||
|
func NewSHA256Crypto() *SHACrypto {
|
||||||
|
return &SHACrypto{
|
||||||
|
Hash: sha256.New,
|
||||||
|
HashName: "sha256",
|
||||||
|
SaltLength: 16, // 16 字节盐值
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSHA512Crypto 创建 SHA-512 加密器
|
||||||
|
func NewSHA512Crypto() *SHACrypto {
|
||||||
|
return &SHACrypto{
|
||||||
|
Hash: sha512.New,
|
||||||
|
HashName: "sha512",
|
||||||
|
SaltLength: 16, // 16 字节盐值
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt 实现密码加密(带盐值)
|
||||||
|
func (s *SHACrypto) Encrypt(password string) (string, error) {
|
||||||
|
// 生成随机盐值
|
||||||
|
salt := make([]byte, s.SaltLength)
|
||||||
|
if _, err := rand.Read(salt); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算哈希
|
||||||
|
hashValue := s.Hash()
|
||||||
|
hashValue.Write(salt)
|
||||||
|
hashValue.Write([]byte(password))
|
||||||
|
hashBytes := hashValue.Sum(nil)
|
||||||
|
|
||||||
|
// 格式: sha256:$salt$hash 或 sha512:$salt$hash
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"%s$%s$%s",
|
||||||
|
s.HashName,
|
||||||
|
base64.RawStdEncoding.EncodeToString(salt),
|
||||||
|
hex.EncodeToString(hashBytes),
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify 验证密码
|
||||||
|
func (s *SHACrypto) Verify(password, encrypted string) (bool, error) {
|
||||||
|
// 解析哈希字符串
|
||||||
|
parts := strings.Split(encrypted, "$")
|
||||||
|
if len(parts) != 3 {
|
||||||
|
return false, errors.New("无效的 SHA 哈希格式")
|
||||||
|
}
|
||||||
|
|
||||||
|
hashName := parts[0]
|
||||||
|
if hashName != s.HashName {
|
||||||
|
return false, fmt.Errorf("哈希算法不匹配: 期望 %s, 实际 %s", s.HashName, hashName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解码盐值
|
||||||
|
salt, err := base64.RawStdEncoding.DecodeString(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解码原始哈希值
|
||||||
|
originalHash, err := hex.DecodeString(parts[2])
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算新哈希
|
||||||
|
hashValue := s.Hash()
|
||||||
|
hashValue.Write(salt)
|
||||||
|
hashValue.Write([]byte(password))
|
||||||
|
newHash := hashValue.Sum(nil)
|
||||||
|
|
||||||
|
// 安全比较
|
||||||
|
return compareHash(newHash, originalHash), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// compareHash 安全比较两个哈希值
|
||||||
|
func compareHash(h1, h2 []byte) bool {
|
||||||
|
if len(h1) != len(h2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
result := 0
|
||||||
|
for i := 0; i < len(h1); i++ {
|
||||||
|
result |= int(h1[i] ^ h2[i])
|
||||||
|
}
|
||||||
|
return result == 0
|
||||||
|
}
|
||||||
53
password/sha_test.go
Normal file
53
password/sha_test.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSHACrypto_EncryptAndVerify_SHA256(t *testing.T) {
|
||||||
|
crypto := NewSHA256Crypto()
|
||||||
|
|
||||||
|
password := "securepassword"
|
||||||
|
encrypted, err := crypto.Encrypt(password)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("加密失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if encrypted == "" {
|
||||||
|
t.Fatal("加密结果为空")
|
||||||
|
}
|
||||||
|
t.Log(encrypted)
|
||||||
|
|
||||||
|
isValid, err := crypto.Verify(password, encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isValid {
|
||||||
|
t.Fatal("验证结果不匹配")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSHACrypto_EncryptAndVerify_SHA512(t *testing.T) {
|
||||||
|
crypto := NewSHA512Crypto()
|
||||||
|
|
||||||
|
password := "securepassword"
|
||||||
|
encrypted, err := crypto.Encrypt(password)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("加密失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if encrypted == "" {
|
||||||
|
t.Fatal("加密结果为空")
|
||||||
|
}
|
||||||
|
t.Log(encrypted)
|
||||||
|
|
||||||
|
isValid, err := crypto.Verify(password, encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isValid {
|
||||||
|
t.Fatal("验证结果不匹配")
|
||||||
|
}
|
||||||
|
}
|
||||||
70
query_parser/README.md
Normal file
70
query_parser/README.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# 查询解析器
|
||||||
|
|
||||||
|
## 排序规则
|
||||||
|
|
||||||
|
排序操作本质上是`SQL`里面的`Order By`条件。
|
||||||
|
|
||||||
|
| 序列 | 示例 | 备注 |
|
||||||
|
|----|--------------------|--------------|
|
||||||
|
| 升序 | `["type"]` | |
|
||||||
|
| 降序 | `["-create_time"]` | 字段名前加`-`是为降序 |
|
||||||
|
|
||||||
|
## 过滤规则
|
||||||
|
|
||||||
|
过滤器操作本质上是`SQL`里面的`WHERE`条件。
|
||||||
|
|
||||||
|
过滤器的规则,遵循了Python的ORM的规则,比如:
|
||||||
|
|
||||||
|
- [Tortoise ORM Filtering](https://tortoise.github.io/query.html#filtering)。
|
||||||
|
- [Django Field lookups](https://docs.djangoproject.com/en/4.2/ref/models/querysets/#field-lookups)
|
||||||
|
|
||||||
|
如果只是普通的查询,只需要传递`字段名`即可,但是如果需要一些特殊的查询,那么就需要加入`操作符`了。
|
||||||
|
|
||||||
|
特殊查询的语法规则其实很简单,就是使用双下划线`__`分割字段名和操作符:
|
||||||
|
|
||||||
|
```text
|
||||||
|
{字段名}__{查找类型} : {值}
|
||||||
|
{字段名}.{JSON字段名}__{查找类型} : {值}
|
||||||
|
```
|
||||||
|
|
||||||
|
| 查找类型 | 示例 | SQL | 备注 |
|
||||||
|
|-------------|---------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------|
|
||||||
|
| not | `{"name__not" : "tom"}` | `WHERE NOT ("name" = "tom")` | |
|
||||||
|
| in | `{"name__in" : "[\"tom\", \"jimmy\"]"}` | `WHERE name IN ("tom", "jimmy")` | |
|
||||||
|
| not_in | `{"name__not_in" : "[\"tom\", \"jimmy\"]"}` | `WHERE name NOT IN ("tom", "jimmy")` | |
|
||||||
|
| gte | `{"create_time__gte" : "2023-10-25"}` | `WHERE "create_time" >= "2023-10-25"` | |
|
||||||
|
| gt | `{"create_time__gt" : "2023-10-25"}` | `WHERE "create_time" > "2023-10-25"` | |
|
||||||
|
| lte | `{"create_time__lte" : "2023-10-25"}` | `WHERE "create_time" <= "2023-10-25"` | |
|
||||||
|
| lt | `{"create_time__lt" : "2023-10-25"}` | `WHERE "create_time" < "2023-10-25"` | |
|
||||||
|
| range | `{"create_time__range" : "[\"2023-10-25\", \"2024-10-25\"]"}` | `WHERE "create_time" BETWEEN "2023-10-25" AND "2024-10-25"` <br>或<br> `WHERE "create_time" >= "2023-10-25" AND "create_time" <= "2024-10-25"` | 需要注意的是: <br>1. 有些数据库的BETWEEN实现的开闭区间可能不一样。<br>2. 日期`2005-01-01`会被隐式转换为:`2005-01-01 00:00:00`,两个日期一致就会导致查询不到数据。 |
|
||||||
|
| isnull | `{"name__isnull" : "True"}` | `WHERE name IS NULL` | |
|
||||||
|
| not_isnull | `{"name__not_isnull" : "False"}` | `WHERE name IS NOT NULL` | |
|
||||||
|
| contains | `{"name__contains" : "L"}` | `WHERE name LIKE '%L%';` | |
|
||||||
|
| icontains | `{"name__icontains" : "L"}` | `WHERE name ILIKE '%L%';` | |
|
||||||
|
| startswith | `{"name__startswith" : "La"}` | `WHERE name LIKE 'La%';` | |
|
||||||
|
| istartswith | `{"name__istartswith" : "La"}` | `WHERE name ILIKE 'La%';` | |
|
||||||
|
| endswith | `{"name__endswith" : "a"}` | `WHERE name LIKE '%a';` | |
|
||||||
|
| iendswith | `{"name__iendswith" : "a"}` | `WHERE name ILIKE '%a';` | |
|
||||||
|
| exact | `{"name__exact" : "a"}` | `WHERE name LIKE 'a';` | |
|
||||||
|
| iexact | `{"name__iexact" : "a"}` | `WHERE name ILIKE 'a';` | |
|
||||||
|
| regex | `{"title__regex" : "^(An?\|The) +"}` | MySQL: `WHERE title REGEXP BINARY '^(An?\|The) +'` <br> Oracle: `WHERE REGEXP_LIKE(title, '^(An?\|The) +', 'c');` <br> PostgreSQL: `WHERE title ~ '^(An?\|The) +';` <br> SQLite: `WHERE title REGEXP '^(An?\|The) +';` | |
|
||||||
|
| iregex | `{"title__iregex" : "^(an?\|the) +"}` | MySQL: `WHERE title REGEXP '^(an?\|the) +'` <br> Oracle: `WHERE REGEXP_LIKE(title, '^(an?\|the) +', 'i');` <br> PostgreSQL: `WHERE title ~* '^(an?\|the) +';` <br> SQLite: `WHERE title REGEXP '(?i)^(an?\|the) +';` | |
|
||||||
|
| search | | | |
|
||||||
|
|
||||||
|
以及将日期提取出来的查找类型:
|
||||||
|
|
||||||
|
| 查找类型 | 示例 | SQL | 备注 |
|
||||||
|
|--------------|--------------------------------------|---------------------------------------------------|----------------------|
|
||||||
|
| date | `{"pub_date__date" : "2023-01-01"}` | `WHERE DATE(pub_date) = '2023-01-01'` | |
|
||||||
|
| year | `{"pub_date__year" : "2023"}` | `WHERE EXTRACT('YEAR' FROM pub_date) = '2023'` | 哪一年 |
|
||||||
|
| iso_year | `{"pub_date__iso_year" : "2023"}` | `WHERE EXTRACT('ISOYEAR' FROM pub_date) = '2023'` | ISO 8601 一年中的周数 |
|
||||||
|
| month | `{"pub_date__month" : "12"}` | `WHERE EXTRACT('MONTH' FROM pub_date) = '12'` | 月份,1-12 |
|
||||||
|
| day | `{"pub_date__day" : "3"}` | `WHERE EXTRACT('DAY' FROM pub_date) = '3'` | 该月的某天(1-31) |
|
||||||
|
| week | `{"pub_date__week" : "7"}` | `WHERE EXTRACT('WEEK' FROM pub_date) = '7'` | ISO 8601 周编号 一年中的周数 |
|
||||||
|
| week_day | `{"pub_date__week_day" : "tom"}` | `` | 星期几 |
|
||||||
|
| iso_week_day | `{"pub_date__iso_week_day" : "tom"}` | `` | |
|
||||||
|
| quarter | `{"pub_date__quarter" : "1"}` | `WHERE EXTRACT('QUARTER' FROM pub_date) = '1'` | 一年中的季度 |
|
||||||
|
| time | `{"pub_date__time" : "12:59:59"}` | `` | |
|
||||||
|
| hour | `{"pub_date__hour" : "12"}` | `WHERE EXTRACT('HOUR' FROM pub_date) = '12'` | 小时(0-23) |
|
||||||
|
| minute | `{"pub_date__minute" : "59"}` | `WHERE EXTRACT('MINUTE' FROM pub_date) = '59'` | 分钟 (0-59) |
|
||||||
|
| second | `{"pub_date__second" : "59"}` | `WHERE EXTRACT('SECOND' FROM pub_date) = '59'` | 秒 (0-59) |
|
||||||
180
query_parser/filter.go
Normal file
180
query_parser/filter.go
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
package query_parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-kratos/kratos/v2/encoding"
|
||||||
|
_ "github.com/go-kratos/kratos/v2/encoding/json"
|
||||||
|
|
||||||
|
"github.com/tx7do/go-utils/stringcase"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FilterOperator string
|
||||||
|
|
||||||
|
const (
|
||||||
|
FilterNot = "not" // 不等于
|
||||||
|
FilterIn = "in" // 检查值是否在列表中
|
||||||
|
FilterNotIn = "not_in" // 不在列表中
|
||||||
|
FilterGTE = "gte" // 大于或等于传递的值
|
||||||
|
FilterGT = "gt" // 大于传递值
|
||||||
|
FilterLTE = "lte" // 小于或等于传递值
|
||||||
|
FilterLT = "lt" // 小于传递值
|
||||||
|
FilterRange = "range" // 是否介于和给定的两个值之间
|
||||||
|
FilterIsNull = "isnull" // 是否为空
|
||||||
|
FilterNotIsNull = "not_isnull" // 是否不为空
|
||||||
|
FilterContains = "contains" // 是否包含指定的子字符串
|
||||||
|
FilterInsensitiveContains = "icontains" // 不区分大小写,是否包含指定的子字符串
|
||||||
|
FilterStartsWith = "startswith" // 以值开头
|
||||||
|
FilterInsensitiveStartsWith = "istartswith" // 不区分大小写,以值开头
|
||||||
|
FilterEndsWith = "endswith" // 以值结尾
|
||||||
|
FilterInsensitiveEndsWith = "iendswith" // 不区分大小写,以值结尾
|
||||||
|
FilterExact = "exact" // 精确匹配
|
||||||
|
FilterInsensitiveExact = "iexact" // 不区分大小写,精确匹配
|
||||||
|
FilterRegex = "regex" // 正则表达式
|
||||||
|
FilterInsensitiveRegex = "iregex" // 不区分大小写,正则表达式
|
||||||
|
FilterSearch = "search" // 全文搜索
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DatePartDate = "date" // 日期
|
||||||
|
DatePartYear = "year" // 年
|
||||||
|
DatePartISOYear = "iso_year" // ISO8601 一年中的周数
|
||||||
|
DatePartQuarter = "quarter" // 季度
|
||||||
|
DatePartMonth = "month" // 月
|
||||||
|
DatePartWeek = "week" // ISO8601 周编号 一年中的周数
|
||||||
|
DatePartWeekDay = "week_day" // 星期几
|
||||||
|
DatePartISOWeekDay = "iso_week_day" // ISO8601 星期几
|
||||||
|
DatePartDay = "day" // 日
|
||||||
|
DatePartTime = "time" // 小时:分钟:秒
|
||||||
|
DatePartHour = "hour" // 小时
|
||||||
|
DatePartMinute = "minute" // 分钟
|
||||||
|
DatePartSecond = "second" // 秒
|
||||||
|
DatePartMicrosecond = "microsecond" // 微秒
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
JSONFilterFieldOperatorDelimiter = "__" // JSON过滤器 - 字段名和操作符的分隔符
|
||||||
|
|
||||||
|
QueryFilterFieldOperatorDelimiter = ":" // 自定义查询字符串过滤器 - 字段名和操作符的分隔符
|
||||||
|
QueryFilterQueriesDelimiter = "," // 自定义查询字符串过滤器 - 多个键值对的分隔符
|
||||||
|
QueryFilterValuesDelimiter = "|" // 自定义查询字符串过滤器 - 多个值的分隔符
|
||||||
|
|
||||||
|
JsonFieldDelimiter = "." // JSON字段分隔符
|
||||||
|
)
|
||||||
|
|
||||||
|
type FilterHandler func(field, operator, value string)
|
||||||
|
|
||||||
|
// ParseFilterJSONString 解析过滤条件的JSON字符串,调用处理函数
|
||||||
|
func ParseFilterJSONString(query string, handler FilterHandler) error {
|
||||||
|
if query == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
codec := encoding.GetCodec("json")
|
||||||
|
|
||||||
|
var err error
|
||||||
|
queryMap := make(map[string]string)
|
||||||
|
if err = codec.Unmarshal([]byte(query), &queryMap); err == nil {
|
||||||
|
for k, v := range queryMap {
|
||||||
|
ParseFilterField(k, v, handler)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var queryMapArray []map[string]string
|
||||||
|
if err = codec.Unmarshal([]byte(query), &queryMapArray); err == nil {
|
||||||
|
for _, item := range queryMapArray {
|
||||||
|
for k, v := range item {
|
||||||
|
ParseFilterField(k, v, handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseFilterQueryString 解析过滤条件的查询字符串,调用处理函数
|
||||||
|
func ParseFilterQueryString(query string, handler FilterHandler) error {
|
||||||
|
if query == "" {
|
||||||
|
return nil // 如果查询字符串为空,直接返回
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按逗号分割查询字符串,得到多个键值对
|
||||||
|
pairs := SplitQueryQueries(query)
|
||||||
|
for _, pair := range pairs {
|
||||||
|
// 按冒号分割键值对,提取字段名和值
|
||||||
|
parts := SplitQueryFieldAndOperator(pair)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
continue // 跳过无效的键值对
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解码字段名
|
||||||
|
key, err := DecodeSpecialCharacters(strings.TrimSpace(parts[0]))
|
||||||
|
if err != nil {
|
||||||
|
continue // 跳过解码失败的键值对
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解码字段值
|
||||||
|
value, err := DecodeSpecialCharacters(strings.TrimSpace(parts[1]))
|
||||||
|
if err != nil {
|
||||||
|
continue // 跳过解码失败的键值对
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用 ParseFilterField 解析字段和操作符
|
||||||
|
ParseFilterField(key, value, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseFilterField 解析过滤条件字符串,调用处理函数
|
||||||
|
func ParseFilterField(key, value string, handler FilterHandler) {
|
||||||
|
if key == "" || value == "" {
|
||||||
|
return // 没有过滤条件
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理字段和操作符
|
||||||
|
parts := SplitJsonFieldAndOperator(key)
|
||||||
|
if len(parts) < 1 {
|
||||||
|
return // 无效的字段格式
|
||||||
|
}
|
||||||
|
|
||||||
|
field := strings.TrimSpace(parts[0])
|
||||||
|
if field == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
field = stringcase.ToSnakeCase(parts[0])
|
||||||
|
|
||||||
|
op := ""
|
||||||
|
if len(parts) > 1 {
|
||||||
|
op = parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
handler(field, op, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SplitJsonFieldAndOperator JSON过滤器 - 分割“字段名”和“操作符”
|
||||||
|
func SplitJsonFieldAndOperator(field string) []string {
|
||||||
|
return strings.Split(field, JSONFilterFieldOperatorDelimiter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SplitQueryFieldAndOperator 自定义查询字符串过滤器 - 分割“字段名”和“操作符”
|
||||||
|
func SplitQueryFieldAndOperator(field string) []string {
|
||||||
|
return strings.Split(field, QueryFilterFieldOperatorDelimiter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SplitQueryQueries 自定义查询字符串过滤器 - 分割多个键值对
|
||||||
|
func SplitQueryQueries(field string) []string {
|
||||||
|
return strings.Split(field, QueryFilterQueriesDelimiter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SplitQueryValues 自定义查询字符串过滤器 - 分割多个值
|
||||||
|
func SplitQueryValues(field string) []string {
|
||||||
|
return strings.Split(field, QueryFilterValuesDelimiter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SplitJSONField 将JSONB字段字符串按分隔符分割成多个字段
|
||||||
|
func SplitJSONField(field string) []string {
|
||||||
|
return strings.Split(field, JsonFieldDelimiter)
|
||||||
|
}
|
||||||
401
query_parser/filter_test.go
Normal file
401
query_parser/filter_test.go
Normal file
@@ -0,0 +1,401 @@
|
|||||||
|
package query_parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseFilterJSONString(t *testing.T) {
|
||||||
|
var results []struct {
|
||||||
|
Field string
|
||||||
|
Operator string
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := func(field, operator, value string) {
|
||||||
|
results = append(results, struct {
|
||||||
|
Field string
|
||||||
|
Operator string
|
||||||
|
Value string
|
||||||
|
}{Field: field, Operator: operator, Value: value})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试解析单个过滤条件
|
||||||
|
results = nil
|
||||||
|
err := ParseFilterJSONString(`{"name__exact":"John"}`, handler)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 1, len(results))
|
||||||
|
assert.Equal(t, "name", results[0].Field)
|
||||||
|
assert.Equal(t, "exact", results[0].Operator)
|
||||||
|
assert.Equal(t, "John", results[0].Value)
|
||||||
|
|
||||||
|
// 测试解析多个过滤条件
|
||||||
|
results = nil
|
||||||
|
err = ParseFilterJSONString(`[{"age__gte":"30"},{"status__exact":"active"}]`, handler)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 2, len(results))
|
||||||
|
assert.Equal(t, "age", results[0].Field)
|
||||||
|
assert.Equal(t, "gte", results[0].Operator)
|
||||||
|
assert.Equal(t, "30", results[0].Value)
|
||||||
|
assert.Equal(t, "status", results[1].Field)
|
||||||
|
assert.Equal(t, "exact", results[1].Operator)
|
||||||
|
assert.Equal(t, "active", results[1].Value)
|
||||||
|
|
||||||
|
// 测试空字符串
|
||||||
|
results = nil
|
||||||
|
err = ParseFilterJSONString("", handler)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 0, len(results))
|
||||||
|
|
||||||
|
// 测试无效的JSON字符串
|
||||||
|
results = nil
|
||||||
|
err = ParseFilterJSONString(`invalid_json`, handler)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, 0, len(results))
|
||||||
|
|
||||||
|
// 测试包含特殊字符的字段和值
|
||||||
|
results = nil
|
||||||
|
err = ParseFilterJSONString(`{"na!me__exact":"Jo@hn"}`, handler)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 1, len(results))
|
||||||
|
assert.Equal(t, "na_me", results[0].Field)
|
||||||
|
assert.Equal(t, "exact", results[0].Operator)
|
||||||
|
assert.Equal(t, "Jo@hn", results[0].Value)
|
||||||
|
|
||||||
|
// 测试包含特殊字符的多个过滤条件
|
||||||
|
results = nil
|
||||||
|
err = ParseFilterJSONString(`[{"ag#e__gte":"30"},{"sta$tus__exact":"ac^tive"}]`, handler)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 2, len(results))
|
||||||
|
assert.Equal(t, "ag_e", results[0].Field)
|
||||||
|
assert.Equal(t, "gte", results[0].Operator)
|
||||||
|
assert.Equal(t, "30", results[0].Value)
|
||||||
|
assert.Equal(t, "sta_tus", results[1].Field)
|
||||||
|
assert.Equal(t, "exact", results[1].Operator)
|
||||||
|
assert.Equal(t, "ac^tive", results[1].Value)
|
||||||
|
|
||||||
|
// 测试包含特殊字符的无效 JSON 字符串
|
||||||
|
results = nil
|
||||||
|
err = ParseFilterJSONString(`{"na!me__exact":Jo@hn}`, handler)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, 0, len(results))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseFilterQueryString(t *testing.T) {
|
||||||
|
var results []struct {
|
||||||
|
Field string
|
||||||
|
Operator string
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := func(field, operator, value string) {
|
||||||
|
results = append(results, struct {
|
||||||
|
Field string
|
||||||
|
Operator string
|
||||||
|
Value string
|
||||||
|
}{Field: field, Operator: operator, Value: value})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试解析单个过滤条件
|
||||||
|
results = nil
|
||||||
|
err := ParseFilterQueryString("name__exact:John", handler)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 1, len(results))
|
||||||
|
assert.Equal(t, "name", results[0].Field)
|
||||||
|
assert.Equal(t, "exact", results[0].Operator)
|
||||||
|
assert.Equal(t, "John", results[0].Value)
|
||||||
|
|
||||||
|
// 测试解析多个过滤条件
|
||||||
|
results = nil
|
||||||
|
err = ParseFilterQueryString("age__gte:30,status__exact:active", handler)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 2, len(results))
|
||||||
|
assert.Equal(t, "age", results[0].Field)
|
||||||
|
assert.Equal(t, "gte", results[0].Operator)
|
||||||
|
assert.Equal(t, "30", results[0].Value)
|
||||||
|
assert.Equal(t, "status", results[1].Field)
|
||||||
|
assert.Equal(t, "exact", results[1].Operator)
|
||||||
|
assert.Equal(t, "active", results[1].Value)
|
||||||
|
|
||||||
|
// 测试空字符串
|
||||||
|
results = nil
|
||||||
|
err = ParseFilterQueryString("", handler)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 0, len(results))
|
||||||
|
|
||||||
|
// 测试无效的查询字符串
|
||||||
|
results = nil
|
||||||
|
err = ParseFilterQueryString("invalid_query", handler)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 0, len(results))
|
||||||
|
|
||||||
|
// 测试包含特殊字符的字段和值
|
||||||
|
results = nil
|
||||||
|
err = ParseFilterQueryString("na!me__exact:Jo@hn", handler)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 1, len(results))
|
||||||
|
assert.Equal(t, "na_me", results[0].Field)
|
||||||
|
assert.Equal(t, "exact", results[0].Operator)
|
||||||
|
assert.Equal(t, "Jo@hn", results[0].Value)
|
||||||
|
|
||||||
|
// 测试包含特殊字符的多个过滤条件
|
||||||
|
results = nil
|
||||||
|
err = ParseFilterQueryString("ag#e__gte:30,sta$tus__exact:ac^tive", handler)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 2, len(results))
|
||||||
|
assert.Equal(t, "ag_e", results[0].Field)
|
||||||
|
assert.Equal(t, "gte", results[0].Operator)
|
||||||
|
assert.Equal(t, "30", results[0].Value)
|
||||||
|
assert.Equal(t, "sta_tus", results[1].Field)
|
||||||
|
assert.Equal(t, "exact", results[1].Operator)
|
||||||
|
assert.Equal(t, "ac^tive", results[1].Value)
|
||||||
|
|
||||||
|
// 测试 Field 中包含分隔符
|
||||||
|
results = nil
|
||||||
|
err = ParseFilterQueryString("na:me__exact:John", handler)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 0, len(results))
|
||||||
|
|
||||||
|
// 测试 Operator 中包含分隔符
|
||||||
|
results = nil
|
||||||
|
err = ParseFilterQueryString("name__ex:act:John", handler)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 0, len(results))
|
||||||
|
|
||||||
|
// 测试 Value 中包含分隔符
|
||||||
|
results = nil
|
||||||
|
err = ParseFilterQueryString("name__exact:Jo|hn", handler)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 1, len(results))
|
||||||
|
assert.Equal(t, "name", results[0].Field)
|
||||||
|
assert.Equal(t, "exact", results[0].Operator)
|
||||||
|
assert.Equal(t, "Jo|hn", results[0].Value)
|
||||||
|
|
||||||
|
// 测试多个过滤条件中包含分隔符
|
||||||
|
results = nil
|
||||||
|
err = ParseFilterQueryString("ag:e__gte:30,sta|tus__exact:ac|tive", handler)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 1, len(results))
|
||||||
|
|
||||||
|
// 测试 Field 中包含编码后的分隔符
|
||||||
|
results = nil
|
||||||
|
encodedField := EncodeSpecialCharacters("na:me")
|
||||||
|
err = ParseFilterQueryString(encodedField+"__exact:John", handler)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 1, len(results))
|
||||||
|
assert.Equal(t, "na_me", results[0].Field) // 注意:这里的字段名会被转换为 snake_case
|
||||||
|
assert.Equal(t, "exact", results[0].Operator)
|
||||||
|
assert.Equal(t, "John", results[0].Value)
|
||||||
|
|
||||||
|
// 测试 Operator 中包含编码后的分隔符
|
||||||
|
results = nil
|
||||||
|
encodedOperator := EncodeSpecialCharacters("ex:act")
|
||||||
|
err = ParseFilterQueryString("name__"+encodedOperator+":John", handler)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 1, len(results))
|
||||||
|
assert.Equal(t, "name", results[0].Field)
|
||||||
|
assert.Equal(t, "ex:act", results[0].Operator)
|
||||||
|
assert.Equal(t, "John", results[0].Value)
|
||||||
|
|
||||||
|
// 测试 Value 中包含编码后的分隔符
|
||||||
|
results = nil
|
||||||
|
encodedValue := EncodeSpecialCharacters("Jo|hn")
|
||||||
|
err = ParseFilterQueryString("name__exact:"+encodedValue, handler)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 1, len(results))
|
||||||
|
assert.Equal(t, "name", results[0].Field)
|
||||||
|
assert.Equal(t, "exact", results[0].Operator)
|
||||||
|
assert.Equal(t, "Jo|hn", results[0].Value)
|
||||||
|
|
||||||
|
// 测试多个过滤条件中包含编码后的分隔符
|
||||||
|
results = nil
|
||||||
|
encodedField1 := EncodeSpecialCharacters("ag:e")
|
||||||
|
encodedValue1 := EncodeSpecialCharacters("30")
|
||||||
|
encodedField2 := EncodeSpecialCharacters("sta|tus")
|
||||||
|
encodedValue2 := EncodeSpecialCharacters("ac|tive")
|
||||||
|
err = ParseFilterQueryString(encodedField1+"__gte:"+encodedValue1+","+encodedField2+"__exact:"+encodedValue2, handler)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 2, len(results))
|
||||||
|
assert.Equal(t, "ag_e", results[0].Field) // 注意:这里的字段名会被转换为 snake_case
|
||||||
|
assert.Equal(t, "gte", results[0].Operator)
|
||||||
|
assert.Equal(t, "30", results[0].Value)
|
||||||
|
assert.Equal(t, "sta_tus", results[1].Field) // 注意:这里的字段名会被转换为 snake_case
|
||||||
|
assert.Equal(t, "exact", results[1].Operator)
|
||||||
|
assert.Equal(t, "ac|tive", results[1].Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseFilterField(t *testing.T) {
|
||||||
|
var results []struct {
|
||||||
|
Field string
|
||||||
|
Operator string
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := func(field, operator, value string) {
|
||||||
|
results = append(results, struct {
|
||||||
|
Field string
|
||||||
|
Operator string
|
||||||
|
Value string
|
||||||
|
}{Field: field, Operator: operator, Value: value})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试正常解析
|
||||||
|
results = nil
|
||||||
|
ParseFilterField("name__exact", "John", handler)
|
||||||
|
assert.Equal(t, 1, len(results))
|
||||||
|
assert.Equal(t, "name", results[0].Field)
|
||||||
|
assert.Equal(t, "exact", results[0].Operator)
|
||||||
|
assert.Equal(t, "John", results[0].Value)
|
||||||
|
|
||||||
|
// 测试无操作符解析
|
||||||
|
results = nil
|
||||||
|
ParseFilterField("name", "John", handler)
|
||||||
|
assert.Equal(t, 1, len(results))
|
||||||
|
assert.Equal(t, "name", results[0].Field)
|
||||||
|
assert.Equal(t, "", results[0].Operator)
|
||||||
|
assert.Equal(t, "John", results[0].Value)
|
||||||
|
|
||||||
|
// 测试空字段
|
||||||
|
results = nil
|
||||||
|
ParseFilterField("", "John", handler)
|
||||||
|
assert.Equal(t, 0, len(results))
|
||||||
|
|
||||||
|
// 测试空值
|
||||||
|
results = nil
|
||||||
|
ParseFilterField("name__exact", "", handler)
|
||||||
|
assert.Equal(t, 0, len(results))
|
||||||
|
|
||||||
|
// 测试无效字段格式
|
||||||
|
results = nil
|
||||||
|
ParseFilterField("__exact", "John", handler)
|
||||||
|
assert.Equal(t, 0, len(results))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSplitJsonFieldAndOperator(t *testing.T) {
|
||||||
|
// 测试正常分割
|
||||||
|
result := SplitJsonFieldAndOperator("name__exact")
|
||||||
|
assert.Equal(t, 2, len(result))
|
||||||
|
assert.Equal(t, "name", result[0])
|
||||||
|
assert.Equal(t, "exact", result[1])
|
||||||
|
|
||||||
|
// 测试无操作符
|
||||||
|
result = SplitJsonFieldAndOperator("name")
|
||||||
|
assert.Equal(t, 1, len(result))
|
||||||
|
assert.Equal(t, "name", result[0])
|
||||||
|
|
||||||
|
// 测试空字符串
|
||||||
|
result = SplitJsonFieldAndOperator("")
|
||||||
|
assert.Equal(t, 1, len(result))
|
||||||
|
assert.Equal(t, "", result[0])
|
||||||
|
|
||||||
|
// 测试多个分隔符
|
||||||
|
result = SplitJsonFieldAndOperator("name__exact__extra")
|
||||||
|
assert.Equal(t, 3, len(result))
|
||||||
|
assert.Equal(t, "name", result[0])
|
||||||
|
assert.Equal(t, "exact", result[1])
|
||||||
|
assert.Equal(t, "extra", result[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSplitJSONField(t *testing.T) {
|
||||||
|
// 测试正常分割
|
||||||
|
result := SplitJSONField("user.address.city")
|
||||||
|
assert.Equal(t, 3, len(result))
|
||||||
|
assert.Equal(t, "user", result[0])
|
||||||
|
assert.Equal(t, "address", result[1])
|
||||||
|
assert.Equal(t, "city", result[2])
|
||||||
|
|
||||||
|
// 测试单个字段
|
||||||
|
result = SplitJSONField("user")
|
||||||
|
assert.Equal(t, 1, len(result))
|
||||||
|
assert.Equal(t, "user", result[0])
|
||||||
|
|
||||||
|
// 测试空字符串
|
||||||
|
result = SplitJSONField("")
|
||||||
|
assert.Equal(t, 1, len(result))
|
||||||
|
assert.Equal(t, "", result[0])
|
||||||
|
|
||||||
|
// 测试多个分隔符
|
||||||
|
result = SplitJSONField("user..address.city")
|
||||||
|
assert.Equal(t, 4, len(result))
|
||||||
|
assert.Equal(t, "user", result[0])
|
||||||
|
assert.Equal(t, "", result[1])
|
||||||
|
assert.Equal(t, "address", result[2])
|
||||||
|
assert.Equal(t, "city", result[3])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSplitQueryFieldAndOperator(t *testing.T) {
|
||||||
|
// 测试正常分割
|
||||||
|
result := SplitQueryFieldAndOperator("name:exact")
|
||||||
|
assert.Equal(t, 2, len(result))
|
||||||
|
assert.Equal(t, "name", result[0])
|
||||||
|
assert.Equal(t, "exact", result[1])
|
||||||
|
|
||||||
|
// 测试无操作符
|
||||||
|
result = SplitQueryFieldAndOperator("name")
|
||||||
|
assert.Equal(t, 1, len(result))
|
||||||
|
assert.Equal(t, "name", result[0])
|
||||||
|
|
||||||
|
// 测试空字符串
|
||||||
|
result = SplitQueryFieldAndOperator("")
|
||||||
|
assert.Equal(t, 1, len(result))
|
||||||
|
assert.Equal(t, "", result[0])
|
||||||
|
|
||||||
|
// 测试多个分隔符
|
||||||
|
result = SplitQueryFieldAndOperator("name:exact:extra")
|
||||||
|
assert.Equal(t, 3, len(result))
|
||||||
|
assert.Equal(t, "name", result[0])
|
||||||
|
assert.Equal(t, "exact", result[1])
|
||||||
|
assert.Equal(t, "extra", result[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSplitQueryQueries(t *testing.T) {
|
||||||
|
// 测试正常分割多个键值对
|
||||||
|
result := SplitQueryQueries("name:John,age:30,status:active")
|
||||||
|
assert.Equal(t, 3, len(result))
|
||||||
|
assert.Equal(t, "name:John", result[0])
|
||||||
|
assert.Equal(t, "age:30", result[1])
|
||||||
|
assert.Equal(t, "status:active", result[2])
|
||||||
|
|
||||||
|
// 测试单个键值对
|
||||||
|
result = SplitQueryQueries("name:John")
|
||||||
|
assert.Equal(t, 1, len(result))
|
||||||
|
assert.Equal(t, "name:John", result[0])
|
||||||
|
|
||||||
|
// 测试空字符串
|
||||||
|
result = SplitQueryQueries("")
|
||||||
|
assert.Equal(t, 1, len(result))
|
||||||
|
assert.Equal(t, "", result[0])
|
||||||
|
|
||||||
|
// 测试多个分隔符
|
||||||
|
result = SplitQueryQueries("name:John,,age:30")
|
||||||
|
assert.Equal(t, 3, len(result))
|
||||||
|
assert.Equal(t, "name:John", result[0])
|
||||||
|
assert.Equal(t, "", result[1])
|
||||||
|
assert.Equal(t, "age:30", result[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSplitQueryValues(t *testing.T) {
|
||||||
|
// 测试正常分割多个值
|
||||||
|
result := SplitQueryValues("value1|value2|value3")
|
||||||
|
assert.Equal(t, 3, len(result))
|
||||||
|
assert.Equal(t, "value1", result[0])
|
||||||
|
assert.Equal(t, "value2", result[1])
|
||||||
|
assert.Equal(t, "value3", result[2])
|
||||||
|
|
||||||
|
// 测试单个值
|
||||||
|
result = SplitQueryValues("value1")
|
||||||
|
assert.Equal(t, 1, len(result))
|
||||||
|
assert.Equal(t, "value1", result[0])
|
||||||
|
|
||||||
|
// 测试空字符串
|
||||||
|
result = SplitQueryValues("")
|
||||||
|
assert.Equal(t, 1, len(result))
|
||||||
|
assert.Equal(t, "", result[0])
|
||||||
|
|
||||||
|
// 测试多个分隔符
|
||||||
|
result = SplitQueryValues("value1||value2")
|
||||||
|
assert.Equal(t, 3, len(result))
|
||||||
|
assert.Equal(t, "value1", result[0])
|
||||||
|
assert.Equal(t, "", result[1])
|
||||||
|
assert.Equal(t, "value2", result[2])
|
||||||
|
}
|
||||||
21
query_parser/go.mod
Normal file
21
query_parser/go.mod
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
module github.com/tx7do/go-utils/query_parser
|
||||||
|
|
||||||
|
go 1.23.0
|
||||||
|
|
||||||
|
toolchain go1.23.2
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/go-kratos/kratos/v2 v2.8.4
|
||||||
|
github.com/stretchr/testify v1.10.0
|
||||||
|
github.com/tx7do/go-utils v1.1.29
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/kr/text v0.2.0 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.6 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
replace github.com/tx7do/go-utils => ../
|
||||||
26
query_parser/go.sum
Normal file
26
query_parser/go.sum
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/go-kratos/kratos/v2 v2.8.4 h1:eIJLE9Qq9WSoKx+Buy2uPyrahtF/lPh+Xf4MTpxhmjs=
|
||||||
|
github.com/go-kratos/kratos/v2 v2.8.4/go.mod h1:mq62W2101a5uYyRxe+7IdWubu7gZCGYqSNKwGFiiRcw=
|
||||||
|
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||||
|
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||||
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||||
|
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
50
query_parser/orderby.go
Normal file
50
query_parser/orderby.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package query_parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OrderByHandler func(field string, desc bool)
|
||||||
|
|
||||||
|
// ParseOrderByString 解析排序字符串,调用处理函数。
|
||||||
|
func ParseOrderByString(orderBy string, handler OrderByHandler) error {
|
||||||
|
if orderBy == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(orderBy, ",")
|
||||||
|
for _, part := range parts {
|
||||||
|
part = strings.TrimSpace(part)
|
||||||
|
ParseOrderByField(part, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseOrderByStrings 解析多个排序字符串,调用处理函数。
|
||||||
|
func ParseOrderByStrings(orderBys []string, handler OrderByHandler) error {
|
||||||
|
for _, v := range orderBys {
|
||||||
|
if v == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ParseOrderByField(v, handler)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseOrderByField 解析单个排序字段,调用处理函数。
|
||||||
|
func ParseOrderByField(orderBy string, handler OrderByHandler) {
|
||||||
|
orderBy = strings.TrimSpace(orderBy)
|
||||||
|
if orderBy == "" {
|
||||||
|
return // 没有排序条件
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(orderBy, "-") {
|
||||||
|
handler(orderBy[1:], true) // 降序
|
||||||
|
} else if strings.HasPrefix(orderBy, "+") {
|
||||||
|
handler(orderBy[1:], false) // 升序
|
||||||
|
} else {
|
||||||
|
handler(orderBy, false) // 升序
|
||||||
|
}
|
||||||
|
}
|
||||||
126
query_parser/orderby_test.go
Normal file
126
query_parser/orderby_test.go
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
package query_parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseOrderByString(t *testing.T) {
|
||||||
|
var results []struct {
|
||||||
|
Field string
|
||||||
|
Desc bool
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := func(field string, desc bool) {
|
||||||
|
results = append(results, struct {
|
||||||
|
Field string
|
||||||
|
Desc bool
|
||||||
|
}{Field: field, Desc: desc})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试正常解析
|
||||||
|
err := ParseOrderByString("name,-age,+created_at", handler)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 3, len(results))
|
||||||
|
assert.Equal(t, "name", results[0].Field)
|
||||||
|
assert.False(t, results[0].Desc)
|
||||||
|
assert.Equal(t, "age", results[1].Field)
|
||||||
|
assert.True(t, results[1].Desc)
|
||||||
|
assert.Equal(t, "created_at", results[2].Field)
|
||||||
|
assert.False(t, results[2].Desc)
|
||||||
|
|
||||||
|
// 测试空字符串
|
||||||
|
results = nil
|
||||||
|
err = ParseOrderByString("", handler)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 0, len(results))
|
||||||
|
|
||||||
|
// 测试只有空格的字符串
|
||||||
|
results = nil
|
||||||
|
err = ParseOrderByString(" ", handler)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 0, len(results))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseOrderByStrings(t *testing.T) {
|
||||||
|
var results []struct {
|
||||||
|
Field string
|
||||||
|
Desc bool
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := func(field string, desc bool) {
|
||||||
|
results = append(results, struct {
|
||||||
|
Field string
|
||||||
|
Desc bool
|
||||||
|
}{Field: field, Desc: desc})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试正常解析
|
||||||
|
err := ParseOrderByStrings([]string{"name", "-age", "+created_at"}, handler)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 3, len(results))
|
||||||
|
assert.Equal(t, "name", results[0].Field)
|
||||||
|
assert.False(t, results[0].Desc)
|
||||||
|
assert.Equal(t, "age", results[1].Field)
|
||||||
|
assert.True(t, results[1].Desc)
|
||||||
|
assert.Equal(t, "created_at", results[2].Field)
|
||||||
|
assert.False(t, results[2].Desc)
|
||||||
|
|
||||||
|
// 测试空字符串数组
|
||||||
|
results = nil
|
||||||
|
err = ParseOrderByStrings([]string{}, handler)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 0, len(results))
|
||||||
|
|
||||||
|
// 测试包含空字符串的数组
|
||||||
|
results = nil
|
||||||
|
err = ParseOrderByStrings([]string{"", " "}, handler)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 0, len(results))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseOrderByField(t *testing.T) {
|
||||||
|
var results []struct {
|
||||||
|
Field string
|
||||||
|
Desc bool
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := func(field string, desc bool) {
|
||||||
|
results = append(results, struct {
|
||||||
|
Field string
|
||||||
|
Desc bool
|
||||||
|
}{Field: field, Desc: desc})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试升序解析
|
||||||
|
results = nil
|
||||||
|
ParseOrderByField("name", handler)
|
||||||
|
assert.Equal(t, 1, len(results))
|
||||||
|
assert.Equal(t, "name", results[0].Field)
|
||||||
|
assert.False(t, results[0].Desc)
|
||||||
|
|
||||||
|
// 测试降序解析
|
||||||
|
results = nil
|
||||||
|
ParseOrderByField("-age", handler)
|
||||||
|
assert.Equal(t, 1, len(results))
|
||||||
|
assert.Equal(t, "age", results[0].Field)
|
||||||
|
assert.True(t, results[0].Desc)
|
||||||
|
|
||||||
|
// 测试带+的升序解析
|
||||||
|
results = nil
|
||||||
|
ParseOrderByField("+created_at", handler)
|
||||||
|
assert.Equal(t, 1, len(results))
|
||||||
|
assert.Equal(t, "created_at", results[0].Field)
|
||||||
|
assert.False(t, results[0].Desc)
|
||||||
|
|
||||||
|
// 测试空字符串
|
||||||
|
results = nil
|
||||||
|
ParseOrderByField("", handler)
|
||||||
|
assert.Equal(t, 0, len(results))
|
||||||
|
|
||||||
|
// 测试只有空格的字符串
|
||||||
|
results = nil
|
||||||
|
ParseOrderByField(" ", handler)
|
||||||
|
assert.Equal(t, 0, len(results))
|
||||||
|
}
|
||||||
13
query_parser/utils.go
Normal file
13
query_parser/utils.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package query_parser
|
||||||
|
|
||||||
|
import "net/url"
|
||||||
|
|
||||||
|
// EncodeSpecialCharacters 对字符串进行编码
|
||||||
|
func EncodeSpecialCharacters(input string) string {
|
||||||
|
return url.QueryEscape(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeSpecialCharacters 对字符串进行解码
|
||||||
|
func DecodeSpecialCharacters(input string) (string, error) {
|
||||||
|
return url.QueryUnescape(input)
|
||||||
|
}
|
||||||
27
rand/rand.go
27
rand/rand.go
@@ -1,16 +1,16 @@
|
|||||||
package rand
|
package rand
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/tx7do/go-utils/math"
|
"github.com/tx7do/go-utils/math"
|
||||||
|
"math/rand"
|
||||||
)
|
)
|
||||||
|
|
||||||
var rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
|
var rnd = rand.New(rand.NewSource(Seed(UnixNanoSeed)))
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
|
if rnd != nil {
|
||||||
|
rnd = rand.New(rand.NewSource(Seed(UnixNanoSeed)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Float32() float32 {
|
func Float32() float32 {
|
||||||
@@ -59,18 +59,17 @@ func RandomInt64(min, max int64) int64 {
|
|||||||
|
|
||||||
// RandomString 随机字符串,包含大小写字母和数字
|
// RandomString 随机字符串,包含大小写字母和数字
|
||||||
func RandomString(l int) string {
|
func RandomString(l int) string {
|
||||||
|
if l <= 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||||
|
|
||||||
bytes := make([]byte, l)
|
bytes := make([]byte, l)
|
||||||
for i := 0; i < l; i++ {
|
for i := 0; i < l; i++ {
|
||||||
x := Intn(3)
|
bytes[i] = charset[Intn(len(charset))]
|
||||||
switch x {
|
|
||||||
case 0:
|
|
||||||
bytes[i] = byte(RandomInt(65, 90)) //大写字母
|
|
||||||
case 1:
|
|
||||||
bytes[i] = byte(RandomInt(97, 122))
|
|
||||||
case 2:
|
|
||||||
bytes[i] = byte(Intn(10))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return string(bytes)
|
return string(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user