Compare commits

...

33 Commits

Author SHA1 Message Date
Bobo
99c8f6e4c1 feat: id utils 2025-06-04 15:52:46 +08:00
Bobo
3f713e489b feat: time util 2025-05-29 00:15:37 +08:00
Bobo
ace6bd4237 feat: time util 2025-05-29 00:07:27 +08:00
Bobo
3ec9bbbd27 feat: time util 2025-05-28 23:58:08 +08:00
Bobo
faa5817857 feat: time util 2025-05-28 23:55:43 +08:00
Bobo
89a69a9d4d feat: entgo 2025-05-28 23:29:21 +08:00
Bobo
63789d090d feat: entgo 2025-05-28 23:08:18 +08:00
Bobo
06c29c57d2 feat: copier util 2025-05-28 23:06:30 +08:00
Bobo
5faa4e06f7 feat: time util 2025-05-28 22:38:29 +08:00
Bobo
5abe88a0d9 feat: jwt util 2025-05-23 14:31:59 +08:00
Bobo
028dd8dd19 feat: trans util 2025-05-23 12:58:21 +08:00
Bobo
50161f8c8a feat: trans util 2025-05-22 14:15:57 +08:00
Bobo
e686e7189c feat: copier util 2025-05-22 10:53:04 +08:00
Bobo
d1aa8e36ad feat: copier util 2025-05-22 10:33:02 +08:00
Bobo
2fb73a0a1f feat: copier util 2025-05-21 22:09:32 +08:00
Bobo
c03ac708f0 feat: copier util 2025-05-21 21:58:26 +08:00
Bobo
9035e79520 feat: time trans utils 2025-05-18 21:41:00 +08:00
Bobo
3b2678de10 feat: translator 2025-05-14 20:43:37 +08:00
Bobo
982e18a991 feat: string utils 2025-05-13 14:35:47 +08:00
Bobo
f2f5388906 feat: time trans 2025-05-11 23:43:07 +08:00
Bobo
9eca340c7e feat: time trans 2025-05-11 23:42:50 +08:00
Bobo
376746f4db feat: time trans 2025-05-11 23:41:53 +08:00
Bobo
efc24c452f feat: crypto 2025-05-07 16:00:40 +08:00
Bobo
78cef077e5 feat: crypto 2025-05-07 15:57:19 +08:00
Bobo
0420e35a30 feat: crypto 2025-05-07 15:05:18 +08:00
Bobo
0b18560901 feat: crypto 2025-05-07 14:42:12 +08:00
Bobo
7b29f09e37 feat: entgo 2025-05-07 14:07:13 +08:00
Bobo
363a18b1c8 feat: crypto 2025-05-07 13:52:07 +08:00
Bobo
e90588b9ca feat: random string 2025-04-22 18:46:41 +08:00
Bobo
713975e7f1 feat: crypto 2025-04-10 15:15:46 +08:00
Bobo
2ab920982a feat: Randomizer 2025-04-08 20:31:32 +08:00
Bobo
3153ff149f feat: Seeder 2025-04-08 19:58:05 +08:00
Bobo
578cf26ee8 feat: Randomizer 2025-04-08 19:55:49 +08:00
71 changed files with 4612 additions and 508 deletions

View File

@@ -3,7 +3,7 @@ module github.com/tx7do/go-utils/bank_card
go 1.20
require (
github.com/mattn/go-sqlite3 v1.14.25
github.com/mattn/go-sqlite3 v1.14.28
github.com/stretchr/testify v1.10.0
)

View File

@@ -1,7 +1,7 @@
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.25 h1:rszkIulEvxqZ8JfFG4yWEZh5u9qAKeSOdea67p8kk6s=
github.com/mattn/go-sqlite3 v1.14.25/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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=

128
copierutil/converters.go Normal file
View 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: &timestamppb.Timestamp{},
Fn: func(src interface{}) (interface{}, error) {
return timeutil.TimeToTimestamppb(src.(*time.Time)), nil
},
}
var TimestamppbToTimeConverter = copier.TypeConverter{
SrcType: &timestamppb.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 := &timestamppb.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))
},
},
}
}

View 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 := &timestamppb.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(&timestamppb.Timestamp{})
assert.NoError(t, err)
assert.IsType(t, srcType, result)
}

24
copierutil/go.mod Normal file
View 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
View 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
View 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/bL2qYySalt 和 Hash前22个字符是盐值后面的字符是哈希值。

100
crypto/aes.go Normal file
View 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
View 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))
}
}

View File

@@ -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
}

View File

@@ -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"

View File

@@ -11,19 +11,19 @@ require (
github.com/go-kratos/kratos/v2 v2.8.4
github.com/google/uuid v1.6.0
github.com/stretchr/testify v1.10.0
github.com/tx7do/go-utils v1.1.14
go.opentelemetry.io/otel v1.35.0
github.com/tx7do/go-utils v1.1.27
go.opentelemetry.io/otel v1.36.0
google.golang.org/protobuf v1.36.6
)
require (
ariga.io/atlas v0.32.0 // indirect
ariga.io/atlas v0.33.1 // indirect
github.com/agext/levenshtein v1.2.3 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/bmatcuk/doublestar v1.3.4 // indirect
github.com/bufbuild/protocompile v0.14.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/inflect v0.21.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
@@ -33,17 +33,17 @@ require (
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.16.2 // 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.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/otel/metric v1.35.0 // indirect
go.opentelemetry.io/otel/trace v1.35.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.24.0 // indirect
golang.org/x/sync v0.12.0 // indirect
golang.org/x/text v0.23.0 // indirect
golang.org/x/tools v0.31.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
)

View File

@@ -1,5 +1,5 @@
ariga.io/atlas v0.32.0 h1:y+77nueMrExLiKlz1CcPKh/nU7VSlWfBbwCShsJyvCw=
ariga.io/atlas v0.32.0/go.mod h1:Oe1xWPuu5q9LzyrWfbZmEZxFYeu4BHTyzfjeW2aZp/w=
ariga.io/atlas v0.33.1 h1:m3rn+m2jkfOZDqdZrgTDvDWh9G3Ldr77IElFDGEwDPk=
ariga.io/atlas v0.33.1/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=
@@ -23,6 +23,8 @@ github.com/go-kratos/kratos/v2 v2.8.4/go.mod h1:mq62W2101a5uYyRxe+7IdWubu7gZCGYq
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/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.21.2 h1:0gClGlGcxifcJR56zwvhaOulnNgnhc4qTAkob5ObnSM=
@@ -53,42 +55,42 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/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/sony/sonyflake v1.2.0 h1:Pfr3A+ejSg+0SPqpoAmQgEtNDAhc2G1SUYk205qVMLQ=
github.com/sony/sonyflake v1.2.0/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y=
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.2 h1:LAJSwc3v81IRBZyUVQDUdZ7hs3SYs9jv0eZJDWHD/70=
github.com/zclconf/go-cty v1.16.2/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
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.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.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
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.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
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.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
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=

View File

@@ -1,15 +1,17 @@
package entgo
import (
"encoding/json"
"fmt"
"github.com/tx7do/go-utils/stringcase"
"strings"
"encoding/json"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
"github.com/go-kratos/kratos/v2/encoding"
"github.com/tx7do/go-utils/stringcase"
)
type FilterOp int

View File

@@ -10,6 +10,7 @@ import (
"github.com/go-kratos/kratos/v2/encoding"
_ "github.com/go-kratos/kratos/v2/encoding/json"
"github.com/stretchr/testify/assert"
)

View File

@@ -8,7 +8,7 @@ require (
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.23.0
golang.org/x/text v0.25.0
)
require (
@@ -16,7 +16,7 @@ require (
github.com/kr/text v0.2.0 // indirect
github.com/oschwald/maxminddb-golang v1.13.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/sys v0.33.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@@ -17,12 +17,12 @@ github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDN
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.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
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=

7
go.mod
View File

@@ -7,17 +7,14 @@ toolchain go1.24.1
require (
github.com/gobwas/glob v0.2.3
github.com/google/uuid v1.6.0
github.com/gosimple/slug v1.15.0
github.com/sony/sonyflake v1.2.0
github.com/stretchr/testify v1.10.0
golang.org/x/crypto v0.36.0
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394
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

14
go.sum
View File

@@ -7,10 +7,6 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/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.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=
@@ -24,14 +20,12 @@ 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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
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=

View File

@@ -1,55 +1,54 @@
module github.com/tx7do/go-utils/gorm
go 1.22
go 1.23.0
toolchain go1.23.2
toolchain go1.24.3
require (
gorm.io/driver/clickhouse v0.6.1
gorm.io/driver/mysql v1.5.7
gorm.io/driver/postgres v1.5.9
gorm.io/driver/sqlite v1.5.6
gorm.io/driver/sqlserver v1.5.3
gorm.io/gorm v1.25.12
gorm.io/plugin/opentelemetry v0.1.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 (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/ClickHouse/ch-go v0.62.0 // indirect
github.com/ClickHouse/clickhouse-go/v2 v2.30.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.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.8.1 // indirect
github.com/go-sql-driver/mysql v1.9.2 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.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-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.7.1 // indirect
github.com/jackc/pgx/v5 v5.7.5 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/mattn/go-sqlite3 v1.14.24 // indirect
github.com/microsoft/go-mssqldb v1.7.2 // 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.21 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect
github.com/segmentio/asm v1.2.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
go.opentelemetry.io/otel v1.31.0 // indirect
go.opentelemetry.io/otel/metric v1.31.0 // indirect
go.opentelemetry.io/otel/sdk v1.25.0 // indirect
go.opentelemetry.io/otel/trace v1.31.0 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.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
)

View File

@@ -1,34 +1,31 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/Azure/azure-sdk-for-go v56.3.0+incompatible h1:DmhwMrUIvpeoTDiWRDtNHqelNUd3Og8JCkrLHQK795c=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0/go.mod h1:ON4tFdPTwRcgWEaVDrN3584Ef+b7GgSJaXxe5fW9t4M=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1 h1:lGlwhPtrX6EVml1hO0ivjkUxsSyl4dsiw9qcA1k/3IQ=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1/go.mod h1:RKUqNu35KJYcVG/fqTRqmuXJZYNhYkBrnC/hX7yGbTA=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 h1:sO0/P7g68FrryJzljemN+6GTssUXdANk6aJ7T1ZxnsQ=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1/go.mod h1:uE9zaUfEQT/nbQjVi2IblCG9iaLtZsuYZ8ne+PuQ02M=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1/go.mod h1:h8hyGFDsU5HMivxiS2iYFZsgDbU9OnnJ163x5UGVKYo=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.2.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 h1:U2rTu3Ef+7w9FHKIAXM6ZyqF3UOWJZ12zIm8zECAFfg=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 h1:6oNBlSdi1QqM1PNW7FPA6xOGA5UNsXnkaYZz9vdPGhA=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.0/go.mod h1:Q28U+75mpCaSCDowNEmhIo/rmgdkqmkmzI7N6TGR4UY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 h1:jBQA3cKT4L2rWMpgE7Yt3Hwh2aUj8KXjIGLxjHeYNNo=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0/go.mod h1:4OG6tQ9EOP/MT0NMjDlRzWoVFxfu9rN9B2X+tlSVktg=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1 h1:MyVTgWR8qd/Jw1Le0NZebGBUCLbtak3bJ3z1OlqZBpw=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1/go.mod h1:GpPjLhVR9dnUoJMyHWSPy71xY9/lcmpzIPZXmF0FCVY=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0/go.mod h1:cw4zVQgBby0Z5f2v0itn6se2dDP17nTjbZFXW5uPyHA=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI=
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
github.com/AzureAD/microsoft-authentication-library-for-go v1.1.0/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 h1:DzHpqpoJVaCgOUdVHxE8QB52S6NiVdDQvGlny1qvPqA=
github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/ClickHouse/ch-go v0.62.0 h1:eXH0hytXeCEEZHgMvOX9IiW7wqBb4w1MJMp9rArbkrc=
github.com/ClickHouse/ch-go v0.62.0/go.mod h1:uzso52/PD9+gZj7tL6XAo8/EYDrx7CIwNF4c6PnO6S0=
github.com/ClickHouse/clickhouse-go/v2 v2.30.0 h1:AG4D/hW39qa58+JHQIFOSnxyL46H6h2lrmGGk17dhFo=
github.com/ClickHouse/clickhouse-go/v2 v2.30.0/go.mod h1:i9ZQAojcayW3RsdCb3YR+n+wC2h65eJsZCscZ1Z1wyo=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/ClickHouse/ch-go v0.66.0 h1:hLslxxAVb2PHpbHr4n0d6aP8CEIpUYGMVT1Yj/Q5Img=
github.com/ClickHouse/ch-go v0.66.0/go.mod h1:noiHWyLMJAZ5wYuq3R/K0TcRhrNA8h7o1AqHX0klEhM=
github.com/ClickHouse/clickhouse-go/v2 v2.34.0 h1:Y4rqkdrRHgExvC4o/NTbLdY5LFQ3LHS77/RNFxFX3Co=
github.com/ClickHouse/clickhouse-go/v2 v2.34.0/go.mod h1:yioSINoRLVZkLyDzdMXPLRIqhDvel8iLBlwh6Iefso8=
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -46,14 +43,13 @@ github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ4
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU=
github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
@@ -62,9 +58,10 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
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.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
@@ -77,8 +74,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs=
github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA=
github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs=
github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
@@ -94,8 +91,8 @@ github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -105,28 +102,27 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/microsoft/go-mssqldb v1.6.0/go.mod h1:00mDtPbeQCRGC1HwOOR5K/gr30P1NcEG0vx6Kbv2aJU=
github.com/microsoft/go-mssqldb v1.7.2 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP3b/PA=
github.com/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/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA=
github.com/microsoft/go-mssqldb v1.8.1 h1:/LPVjSb992vTa8CMVvliTMT//UAKj/jpe1xb/jJBjIk=
github.com/microsoft/go-mssqldb v1.8.1/go.mod h1:vp38dT33FGfVotRiTmDo3bFyaHq+p3LektQrjTULowo=
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU=
github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/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/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/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
@@ -141,10 +137,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.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=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
@@ -156,25 +151,30 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
go.opentelemetry.io/otel/sdk v1.25.0 h1:PDryEJPC8YJZQSyLY5eqLeafHtG+X7FWnf3aXMtxbqo=
go.opentelemetry.io/otel/sdk v1.25.0/go.mod h1:oFgzCM2zdsxKzz6zwpTZYLLQsFwc+K0daArPdIhuxkw=
go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys=
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
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/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@@ -183,7 +183,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@@ -192,17 +191,21 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
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.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -212,18 +215,27 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -231,9 +243,12 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
@@ -261,15 +276,15 @@ gorm.io/driver/clickhouse v0.6.1 h1:t7JMB6sLBXxN8hEO6RdzCbJCwq/jAEVZdwXlmQs1Sd4=
gorm.io/driver/clickhouse v0.6.1/go.mod h1:riMYpJcGZ3sJ/OAZZ1rEP1j/Y0H6cByOAnwz7fo2AyM=
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8=
gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE=
gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
gorm.io/driver/sqlserver v1.5.3 h1:rjupPS4PVw+rjJkfvr8jn2lJ8BMhT4UW5FwuJY0P3Z0=
gorm.io/driver/sqlserver v1.5.3/go.mod h1:B+CZ0/7oFJ6tAlefsKoyxdgDCXJKSgwS2bMOQZT0I00=
gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314=
gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
gorm.io/driver/sqlserver v1.5.4 h1:xA+Y1KDNspv79q43bPyjDMUgHoYHLhXYmdFcYPobg8g=
gorm.io/driver/sqlserver v1.5.4/go.mod h1:+frZ/qYmuna11zHPlh5oc2O6ZA/lS88Keb0XSH1Zh/g=
gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
gorm.io/plugin/opentelemetry v0.1.7 h1:oKiwIOpNn0DlqZyHvpg7m8AT/YJ6XBBD1vp0F0rFtto=
gorm.io/plugin/opentelemetry v0.1.7/go.mod h1:TYGUagk7h8WwuCsDDznEzznY31PP3+NRpfh6FH7Yqfs=
gorm.io/gorm v1.26.1 h1:ghB2gUI9FkS46luZtn6DLZ0f6ooBJ5IbVej2ENFDjRw=
gorm.io/gorm v1.26.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
gorm.io/plugin/opentelemetry v0.1.14 h1:xivP39t/0JgcceDl+BLwVAJHihjFEUj0ZocMSBwZ7ZY=
gorm.io/plugin/opentelemetry v0.1.14/go.mod h1:ZAp4v5vU1CCcK9Oo8/va5rl6NStrzpSU+a70evd+W/g=

28
id/README.md Normal file
View 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
View 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
View 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
View 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
View 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)
}

61
id/snowflake.go Normal file
View File

@@ -0,0 +1,61 @@
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
}

25
id/sonyflake.go Normal file
View File

@@ -0,0 +1,25 @@
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()
}

36
id/uuid.go Normal file
View 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
View 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
View 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
View 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
View 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
View 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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -1,16 +1,16 @@
package rand
import (
"math/rand"
"time"
"github.com/tx7do/go-utils/math"
"math/rand"
)
var rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
var rnd = rand.New(rand.NewSource(Seed(UnixNanoSeed)))
func init() {
rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
if rnd != nil {
rnd = rand.New(rand.NewSource(Seed(UnixNanoSeed)))
}
}
func Float32() float32 {
@@ -59,18 +59,17 @@ func RandomInt64(min, max int64) int64 {
// RandomString 随机字符串,包含大小写字母和数字
func RandomString(l int) string {
if l <= 0 {
return ""
}
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
bytes := make([]byte, l)
for i := 0; i < l; i++ {
x := Intn(3)
switch x {
case 0:
bytes[i] = byte(RandomInt(65, 90)) //大写字母
case 1:
bytes[i] = byte(RandomInt(97, 122))
case 2:
bytes[i] = byte(Intn(10))
}
bytes[i] = charset[Intn(len(charset))]
}
return string(bytes)
}

View File

@@ -208,11 +208,13 @@ func TestRandomInt64_ZeroRange(t *testing.T) {
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) {
@@ -220,6 +222,7 @@ func TestRandomString_ContainsOnlyValidCharacters(t *testing.T) {
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) {

179
rand/randomizer.go Normal file
View 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
View 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
View 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
View 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
View 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
View 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=

View File

@@ -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
}

View File

@@ -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
View 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))
}

View 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)
}

15
tag.bat
View File

@@ -1,9 +1,14 @@
git tag v1.1.15
git tag v1.1.28
git tag bank_card/v1.1.4
git tag geoip/v1.1.4
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.1
git tag slug/v0.0.1
git tag entgo/v1.1.25
git tag gorm/v1.1.4
git tag entgo/v1.1.30
git tag gorm/v1.1.6
git push origin --tags

View File

@@ -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))

View File

@@ -1,7 +1,9 @@
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
View 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)
}
}
}

View File

@@ -9,31 +9,90 @@ import (
"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 {
@@ -43,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
}
@@ -73,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 字符串 -> 时间
@@ -85,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
}
@@ -115,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
}
@@ -138,7 +219,16 @@ func FloatToDurationpb(duration *float64, timePrecision time.Duration) *duration
if duration == nil {
return nil
}
return durationpb.New(time.Duration(*duration) * timePrecision)
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 {
@@ -180,3 +270,29 @@ func DurationpbToDuration(duration *durationpb.Duration) *time.Duration {
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())
}

View File

@@ -5,8 +5,11 @@ import (
"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) {
@@ -28,11 +31,195 @@ func TestUnixMilliToStringPtr(t *testing.T) {
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) {
@@ -56,3 +243,202 @@ func TestDurationpb(t *testing.T) {
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)
}

View File

@@ -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
}

View File

@@ -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
View 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
View 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
View 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=

View File

@@ -0,0 +1,20 @@
package google
type Option func(*options)
type options struct {
version string
apiKey string
}
func WithVersion(version string) Option {
return func(o *options) {
o.version = version
}
}
func WithApiKey(key string) Option {
return func(o *options) {
o.apiKey = key
}
}

View File

@@ -0,0 +1,40 @@
package google
import (
translateV2 "cloud.google.com/go/translate"
translateV3 "cloud.google.com/go/translate/apiv3"
)
type Translator struct {
options *options
clientV2 *translateV2.Client
clientV3 *translateV3.TranslationClient
}
func NewTranslator(opts ...Option) *Translator {
op := options{}
for _, o := range opts {
o(&op)
}
return &Translator{
options: &op,
}
}
func (t *Translator) Translate(source, sourceLang, targetLang string) (string, error) {
switch t.options.version {
default:
fallthrough
case "v1":
return t.TranslateV1(source, sourceLang, targetLang)
case "v2":
text, _, err := t.TranslateV2(source, sourceLang, targetLang)
return text, err
case "v3":
return t.TranslateV3(source, sourceLang, targetLang)
}
}

View File

@@ -0,0 +1,98 @@
package google
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"golang.org/x/text/language"
)
func TestTranslateV1(t *testing.T) {
translator := NewTranslator(
WithVersion("v1"),
)
assert.NotNil(t, translator)
const text string = `Hello, World!`
// you can use "auto" for source language
// so, translator will detect language
result, err := translator.TranslateV1(text, "en", "es")
assert.Nil(t, err)
fmt.Println(result)
// Output: "Hola, Mundo!"
result, err = translator.TranslateV1(text, "en", "zh-CN")
assert.Nil(t, err)
fmt.Println(result)
result, err = translator.TranslateV1(text, "en", "lo")
assert.Nil(t, err)
fmt.Println(result)
result, err = translator.TranslateV1(text, "en", "my")
assert.Nil(t, err)
fmt.Println(result)
}
func TestTranslateV2(t *testing.T) {
translator := NewTranslator(
WithVersion("v2"),
WithApiKey("apikey"),
)
assert.NotNil(t, translator)
const text string = `Hello, World!`
result, _, err := translator.TranslateV2(text, "en", "es")
assert.Nil(t, err)
fmt.Println(result)
// Output: "Hola, Mundo!"
result, _, err = translator.TranslateV2(text, "en", "zh-CN")
assert.Nil(t, err)
fmt.Println(result)
result, _, err = translator.TranslateV2(text, "en", "lo")
assert.Nil(t, err)
fmt.Println(result)
result, _, err = translator.TranslateV2(text, "en", "my")
assert.Nil(t, err)
fmt.Println(result)
}
func TestTranslateV3(t *testing.T) {
translator := NewTranslator(
WithVersion("v3"),
WithApiKey("apikey"),
)
assert.NotNil(t, translator)
const text string = `Hello, World!`
// you can use "auto" for source language
// so, translator will detect language
result, err := translator.TranslateV3(text, "en", "es")
assert.Nil(t, err)
fmt.Println(result)
// Output: "Hola, Mundo!"
result, err = translator.TranslateV3(text, "en", "zh-CN")
assert.Nil(t, err)
fmt.Println(result)
result, err = translator.TranslateV3(text, "en", "lo")
assert.Nil(t, err)
fmt.Println(result)
result, err = translator.TranslateV3(text, "en", "my")
assert.Nil(t, err)
fmt.Println(result)
}
func TestLanguageParse(t *testing.T) {
lang, _ := language.Parse("en")
assert.Equal(t, lang, language.English)
fmt.Println(lang)
fmt.Println(language.English.String())
}

View File

@@ -0,0 +1,9 @@
package google
import "net/url"
// javascript "encodeURI()"
// so we embed js to our golang program
func encodeURI(s string) string {
return url.QueryEscape(s)
}

55
translator/google/v1.go Normal file
View File

@@ -0,0 +1,55 @@
package google
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strings"
)
func (t *Translator) TranslateV1(source, sourceLang, targetLang string) (string, error) {
var text []string
var result []interface{}
encodedSource := encodeURI(source)
uri := "https://translate.googleapis.com/translate_a/single?client=gtx&sl=" +
sourceLang + "&tl=" + targetLang + "&dt=t&q=" + encodedSource
r, err := http.Get(uri)
if err != nil {
return "err", errors.New("error getting translate.googleapis.com")
}
defer r.Body.Close()
body, err := io.ReadAll(r.Body)
if err != nil {
return "err", errors.New("error reading response body")
}
bReq := strings.Contains(string(body), `<title>Error 400 (Bad Request)`)
if bReq {
return "err", errors.New("error 400 (Bad Request)")
}
err = json.Unmarshal(body, &result)
if err != nil {
return "err", errors.New("error unmarshalling data")
}
if len(result) > 0 {
inner := result[0]
for _, slice := range inner.([]interface{}) {
for _, translatedText := range slice.([]interface{}) {
text = append(text, fmt.Sprintf("%v", translatedText))
break
}
}
cText := strings.Join(text, "")
return cText, nil
} else {
return "err", errors.New("no translated data in response")
}
}

33
translator/google/v2.go Normal file
View File

@@ -0,0 +1,33 @@
package google
import (
"context"
translateV2 "cloud.google.com/go/translate"
"golang.org/x/text/language"
"google.golang.org/api/option"
)
func (t *Translator) TranslateV2(source, _, targetLanguage string) (string, language.Tag, error) {
ctx := context.Background()
lang, err := language.Parse(targetLanguage)
if err != nil {
return "", language.English, err
}
if t.clientV2 == nil {
client, err := translateV2.NewClient(ctx, option.WithAPIKey(t.options.apiKey))
if err != nil {
return "", language.English, err
}
t.clientV2 = client
}
resp, err := t.clientV2.Translate(ctx, []string{source}, lang, nil)
if err != nil {
return "", language.English, err
}
return resp[0].Text, resp[0].Source, nil
}

40
translator/google/v3.go Normal file
View File

@@ -0,0 +1,40 @@
package google
import (
"context"
"fmt"
translateV3 "cloud.google.com/go/translate/apiv3"
"cloud.google.com/go/translate/apiv3/translatepb"
"google.golang.org/api/option"
)
func (t *Translator) TranslateV3(source, sourceLang, targetLang string) (string, error) {
ctx := context.Background()
if t.clientV3 == nil {
client, err := translateV3.NewTranslationClient(ctx, option.WithAPIKey(t.options.apiKey))
if err != nil {
return "", fmt.Errorf("NewTranslationClient: %w", err)
}
t.clientV3 = client
}
req := &translatepb.TranslateTextRequest{
SourceLanguageCode: sourceLang,
TargetLanguageCode: targetLang,
MimeType: "text/plain", // Mime types: "text/plain", "text/html"
Contents: []string{source},
}
resp, err := t.clientV3.TranslateText(ctx, req)
if err != nil {
return "", fmt.Errorf("TranslateText: %w", err)
}
if len(resp.GetTranslations()) != 1 {
return "", fmt.Errorf("TranslateText: %w", err)
}
return resp.GetTranslations()[0].GetTranslatedText(), nil
}

7
translator/translator.go Normal file
View File

@@ -0,0 +1,7 @@
package translator
// Translator 翻译器
type Translator interface {
// Translate 翻译
Translate(source, sourceLang, targetLang string) (string, error)
}

View File

@@ -0,0 +1,9 @@
package translator
import (
"testing"
)
func TestTranslator(t *testing.T) {
}

View File

@@ -10,10 +10,22 @@ cd %DIR%/bank_card
go get all
go mod tidy
cd %DIR%/geoip
go get all
go mod tidy
cd %DIR%/copierutil
go get all
go mod tidy
cd %DIR%/translator
go get all
go mod tidy
cd %DIR%/entgo
go get all
go mod tidy
cd %DIR%/geoip
cd %DIR%/gorm
go get all
go mod tidy
go mod tidy

View File

@@ -1,32 +0,0 @@
package uuid
import (
"github.com/google/uuid"
"github.com/tx7do/go-utils/trans"
)
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 = trans.String(id.String())
}
return strUUID
}

View File

@@ -1,66 +0,0 @@
package uuid
import (
"testing"
"github.com/google/uuid"
)
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)
}
})
}