Compare commits
56 Commits
gorm/v1.1.
...
mapper/v0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
71cb2d28e1 | ||
|
|
e8e61fc7a1 | ||
|
|
f7abc4e941 |
@@ -3,8 +3,8 @@ module github.com/tx7do/go-utils/bank_card
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/mattn/go-sqlite3 v1.14.22
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/mattn/go-sqlite3 v1.14.28
|
||||
github.com/stretchr/testify v1.10.0
|
||||
)
|
||||
|
||||
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
|
||||
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
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
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
|
||||
"encoding/hex"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// DefaultCost 最小值=4 最大值=31 默认值=10
|
||||
var DefaultCost = 10
|
||||
|
||||
// HashPassword 加密密码
|
||||
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
|
||||
}
|
||||
|
||||
// CheckPasswordHash 校验密码
|
||||
func CheckPasswordHash(password, hash string) bool {
|
||||
// HashPasswordWithSalt 对密码进行加盐哈希处理
|
||||
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))
|
||||
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 (
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestHashPassword(t *testing.T) {
|
||||
@@ -13,16 +14,27 @@ func TestHashPassword(t *testing.T) {
|
||||
fmt.Println(hash)
|
||||
}
|
||||
|
||||
func TestCheckPasswordHash(t *testing.T) {
|
||||
func TestVerifyPassword(t *testing.T) {
|
||||
text := "123456"
|
||||
|
||||
// Prefix + Cost + Salt + Hashed Text
|
||||
hash3 := "$2a$10$ygWrRwHCzg2GUpz0UK40kuWAGva121VkScpcdMNsDCih2U/bL2qYy"
|
||||
bMatched := CheckPasswordHash(text, hash3)
|
||||
bMatched := VerifyPassword(text, hash3)
|
||||
assert.True(t, bMatched)
|
||||
|
||||
bMatched = CheckPasswordHash(text, hash3)
|
||||
bMatched = VerifyPassword(text, hash3)
|
||||
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) {
|
||||
const bearerWord string = "Bearer"
|
||||
token := "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjowfQ.XgcKAAjHbA6o4sxxbEaMi05ingWvKdCNnyW9wowbJvs"
|
||||
|
||||
@@ -15,16 +15,16 @@ import (
|
||||
entSql "entgo.io/ent/dialect/sql"
|
||||
)
|
||||
|
||||
type entClientInterface interface {
|
||||
type EntClientInterface interface {
|
||||
Close() error
|
||||
}
|
||||
|
||||
type EntClient[T entClientInterface] struct {
|
||||
type EntClient[T EntClientInterface] struct {
|
||||
db T
|
||||
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]{
|
||||
db: db,
|
||||
drv: drv,
|
||||
|
||||
63
entgo/go.mod
63
entgo/go.mod
@@ -1,46 +1,57 @@
|
||||
module github.com/tx7do/go-utils/entgo
|
||||
|
||||
go 1.21
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.22.1
|
||||
toolchain go1.23.2
|
||||
|
||||
replace github.com/tx7do/go-utils/id => ../id
|
||||
|
||||
require (
|
||||
entgo.io/contrib v0.5.0
|
||||
entgo.io/ent v0.13.1
|
||||
github.com/go-kratos/kratos/v2 v2.7.3
|
||||
entgo.io/contrib v0.6.0
|
||||
entgo.io/ent v0.14.4
|
||||
github.com/XSAM/otelsql v0.38.0
|
||||
github.com/go-kratos/kratos/v2 v2.8.4
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/tx7do/go-utils v1.1.11
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/tx7do/go-utils v1.1.28
|
||||
github.com/tx7do/go-utils/id v0.0.2
|
||||
go.opentelemetry.io/otel v1.36.0
|
||||
google.golang.org/protobuf v1.36.6
|
||||
)
|
||||
|
||||
require (
|
||||
ariga.io/atlas v0.19.1-0.20240203083654-5948b60a8e43 // indirect
|
||||
github.com/XSAM/otelsql v0.30.0 // indirect
|
||||
ariga.io/atlas v0.34.0 // indirect
|
||||
github.com/agext/levenshtein v1.2.3 // indirect
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||
github.com/bufbuild/protocompile v0.6.0 // indirect
|
||||
github.com/bmatcuk/doublestar v1.3.4 // 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/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/inflect v0.19.0 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/hashicorp/hcl/v2 v2.19.1 // indirect
|
||||
github.com/jhump/protoreflect v1.15.3 // indirect
|
||||
github.com/go-openapi/inflect v0.21.2 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/hashicorp/hcl/v2 v2.23.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/mapstructure v1.5.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/sony/sonyflake v1.2.0 // indirect
|
||||
github.com/zclconf/go-cty v1.14.1 // indirect
|
||||
go.opentelemetry.io/otel v1.26.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.26.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.26.0 // indirect
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/segmentio/ksuid v1.0.4 // indirect
|
||||
github.com/sony/sonyflake v1.2.1 // 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.3 // 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
|
||||
golang.org/x/mod v0.15.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/tools v0.18.0 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
golang.org/x/mod v0.24.0 // indirect
|
||||
golang.org/x/sync v0.14.0 // indirect
|
||||
golang.org/x/text v0.25.0 // indirect
|
||||
golang.org/x/tools v0.33.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
|
||||
145
entgo/go.sum
145
entgo/go.sum
@@ -1,50 +1,52 @@
|
||||
ariga.io/atlas v0.19.1-0.20240203083654-5948b60a8e43 h1:GwdJbXydHCYPedeeLt4x/lrlIISQ4JTH1mRWuE5ZZ14=
|
||||
ariga.io/atlas v0.19.1-0.20240203083654-5948b60a8e43/go.mod h1:uj3pm+hUTVN/X5yfdBexHlZv+1Xu5u5ZbZx7+CDavNU=
|
||||
entgo.io/contrib v0.5.0 h1:M4IqodImfUm327RDwNAITLNz3PsxVeC3rD4DPeVA8Gs=
|
||||
entgo.io/contrib v0.5.0/go.mod h1:q8dXQCmzqpSlVdT2bWDydjgznGcy3y4zmsYmVFC9V/U=
|
||||
entgo.io/ent v0.13.1 h1:uD8QwN1h6SNphdCCzmkMN3feSUzNnVvV/WIkHKMbzOE=
|
||||
entgo.io/ent v0.13.1/go.mod h1:qCEmo+biw3ccBn9OyL4ZK5dfpwg++l1Gxwac5B1206A=
|
||||
ariga.io/atlas v0.34.0 h1:4hdy+2x+xNs6Lx2anuJ/4Q7lCaqddbEj5CtRDVOBu0M=
|
||||
ariga.io/atlas v0.34.0/go.mod h1:WJesu2UCpGQvgUh3oVP94EiRT61nNy1W/VN5g+vqP1I=
|
||||
entgo.io/contrib v0.6.0 h1:xfo4TbJE7sJZWx7BV7YrpSz7IPFvS8MzL3fnfzZjKvQ=
|
||||
entgo.io/contrib v0.6.0/go.mod h1:3qWIseJ/9Wx2Hu5zVh15FDzv7d/UvKNcYKdViywWCQg=
|
||||
entgo.io/ent v0.14.4 h1:/DhDraSLXIkBhyiVoJeSshr4ZYi7femzhj6/TckzZuI=
|
||||
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/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/XSAM/otelsql v0.30.0 h1:yd6Ds3xQkKtP5+JztH2un5Hfy9uyo1eUgv34dGpSIK4=
|
||||
github.com/XSAM/otelsql v0.30.0/go.mod h1:12ObuENPHhAcc2cU89u7Yr0uT60+FliFCTP9Sd4N68o=
|
||||
github.com/XSAM/otelsql v0.38.0 h1:zWU0/YM9cJhPE71zJcQ2EBHwQDp+G4AX2tPpljslaB8=
|
||||
github.com/XSAM/otelsql v0.38.0/go.mod h1:5ePOgcLEkWvZtN9H3GV4BUlPeM3p3pzLDCnRG73X8h8=
|
||||
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
|
||||
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/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
|
||||
github.com/bufbuild/protocompile v0.6.0 h1:Uu7WiSQ6Yj9DbkdnOe7U4mNKp58y9WDMKDn28/ZlunY=
|
||||
github.com/bufbuild/protocompile v0.6.0/go.mod h1:YNP35qEYoYGme7QMtz5SBCoN4kL4g12jTtjuzRNdjpE=
|
||||
github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0=
|
||||
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/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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-kratos/kratos/v2 v2.7.3 h1:T9MS69qk4/HkVUuHw5GS9PDVnOfzn+kxyF0CL5StqxA=
|
||||
github.com/go-kratos/kratos/v2 v2.7.3/go.mod h1:CQZ7V0qyVPwrotIpS5VNNUJNzEbcyRUl5pRtxLOIvn4=
|
||||
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/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
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/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4=
|
||||
github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
|
||||
github.com/go-openapi/inflect v0.21.2 h1:0gClGlGcxifcJR56zwvhaOulnNgnhc4qTAkob5ObnSM=
|
||||
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/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
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.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
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/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hashicorp/hcl/v2 v2.19.1 h1://i05Jqznmb2EXqa39Nsvyan2o5XyMowW5fnCKW5RPI=
|
||||
github.com/hashicorp/hcl/v2 v2.19.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE=
|
||||
github.com/jhump/protoreflect v1.15.3 h1:6SFRuqU45u9hIZPJAoZ8c28T3nK64BNdp9w6jFonzls=
|
||||
github.com/jhump/protoreflect v1.15.3/go.mod h1:4ORHmSBmlCW8fh3xHmJMGyul1zNqZK4Elxc8qKP+p1k=
|
||||
github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos=
|
||||
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/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/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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
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/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
|
||||
@@ -53,46 +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/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/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/sony/sonyflake v1.2.0 h1:Pfr3A+ejSg+0SPqpoAmQgEtNDAhc2G1SUYk205qVMLQ=
|
||||
github.com/sony/sonyflake v1.2.0/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/zclconf/go-cty v1.14.1 h1:t9fyA35fwjjUMcmL5hLER+e/rEPqrbCK1/OSE4SI9KA=
|
||||
github.com/zclconf/go-cty v1.14.1/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
|
||||
go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs=
|
||||
go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4=
|
||||
go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30=
|
||||
go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4=
|
||||
go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA=
|
||||
go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0=
|
||||
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=
|
||||
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/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
|
||||
github.com/zclconf/go-cty-yaml v1.1.0 h1:nP+jp0qPHv2IhUVqmQSzjvqAWcObN0KBkUl2rWBdig0=
|
||||
github.com/zclconf/go-cty-yaml v1.1.0/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs=
|
||||
go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ=
|
||||
go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
|
||||
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
|
||||
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
|
||||
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
|
||||
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
|
||||
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
|
||||
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/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ=
|
||||
golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto v0.0.0-20230629202037-9506855d4529 h1:9JucMWR7sPvCxUFd6UsOUNmA5kCcWOfORaT3tpAsKQs=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529 h1:DEH99RbiLZhMxrpEJCZ0A+wdTe0EOgou/poSLx9vWf4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
|
||||
google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
|
||||
google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
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.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
|
||||
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
|
||||
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/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU=
|
||||
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/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=
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"entgo.io/ent/schema/index"
|
||||
"entgo.io/ent/schema/mixin"
|
||||
|
||||
"github.com/tx7do/go-utils/sonyflake"
|
||||
"github.com/tx7do/go-utils/id"
|
||||
)
|
||||
|
||||
type SnowflackId struct {
|
||||
@@ -18,7 +18,7 @@ func (SnowflackId) Fields() []ent.Field {
|
||||
return []ent.Field{
|
||||
field.Uint64("id").
|
||||
Comment("id").
|
||||
DefaultFunc(sonyflake.GenerateSonyflake).
|
||||
DefaultFunc(id.GenerateSonyflakeID).
|
||||
Positive().
|
||||
Immutable().
|
||||
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`。具体规则请见:[过滤规则](#过滤规则) |
|
||||
| or | `string` | `json object` 或 `json object array` | OR过滤条件 | 同 AND过滤条件 | |
|
||||
| orderBy | `string` | `json string array` | 排序条件 | json字符串:`["-create_time", "type"]` | json的`string array`,字段名前加`-`是为降序,不加为升序。具体规则请见:[排序规则](#排序规则) |
|
||||
| nopaging | `boolean` | | 是否不分页 | | 此字段为`true`时,`page`、`pageSize`字段的传入将无效用。 |
|
||||
| fieldMask | `string` | `json string array` | 字段掩码 | | 此字段是`SELECT`条件,为空的时候是为`*`。 |
|
||||
| noPaging | `boolean` | | 是否不分页 | | 此字段为`true`时,`page`、`pageSize`字段的传入将无效用。 |
|
||||
| fieldMask | `string` | 其语法为使用逗号分隔字段名 | 字段掩码 | 例如:id,realName,userName。 | 此字段是`SELECT`条件,为空的时候是为`*`。 |
|
||||
|
||||
## 排序规则
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
|
||||
```text
|
||||
{字段名}__{查找类型} : {值}
|
||||
{字段名}.{JSON字段名}__{查找类型} : {值}
|
||||
```
|
||||
|
||||
| 查找类型 | 示例 | SQL | 备注 |
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package entgo
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"encoding/json"
|
||||
|
||||
"entgo.io/ent/dialect"
|
||||
"entgo.io/ent/dialect/sql"
|
||||
|
||||
@@ -63,16 +64,6 @@ var ops = [...]string{
|
||||
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
|
||||
|
||||
const (
|
||||
@@ -109,6 +100,38 @@ var dateParts = [...]string{
|
||||
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 {
|
||||
str = strings.ToLower(str)
|
||||
for _, item := range dateParts {
|
||||
@@ -119,6 +142,32 @@ func hasDatePart(str string) bool {
|
||||
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 查询命令转换为选择条件
|
||||
func QueryCommandToWhereConditions(strJson string, isOr bool) (error, func(s *sql.Selector)) {
|
||||
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 {
|
||||
var ps []*sql.Predicate
|
||||
for k, v := range queryMap {
|
||||
key := stringcase.ToSnakeCase(k)
|
||||
keys := splitQueryKey(k)
|
||||
|
||||
keys := strings.Split(key, "__")
|
||||
|
||||
if cond := oneFieldFilter(s, keys, v); cond != nil {
|
||||
if cond := makeFieldFilter(s, keys, v); cond != nil {
|
||||
ps = append(ps, cond)
|
||||
}
|
||||
}
|
||||
@@ -165,32 +213,8 @@ func processQueryMap(s *sql.Selector, queryMap map[string]string) []*sql.Predica
|
||||
return ps
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func oneFieldFilter(s *sql.Selector, keys []string, value string) *sql.Predicate {
|
||||
// makeFieldFilter 构建一个字段过滤器
|
||||
func makeFieldFilter(s *sql.Selector, keys []string, value string) *sql.Predicate {
|
||||
if len(keys) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -207,6 +231,21 @@ func oneFieldFilter(s *sql.Selector, keys []string, value string) *sql.Predicate
|
||||
|
||||
switch len(keys) {
|
||||
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)
|
||||
|
||||
case 2:
|
||||
@@ -215,6 +254,19 @@ func oneFieldFilter(s *sql.Selector, keys []string, value string) *sql.Predicate
|
||||
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
|
||||
if hasOperations(op) {
|
||||
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
|
||||
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)
|
||||
|
||||
if hasOperations(op2) {
|
||||
return processOp(s, p, op2, str, value)
|
||||
}
|
||||
|
||||
return nil
|
||||
} else {
|
||||
str := filterJsonbField(s, op1, field)
|
||||
@@ -547,6 +611,7 @@ func filterDatePart(s *sql.Selector, p *sql.Predicate, datePart, field string) *
|
||||
return p
|
||||
}
|
||||
|
||||
// filterDatePartField 日期
|
||||
func filterDatePartField(s *sql.Selector, datePart, field string) string {
|
||||
p := sql.P()
|
||||
switch s.Builder.Dialect() {
|
||||
@@ -565,12 +630,14 @@ func filterDatePartField(s *sql.Selector, datePart, field string) string {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
field = stringcase.ToSnakeCase(field)
|
||||
|
||||
p.Append(func(b *sql.Builder) {
|
||||
switch s.Builder.Dialect() {
|
||||
case dialect.Postgres:
|
||||
b.Ident(s.C(field)).WriteString(" -> ").WriteString(jsonbField)
|
||||
b.Ident(s.C(field)).WriteString(" ->> ").WriteString("'" + jsonbField + "'")
|
||||
//b.Arg(strings.ToLower(value))
|
||||
break
|
||||
|
||||
@@ -584,11 +651,14 @@ func filterJsonb(s *sql.Selector, p *sql.Predicate, jsonbField, field string) *s
|
||||
return p
|
||||
}
|
||||
|
||||
// filterJsonbField JSONB字段
|
||||
func filterJsonbField(s *sql.Selector, jsonbField, field string) string {
|
||||
field = stringcase.ToSnakeCase(field)
|
||||
|
||||
p := sql.P()
|
||||
switch s.Builder.Dialect() {
|
||||
case dialect.Postgres:
|
||||
p.Ident(s.C(field)).WriteString(" -> ").WriteString(jsonbField)
|
||||
p.Ident(s.C(field)).WriteString(" ->> ").WriteString("'" + jsonbField + "'")
|
||||
//b.Arg(strings.ToLower(value))
|
||||
break
|
||||
|
||||
|
||||
@@ -681,14 +681,104 @@ func TestFilter(t *testing.T) {
|
||||
s.Where(p)
|
||||
|
||||
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.Equal(t, args[0], "true")
|
||||
})
|
||||
}
|
||||
|
||||
func TestFilterJsonbField(t *testing.T) {
|
||||
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("app_profile"))
|
||||
str := filterJsonbField(s, "daily_email", "preferences")
|
||||
fmt.Println(str)
|
||||
t.Run("filterJsonbField", func(t *testing.T) {
|
||||
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("app_profile"))
|
||||
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 (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"entgo.io/ent/dialect"
|
||||
@@ -11,6 +10,7 @@ import (
|
||||
|
||||
"github.com/go-kratos/kratos/v2/encoding"
|
||||
_ "github.com/go-kratos/kratos/v2/encoding/json"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -85,11 +85,11 @@ func TestJsonCodec(t *testing.T) {
|
||||
func TestSplitQuery(t *testing.T) {
|
||||
var keys []string
|
||||
|
||||
keys = strings.Split("id", "__")
|
||||
keys = splitQueryKey("id")
|
||||
assert.Equal(t, len(keys), 1)
|
||||
assert.Equal(t, keys[0], "id")
|
||||
|
||||
keys = strings.Split("id__not", "__")
|
||||
keys = splitQueryKey("id__not")
|
||||
assert.Equal(t, len(keys), 2)
|
||||
assert.Equal(t, keys[0], "id")
|
||||
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"},
|
||||
|
||||
{"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"},
|
||||
{"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"},
|
||||
{"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"},
|
||||
{"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"},
|
||||
{"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 {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
package entgo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"entgo.io/ent/dialect/sql"
|
||||
|
||||
"github.com/tx7do/go-utils/fieldmaskutil"
|
||||
"github.com/tx7do/go-utils/stringcase"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
)
|
||||
|
||||
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)) {
|
||||
if len(fields) > 0 {
|
||||
return nil, func(u *sql.UpdateBuilder) {
|
||||
BuildSetNullUpdate(u, fields)
|
||||
}
|
||||
} else {
|
||||
return nil, nil
|
||||
// BuildSetNullUpdater 构建一个UpdateBuilder,用于清空字段的值
|
||||
func BuildSetNullUpdater(fields []string) func(u *sql.UpdateBuilder) {
|
||||
if len(fields) == 0 {
|
||||
return 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, ",")),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
17
geoip/go.mod
17
geoip/go.mod
@@ -1,21 +1,22 @@
|
||||
module github.com/tx7do/go-utils/geoip
|
||||
|
||||
go 1.20
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.24.1
|
||||
|
||||
require (
|
||||
github.com/go-kratos/kratos/v2 v2.7.3
|
||||
github.com/oschwald/geoip2-golang v1.9.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
golang.org/x/text v0.14.0
|
||||
github.com/go-kratos/kratos/v2 v2.8.4
|
||||
github.com/oschwald/geoip2-golang v1.11.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
golang.org/x/text v0.25.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/oschwald/maxminddb-golang v1.12.0 // indirect
|
||||
github.com/oschwald/maxminddb-golang v1.13.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.19.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
|
||||
33
geoip/go.sum
33
geoip/go.sum
@@ -1,27 +1,30 @@
|
||||
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.7.3 h1:T9MS69qk4/HkVUuHw5GS9PDVnOfzn+kxyF0CL5StqxA=
|
||||
github.com/go-kratos/kratos/v2 v2.7.3/go.mod h1:CQZ7V0qyVPwrotIpS5VNNUJNzEbcyRUl5pRtxLOIvn4=
|
||||
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/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/oschwald/geoip2-golang v1.9.0 h1:uvD3O6fXAXs+usU+UGExshpdP13GAqp4GBrzN7IgKZc=
|
||||
github.com/oschwald/geoip2-golang v1.9.0/go.mod h1:BHK6TvDyATVQhKNbQBdrj9eAvuwOMi2zSFXizL3K81Y=
|
||||
github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
|
||||
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
|
||||
github.com/oschwald/geoip2-golang v1.11.0 h1:hNENhCn1Uyzhf9PTmquXENiWS6AlxAEnBII6r8krA3w=
|
||||
github.com/oschwald/geoip2-golang v1.11.0/go.mod h1:P9zG+54KPEFOliZ29i7SeYZ/GM6tfEL+rgSn03hYuUo=
|
||||
github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=
|
||||
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
|
||||
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/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
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/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
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 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=
|
||||
|
||||
15
go.mod
15
go.mod
@@ -1,21 +1,20 @@
|
||||
module github.com/tx7do/go-utils
|
||||
|
||||
go 1.20
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.24.1
|
||||
|
||||
require (
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gosimple/slug v1.14.0
|
||||
github.com/sony/sonyflake v1.2.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
golang.org/x/crypto v0.22.0
|
||||
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0
|
||||
google.golang.org/protobuf v1.33.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
golang.org/x/crypto v0.38.0
|
||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6
|
||||
google.golang.org/protobuf v1.36.6
|
||||
)
|
||||
|
||||
require (
|
||||
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/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
||||
|
||||
25
go.sum
25
go.sum
@@ -3,13 +3,10 @@ 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/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
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.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
@@ -23,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.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
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/sony/sonyflake v1.2.0/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 h1:985EYyeCOxTpcgOTJpflJUwOeEz0CQOdPt73OzpE9F8=
|
||||
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
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/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=
|
||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
|
||||
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=
|
||||
|
||||
68
gorm/go.mod
68
gorm/go.mod
@@ -1,52 +1,54 @@
|
||||
module github.com/tx7do/go-utils/gorm
|
||||
|
||||
go 1.21
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.22.1
|
||||
toolchain go1.24.3
|
||||
|
||||
require (
|
||||
gorm.io/driver/clickhouse v0.6.0
|
||||
gorm.io/driver/mysql v1.5.6
|
||||
gorm.io/driver/postgres v1.5.7
|
||||
gorm.io/driver/sqlite v1.5.5
|
||||
gorm.io/driver/sqlserver v1.5.3
|
||||
gorm.io/gorm v1.25.10
|
||||
gorm.io/plugin/opentelemetry v0.1.4
|
||||
gorm.io/driver/clickhouse v0.6.1
|
||||
gorm.io/driver/mysql v1.5.7
|
||||
gorm.io/driver/postgres v1.5.11
|
||||
gorm.io/driver/sqlite v1.5.7
|
||||
gorm.io/driver/sqlserver v1.5.4
|
||||
gorm.io/gorm v1.26.1
|
||||
gorm.io/plugin/opentelemetry v0.1.14
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/ClickHouse/ch-go v0.58.2 // indirect
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.15.0 // indirect
|
||||
github.com/andybalholm/brotli v1.0.6 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/ClickHouse/ch-go v0.66.0 // indirect
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.34.0 // indirect
|
||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||
github.com/go-faster/city v1.0.1 // indirect
|
||||
github.com/go-faster/errors v0.6.1 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-faster/errors v0.7.1 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-sql-driver/mysql v1.7.0 // 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/sqlexp v0.1.0 // indirect
|
||||
github.com/google/uuid v1.3.1 // indirect
|
||||
github.com/hashicorp/go-version v1.6.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/hashicorp/go-version v1.7.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/pgx/v5 v5.4.3 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.7.5 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/klauspost/compress v1.16.7 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.17 // indirect
|
||||
github.com/microsoft/go-mssqldb v1.6.0 // indirect
|
||||
github.com/paulmach/orb v0.10.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.18 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.28 // indirect
|
||||
github.com/microsoft/go-mssqldb v1.8.1 // indirect
|
||||
github.com/paulmach/orb v0.11.1 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||
github.com/segmentio/asm v1.2.0 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
go.opentelemetry.io/otel v1.25.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.25.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.25.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.25.0 // indirect
|
||||
golang.org/x/crypto v0.14.0 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/otel v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.36.0 // indirect
|
||||
golang.org/x/crypto v0.38.0 // indirect
|
||||
golang.org/x/sync v0.14.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/text v0.25.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
|
||||
2888
gorm/go.sum
2888
gorm/go.sum
File diff suppressed because it is too large
Load Diff
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()
|
||||
}
|
||||
266
id/uuid_test.go
Normal file
266
id/uuid_test.go
Normal file
@@ -0,0 +1,266 @@
|
||||
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 应该非空")
|
||||
|
||||
// 测试生成的 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)
|
||||
}
|
||||
74
mapper/enum_converter.go
Normal file
74
mapper/enum_converter.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package mapper
|
||||
|
||||
import (
|
||||
"github.com/jinzhu/copier"
|
||||
)
|
||||
|
||||
type EnumTypeConverter[DTO ~string, MODEL ~int32] struct {
|
||||
nameMap map[int32]string
|
||||
valueMap map[string]int32
|
||||
}
|
||||
|
||||
func NewEnumTypeConverter[DTO ~string, MODEL ~int32](nameMap map[int32]string, valueMap map[string]int32) *EnumTypeConverter[DTO, MODEL] {
|
||||
return &EnumTypeConverter[DTO, MODEL]{
|
||||
valueMap: valueMap,
|
||||
nameMap: nameMap,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *EnumTypeConverter[DTO, MODEL]) ToModel(dto *DTO) *MODEL {
|
||||
if dto == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
find, ok := m.valueMap[string(*dto)]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
model := MODEL(find)
|
||||
return &model
|
||||
}
|
||||
|
||||
func (m *EnumTypeConverter[DTO, MODEL]) ToDto(model *MODEL) *DTO {
|
||||
if model == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
find, ok := m.nameMap[int32(*model)]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
dto := DTO(find)
|
||||
return &dto
|
||||
}
|
||||
|
||||
func (m *EnumTypeConverter[DTO, MODEL]) NewConverterPair() []copier.TypeConverter {
|
||||
srcType := MODEL(0)
|
||||
dstType := DTO("")
|
||||
|
||||
fromFn := m.ToDto
|
||||
toFn := m.ToModel
|
||||
|
||||
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 DTOs and models.
|
||||
type Mapper[DTO any, MODEL any] interface {
|
||||
// ToModel converts a DTO to a MODEL.
|
||||
ToModel(*DTO) *MODEL
|
||||
|
||||
// ToDto converts a MODEL to a DTO.
|
||||
ToDto(*MODEL) *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, MODEL any] struct {
|
||||
copierOption copier.Option
|
||||
}
|
||||
|
||||
func NewCopierMapper[DTO any, MODEL any]() *CopierMapper[DTO, MODEL] {
|
||||
return &CopierMapper[DTO, MODEL]{
|
||||
copierOption: copier.Option{
|
||||
Converters: []copier.TypeConverter{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (m *CopierMapper[DTO, MODEL]) AppendConverter(converter copier.TypeConverter) {
|
||||
m.copierOption.Converters = append(m.copierOption.Converters, converter)
|
||||
}
|
||||
|
||||
func (m *CopierMapper[DTO, MODEL]) AppendConverters(converters []copier.TypeConverter) {
|
||||
m.copierOption.Converters = append(m.copierOption.Converters, converters...)
|
||||
}
|
||||
|
||||
func (m *CopierMapper[DTO, MODEL]) ToModel(dto *DTO) *MODEL {
|
||||
if dto == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var model MODEL
|
||||
if err := copier.CopyWithOption(&model, dto, m.copierOption); err != nil {
|
||||
panic(err) // Handle error appropriately in production code
|
||||
}
|
||||
|
||||
return &model
|
||||
}
|
||||
|
||||
func (m *CopierMapper[DTO, MODEL]) ToDto(model *MODEL) *DTO {
|
||||
if model == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var dto DTO
|
||||
if err := copier.CopyWithOption(&dto, model, 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 ModelType struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
|
||||
mapper := NewCopierMapper[DtoType, ModelType]()
|
||||
|
||||
// 测试 ToModel 方法
|
||||
dto := &DtoType{Name: "Alice", Age: 25}
|
||||
model := mapper.ToModel(dto)
|
||||
assert.NotNil(t, model)
|
||||
assert.Equal(t, "Alice", model.Name)
|
||||
assert.Equal(t, 25, model.Age)
|
||||
|
||||
// 测试 ToModel 方法,传入 nil
|
||||
modelNil := mapper.ToModel(nil)
|
||||
assert.Nil(t, modelNil)
|
||||
|
||||
// 测试 ToDto 方法
|
||||
model = &ModelType{Name: "Bob", Age: 30}
|
||||
dtoResult := mapper.ToDto(model)
|
||||
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 string
|
||||
type ModelType int32
|
||||
|
||||
const (
|
||||
DtoTypeOne DtoType = "One"
|
||||
DtoTypeTwo DtoType = "Two"
|
||||
)
|
||||
|
||||
const (
|
||||
ModelTypeOne ModelType = 1
|
||||
ModelTypeTwo ModelType = 2
|
||||
)
|
||||
|
||||
nameMap := map[int32]string{
|
||||
1: "One",
|
||||
2: "Two",
|
||||
}
|
||||
valueMap := map[string]int32{
|
||||
"One": 1,
|
||||
"Two": 2,
|
||||
}
|
||||
|
||||
converter := NewEnumTypeConverter[DtoType, ModelType](nameMap, valueMap)
|
||||
|
||||
// 测试 ToModel 方法
|
||||
dto := DtoTypeOne
|
||||
model := converter.ToModel(&dto)
|
||||
assert.NotNil(t, model)
|
||||
assert.Equal(t, int32(1), int32(*model))
|
||||
|
||||
// 测试 ToModel 方法,传入不存在的值
|
||||
dtoInvalid := DtoType("Three")
|
||||
modelInvalid := converter.ToModel(&dtoInvalid)
|
||||
assert.Nil(t, modelInvalid)
|
||||
|
||||
// 测试 ToDto 方法
|
||||
tmpModelTwo := ModelTypeTwo
|
||||
model = &tmpModelTwo
|
||||
dtoResult := converter.ToDto(model)
|
||||
assert.NotNil(t, dtoResult)
|
||||
assert.Equal(t, "Two", string(*dtoResult))
|
||||
|
||||
// 测试 ToDto 方法,传入不存在的值
|
||||
tmpModelThree := ModelType(3)
|
||||
modelInvalid = &tmpModelThree
|
||||
dtoInvalidResult := converter.ToDto(modelInvalid)
|
||||
assert.Nil(t, dtoInvalidResult)
|
||||
}
|
||||
27
math/math.go
27
math/math.go
@@ -42,3 +42,30 @@ func StandardDeviation(num []float64) float64 {
|
||||
var variance = Variance(mean, num)
|
||||
return math.Sqrt(variance)
|
||||
}
|
||||
|
||||
// SumInt 计算整数数组的和
|
||||
func SumInt[T int | int32 | int64](array []T) int64 {
|
||||
var sum int64
|
||||
for _, v := range array {
|
||||
sum = sum + int64(v)
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
// SumUint 计算整数数组的和
|
||||
func SumUint[T uint | uint32 | uint64](array []T) uint64 {
|
||||
var sum uint64
|
||||
for _, v := range array {
|
||||
sum = sum + uint64(v)
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
// SumFloat 计算浮点数数组的和
|
||||
func SumFloat[T float32 | float64](array []T) float64 {
|
||||
var sum float64
|
||||
for _, v := range array {
|
||||
sum = sum + float64(v)
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
132
rand/rand.go
132
rand/rand.go
@@ -1,18 +1,52 @@
|
||||
package rand
|
||||
|
||||
import (
|
||||
"github.com/tx7do/go-utils/math"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
var RANDOM = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
var rnd = rand.New(rand.NewSource(Seed(UnixNanoSeed)))
|
||||
|
||||
func init() {
|
||||
if rnd != nil {
|
||||
rnd = rand.New(rand.NewSource(Seed(UnixNanoSeed)))
|
||||
}
|
||||
}
|
||||
|
||||
func Float32() float32 {
|
||||
return rnd.Float32()
|
||||
}
|
||||
|
||||
func Float64() float64 {
|
||||
return rnd.Float64()
|
||||
}
|
||||
|
||||
func Intn(n int) int {
|
||||
return rnd.Intn(n)
|
||||
}
|
||||
|
||||
func Int31n(n int32) int32 {
|
||||
return rnd.Int31n(n)
|
||||
}
|
||||
|
||||
func Int63n(n int64) int64 {
|
||||
return rnd.Int63n(n)
|
||||
}
|
||||
|
||||
// RandomInt 根据区间产生随机数
|
||||
func RandomInt(min, max int) int {
|
||||
if min >= max {
|
||||
return max
|
||||
}
|
||||
return RANDOM.Intn(max-min) + min
|
||||
return min + Intn(max-min+1)
|
||||
}
|
||||
|
||||
// RandomInt32 根据区间产生随机数
|
||||
func RandomInt32(min, max int32) int32 {
|
||||
if min >= max {
|
||||
return max
|
||||
}
|
||||
return min + Int31n(max-min+1)
|
||||
}
|
||||
|
||||
// RandomInt64 根据区间产生随机数
|
||||
@@ -20,5 +54,95 @@ func RandomInt64(min, max int64) int64 {
|
||||
if min >= max {
|
||||
return max
|
||||
}
|
||||
return RANDOM.Int63n(max-min) + min
|
||||
return min + Int63n(max-min+1)
|
||||
}
|
||||
|
||||
// RandomString 随机字符串,包含大小写字母和数字
|
||||
func RandomString(l int) string {
|
||||
if l <= 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
|
||||
bytes := make([]byte, l)
|
||||
for i := 0; i < l; i++ {
|
||||
bytes[i] = charset[Intn(len(charset))]
|
||||
}
|
||||
|
||||
return string(bytes)
|
||||
}
|
||||
|
||||
// RandomChoice 随机选择数组中的元素
|
||||
func RandomChoice[T any](array []T, n int) []T {
|
||||
if n <= 0 {
|
||||
return nil
|
||||
}
|
||||
if n == 1 {
|
||||
return []T{array[Intn(len(array))]}
|
||||
}
|
||||
|
||||
tmp := make([]T, len(array))
|
||||
copy(tmp, array)
|
||||
if len(tmp) <= n {
|
||||
return tmp
|
||||
}
|
||||
|
||||
Shuffle(tmp)
|
||||
|
||||
return tmp[:n]
|
||||
}
|
||||
|
||||
// Shuffle 随机打乱数组
|
||||
func Shuffle[T any](array []T) {
|
||||
if array == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for i := range array {
|
||||
j := Intn(i + 1)
|
||||
array[i], array[j] = array[j], array[i]
|
||||
}
|
||||
}
|
||||
|
||||
// WeightedChoice 根据权重随机,返回对应选项的索引,O(n)
|
||||
func WeightedChoice(weightArray []int) int {
|
||||
if weightArray == nil {
|
||||
return -1
|
||||
}
|
||||
|
||||
total := math.SumInt(weightArray)
|
||||
rv := Int63n(total)
|
||||
for i, v := range weightArray {
|
||||
if rv < int64(v) {
|
||||
return i
|
||||
}
|
||||
rv -= int64(v)
|
||||
}
|
||||
|
||||
return len(weightArray) - 1
|
||||
}
|
||||
|
||||
// NonWeightedChoice 根据权重随机,返回对应选项的索引,O(n). 权重大于等于0
|
||||
func NonWeightedChoice(weightArray []int) int {
|
||||
if weightArray == nil {
|
||||
return -1
|
||||
}
|
||||
|
||||
for i, weight := range weightArray {
|
||||
if weight < 0 {
|
||||
weightArray[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
total := math.SumInt(weightArray)
|
||||
rv := Int63n(total)
|
||||
for i, v := range weightArray {
|
||||
if rv < int64(v) {
|
||||
return i
|
||||
}
|
||||
rv -= int64(v)
|
||||
}
|
||||
|
||||
return len(weightArray) - 1
|
||||
}
|
||||
|
||||
@@ -7,11 +7,289 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRandomInt(t *testing.T) {
|
||||
func TestFloat32_GeneratesValueWithinRange(t *testing.T) {
|
||||
for i := 0; i < 1000; i++ {
|
||||
n := RandomInt(1, 100)
|
||||
fmt.Println(n)
|
||||
assert.True(t, n >= 1)
|
||||
assert.True(t, n < 100)
|
||||
value := Float32()
|
||||
assert.True(t, value >= 0.0)
|
||||
assert.True(t, value < 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFloat32_GeneratesDifferentValues(t *testing.T) {
|
||||
values := make(map[float32]bool)
|
||||
for i := 0; i < 1000; i++ {
|
||||
value := Float32()
|
||||
values[value] = true
|
||||
}
|
||||
assert.Greater(t, len(values), 1)
|
||||
}
|
||||
|
||||
func TestFloat64_GeneratesValueWithinRange(t *testing.T) {
|
||||
for i := 0; i < 1000; i++ {
|
||||
value := Float64()
|
||||
assert.True(t, value >= 0.0)
|
||||
assert.True(t, value < 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFloat64_GeneratesDifferentValues(t *testing.T) {
|
||||
values := make(map[float64]bool)
|
||||
for i := 0; i < 1000; i++ {
|
||||
value := Float64()
|
||||
values[value] = true
|
||||
}
|
||||
assert.Greater(t, len(values), 1)
|
||||
}
|
||||
|
||||
func TestRandomInt(t *testing.T) {
|
||||
for i := 0; i < 1000; i++ {
|
||||
n := RandomInt(1, 10)
|
||||
fmt.Println(n)
|
||||
assert.True(t, n >= 1)
|
||||
assert.True(t, n <= 100)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandomInt_MinEqualsMax(t *testing.T) {
|
||||
n := RandomInt(5, 5)
|
||||
assert.Equal(t, 5, n)
|
||||
}
|
||||
|
||||
func TestRandomInt_MinGreaterThanMax(t *testing.T) {
|
||||
n := RandomInt(10, 5)
|
||||
assert.Equal(t, 5, n)
|
||||
}
|
||||
|
||||
func TestRandomInt_NegativeRange(t *testing.T) {
|
||||
for i := 0; i < 1000; i++ {
|
||||
n := RandomInt(-10, -1)
|
||||
assert.True(t, n >= -10)
|
||||
assert.True(t, n <= -1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandomInt_ZeroRange(t *testing.T) {
|
||||
for i := 0; i < 1000; i++ {
|
||||
n := RandomInt(0, 0)
|
||||
assert.Equal(t, 0, n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShuffle_EmptyArray(t *testing.T) {
|
||||
var array []int
|
||||
Shuffle(array)
|
||||
assert.Equal(t, array, array)
|
||||
}
|
||||
|
||||
func TestShuffle_SingleElementArray(t *testing.T) {
|
||||
array := []int{1}
|
||||
Shuffle(array)
|
||||
assert.Equal(t, []int{1}, array)
|
||||
}
|
||||
|
||||
func TestShuffle_MultipleElementsArray(t *testing.T) {
|
||||
array := []int{1, 2, 3, 4, 5}
|
||||
original := make([]int, len(array))
|
||||
copy(original, array)
|
||||
Shuffle(array)
|
||||
assert.ElementsMatch(t, original, array)
|
||||
}
|
||||
|
||||
func TestShuffle_ArrayWithDuplicates(t *testing.T) {
|
||||
array := []int{1, 2, 2, 3, 3, 3}
|
||||
original := make([]int, len(array))
|
||||
copy(original, array)
|
||||
Shuffle(array)
|
||||
fmt.Println(array)
|
||||
assert.ElementsMatch(t, original, array)
|
||||
}
|
||||
|
||||
func TestRandomChoice_EmptyArray(t *testing.T) {
|
||||
result := RandomChoice([]int{}, 3)
|
||||
assert.Nil(t, result)
|
||||
}
|
||||
|
||||
func TestRandomChoice_NegativeN(t *testing.T) {
|
||||
array := []int{1, 2, 3}
|
||||
result := RandomChoice(array, -1)
|
||||
assert.Nil(t, result)
|
||||
}
|
||||
|
||||
func TestRandomChoice_ZeroN(t *testing.T) {
|
||||
array := []int{1, 2, 3}
|
||||
result := RandomChoice(array, 0)
|
||||
assert.Nil(t, result)
|
||||
}
|
||||
|
||||
func TestRandomChoice_NGreaterThanArrayLength(t *testing.T) {
|
||||
array := []int{1, 2, 3}
|
||||
result := RandomChoice(array, 5)
|
||||
assert.ElementsMatch(t, array, result)
|
||||
}
|
||||
|
||||
func TestRandomChoice_NEqualToArrayLength(t *testing.T) {
|
||||
array := []int{1, 2, 3}
|
||||
result := RandomChoice(array, 3)
|
||||
assert.ElementsMatch(t, array, result)
|
||||
}
|
||||
|
||||
func TestRandomChoice_NLessThanArrayLength(t *testing.T) {
|
||||
array := []int{1, 2, 3, 4, 5}
|
||||
result := RandomChoice(array, 3)
|
||||
assert.Len(t, result, 3)
|
||||
}
|
||||
|
||||
func TestRandomInt32_MinEqualsMax(t *testing.T) {
|
||||
result := RandomInt32(5, 5)
|
||||
assert.Equal(t, int32(5), result)
|
||||
}
|
||||
|
||||
func TestRandomInt32_MinGreaterThanMax(t *testing.T) {
|
||||
result := RandomInt32(10, 5)
|
||||
assert.Equal(t, int32(5), result)
|
||||
}
|
||||
|
||||
func TestRandomInt32_PositiveRange(t *testing.T) {
|
||||
for i := 0; i < 1000; i++ {
|
||||
result := RandomInt32(1, 10)
|
||||
assert.True(t, result >= 1)
|
||||
assert.True(t, result <= 10)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandomInt32_NegativeRange(t *testing.T) {
|
||||
for i := 0; i < 1000; i++ {
|
||||
result := RandomInt32(-10, -1)
|
||||
assert.True(t, result >= -10)
|
||||
assert.True(t, result <= -1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandomInt32_ZeroRange(t *testing.T) {
|
||||
for i := 0; i < 1000; i++ {
|
||||
result := RandomInt32(0, 0)
|
||||
assert.Equal(t, int32(0), result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandomInt64_MinEqualsMax(t *testing.T) {
|
||||
result := RandomInt64(5, 5)
|
||||
assert.Equal(t, int64(5), result)
|
||||
}
|
||||
|
||||
func TestRandomInt64_MinGreaterThanMax(t *testing.T) {
|
||||
result := RandomInt64(10, 5)
|
||||
assert.Equal(t, int64(5), result)
|
||||
}
|
||||
|
||||
func TestRandomInt64_PositiveRange(t *testing.T) {
|
||||
for i := 0; i < 1000; i++ {
|
||||
result := RandomInt64(1, 10)
|
||||
assert.True(t, result >= 1)
|
||||
assert.True(t, result <= 10)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandomInt64_NegativeRange(t *testing.T) {
|
||||
for i := 0; i < 1000; i++ {
|
||||
result := RandomInt64(-10, -1)
|
||||
assert.True(t, result >= -10)
|
||||
assert.True(t, result <= -1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandomInt64_ZeroRange(t *testing.T) {
|
||||
for i := 0; i < 1000; i++ {
|
||||
result := RandomInt64(0, 0)
|
||||
assert.Equal(t, int64(0), result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandomString_LengthZero(t *testing.T) {
|
||||
result := RandomString(0)
|
||||
assert.Equal(t, "", result)
|
||||
t.Logf("LengthZero: %s", result)
|
||||
}
|
||||
|
||||
func TestRandomString_PositiveLength(t *testing.T) {
|
||||
result := RandomString(10)
|
||||
assert.Len(t, result, 10)
|
||||
t.Logf("PositiveLength: %s", result)
|
||||
}
|
||||
|
||||
func TestRandomString_ContainsOnlyValidCharacters(t *testing.T) {
|
||||
result := RandomString(100)
|
||||
for _, char := range result {
|
||||
assert.True(t, (char >= 'A' && char <= 'Z') || (char >= 'a' && char <= 'z') || (char >= '0' && char <= '9'))
|
||||
}
|
||||
t.Logf("ContainsOnlyValidCharacters: %s", result)
|
||||
}
|
||||
|
||||
func TestRandomString_NegativeLength(t *testing.T) {
|
||||
result := RandomString(-5)
|
||||
assert.Equal(t, "", result)
|
||||
}
|
||||
|
||||
func TestWeightedChoice_EmptyArray(t *testing.T) {
|
||||
result := WeightedChoice([]int{})
|
||||
assert.Equal(t, -1, result)
|
||||
}
|
||||
|
||||
func TestWeightedChoice_AllZeroWeights(t *testing.T) {
|
||||
result := WeightedChoice([]int{0, 0, 0})
|
||||
assert.Equal(t, 2, result)
|
||||
}
|
||||
|
||||
func TestWeightedChoice_NegativeWeights(t *testing.T) {
|
||||
result := WeightedChoice([]int{-1, -2, -3})
|
||||
assert.Equal(t, 2, result)
|
||||
}
|
||||
|
||||
func TestWeightedChoice_MixedWeights(t *testing.T) {
|
||||
weightArray := []int{1, 0, 3, 0, 2}
|
||||
counts := make([]int, len(weightArray))
|
||||
for i := 0; i < 1000; i++ {
|
||||
choice := WeightedChoice(weightArray)
|
||||
counts[choice]++
|
||||
}
|
||||
assert.Greater(t, counts[0], 0)
|
||||
assert.Greater(t, counts[2], 0)
|
||||
assert.Greater(t, counts[4], 0)
|
||||
}
|
||||
|
||||
func TestWeightedChoice_SingleElement(t *testing.T) {
|
||||
result := WeightedChoice([]int{5})
|
||||
assert.Equal(t, 0, result)
|
||||
}
|
||||
|
||||
func TestNonWeightedChoice_EmptyArray(t *testing.T) {
|
||||
result := NonWeightedChoice([]int{})
|
||||
assert.Equal(t, -1, result)
|
||||
}
|
||||
|
||||
func TestNonWeightedChoice_AllZeroWeights(t *testing.T) {
|
||||
result := NonWeightedChoice([]int{0, 0, 0})
|
||||
assert.Equal(t, 2, result)
|
||||
}
|
||||
|
||||
func TestNonWeightedChoice_NegativeWeights(t *testing.T) {
|
||||
result := NonWeightedChoice([]int{-1, -2, -3})
|
||||
assert.Equal(t, 2, result)
|
||||
}
|
||||
|
||||
func TestNonWeightedChoice_MixedWeights(t *testing.T) {
|
||||
weightArray := []int{1, 0, 3, 0, 2}
|
||||
counts := make([]int, len(weightArray))
|
||||
for i := 0; i < 1000; i++ {
|
||||
choice := NonWeightedChoice(weightArray)
|
||||
counts[choice]++
|
||||
}
|
||||
assert.Greater(t, counts[0], 0)
|
||||
assert.Greater(t, counts[2], 0)
|
||||
assert.Greater(t, counts[4], 0)
|
||||
}
|
||||
|
||||
func TestNonWeightedChoice_SingleElement(t *testing.T) {
|
||||
result := NonWeightedChoice([]int{5})
|
||||
assert.Equal(t, 0, result)
|
||||
}
|
||||
|
||||
179
rand/randomizer.go
Normal file
179
rand/randomizer.go
Normal file
@@ -0,0 +1,179 @@
|
||||
package rand
|
||||
|
||||
import (
|
||||
"github.com/tx7do/go-utils/math"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
type Randomizer struct {
|
||||
rnd *rand.Rand
|
||||
}
|
||||
|
||||
func NewRandomizer(seedType SeedType) *Randomizer {
|
||||
return &Randomizer{
|
||||
rnd: rand.New(rand.NewSource(Seed(seedType))),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Randomizer) Float32() float32 {
|
||||
return r.rnd.Float32()
|
||||
}
|
||||
|
||||
func (r *Randomizer) Float64() float64 {
|
||||
return r.rnd.Float64()
|
||||
}
|
||||
|
||||
func (r *Randomizer) Int() int {
|
||||
return r.rnd.Int()
|
||||
}
|
||||
|
||||
func (r *Randomizer) Int31() int32 {
|
||||
return r.rnd.Int31()
|
||||
}
|
||||
|
||||
func (r *Randomizer) Int63() int64 {
|
||||
return r.rnd.Int63()
|
||||
}
|
||||
|
||||
func (r *Randomizer) Uint32() uint32 {
|
||||
return r.rnd.Uint32()
|
||||
}
|
||||
|
||||
func (r *Randomizer) Uint64() uint64 {
|
||||
return r.rnd.Uint64()
|
||||
}
|
||||
|
||||
func (r *Randomizer) Intn(n int) int {
|
||||
return r.rnd.Intn(n)
|
||||
}
|
||||
|
||||
func (r *Randomizer) Int31n(n int32) int32 {
|
||||
return r.rnd.Int31n(n)
|
||||
}
|
||||
|
||||
func (r *Randomizer) Int63n(n int64) int64 {
|
||||
return r.rnd.Int63n(n)
|
||||
}
|
||||
|
||||
// RangeInt 根据区间产生随机数
|
||||
func (r *Randomizer) RangeInt(min, max int) int {
|
||||
if min >= max {
|
||||
return max
|
||||
}
|
||||
return min + r.Intn(max-min+1)
|
||||
}
|
||||
|
||||
// RangeInt32 根据区间产生随机数
|
||||
func (r *Randomizer) RangeInt32(min, max int32) int32 {
|
||||
if min >= max {
|
||||
return max
|
||||
}
|
||||
return min + r.Int31n(max-min+1)
|
||||
}
|
||||
|
||||
// RangeInt64 根据区间产生随机数
|
||||
func (r *Randomizer) RangeInt64(min, max int64) int64 {
|
||||
if min >= max {
|
||||
return max
|
||||
}
|
||||
return min + r.Int63n(max-min+1)
|
||||
}
|
||||
|
||||
// RangeUint 根据区间产生随机数
|
||||
func (r *Randomizer) RangeUint(min, max uint) uint {
|
||||
if min >= max {
|
||||
return max
|
||||
}
|
||||
return min + uint(r.Intn(int(max-min+1)))
|
||||
}
|
||||
|
||||
// RangeUint32 根据区间产生随机数
|
||||
func (r *Randomizer) RangeUint32(min, max uint32) uint32 {
|
||||
if min >= max {
|
||||
return max
|
||||
}
|
||||
return min + uint32(r.Int31n(int32(max-min+1)))
|
||||
}
|
||||
|
||||
// RangeUint64 根据区间产生随机数
|
||||
func (r *Randomizer) RangeUint64(min, max uint64) uint64 {
|
||||
if min >= max {
|
||||
return max
|
||||
}
|
||||
return min + uint64(r.Int63n(int64(max-min+1)))
|
||||
}
|
||||
|
||||
// RangeFloat32 根据区间产生随机数
|
||||
func (r *Randomizer) RangeFloat32(min, max float32) float32 {
|
||||
if min >= max {
|
||||
return max
|
||||
}
|
||||
return min + r.Float32()*(max-min)
|
||||
}
|
||||
|
||||
// RangeFloat64 根据区间产生随机数
|
||||
func (r *Randomizer) RangeFloat64(min, max float64) float64 {
|
||||
if min >= max {
|
||||
return max
|
||||
}
|
||||
return min + r.Float64()*(max-min)
|
||||
}
|
||||
|
||||
// RandomString 随机字符串,包含大小写字母和数字
|
||||
func (r *Randomizer) RandomString(l int) string {
|
||||
bytes := make([]byte, l)
|
||||
for i := 0; i < l; i++ {
|
||||
x := r.Intn(3)
|
||||
switch x {
|
||||
case 0:
|
||||
bytes[i] = byte(r.RangeInt(65, 90)) //大写字母
|
||||
case 1:
|
||||
bytes[i] = byte(r.RangeInt(97, 122))
|
||||
case 2:
|
||||
bytes[i] = byte(r.Intn(10))
|
||||
}
|
||||
}
|
||||
return string(bytes)
|
||||
}
|
||||
|
||||
// WeightedChoice 根据权重随机,返回对应选项的索引,O(n)
|
||||
func (r *Randomizer) WeightedChoice(weightArray []int) int {
|
||||
if weightArray == nil {
|
||||
return -1
|
||||
}
|
||||
|
||||
total := math.SumInt(weightArray)
|
||||
rv := r.Int63n(total)
|
||||
for i, v := range weightArray {
|
||||
if rv < int64(v) {
|
||||
return i
|
||||
}
|
||||
rv -= int64(v)
|
||||
}
|
||||
|
||||
return len(weightArray) - 1
|
||||
}
|
||||
|
||||
// NonWeightedChoice 根据权重随机,返回对应选项的索引,O(n). 权重大于等于0
|
||||
func (r *Randomizer) NonWeightedChoice(weightArray []int) int {
|
||||
if weightArray == nil {
|
||||
return -1
|
||||
}
|
||||
|
||||
for i, weight := range weightArray {
|
||||
if weight < 0 {
|
||||
weightArray[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
total := math.SumInt(weightArray)
|
||||
rv := r.Int63n(total)
|
||||
for i, v := range weightArray {
|
||||
if rv < int64(v) {
|
||||
return i
|
||||
}
|
||||
rv -= int64(v)
|
||||
}
|
||||
|
||||
return len(weightArray) - 1
|
||||
}
|
||||
86
rand/randomizer_test.go
Normal file
86
rand/randomizer_test.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package rand
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRandomizer_RangeUint32_MinEqualsMax(t *testing.T) {
|
||||
r := NewRandomizer(UnixNanoSeed)
|
||||
result := r.RangeUint32(5, 5)
|
||||
assert.Equal(t, uint32(5), result)
|
||||
}
|
||||
|
||||
func TestRandomizer_RangeUint32_MinGreaterThanMax(t *testing.T) {
|
||||
r := NewRandomizer(UnixNanoSeed)
|
||||
result := r.RangeUint32(10, 5)
|
||||
assert.Equal(t, uint32(5), result)
|
||||
}
|
||||
|
||||
func TestRandomizer_RangeUint32_PositiveRange(t *testing.T) {
|
||||
r := NewRandomizer(UnixNanoSeed)
|
||||
for i := 0; i < 1000; i++ {
|
||||
result := r.RangeUint32(1, 10)
|
||||
assert.True(t, result >= 1)
|
||||
assert.True(t, result <= 10)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandomizer_RangeUint64_MinEqualsMax(t *testing.T) {
|
||||
r := NewRandomizer(UnixNanoSeed)
|
||||
result := r.RangeUint64(5, 5)
|
||||
assert.Equal(t, uint64(5), result)
|
||||
}
|
||||
|
||||
func TestRandomizer_RangeUint64_MinGreaterThanMax(t *testing.T) {
|
||||
r := NewRandomizer(UnixNanoSeed)
|
||||
result := r.RangeUint64(10, 5)
|
||||
assert.Equal(t, uint64(5), result)
|
||||
}
|
||||
|
||||
func TestRandomizer_RangeUint64_PositiveRange(t *testing.T) {
|
||||
r := NewRandomizer(UnixNanoSeed)
|
||||
for i := 0; i < 1000; i++ {
|
||||
result := r.RangeUint64(1, 10)
|
||||
assert.True(t, result >= 1)
|
||||
assert.True(t, result <= 10)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandomizer_WeightedChoice_EmptyArray(t *testing.T) {
|
||||
r := NewRandomizer(UnixNanoSeed)
|
||||
result := r.WeightedChoice([]int{})
|
||||
assert.Equal(t, -1, result)
|
||||
}
|
||||
|
||||
func TestRandomizer_WeightedChoice_AllZeroWeights(t *testing.T) {
|
||||
r := NewRandomizer(UnixNanoSeed)
|
||||
result := r.WeightedChoice([]int{0, 0, 0})
|
||||
assert.Equal(t, 2, result)
|
||||
}
|
||||
|
||||
func TestRandomizer_WeightedChoice_MixedWeights(t *testing.T) {
|
||||
r := NewRandomizer(UnixNanoSeed)
|
||||
weightArray := []int{1, 0, 3, 0, 2}
|
||||
counts := make([]int, len(weightArray))
|
||||
for i := 0; i < 1000; i++ {
|
||||
choice := r.WeightedChoice(weightArray)
|
||||
counts[choice]++
|
||||
}
|
||||
assert.Greater(t, counts[0], 0)
|
||||
assert.Greater(t, counts[2], 0)
|
||||
assert.Greater(t, counts[4], 0)
|
||||
}
|
||||
|
||||
func TestRandomizer_RandomString_LengthZero(t *testing.T) {
|
||||
r := NewRandomizer(UnixNanoSeed)
|
||||
result := r.RandomString(0)
|
||||
assert.Equal(t, "", result)
|
||||
}
|
||||
|
||||
func TestRandomizer_RandomString_CorrectLength(t *testing.T) {
|
||||
r := NewRandomizer(UnixNanoSeed)
|
||||
length := 50
|
||||
result := r.RandomString(length)
|
||||
assert.Equal(t, length, len(result))
|
||||
}
|
||||
99
rand/seeder.go
Normal file
99
rand/seeder.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package rand
|
||||
|
||||
import (
|
||||
cryptoRand "crypto/rand"
|
||||
"encoding/binary"
|
||||
"golang.org/x/exp/rand"
|
||||
"hash/maphash"
|
||||
mathRand "math/rand"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SeedType string
|
||||
|
||||
const (
|
||||
UnixNanoSeed SeedType = "UnixNano"
|
||||
MapHashSeed SeedType = "MapHash"
|
||||
CryptoRandSeed SeedType = "CryptoRand"
|
||||
RandomStringSeed SeedType = "RandomString"
|
||||
)
|
||||
|
||||
type Seeder struct {
|
||||
seedType SeedType
|
||||
}
|
||||
|
||||
func NewSeeder(seedType SeedType) *Seeder {
|
||||
return &Seeder{
|
||||
seedType: seedType,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Seeder) UnixNano() int64 {
|
||||
// 获取当前时间戳
|
||||
timestamp := time.Now().UnixNano()
|
||||
|
||||
// 生成一个随机数
|
||||
var randomBytes [8]byte
|
||||
_, err := rand.Read(randomBytes[:])
|
||||
if err != nil {
|
||||
panic("failed to generate random bytes")
|
||||
}
|
||||
randomPart := int64(binary.LittleEndian.Uint64(randomBytes[:]))
|
||||
|
||||
// 获取 Goroutine ID(或其他唯一标识)
|
||||
goroutineID := int64(runtime.NumGoroutine())
|
||||
|
||||
// 结合时间戳、随机数和 Goroutine ID
|
||||
seed := timestamp ^ randomPart ^ goroutineID
|
||||
|
||||
return seed
|
||||
}
|
||||
|
||||
func (r *Seeder) MapHash() int64 {
|
||||
return int64(new(maphash.Hash).Sum64())
|
||||
}
|
||||
|
||||
func (r *Seeder) CryptoRand() int64 {
|
||||
var b [8]byte
|
||||
_, err := cryptoRand.Read(b[:])
|
||||
if err != nil {
|
||||
panic("cannot seed math/rand package with cryptographically secure random number generator")
|
||||
}
|
||||
seed := int64(binary.LittleEndian.Uint64(b[:]))
|
||||
return seed
|
||||
}
|
||||
|
||||
var Alpha = "abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789"
|
||||
|
||||
func (r *Seeder) RandomString() int64 {
|
||||
const size = 8
|
||||
buf := make([]byte, size)
|
||||
for i := 0; i < size; i++ {
|
||||
buf[i] = Alpha[mathRand.Intn(len(Alpha))]
|
||||
}
|
||||
seed := int64(binary.LittleEndian.Uint64(buf[:]))
|
||||
|
||||
return seed
|
||||
}
|
||||
|
||||
func (r *Seeder) Seed() int64 {
|
||||
switch r.seedType {
|
||||
default:
|
||||
fallthrough
|
||||
case UnixNanoSeed:
|
||||
return r.UnixNano()
|
||||
case MapHashSeed:
|
||||
return r.MapHash()
|
||||
case CryptoRandSeed:
|
||||
return r.CryptoRand()
|
||||
case RandomStringSeed:
|
||||
return r.RandomString()
|
||||
}
|
||||
}
|
||||
|
||||
// Seed generates a seed based on the specified SeedType.
|
||||
func Seed(seedType SeedType) int64 {
|
||||
randomizer := NewSeeder(seedType)
|
||||
return randomizer.Seed()
|
||||
}
|
||||
80
rand/seeder_test.go
Normal file
80
rand/seeder_test.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package rand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUnixNanoSeed(t *testing.T) {
|
||||
seeder := NewSeeder(UnixNanoSeed)
|
||||
|
||||
var seeds = make(map[int64]bool)
|
||||
for i := 0; i < 100000; i++ {
|
||||
seed := seeder.Seed()
|
||||
seeds[seed] = true
|
||||
}
|
||||
fmt.Println("UnixNano Seed", len(seeds))
|
||||
}
|
||||
|
||||
func TestMapHashSeed(t *testing.T) {
|
||||
seeder := NewSeeder(MapHashSeed)
|
||||
|
||||
var seeds = make(map[int64]bool)
|
||||
for i := 0; i < 100000; i++ {
|
||||
seed := seeder.Seed()
|
||||
seeds[seed] = true
|
||||
}
|
||||
fmt.Println("MapHash Seed", len(seeds))
|
||||
}
|
||||
|
||||
func TestCryptoRandSeed(t *testing.T) {
|
||||
seeder := NewSeeder(CryptoRandSeed)
|
||||
|
||||
var seeds = make(map[int64]bool)
|
||||
for i := 0; i < 100000; i++ {
|
||||
seed := seeder.Seed()
|
||||
seeds[seed] = true
|
||||
}
|
||||
fmt.Println("CryptoRand Seed", len(seeds))
|
||||
}
|
||||
|
||||
func TestRandomStringSeed(t *testing.T) {
|
||||
seeder := NewSeeder(RandomStringSeed)
|
||||
|
||||
var seeds = make(map[int64]bool)
|
||||
for i := 0; i < 100000; i++ {
|
||||
seed := seeder.Seed()
|
||||
seeds[seed] = true
|
||||
}
|
||||
fmt.Println("RandomString Seed", len(seeds))
|
||||
}
|
||||
|
||||
func TestSeed(t *testing.T) {
|
||||
var seeds = make(map[int64]bool)
|
||||
for i := 0; i < 100000; i++ {
|
||||
seed := Seed(UnixNanoSeed)
|
||||
seeds[seed] = true
|
||||
}
|
||||
fmt.Println("UnixNano Seed", len(seeds))
|
||||
|
||||
seeds = make(map[int64]bool)
|
||||
for i := 0; i < 100000; i++ {
|
||||
seed := Seed(MapHashSeed)
|
||||
seeds[seed] = true
|
||||
}
|
||||
fmt.Println("MapHash Seed", len(seeds))
|
||||
|
||||
seeds = make(map[int64]bool)
|
||||
for i := 0; i < 100000; i++ {
|
||||
seed := Seed(CryptoRandSeed)
|
||||
seeds[seed] = true
|
||||
}
|
||||
fmt.Println("CryptoRand Seed", len(seeds))
|
||||
|
||||
seeds = make(map[int64]bool)
|
||||
for i := 0; i < 100000; i++ {
|
||||
seed := Seed(RandomStringSeed)
|
||||
seeds[seed] = true
|
||||
}
|
||||
fmt.Println("RandomString Seed", len(seeds))
|
||||
}
|
||||
22
slug/go.mod
Normal file
22
slug/go.mod
Normal file
@@ -0,0 +1,22 @@
|
||||
module github.com/tx7do/go-utils/slug
|
||||
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.23.2
|
||||
|
||||
require (
|
||||
github.com/gosimple/slug v1.15.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
)
|
||||
|
||||
require (
|
||||
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/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.13.1 // 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 => ../
|
||||
27
slug/go.sum
Normal file
27
slug/go.sum
Normal file
@@ -0,0 +1,27 @@
|
||||
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/gosimple/slug v1.15.0 h1:wRZHsRrRcs6b0XnxMUBM6WK1U1Vg5B0R7VkIf1Xzobo=
|
||||
github.com/gosimple/slug v1.15.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.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.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
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=
|
||||
@@ -1,50 +0,0 @@
|
||||
package sonyflake
|
||||
|
||||
import (
|
||||
"github.com/sony/sonyflake"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
sf *sonyflake.Sonyflake
|
||||
)
|
||||
|
||||
func startTime() time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
func getMachineID() (uint16, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func checkMachineID(uint16) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// InitSonyflake 初始化Sonyflake节点单体
|
||||
func InitSonyflake() {
|
||||
settings := sonyflake.Settings{
|
||||
/*StartTime: startTime(),
|
||||
MachineID: getMachineID,
|
||||
CheckMachineID: checkMachineID,*/
|
||||
}
|
||||
sf = sonyflake.NewSonyflake(settings)
|
||||
if sf == nil {
|
||||
panic("sonyflake not created")
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateSonyflake 生成 Sonyflake ID
|
||||
func GenerateSonyflake() uint64 {
|
||||
if sf == nil {
|
||||
InitSonyflake()
|
||||
}
|
||||
if sf == nil {
|
||||
return 0
|
||||
}
|
||||
id, err := sf.NextID()
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return id
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package sonyflake
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
InitSonyflake()
|
||||
}
|
||||
|
||||
func TestGenerateSonyflake(t *testing.T) {
|
||||
for i := 0; i < 100; i++ {
|
||||
id := GenerateSonyflake()
|
||||
fmt.Println(id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateTime(t *testing.T) {
|
||||
// 秒
|
||||
fmt.Printf("时间戳(秒):%v;\n", time.Now().Unix())
|
||||
fmt.Printf("时间戳(纳秒转换为秒):%v;\n", time.Now().UnixNano()/1e9)
|
||||
|
||||
// 毫秒
|
||||
fmt.Printf("时间戳(毫秒):%v;\n", time.Now().UnixMilli())
|
||||
fmt.Printf("时间戳(纳秒转换为毫秒):%v;\n", time.Now().UnixNano()/1e6)
|
||||
|
||||
// 微秒
|
||||
fmt.Printf("时间戳(微秒):%v;\n", time.Now().UnixMicro())
|
||||
fmt.Printf("时间戳(纳秒转换为微秒):%v;\n", time.Now().UnixNano()/1e3)
|
||||
|
||||
// 纳秒
|
||||
fmt.Printf("时间戳(纳秒):%v;\n", time.Now().UnixNano())
|
||||
}
|
||||
20
stringutil/replace.go
Normal file
20
stringutil/replace.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package stringutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// ReplaceJSONField 使用正则表达式替换 JSON 字符串中指定字段的值
|
||||
// fieldNames: 要替换的多个字段名,使用竖线(|)分隔(例如:"tenantId|tenant_id")
|
||||
// newValue: 新的值(字符串形式,将被包装在引号中)
|
||||
// jsonStr: 原始 JSON 字符串
|
||||
func ReplaceJSONField(fieldNames, newValue, jsonStr string) string {
|
||||
// 构建正则表达式模式
|
||||
// 匹配模式: ("field1"|"field2"|...): "任意值"
|
||||
pattern := fmt.Sprintf(`(?i)("(%s)")\s*:\s*"([^"]*)"`, fieldNames)
|
||||
re := regexp.MustCompile(pattern)
|
||||
|
||||
// 替换匹配到的内容
|
||||
return re.ReplaceAllString(jsonStr, fmt.Sprintf(`${1}: "%s"`, newValue))
|
||||
}
|
||||
38
stringutil/replace_test.go
Normal file
38
stringutil/replace_test.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package stringutil
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReplaceJSONField(t *testing.T) {
|
||||
// 测试替换单个字段
|
||||
jsonStr := `{"tenantId": "123", "name": "test"}`
|
||||
result := ReplaceJSONField("tenantId", "456", jsonStr)
|
||||
expected := `{"tenantId": "456", "name": "test"}`
|
||||
assert.Equal(t, expected, result)
|
||||
|
||||
// 测试替换多个字段
|
||||
jsonStr = `{"tenantId": "123", "tenant_id": "789", "name": "test"}`
|
||||
result = ReplaceJSONField("tenantId|tenant_id", "456", jsonStr)
|
||||
expected = `{"tenantId": "456", "tenant_id": "456", "name": "test"}`
|
||||
assert.Equal(t, expected, result)
|
||||
|
||||
// 测试字段不存在
|
||||
jsonStr = `{"name": "test"}`
|
||||
result = ReplaceJSONField("tenantId", "456", jsonStr)
|
||||
expected = `{"name": "test"}`
|
||||
assert.Equal(t, expected, result)
|
||||
|
||||
// 测试空 JSON 字符串
|
||||
jsonStr = ``
|
||||
result = ReplaceJSONField("tenantId", "456", jsonStr)
|
||||
expected = ``
|
||||
assert.Equal(t, expected, result)
|
||||
|
||||
// 测试空字段名
|
||||
jsonStr = `{"tenantId": "123"}`
|
||||
result = ReplaceJSONField("", "456", jsonStr)
|
||||
expected = `{"tenantId": "123"}`
|
||||
assert.Equal(t, expected, result)
|
||||
}
|
||||
17
tag.bat
17
tag.bat
@@ -1,9 +1,16 @@
|
||||
git tag v1.1.11
|
||||
git tag v1.1.28
|
||||
|
||||
git tag bank_card/v1.1.1
|
||||
git tag geoip/v1.1.1
|
||||
git tag bank_card/v1.1.5
|
||||
git tag geoip/v1.1.5
|
||||
git tag translator/v1.1.2
|
||||
git tag copierutil/v0.0.5
|
||||
git tag jwtutil/v0.0.2
|
||||
git tag id/v0.0.2
|
||||
git tag slug/v0.0.1
|
||||
git tag name_generator/v0.0.1
|
||||
git tag mapper/v0.0.1
|
||||
|
||||
git tag entgo/v1.1.14
|
||||
git tag gorm/v1.1.1
|
||||
git tag entgo/v1.1.31
|
||||
git tag gorm/v1.1.6
|
||||
|
||||
git push origin --tags
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package util
|
||||
package timeutil
|
||||
|
||||
import "time"
|
||||
|
||||
@@ -10,4 +10,52 @@ const (
|
||||
DefaultTimeLocationName = "Asia/Shanghai"
|
||||
)
|
||||
|
||||
// More predefined layouts for use in Time.Format and time.Parse.
|
||||
const (
|
||||
DT14 = "20060102150405"
|
||||
DT8 = "20060102"
|
||||
DT8MDY = "01022006"
|
||||
DT6 = "200601"
|
||||
MonthDay = "1/2"
|
||||
DIN5008FullDate = "02.01.2006" // German DIN 5008 standard
|
||||
DIN5008Date = "02.01.06"
|
||||
RFC3339FullDate = time.DateOnly
|
||||
RFC3339Milli = "2006-01-02T15:04:05.999Z07:00"
|
||||
RFC3339Dash = "2006-01-02T15-04-05Z07-00"
|
||||
ISO8601 = "2006-01-02T15:04:05Z0700"
|
||||
ISO8601TZHour = "2006-01-02T15:04:05Z07"
|
||||
ISO8601NoTZ = "2006-01-02T15:04:05"
|
||||
ISO8601MilliNoTZ = "2006-01-02T15:04:05.999"
|
||||
ISO8601Milli = "2006-01-02T15:04:05.999Z0700"
|
||||
ISO8601CompactZ = "20060102T150405Z0700"
|
||||
ISO8601CompactNoTZ = "20060102T150405"
|
||||
ISO8601YM = "2006-01"
|
||||
ISO9075 = time.DateTime // ISO/IEC 9075 used by MySQL, BigQuery, etc.
|
||||
ISO9075MicroTZ = "2006-01-02 15:04:05.999999-07" // ISO/IEC 9075 used by PostgreSQL
|
||||
RFC5322 = "Mon, 2 Jan 2006 15:04:05 -0700" // RFC5322 = "Mon Jan 02 15:04:05 -0700 2006"
|
||||
SQLTimestamp = ISO9075
|
||||
SQLTimestampMinutes = "2006-01-02 15:04"
|
||||
Ruby = "2006-01-02 15:04:05 -0700" // Ruby Time.now.to_s
|
||||
InsightlyAPIQuery = "_1/_2/2006 _3:04:05 PM"
|
||||
DateMDY = "1/2/2006" // an underscore results in a space.
|
||||
DateMDYSlash = "01/02/2006"
|
||||
DateDMYDash = "_2-01-2006" // Jira XML Date format
|
||||
DateDMYHM2 = "02:01:06 15:04" // GMT time in format dd:mm:yy hh:mm
|
||||
DateYMD = RFC3339FullDate
|
||||
DateTextUS = "January 2, 2006"
|
||||
DateTextUSAbbr3 = "Jan 2, 2006"
|
||||
DateTextEU = "2 January 2006"
|
||||
DateTextEUAbbr3 = "2 Jan 2006"
|
||||
MonthAbbrYear = "Jan 2006"
|
||||
MonthYear = "January 2006"
|
||||
)
|
||||
|
||||
const (
|
||||
RFC3339Min = "0000-01-01T00:00:00Z"
|
||||
RFC3339Max = "9999-12-31T23:59:59Z"
|
||||
RFC3339Zero = "0001-01-01T00:00:00Z" // Golang zero value
|
||||
RFC3339ZeroUnix = "1970-01-01T00:00:00Z"
|
||||
RFC3339YMDZeroUnix = int64(-62135596800)
|
||||
)
|
||||
|
||||
var ReferenceTimeValue time.Time = time.Date(2006, 1, 2, 15, 4, 5, 999999999, time.FixedZone("MST", -7*60*60))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package util
|
||||
package timeutil
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package util
|
||||
package timeutil
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package util
|
||||
package timeutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -43,3 +45,291 @@ func DurationHMS(d time.Duration) (int, int, int) {
|
||||
|
||||
return int(h), int(m), int(s)
|
||||
}
|
||||
|
||||
// Reformat a time string from one format to another
|
||||
// Deprecated...
|
||||
func FromTo(value, fromLayout, toLayout string) (string, error) {
|
||||
t, err := time.Parse(fromLayout, strings.TrimSpace(value))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return t.Format(toLayout), nil
|
||||
}
|
||||
|
||||
// Reformat a time string from one format to another
|
||||
func FromTo2(fromLayout, toLayout, value string) (string, error) {
|
||||
t, err := time.Parse(fromLayout, strings.TrimSpace(value))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return t.Format(toLayout), nil
|
||||
}
|
||||
|
||||
func FromToFirstValueOrEmpty(fromLayout, toLayout string, values []string) string {
|
||||
dtString, err := FromToFirstValue(fromLayout, toLayout, values)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return dtString
|
||||
}
|
||||
|
||||
func FromToFirstValue(fromLayout, toLayout string, values []string) (string, error) {
|
||||
for _, val := range values {
|
||||
dt, err := time.Parse(fromLayout, val)
|
||||
if err == nil {
|
||||
return dt.Format(toLayout), nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("no match")
|
||||
}
|
||||
|
||||
func ParseFirstValueOrZero(layout string, values []string) time.Time {
|
||||
dt, err := ParseFirstValue(layout, values)
|
||||
if err != nil {
|
||||
return TimeZeroRFC3339()
|
||||
}
|
||||
return dt
|
||||
}
|
||||
|
||||
func ParseFirstValue(layout string, values []string) (time.Time, error) {
|
||||
for _, val := range values {
|
||||
try, err := time.Parse(layout, val)
|
||||
if err == nil {
|
||||
return try, nil
|
||||
}
|
||||
}
|
||||
numVals := len(values)
|
||||
if numVals == 0 {
|
||||
return time.Now(), errors.New("no time values supplied")
|
||||
}
|
||||
return time.Now(), fmt.Errorf("no valid string of [%v] supplied values", strconv.Itoa(numVals))
|
||||
}
|
||||
|
||||
// ParseOrZero returns a parsed time.Time or the RFC-3339 zero time.
|
||||
func ParseOrZero(layout, value string) time.Time {
|
||||
t, err := time.Parse(layout, value)
|
||||
if err != nil {
|
||||
return TimeZeroRFC3339()
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// ParseFirst attempts to parse a string with a set of layouts.
|
||||
func ParseFirst(layouts []string, value string) (time.Time, error) {
|
||||
value = strings.TrimSpace(value)
|
||||
if len(value) == 0 || len(layouts) == 0 {
|
||||
return time.Now(), fmt.Errorf(
|
||||
"requires value [%v] and at least one layout [%v]", value, strings.Join(layouts, ","))
|
||||
}
|
||||
for _, layout := range layouts {
|
||||
layout = strings.TrimSpace(layout)
|
||||
if len(layout) == 0 {
|
||||
continue
|
||||
}
|
||||
if dt, err := time.Parse(layout, value); err == nil {
|
||||
return dt, nil
|
||||
}
|
||||
}
|
||||
return time.Now(), fmt.Errorf("cannot parse time [%v] with layouts [%v]",
|
||||
value, strings.Join(layouts, ","))
|
||||
}
|
||||
|
||||
var FormatMap = map[string]string{
|
||||
"RFC3339": time.RFC3339,
|
||||
"RFC3339YMD": RFC3339FullDate,
|
||||
"ISO8601YM": ISO8601YM,
|
||||
}
|
||||
|
||||
func GetFormat(formatName string) (string, error) {
|
||||
format, ok := FormatMap[strings.TrimSpace(formatName)]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("format Not Found: %v", format)
|
||||
}
|
||||
return format, nil
|
||||
}
|
||||
|
||||
func TimeMinRFC3339() time.Time {
|
||||
t0, _ := time.Parse(time.RFC3339, RFC3339Min)
|
||||
return t0
|
||||
}
|
||||
|
||||
func TimeZeroRFC3339() time.Time {
|
||||
t0, _ := time.Parse(time.RFC3339, RFC3339Zero)
|
||||
return t0
|
||||
}
|
||||
|
||||
func TimeZeroUnix() time.Time {
|
||||
t0, _ := time.Parse(time.RFC3339, RFC3339ZeroUnix)
|
||||
return t0
|
||||
}
|
||||
|
||||
func isZeroAny(u time.Time) bool {
|
||||
return u.Equal(TimeZeroRFC3339()) ||
|
||||
u.Equal(TimeMinRFC3339()) ||
|
||||
u.Equal(TimeZeroUnix())
|
||||
}
|
||||
|
||||
type RFC3339YMDTime struct{ time.Time }
|
||||
|
||||
type ISO8601NoTzMilliTime struct{ time.Time }
|
||||
|
||||
func (t *RFC3339YMDTime) UnmarshalJSON(buf []byte) error {
|
||||
tt, isNil, err := timeUnmarshalJSON(buf, RFC3339FullDate)
|
||||
if err != nil || isNil {
|
||||
return err
|
||||
}
|
||||
t.Time = tt
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t RFC3339YMDTime) MarshalJSON() ([]byte, error) {
|
||||
return timeMarshalJSON(t.Time, RFC3339FullDate)
|
||||
}
|
||||
|
||||
func (t *ISO8601NoTzMilliTime) UnmarshalJSON(buf []byte) error {
|
||||
tt, isNil, err := timeUnmarshalJSON(buf, ISO8601MilliNoTZ)
|
||||
if err != nil || isNil {
|
||||
return err
|
||||
}
|
||||
t.Time = tt
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t ISO8601NoTzMilliTime) MarshalJSON() ([]byte, error) {
|
||||
return timeMarshalJSON(t.Time, ISO8601MilliNoTZ)
|
||||
}
|
||||
|
||||
func timeUnmarshalJSON(buf []byte, layout string) (time.Time, bool, error) {
|
||||
str := string(buf)
|
||||
isNil := true
|
||||
if str == "null" || str == "\"\"" {
|
||||
return time.Time{}, isNil, nil
|
||||
}
|
||||
tt, err := time.Parse(layout, strings.Trim(str, `"`))
|
||||
if err != nil {
|
||||
return time.Time{}, false, err
|
||||
}
|
||||
return tt, false, nil
|
||||
}
|
||||
|
||||
func timeMarshalJSON(t time.Time, layout string) ([]byte, error) {
|
||||
return []byte(`"` + t.Format(layout) + `"`), nil
|
||||
}
|
||||
|
||||
func ParseSlice(layout string, strings []string) ([]time.Time, error) {
|
||||
var times []time.Time
|
||||
for _, raw := range strings {
|
||||
t, err := time.Parse(layout, raw)
|
||||
if err != nil {
|
||||
return times, err
|
||||
}
|
||||
times = append(times, t)
|
||||
}
|
||||
return times, nil
|
||||
}
|
||||
|
||||
// FormatTimeMulti formats a `time.Time` object or
|
||||
// an epoch number. It is adapted from `github.com/wcharczuk/go-chart`.
|
||||
func FormatTimeMulti(dateFormat string, v any) string {
|
||||
if typed, isTyped := v.(time.Time); isTyped {
|
||||
return typed.Format(dateFormat)
|
||||
}
|
||||
if typed, isTyped := v.(int64); isTyped {
|
||||
return time.Unix(0, typed).Format(dateFormat)
|
||||
}
|
||||
if typed, isTyped := v.(float64); isTyped {
|
||||
return time.Unix(0, int64(typed)).Format(dateFormat)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func FormatTimeToString(format string) func(time.Time) string {
|
||||
return func(dt time.Time) string {
|
||||
return dt.Format(format)
|
||||
}
|
||||
}
|
||||
|
||||
// OffsetFormat converts an integer offset value to a string
|
||||
// value to use in string time formats. Note: RFC-3339 times
|
||||
// use colons and the UTC "Z" offset.
|
||||
func OffsetFormat(offset int, useColon, useZ bool) string {
|
||||
offsetStr := "+0000"
|
||||
if offset == 0 {
|
||||
if useZ {
|
||||
offsetStr = "Z"
|
||||
} else if useColon {
|
||||
offsetStr = "+00:00"
|
||||
}
|
||||
} else if offset > 0 {
|
||||
if useColon {
|
||||
hr := offset / 100
|
||||
mn := offset - (hr * 100)
|
||||
offsetStr = "+" + fmt.Sprintf("%02d:%02d", hr, mn)
|
||||
} else {
|
||||
offsetStr = "+" + fmt.Sprintf("%04d", offset)
|
||||
}
|
||||
} else if offset < 0 {
|
||||
if useColon {
|
||||
offsetPositive := -1 * offset
|
||||
hr := offsetPositive / 100
|
||||
mn := offsetPositive - (hr * 100)
|
||||
offsetStr = "-" + fmt.Sprintf("%02d:%02d", hr, mn)
|
||||
} else {
|
||||
offsetStr = "-" + fmt.Sprintf("%04d", -1*offset)
|
||||
}
|
||||
}
|
||||
return offsetStr
|
||||
}
|
||||
|
||||
// var rxSQLTimestamp = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}$`)
|
||||
|
||||
func ParseTimeUsingOffset(format, raw, sep string, offset int, useColon, useZ bool) (time.Time, error) {
|
||||
return time.Parse(format, raw+sep+OffsetFormat(offset, useColon, useZ))
|
||||
}
|
||||
|
||||
// ParseTimeSQLTimestampUsingOffset converts a SQL timestamp without timezone
|
||||
// adding in a manual integer timezone.
|
||||
func ParseTimeSQLTimestampUsingOffset(timeStr string, offset int) (time.Time, error) {
|
||||
return ParseTimeUsingOffset(Ruby, timeStr, " ", offset, false, false)
|
||||
/*
|
||||
timeStr = strings.TrimSpace(timeStr)
|
||||
if !rxSQLTimestamp.MatchString(timeStr) {
|
||||
return time.Now(), fmt.Errorf("E_INVALID_SQL_TIMESTAMP [%v]", timeStr)
|
||||
}
|
||||
offsetStr := OffsetFormat(offset, useColon, useZ)
|
||||
timeStr += " " + offsetStr
|
||||
dt, err := time.Parse(Ruby, timeStr)
|
||||
return dt, err
|
||||
*/
|
||||
}
|
||||
|
||||
const (
|
||||
LayoutNameDT4 = "dt4"
|
||||
LayoutNameDT6 = "dt6"
|
||||
LayoutNameDT8 = "dt8"
|
||||
LayoutNameDT14 = "dt14"
|
||||
)
|
||||
|
||||
// IsDTX returns the dtx format if conformant to various DTX values (dt4, dt6, dt8, dt14).
|
||||
func IsDTX(d int) (string, error) {
|
||||
switch len(strconv.Itoa(d)) {
|
||||
case 4:
|
||||
return LayoutNameDT4, nil
|
||||
case 6:
|
||||
m := d - ((d / 100) * 100)
|
||||
if m < 1 || m > 12 {
|
||||
return LayoutNameDT6, errors.New("dt6 length month value is out of bounds")
|
||||
}
|
||||
return LayoutNameDT6, nil
|
||||
case 8:
|
||||
dy := d - ((d / 100) * 100)
|
||||
if dy < 1 || dy > 31 {
|
||||
return LayoutNameDT6, errors.New("dt8 day value is out of bounds")
|
||||
}
|
||||
return LayoutNameDT8, nil
|
||||
case 14:
|
||||
return LayoutNameDT14, nil
|
||||
default:
|
||||
return "", errors.New("length mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
132
timeutil/format_test.go
Normal file
132
timeutil/format_test.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package timeutil
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var dmyhm2ParseTests = []struct {
|
||||
v string
|
||||
want string
|
||||
}{
|
||||
{"02:01:06 15:04", "2006-01-02T15:04:00Z"},
|
||||
}
|
||||
|
||||
// TestDMYHM2ParseTime ensures timeutil.DateDMYHM2 is parsed to GMT timezone.
|
||||
func TestDMYHM2ParseTime(t *testing.T) {
|
||||
for _, tt := range dmyhm2ParseTests {
|
||||
got, err := FromTo(tt.v, DateDMYHM2, time.RFC3339)
|
||||
if err != nil {
|
||||
t.Errorf("time.Parse(DateDMYHM2) Error: with %v, want %v, err %v", tt.v, tt.want, err)
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("time.Parse(\"%v\", DateDMYHM2) Mismatch: want %v, got %v", tt.v, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var rfc3339YMDTimeTests = []struct {
|
||||
v string
|
||||
want string
|
||||
}{
|
||||
{`{"MyTime":"2001-02-03"}`, `{"MyTime":"2001-02-03"}`},
|
||||
{`{"MyTime":"0001-01-01"}`, `{"MyTime":"0001-01-01"}`},
|
||||
{`{"MyTime":""}`, `{"MyTime":"0001-01-01"}`},
|
||||
{`{}`, `{"MyTime":"0001-01-01"}`}}
|
||||
|
||||
type myStruct struct {
|
||||
MyTime RFC3339YMDTime
|
||||
}
|
||||
|
||||
func TestRfc3339YMDTime(t *testing.T) {
|
||||
for _, tt := range rfc3339YMDTimeTests {
|
||||
my := myStruct{}
|
||||
//fmt.Println(tt.v)
|
||||
err := json.Unmarshal([]byte(tt.v), &my)
|
||||
if err != nil {
|
||||
t.Errorf("Rfc3339YMDTime.Unmarshal: with %v, want %v, err %v", tt.v, tt.want, err)
|
||||
}
|
||||
|
||||
bytes, err := json.Marshal(my)
|
||||
if err != nil {
|
||||
t.Errorf("Rfc3339YMDTime.Marshal: with %v, want %v, err %v", tt.v, tt.want, err)
|
||||
}
|
||||
|
||||
got := string(bytes)
|
||||
|
||||
if got != tt.want {
|
||||
t.Errorf("Rfc3339YMDTime(%v): want %v, got %v", tt.v, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var offsetFormatTests = []struct {
|
||||
input int
|
||||
useColon bool
|
||||
useZ bool
|
||||
want string
|
||||
}{
|
||||
{0, false, false, "+0000"},
|
||||
{0, true, false, "+00:00"},
|
||||
{0, true, true, "Z"},
|
||||
{400, false, false, "+0400"},
|
||||
{-400, false, false, "-0400"},
|
||||
{530, false, false, "+0530"},
|
||||
{-530, false, false, "-0530"},
|
||||
{700, true, false, "+07:00"},
|
||||
{-700, true, false, "-07:00"},
|
||||
{845, true, false, "+08:45"},
|
||||
{-845, true, false, "-08:45"},
|
||||
}
|
||||
|
||||
func TestOffsetFormat(t *testing.T) {
|
||||
for _, tt := range offsetFormatTests {
|
||||
got := OffsetFormat(tt.input, tt.useColon, tt.useZ)
|
||||
if got != tt.want {
|
||||
t.Errorf("time.OffsetFormat(\"%v\",%v,%v) Mismatch: want [%v], got [%v]", tt.input, tt.useColon, tt.useZ, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var isDTXTests = []struct {
|
||||
v int
|
||||
want string
|
||||
}{
|
||||
{2004, LayoutNameDT4},
|
||||
{200401, LayoutNameDT6},
|
||||
}
|
||||
|
||||
func TestIsDTX(t *testing.T) {
|
||||
for _, tt := range isDTXTests {
|
||||
got, err := IsDTX(tt.v)
|
||||
if err != nil {
|
||||
t.Errorf("time.IsDTX(%d) Error: want (%s), err (%v)", tt.v, tt.want, err)
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("time.IsDTX(%d) Mismatch: want (%s), got (%s)", tt.v, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var formatsTests = []struct {
|
||||
v string
|
||||
format string
|
||||
want string
|
||||
}{
|
||||
{"2023-06-18T00:00:00Z", DateTextUS, "June 18, 2023"},
|
||||
{"2023-06-18T00:00:00Z", DateTextUSAbbr3, "Jun 18, 2023"},
|
||||
}
|
||||
|
||||
func TestFormats(t *testing.T) {
|
||||
for _, tt := range formatsTests {
|
||||
dt, err := time.Parse(time.RFC3339, tt.v)
|
||||
if err != nil {
|
||||
t.Errorf("time.Parse(time.RFC3339, %s) Error: err (%s)", tt.v, err.Error())
|
||||
}
|
||||
got := dt.Format(tt.format)
|
||||
if got != tt.want {
|
||||
t.Errorf("time.Format(%s) dt (%s) Mismatch: want (%s), got (%s)", tt.format, tt.v, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package util
|
||||
package timeutil
|
||||
|
||||
import "time"
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package util
|
||||
package timeutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
@@ -1,38 +1,98 @@
|
||||
package util
|
||||
package timeutil
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/tx7do/go-utils/trans"
|
||||
)
|
||||
|
||||
var DefaultTimeLocation *time.Location
|
||||
var defaultTimeLocation *time.Location
|
||||
|
||||
func RefreshDefaultTimeLocation(name string) {
|
||||
DefaultTimeLocation, _ = time.LoadLocation(name)
|
||||
func RefreshDefaultTimeLocation(name string) *time.Location {
|
||||
if defaultTimeLocation == nil {
|
||||
defaultTimeLocation, _ = time.LoadLocation(name)
|
||||
}
|
||||
return defaultTimeLocation
|
||||
}
|
||||
|
||||
func GetDefaultTimeLocation() *time.Location {
|
||||
if defaultTimeLocation == nil {
|
||||
RefreshDefaultTimeLocation(DefaultTimeLocationName)
|
||||
}
|
||||
return defaultTimeLocation
|
||||
}
|
||||
|
||||
// UnixMilliToStringPtr 毫秒时间戳 -> 字符串
|
||||
func UnixMilliToStringPtr(tm *int64) *string {
|
||||
if tm == nil {
|
||||
func UnixMilliToStringPtr(milli *int64) *string {
|
||||
if milli == nil {
|
||||
return nil
|
||||
}
|
||||
str := time.UnixMilli(*tm).Format(TimeLayout)
|
||||
|
||||
tm := time.UnixMilli(*milli)
|
||||
|
||||
str := tm.In(GetDefaultTimeLocation()).Format(TimeLayout)
|
||||
return &str
|
||||
}
|
||||
|
||||
// StringToUnixMilliInt64Ptr 字符串 -> 毫秒时间戳
|
||||
func StringToUnixMilliInt64Ptr(tm *string) *int64 {
|
||||
if tm == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
theTime := StringTimeToTime(tm)
|
||||
if theTime == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
unixTime := theTime.UnixMilli()
|
||||
return &unixTime
|
||||
}
|
||||
|
||||
// UnixMilliToTimePtr 毫秒时间戳 -> 时间
|
||||
func UnixMilliToTimePtr(milli *int64) *time.Time {
|
||||
if milli == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
unixMilli := time.UnixMilli(*milli)
|
||||
return &unixMilli
|
||||
}
|
||||
|
||||
// TimeToUnixMilliInt64Ptr 时间 -> 毫秒时间戳
|
||||
func TimeToUnixMilliInt64Ptr(tm *time.Time) *int64 {
|
||||
if tm == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
unixTime := tm.UnixMilli()
|
||||
return &unixTime
|
||||
}
|
||||
|
||||
// UnixSecondToTimePtr 秒时间戳 -> 时间
|
||||
func UnixSecondToTimePtr(second *int64) *time.Time {
|
||||
if second == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
unixMilli := time.Unix(*second, 0)
|
||||
|
||||
return &unixMilli
|
||||
}
|
||||
|
||||
// TimeToUnixSecondInt64Ptr 时间 -> 秒时间戳
|
||||
func TimeToUnixSecondInt64Ptr(tm *time.Time) *int64 {
|
||||
if tm == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
unixTime := tm.Unix()
|
||||
return &unixTime
|
||||
}
|
||||
|
||||
// StringTimeToTime 时间字符串 -> 时间
|
||||
func StringTimeToTime(str *string) *time.Time {
|
||||
if str == nil {
|
||||
@@ -42,25 +102,22 @@ func StringTimeToTime(str *string) *time.Time {
|
||||
return nil
|
||||
}
|
||||
|
||||
if DefaultTimeLocation == nil {
|
||||
RefreshDefaultTimeLocation(DefaultTimeLocationName)
|
||||
}
|
||||
|
||||
var err error
|
||||
var theTime time.Time
|
||||
|
||||
theTime, err = time.ParseInLocation(TimeLayout, *str, DefaultTimeLocation)
|
||||
if err == nil {
|
||||
if theTime, err = time.ParseInLocation(TimeLayout, *str, GetDefaultTimeLocation()); err == nil {
|
||||
return &theTime
|
||||
}
|
||||
|
||||
theTime, err = time.ParseInLocation(DateLayout, *str, DefaultTimeLocation)
|
||||
if err == nil {
|
||||
if theTime, err = time.ParseInLocation(DateLayout, *str, GetDefaultTimeLocation()); err == nil {
|
||||
return &theTime
|
||||
}
|
||||
|
||||
theTime, err = time.ParseInLocation(ClockLayout, *str, DefaultTimeLocation)
|
||||
if err == nil {
|
||||
if theTime, err = time.ParseInLocation(ClockLayout, *str, GetDefaultTimeLocation()); err == nil {
|
||||
return &theTime
|
||||
}
|
||||
|
||||
if theTime, err = time.ParseInLocation(ISO9075MicroTZ, *str, GetDefaultTimeLocation()); err == nil {
|
||||
return &theTime
|
||||
}
|
||||
|
||||
@@ -72,7 +129,8 @@ func TimeToTimeString(tm *time.Time) *string {
|
||||
if tm == nil {
|
||||
return nil
|
||||
}
|
||||
return trans.String(tm.Format(TimeLayout))
|
||||
|
||||
return trans.String(tm.In(GetDefaultTimeLocation()).Format(TimeLayout))
|
||||
}
|
||||
|
||||
// StringDateToTime 字符串 -> 时间
|
||||
@@ -84,24 +142,20 @@ func StringDateToTime(str *string) *time.Time {
|
||||
return nil
|
||||
}
|
||||
|
||||
if DefaultTimeLocation == nil {
|
||||
RefreshDefaultTimeLocation(DefaultTimeLocationName)
|
||||
}
|
||||
|
||||
var err error
|
||||
var theTime time.Time
|
||||
|
||||
theTime, err = time.ParseInLocation(TimeLayout, *str, DefaultTimeLocation)
|
||||
theTime, err = time.ParseInLocation(TimeLayout, *str, GetDefaultTimeLocation())
|
||||
if err == nil {
|
||||
return &theTime
|
||||
}
|
||||
|
||||
theTime, err = time.ParseInLocation(DateLayout, *str, DefaultTimeLocation)
|
||||
theTime, err = time.ParseInLocation(DateLayout, *str, GetDefaultTimeLocation())
|
||||
if err == nil {
|
||||
return &theTime
|
||||
}
|
||||
|
||||
theTime, err = time.ParseInLocation(ClockLayout, *str, DefaultTimeLocation)
|
||||
theTime, err = time.ParseInLocation(ClockLayout, *str, GetDefaultTimeLocation())
|
||||
if err == nil {
|
||||
return &theTime
|
||||
}
|
||||
@@ -114,13 +168,41 @@ func TimeToDateString(tm *time.Time) *string {
|
||||
if tm == nil {
|
||||
return nil
|
||||
}
|
||||
return trans.String(tm.Format(DateLayout))
|
||||
|
||||
return trans.String(tm.In(GetDefaultTimeLocation()).Format(DateLayout))
|
||||
}
|
||||
|
||||
func StringToTime(str *string, layout string) *time.Time {
|
||||
if str == nil {
|
||||
return nil
|
||||
}
|
||||
if len(*str) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
var theTime time.Time
|
||||
|
||||
theTime, err = time.ParseInLocation(layout, *str, GetDefaultTimeLocation())
|
||||
if err == nil {
|
||||
return &theTime
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TimeToString(tm *time.Time, layout string) *string {
|
||||
if tm == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return trans.String(tm.Format(layout))
|
||||
}
|
||||
|
||||
// TimestamppbToTime timestamppb.Timestamp -> time.Time
|
||||
func TimestamppbToTime(tm *timestamppb.Timestamp) *time.Time {
|
||||
if tm != nil {
|
||||
return trans.Ptr(tm.AsTime())
|
||||
func TimestamppbToTime(timestamp *timestamppb.Timestamp) *time.Time {
|
||||
if timestamp != nil {
|
||||
return trans.Ptr(timestamp.AsTime())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -132,3 +214,85 @@ func TimeToTimestamppb(tm *time.Time) *timestamppb.Timestamp {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func FloatToDurationpb(duration *float64, timePrecision time.Duration) *durationpb.Duration {
|
||||
if duration == nil {
|
||||
return nil
|
||||
}
|
||||
return durationpb.New(time.Duration(*duration * float64(timePrecision)))
|
||||
}
|
||||
|
||||
func Float64ToDurationpb(d float64) *durationpb.Duration {
|
||||
duration := time.Duration(d * float64(time.Second))
|
||||
return durationpb.New(duration)
|
||||
}
|
||||
|
||||
func SecondToDurationpb(seconds *float64) *durationpb.Duration {
|
||||
return FloatToDurationpb(seconds, time.Second)
|
||||
}
|
||||
|
||||
func DurationpbToFloat(duration *durationpb.Duration, timePrecision time.Duration) *float64 {
|
||||
if duration == nil {
|
||||
return nil
|
||||
}
|
||||
seconds := duration.AsDuration().Seconds()
|
||||
secondsWithPrecision := seconds / timePrecision.Seconds()
|
||||
return &secondsWithPrecision
|
||||
}
|
||||
|
||||
func NumberToDurationpb[T int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64](duration *T, timePrecision time.Duration) *durationpb.Duration {
|
||||
if duration == nil {
|
||||
return nil
|
||||
}
|
||||
return durationpb.New(time.Duration(*duration) * timePrecision)
|
||||
}
|
||||
|
||||
func DurationpbToNumber[T int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64](duration *durationpb.Duration, timePrecision time.Duration) *T {
|
||||
if duration == nil {
|
||||
return nil
|
||||
}
|
||||
seconds := duration.AsDuration().Seconds()
|
||||
secondsWithPrecision := T(seconds / timePrecision.Seconds())
|
||||
return &secondsWithPrecision
|
||||
}
|
||||
|
||||
func DurationToDurationpb(duration *time.Duration) *durationpb.Duration {
|
||||
if duration == nil {
|
||||
return nil
|
||||
}
|
||||
return durationpb.New(*duration)
|
||||
}
|
||||
|
||||
func DurationpbToDuration(duration *durationpb.Duration) *time.Duration {
|
||||
if duration == nil {
|
||||
return nil
|
||||
}
|
||||
d := duration.AsDuration()
|
||||
return &d
|
||||
}
|
||||
|
||||
func DurationpbToSecond(duration *durationpb.Duration) *float64 {
|
||||
if duration == nil {
|
||||
return nil
|
||||
}
|
||||
seconds := duration.AsDuration().Seconds()
|
||||
secondsInt64 := seconds
|
||||
return &secondsInt64
|
||||
}
|
||||
|
||||
func StringToDurationpb(in *string) *durationpb.Duration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
f, _ := time.ParseDuration(*in)
|
||||
return durationpb.New(f)
|
||||
}
|
||||
|
||||
func DurationpbToString(in *durationpb.Duration) *string {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return trans.Ptr(in.AsDuration().String())
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
package util
|
||||
package timeutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tx7do/go-utils/trans"
|
||||
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
func TestUnixMilliToStringPtr(t *testing.T) {
|
||||
@@ -19,17 +23,422 @@ func TestUnixMilliToStringPtr(t *testing.T) {
|
||||
fmt.Println(*UnixMilliToStringPtr(trans.Int64(1677647946234)))
|
||||
fmt.Println(*UnixMilliToStringPtr(trans.Int64(1678245350773)))
|
||||
|
||||
fmt.Println("START: ", *StringToUnixMilliInt64Ptr(trans.String("2023-03-09 00:00:00")))
|
||||
fmt.Println("END: ", *StringToUnixMilliInt64Ptr(trans.String("2023-03-09 23:59:59")))
|
||||
fmt.Println("START: ", *StringToUnixMilliInt64Ptr(trans.Ptr("2023-03-09 00:00:00")))
|
||||
fmt.Println("END: ", *StringToUnixMilliInt64Ptr(trans.Ptr("2023-03-09 23:59:59")))
|
||||
|
||||
fmt.Println(StringTimeToTime(trans.String("2023-03-01 00:00:00")))
|
||||
fmt.Println(*StringDateToTime(trans.String("2023-03-01")))
|
||||
fmt.Println(StringTimeToTime(trans.Ptr("2023-03-01 00:00:00")))
|
||||
fmt.Println(*StringDateToTime(trans.Ptr("2023-03-01")))
|
||||
|
||||
fmt.Println(StringTimeToTime(trans.String("2023-03-08 00:00:00")).UnixMilli())
|
||||
fmt.Println(StringDateToTime(trans.String("2023-03-07")).UnixMilli())
|
||||
fmt.Println(StringTimeToTime(trans.Ptr("2023-03-08 00:00:00")).UnixMilli())
|
||||
fmt.Println(StringDateToTime(trans.Ptr("2023-03-07")).UnixMilli())
|
||||
|
||||
// 测试有效输入
|
||||
now = time.Now().UnixMilli()
|
||||
result := UnixMilliToStringPtr(&now)
|
||||
assert.NotNil(t, result)
|
||||
expected := time.UnixMilli(now).Format(TimeLayout)
|
||||
assert.Equal(t, expected, *result)
|
||||
|
||||
// 测试空输入
|
||||
result = UnixMilliToStringPtr(nil)
|
||||
assert.Nil(t, result)
|
||||
}
|
||||
|
||||
func TestStringToUnixMilliInt64Ptr(t *testing.T) {
|
||||
// 测试有效输入
|
||||
input := "2023-03-09 00:00:00"
|
||||
expected := time.Date(2023, 3, 9, 0, 0, 0, 0, GetDefaultTimeLocation()).UnixMilli()
|
||||
result := StringToUnixMilliInt64Ptr(&input)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, expected, *result)
|
||||
|
||||
// 测试无效输入
|
||||
invalidInput := "invalid-date"
|
||||
result = StringToUnixMilliInt64Ptr(&invalidInput)
|
||||
assert.Nil(t, result)
|
||||
|
||||
// 测试空字符串输入
|
||||
emptyInput := ""
|
||||
result = StringToUnixMilliInt64Ptr(&emptyInput)
|
||||
assert.Nil(t, result)
|
||||
|
||||
// 测试空指针输入
|
||||
result = StringToUnixMilliInt64Ptr(nil)
|
||||
assert.Nil(t, result)
|
||||
}
|
||||
|
||||
func TestStringTimeToTime(t *testing.T) {
|
||||
// 测试有效时间字符串输入
|
||||
input := "2023-03-09 12:34:56"
|
||||
expected := time.Date(2023, 3, 9, 12, 34, 56, 0, GetDefaultTimeLocation())
|
||||
result := StringTimeToTime(&input)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, expected, *result)
|
||||
|
||||
// 测试有效日期字符串输入
|
||||
input = "2023-03-09"
|
||||
expected = time.Date(2023, 3, 9, 0, 0, 0, 0, GetDefaultTimeLocation())
|
||||
result = StringTimeToTime(&input)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, expected, *result)
|
||||
|
||||
// 测试无效时间字符串输入
|
||||
invalidInput := "invalid-date"
|
||||
result = StringTimeToTime(&invalidInput)
|
||||
assert.Nil(t, result)
|
||||
|
||||
// 测试空字符串输入
|
||||
emptyInput := ""
|
||||
result = StringTimeToTime(&emptyInput)
|
||||
assert.Nil(t, result)
|
||||
|
||||
// 测试空指针输入
|
||||
result = StringTimeToTime(nil)
|
||||
assert.Nil(t, result)
|
||||
}
|
||||
|
||||
func TestTimeToTimeString(t *testing.T) {
|
||||
// 测试非空输入
|
||||
now := time.Now()
|
||||
result := TimeToTimeString(&now)
|
||||
assert.NotNil(t, result)
|
||||
expected := now.Format(TimeLayout)
|
||||
assert.Equal(t, expected, *result)
|
||||
|
||||
// 测试空输入
|
||||
result = TimeToTimeString(nil)
|
||||
assert.Nil(t, result)
|
||||
}
|
||||
|
||||
func TestStringDateToTime(t *testing.T) {
|
||||
// 测试有效日期字符串输入
|
||||
input := "2023-03-09"
|
||||
expected := time.Date(2023, 3, 9, 0, 0, 0, 0, GetDefaultTimeLocation())
|
||||
result := StringDateToTime(&input)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, expected, *result)
|
||||
|
||||
// 测试无效日期字符串输入
|
||||
invalidInput := "invalid-date"
|
||||
result = StringDateToTime(&invalidInput)
|
||||
assert.Nil(t, result)
|
||||
|
||||
// 测试空字符串输入
|
||||
emptyInput := ""
|
||||
result = StringDateToTime(&emptyInput)
|
||||
assert.Nil(t, result)
|
||||
|
||||
// 测试空指针输入
|
||||
result = StringDateToTime(nil)
|
||||
assert.Nil(t, result)
|
||||
}
|
||||
|
||||
func TestTimeToDateString(t *testing.T) {
|
||||
fmt.Println(*TimeToTimeString(trans.Time(time.Now())))
|
||||
fmt.Println(*TimeToDateString(trans.Time(time.Now())))
|
||||
|
||||
// 测试非空输入
|
||||
now := time.Now()
|
||||
result := TimeToDateString(&now)
|
||||
assert.NotNil(t, result)
|
||||
expected := now.Format(DateLayout)
|
||||
assert.Equal(t, expected, *result)
|
||||
|
||||
// 测试空输入
|
||||
result = TimeToDateString(nil)
|
||||
assert.Nil(t, result)
|
||||
}
|
||||
|
||||
func TestTimeToString(t *testing.T) {
|
||||
now := time.Now()
|
||||
|
||||
fmt.Println(*TimeToString(&now, DateLayout))
|
||||
fmt.Println(*TimeToString(&now, ISO8601))
|
||||
fmt.Println(*TimeToString(&now, ISO8601TZHour))
|
||||
fmt.Println(*TimeToString(&now, ISO8601NoTZ))
|
||||
|
||||
layout := "2006-01-02"
|
||||
expected := now.Format(layout)
|
||||
result := TimeToString(&now, layout)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, expected, *result)
|
||||
|
||||
// 测试空输入
|
||||
result = TimeToString(nil, layout)
|
||||
assert.Nil(t, result)
|
||||
}
|
||||
|
||||
func TestStringToTime(t *testing.T) {
|
||||
// 测试有效时间字符串输入
|
||||
input := "2023-03-09 12:34:56"
|
||||
layout := "2006-01-02 15:04:05"
|
||||
expected := time.Date(2023, 3, 9, 12, 34, 56, 0, GetDefaultTimeLocation())
|
||||
result := StringToTime(&input, layout)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, expected, *result)
|
||||
|
||||
// 测试无效时间字符串输入
|
||||
invalidInput := "invalid-date"
|
||||
result = StringToTime(&invalidInput, layout)
|
||||
assert.Nil(t, result)
|
||||
|
||||
// 测试空字符串输入
|
||||
emptyInput := ""
|
||||
result = StringToTime(&emptyInput, layout)
|
||||
assert.Nil(t, result)
|
||||
|
||||
// 测试空指针输入
|
||||
result = StringToTime(nil, layout)
|
||||
assert.Nil(t, result)
|
||||
}
|
||||
|
||||
func TestTimestamppbToTime(t *testing.T) {
|
||||
// 测试有效输入
|
||||
timestamp := timestamppb.Now()
|
||||
result := TimestamppbToTime(timestamp)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, timestamp.AsTime(), *result)
|
||||
|
||||
// 测试零时间输入
|
||||
zeroTimestamp := timestamppb.New(time.Time{})
|
||||
result = TimestamppbToTime(zeroTimestamp)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, time.Time{}, *result)
|
||||
|
||||
// 测试空输入
|
||||
result = TimestamppbToTime(nil)
|
||||
assert.Nil(t, result)
|
||||
}
|
||||
|
||||
func TestTimeToTimestamppb(t *testing.T) {
|
||||
// 测试非空输入
|
||||
now := time.Now()
|
||||
result := TimeToTimestamppb(&now)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, timestamppb.New(now), result)
|
||||
|
||||
// 测试空输入
|
||||
result = TimeToTimestamppb(nil)
|
||||
assert.Nil(t, result)
|
||||
}
|
||||
|
||||
func TestDurationpb(t *testing.T) {
|
||||
fmt.Println(FloatToDurationpb(trans.Ptr(100.0), time.Nanosecond).String())
|
||||
fmt.Println(*DurationpbToFloat(durationpb.New(100*time.Nanosecond), time.Nanosecond))
|
||||
|
||||
fmt.Println(FloatToDurationpb(trans.Ptr(100.0), time.Second).String())
|
||||
fmt.Println(*DurationpbToFloat(durationpb.New(100*time.Second), time.Second))
|
||||
|
||||
fmt.Println(FloatToDurationpb(trans.Ptr(100.0), time.Minute).String())
|
||||
fmt.Println(*DurationpbToFloat(durationpb.New(100*time.Minute), time.Minute))
|
||||
|
||||
//
|
||||
|
||||
fmt.Println(NumberToDurationpb(trans.Ptr(100.0), time.Nanosecond).String())
|
||||
fmt.Println(*DurationpbToNumber[float64](durationpb.New(100*time.Nanosecond), time.Nanosecond))
|
||||
|
||||
fmt.Println(NumberToDurationpb(trans.Ptr(100.0), time.Second).String())
|
||||
fmt.Println(*DurationpbToNumber[float64](durationpb.New(100*time.Second), time.Second))
|
||||
|
||||
fmt.Println(NumberToDurationpb(trans.Ptr(100.0), time.Minute).String())
|
||||
fmt.Println(*DurationpbToNumber[float64](durationpb.New(100*time.Minute), time.Minute))
|
||||
}
|
||||
|
||||
func TestFloatToDurationpb(t *testing.T) {
|
||||
// 测试有效输入
|
||||
input := 1.5 // 1.5秒
|
||||
timePrecision := time.Second
|
||||
expected := durationpb.New(1500 * time.Millisecond)
|
||||
result := FloatToDurationpb(&input, timePrecision)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, expected, result)
|
||||
|
||||
// 测试零输入
|
||||
input = 0.0
|
||||
expected = durationpb.New(0)
|
||||
result = FloatToDurationpb(&input, timePrecision)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, expected, result)
|
||||
|
||||
// 测试空输入
|
||||
result = FloatToDurationpb(nil, timePrecision)
|
||||
assert.Nil(t, result)
|
||||
}
|
||||
|
||||
func TestDurationToDurationpb(t *testing.T) {
|
||||
// 测试非空输入
|
||||
duration := time.Duration(5 * time.Second)
|
||||
result := DurationToDurationpb(&duration)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, durationpb.New(duration), result)
|
||||
|
||||
// 测试空输入
|
||||
result = DurationToDurationpb(nil)
|
||||
assert.Nil(t, result)
|
||||
}
|
||||
|
||||
func TestDurationpbToDuration(t *testing.T) {
|
||||
// 测试非空输入
|
||||
durationpbValue := durationpb.New(5 * time.Second)
|
||||
result := DurationpbToDuration(durationpbValue)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, 5*time.Second, *result)
|
||||
|
||||
// 测试空输入
|
||||
result = DurationpbToDuration(nil)
|
||||
assert.Nil(t, result)
|
||||
}
|
||||
|
||||
func TestFloat64ToDurationpb(t *testing.T) {
|
||||
// 测试有效输入
|
||||
input := 1.5 // 1.5秒
|
||||
expected := durationpb.New(1500 * time.Millisecond)
|
||||
result := Float64ToDurationpb(input)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, expected, result)
|
||||
|
||||
// 测试零输入
|
||||
input = 0.0
|
||||
expected = durationpb.New(0)
|
||||
result = Float64ToDurationpb(input)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, expected, result)
|
||||
|
||||
// 测试负数输入
|
||||
input = -2.5 // -2.5秒
|
||||
expected = durationpb.New(-2500 * time.Millisecond)
|
||||
result = Float64ToDurationpb(input)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, expected, result)
|
||||
}
|
||||
|
||||
func TestSecondToDurationpb(t *testing.T) {
|
||||
// 测试有效输入
|
||||
input := 2.5 // 2.5秒
|
||||
expected := durationpb.New(2500 * time.Millisecond)
|
||||
result := SecondToDurationpb(&input)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, expected, result)
|
||||
|
||||
// 测试零输入
|
||||
input = 0.0
|
||||
expected = durationpb.New(0)
|
||||
result = SecondToDurationpb(&input)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, expected, result)
|
||||
|
||||
// 测试空输入
|
||||
result = SecondToDurationpb(nil)
|
||||
assert.Nil(t, result)
|
||||
}
|
||||
|
||||
func TestDurationpbToSecond(t *testing.T) {
|
||||
// 测试非空输入
|
||||
duration := durationpb.New(5 * time.Second)
|
||||
result := DurationpbToSecond(duration)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, 5.0, *result, "应返回正确的秒数")
|
||||
|
||||
// 测试零输入
|
||||
duration = durationpb.New(0)
|
||||
result = DurationpbToSecond(duration)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, 0.0, *result, "应返回零秒")
|
||||
|
||||
// 测试空输入
|
||||
result = DurationpbToSecond(nil)
|
||||
assert.Nil(t, result, "空输入应返回nil")
|
||||
}
|
||||
|
||||
func TestUnixMilliToTimePtr(t *testing.T) {
|
||||
// 测试有效输入
|
||||
now := time.Now().UnixMilli()
|
||||
result := UnixMilliToTimePtr(&now)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, time.UnixMilli(now), *result)
|
||||
|
||||
// 测试空输入
|
||||
result = UnixMilliToTimePtr(nil)
|
||||
assert.Nil(t, result)
|
||||
}
|
||||
|
||||
func TestTimeToUnixMilliInt64Ptr(t *testing.T) {
|
||||
// 测试有效输入
|
||||
now := time.Now()
|
||||
result := TimeToUnixMilliInt64Ptr(&now)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, now.UnixMilli(), *result)
|
||||
|
||||
// 测试空输入
|
||||
result = TimeToUnixMilliInt64Ptr(nil)
|
||||
assert.Nil(t, result)
|
||||
}
|
||||
|
||||
func TestUnixSecondToTimePtr(t *testing.T) {
|
||||
// 测试有效输入
|
||||
now := time.Now().Unix()
|
||||
result := UnixSecondToTimePtr(&now)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, time.Unix(now, 0), *result)
|
||||
|
||||
// 测试空输入
|
||||
result = UnixSecondToTimePtr(nil)
|
||||
assert.Nil(t, result)
|
||||
}
|
||||
|
||||
func TestTimeToUnixSecondInt64Ptr(t *testing.T) {
|
||||
// 测试有效输入
|
||||
now := time.Now()
|
||||
result := TimeToUnixSecondInt64Ptr(&now)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, now.Unix(), *result)
|
||||
|
||||
// 测试空输入
|
||||
result = TimeToUnixSecondInt64Ptr(nil)
|
||||
assert.Nil(t, result)
|
||||
}
|
||||
|
||||
func TestStringToDurationpb(t *testing.T) {
|
||||
// 测试有效输入
|
||||
validInput := "1h30m"
|
||||
expected := durationpb.New(90 * time.Minute)
|
||||
result := StringToDurationpb(&validInput)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, expected, result)
|
||||
|
||||
// 测试无效输入
|
||||
invalidInput := "invalid-duration"
|
||||
result = StringToDurationpb(&invalidInput)
|
||||
assert.NotNil(t, result) // 即使输入无效,time.ParseDuration 返回零值
|
||||
assert.Equal(t, durationpb.New(0), result)
|
||||
|
||||
// 测试空字符串输入
|
||||
emptyInput := ""
|
||||
result = StringToDurationpb(&emptyInput)
|
||||
assert.NotNil(t, result) // 空字符串解析为零值
|
||||
assert.Equal(t, durationpb.New(0), result)
|
||||
|
||||
// 测试空指针输入
|
||||
result = StringToDurationpb(nil)
|
||||
assert.Nil(t, result)
|
||||
}
|
||||
|
||||
func TestDurationpbToString(t *testing.T) {
|
||||
// 测试有效输入
|
||||
duration := durationpb.New(90 * time.Second) // 90秒
|
||||
expected := "1m30s"
|
||||
result := DurationpbToString(duration)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, expected, *result)
|
||||
|
||||
// 测试零值输入
|
||||
duration = durationpb.New(0)
|
||||
expected = "0s"
|
||||
result = DurationpbToString(duration)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, expected, *result)
|
||||
|
||||
// 测试空输入
|
||||
result = DurationpbToString(nil)
|
||||
assert.Nil(t, result)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package trans
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func String(a string) *string {
|
||||
return &a
|
||||
@@ -527,3 +531,28 @@ func MapValues[TKey mapKeyValueType, TValue mapKeyValueType](source map[TKey]TVa
|
||||
}
|
||||
return target
|
||||
}
|
||||
|
||||
func ToUuidPtr(str *string) *uuid.UUID {
|
||||
var id *uuid.UUID
|
||||
if str != nil {
|
||||
_id, err := uuid.Parse(*str)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
id = &_id
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
func ToUuid(str string) uuid.UUID {
|
||||
id, _ := uuid.Parse(str)
|
||||
return id
|
||||
}
|
||||
|
||||
func ToStringPtr(id *uuid.UUID) *string {
|
||||
var strUUID *string
|
||||
if id != nil {
|
||||
strUUID = Ptr(id.String())
|
||||
}
|
||||
return strUUID
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -167,3 +168,62 @@ func Test_Trans(t *testing.T) {
|
||||
assert.Equal(t, time.Now(), tmVal)
|
||||
assert.Equal(t, time.Now(), TimeValue(nil))
|
||||
}
|
||||
|
||||
func TestUUID(t *testing.T) {
|
||||
t.Run("ToUuidPtr_NilString", func(t *testing.T) {
|
||||
var str *string
|
||||
result := ToUuidPtr(str)
|
||||
if result != nil {
|
||||
t.Errorf("expected nil, got %v", result)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ToUuidPtr_ValidString", func(t *testing.T) {
|
||||
str := "550e8400-e29b-41d4-a716-446655440000"
|
||||
result := ToUuidPtr(&str)
|
||||
if result == nil || result.String() != str {
|
||||
t.Errorf("expected %v, got %v", str, result)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ToUuidPtr_InvalidString", func(t *testing.T) {
|
||||
str := "invalid-uuid"
|
||||
result := ToUuidPtr(&str)
|
||||
if result != nil {
|
||||
t.Errorf("expected nil, got %v", result)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ToUuid_ValidString", func(t *testing.T) {
|
||||
str := "550e8400-e29b-41d4-a716-446655440000"
|
||||
result := ToUuid(str)
|
||||
if result.String() != str {
|
||||
t.Errorf("expected %v, got %v", str, result)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ToUuid_InvalidString", func(t *testing.T) {
|
||||
str := "invalid-uuid"
|
||||
result := ToUuid(str)
|
||||
if result.String() == str {
|
||||
t.Errorf("expected invalid UUID, got %v", result)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ToStringPtr_NilUUID", func(t *testing.T) {
|
||||
var id *uuid.UUID
|
||||
result := ToStringPtr(id)
|
||||
if result != nil {
|
||||
t.Errorf("expected nil, got %v", result)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ToStringPtr_ValidUUID", func(t *testing.T) {
|
||||
id := uuid.MustParse("550e8400-e29b-41d4-a716-446655440000")
|
||||
result := ToStringPtr(&id)
|
||||
expected := "550e8400-e29b-41d4-a716-446655440000"
|
||||
if result == nil || *result != expected {
|
||||
t.Errorf("expected %v, got %v", expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
113
translator/README.md
Normal file
113
translator/README.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# 翻译器
|
||||
|
||||
## 目标语言
|
||||
|
||||
1. 阿尔巴尼亚语: `sq`
|
||||
2. 阿拉伯语: `ar`
|
||||
3. 阿姆哈拉语: `am`
|
||||
4. 阿塞拜疆语: `az`
|
||||
5. 爱尔兰语: `ga`
|
||||
6. 爱沙尼亚语: `et`
|
||||
7. 奥利亚语: `or`
|
||||
8. 巴斯克语: `eu`
|
||||
9. 白俄罗斯语: `be`
|
||||
10. 保加利亚语: `bg`
|
||||
11. 冰岛语: `is`
|
||||
12. 波兰语: `pl`
|
||||
13. 波斯尼亚语: `bs`
|
||||
14. 波斯语: `fa`
|
||||
15. 布尔语(南非荷兰语): `af`
|
||||
16. 鞑靼语: `tt`
|
||||
17. 丹麦语: `da`
|
||||
18. 德语: `de`
|
||||
19. 俄语: `ru`
|
||||
20. 法语: `fr`
|
||||
21. 菲律宾语: `tl`
|
||||
22. 芬兰语: `fi`
|
||||
23. 弗里西语: `fy`
|
||||
24. 高棉语: `km`
|
||||
25. 格鲁吉亚语: `ka`
|
||||
26. 古吉拉特语: `gu`
|
||||
27. 哈萨克语: `kk`
|
||||
28. 海地克里奥尔语: `ht`
|
||||
29. 韩语: `ko`
|
||||
30. 豪萨语: `ha`
|
||||
31. 荷兰语: `nl`
|
||||
32. 吉尔吉斯语: `ky`
|
||||
33. 加利西亚语: `gl`
|
||||
34. 加泰罗尼亚语: `ca`
|
||||
35. 捷克语: `cs`
|
||||
36. 卡纳达语: `kn`
|
||||
37. 科西嘉语: `co`
|
||||
38. 克罗地亚语: `hr`
|
||||
39. 库尔德语: `ku`
|
||||
40. 拉丁语: `la`
|
||||
41. 拉脱维亚语: `lv`
|
||||
42. 老挝语: `lo`
|
||||
43. 立陶宛语: `lt`
|
||||
44. 卢森堡语: `lb`
|
||||
45. 卢旺达语: `rw`
|
||||
46. 罗马尼亚语: `ro`
|
||||
47. 马尔加什语: `mg`
|
||||
48. 马耳他语: `mt`
|
||||
49. 马拉地语: `mr`
|
||||
50. 马拉雅拉姆语: `ml`
|
||||
51. 马来语: `ms`
|
||||
52. 马其顿语: `mk`
|
||||
53. 毛利语: `mi`
|
||||
54. 蒙古语: `mn`
|
||||
55. 孟加拉语: `bn`
|
||||
56. 缅甸语: `my`
|
||||
57. 苗语: `hmn`
|
||||
58. 南非科萨语: `xh`
|
||||
59. 南非祖鲁语: `zu`
|
||||
60. 尼泊尔语: `ne`
|
||||
61. 挪威语: `no`
|
||||
62. 旁遮普语: `pa`
|
||||
63. 葡萄牙语: `pt`
|
||||
64. 普什图语: `ps`
|
||||
65. 齐切瓦语: `ny`
|
||||
66. 日语: `ja`
|
||||
67. 瑞典语: `sv`
|
||||
68. 萨摩亚语: `sm`
|
||||
69. 塞尔维亚语: `sr`
|
||||
70. 塞索托语: `st`
|
||||
71. 僧伽罗语: `si`
|
||||
72. 世界语: `eo`
|
||||
73. 斯洛伐克语: `sk`
|
||||
74. 斯洛文尼亚语: `sl`
|
||||
75. 斯瓦希里语: `sw`
|
||||
76. 苏格兰盖尔语: `gd`
|
||||
77. 宿务语: `ceb`
|
||||
78. 索马里语: `so`
|
||||
79. 塔吉克语: `tg`
|
||||
80. 泰卢固语: `te`
|
||||
81. 泰米尔语: `ta`
|
||||
82. 泰语: `th`
|
||||
83. 土耳其语: `tr`
|
||||
84. 土库曼语: `tk`
|
||||
85. 威尔士语: `cy`
|
||||
86. 维吾尔语: `ug`
|
||||
87. 乌尔都语: `ur`
|
||||
88. 乌克兰语: `uk`
|
||||
89. 乌兹别克语: `uz`
|
||||
90. 西班牙语: `es`
|
||||
91. 希伯来语: `iw`
|
||||
92. 希腊语: `el`
|
||||
93. 夏威夷语: `haw`
|
||||
94. 信德语: `sd`
|
||||
95. 匈牙利语: `hu`
|
||||
96. 修纳语: `sn`
|
||||
97. 亚美尼亚语: `hy`
|
||||
98. 伊博语: `ig`
|
||||
99. 意大利语: `it`
|
||||
100. 意第绪语: `yi`
|
||||
101. 印地语: `hi`
|
||||
102. 印尼巽他语: `su`
|
||||
103. 印尼语: `id`
|
||||
104. 印尼爪哇语: `jw`
|
||||
105. 英语: `en`
|
||||
106. 约鲁巴语: `yo`
|
||||
107. 越南语: `vi`
|
||||
108. 中文(繁体): `zh-TW`
|
||||
109. 中文(简体): `zh-CN`
|
||||
49
translator/go.mod
Normal file
49
translator/go.mod
Normal file
@@ -0,0 +1,49 @@
|
||||
module github.com/tx7do/go-utils/translator
|
||||
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.24.3
|
||||
|
||||
require (
|
||||
cloud.google.com/go/translate v1.12.5
|
||||
github.com/stretchr/testify v1.10.0
|
||||
golang.org/x/text v0.25.0
|
||||
google.golang.org/api v0.233.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.121.1 // indirect
|
||||
cloud.google.com/go/auth v0.16.1 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.7.0 // indirect
|
||||
cloud.google.com/go/longrunning v0.6.7 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.14.2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
|
||||
go.opentelemetry.io/otel v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.36.0 // indirect
|
||||
golang.org/x/crypto v0.38.0 // indirect
|
||||
golang.org/x/net v0.40.0 // indirect
|
||||
golang.org/x/oauth2 v0.30.0 // indirect
|
||||
golang.org/x/sync v0.14.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/time v0.11.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20250519155744-55703ea1f237 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect
|
||||
google.golang.org/grpc v1.72.1 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
replace github.com/tx7do/go-utils => ../
|
||||
90
translator/go.sum
Normal file
90
translator/go.sum
Normal file
@@ -0,0 +1,90 @@
|
||||
cloud.google.com/go v0.121.1 h1:S3kTQSydxmu1JfLRLpKtxRPA7rSrYPRPEUmL/PavVUw=
|
||||
cloud.google.com/go v0.121.1/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw=
|
||||
cloud.google.com/go/auth v0.16.1 h1:XrXauHMd30LhQYVRHLGvJiYeczweKQXZxsTbV9TiguU=
|
||||
cloud.google.com/go/auth v0.16.1/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||
cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
|
||||
cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
|
||||
cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
|
||||
cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
|
||||
cloud.google.com/go/translate v1.12.5 h1:QPMNi4WCtHwc2PPfxbyUMwdN/0+cyCGLaKi2tig41J8=
|
||||
cloud.google.com/go/translate v1.12.5/go.mod h1:o/v+QG/bdtBV1d1edmtau0PwTfActvxPk/gtqdSDBi4=
|
||||
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/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
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.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||
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/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
|
||||
github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0=
|
||||
github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w=
|
||||
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.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
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.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
|
||||
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
|
||||
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
|
||||
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
|
||||
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
|
||||
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
|
||||
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
|
||||
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.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
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/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
google.golang.org/api v0.233.0 h1:iGZfjXAJiUFSSaekVB7LzXl6tRfEKhUN7FkZN++07tI=
|
||||
google.golang.org/api v0.233.0/go.mod h1:TCIVLLlcwunlMpZIhIp7Ltk77W+vUSdUKAAIlbxY44c=
|
||||
google.golang.org/genproto v0.0.0-20250519155744-55703ea1f237 h1:2zGWyk04EwQ3mmV4dd4M4U7P/igHi5p7CBJEg1rI6A8=
|
||||
google.golang.org/genproto v0.0.0-20250519155744-55703ea1f237/go.mod h1:LhI4bRmX3rqllzQ+BGneexULkEjBf2gsAfkbeCA8IbU=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
|
||||
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
||||
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=
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user