Compare commits

..

39 Commits

Author SHA1 Message Date
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
Bobo
ef08927a50 feat: time trans 2025-04-02 15:18:08 +08:00
Bobo
6d209d3612 feat: time trans 2025-04-02 15:08:14 +08:00
Bobo
8f957a7d29 feat: time trans 2025-04-02 14:21:38 +08:00
Bobo
c83b10ca22 feat: entgo 2025-02-16 10:55:51 +08:00
Bobo
15b6b012c2 feat: entgo 2025-02-08 16:09:02 +08:00
Bobo
fdcb900700 feat: entgo 2025-02-08 16:00:54 +08:00
Bobo
ce04468424 feat: entgo. 2025-02-08 15:31:39 +08:00
Bobo
d1418caf84 feat: entgo. 2025-02-08 14:44:21 +08:00
Bobo
f73f016ec5 feat: entgo. 2025-02-08 00:08:10 +08:00
Bobo
d549d305ae feat: entgo. 2025-02-08 00:00:50 +08:00
Bobo
23dcad60a3 feat: time utils. 2025-02-07 22:42:19 +08:00
Bobo
fa7ae4f876 feat: time trans. 2025-01-11 13:22:23 +08:00
tx7do
9f6a4eba80 feat: ent support update json field. 2024-12-14 14:41:44 +08:00
tx7do
801deb98cd feat: ent support update json field. 2024-12-14 13:07:01 +08:00
tx7do
c9a0909d46 feat: upgrade go.mod. 2024-11-14 11:20:41 +08:00
tx7do
9be498db69 feat: upgrade go.mod. 2024-11-14 11:18:05 +08:00
tx7do
71cb2d28e1 feat: upgrade go.mod. 2024-10-18 12:55:04 +08:00
tx7do
e8e61fc7a1 feat: upgrade go.mod. 2024-10-18 12:52:55 +08:00
tx7do
f7abc4e941 feat: math. 2024-09-23 21:31:56 +08:00
59 changed files with 3181 additions and 3069 deletions

View File

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

View File

@@ -1,11 +1,11 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

104
copierutil/converters.go Normal file
View File

@@ -0,0 +1,104 @@
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 MakeTypeConverter(srcType, dstType interface{}, fn func(src interface{}) (interface{}, error)) copier.TypeConverter {
return copier.TypeConverter{
SrcType: srcType,
DstType: dstType,
Fn: fn,
}
}
func MakeTypeConverterPair(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 MakeGenericTypeConverterPair[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 MakeErrorHandlingTypeConverterPair[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,152 @@
package copierutil
import (
"testing"
"time"
"github.com/tx7do/go-utils/timeutil"
"github.com/tx7do/go-utils/trans"
)
func TestMakeTypeConverter(t *testing.T) {
srcType := &time.Time{}
dstType := trans.Ptr("")
fn := func(src interface{}) (interface{}, error) {
return timeutil.TimeToTimeString(src.(*time.Time)), nil
}
converter := MakeTypeConverter(srcType, dstType, fn)
// 验证转换器的类型
if converter.SrcType != srcType || converter.DstType != dstType {
t.Errorf("converter types mismatch")
}
// 验证转换器的功能
result, err := converter.Fn(&time.Time{})
if err != nil {
t.Errorf("converter function failed: %v", err)
}
if _, ok := result.(*string); !ok {
t.Errorf("converter result type mismatch")
}
}
func TestMakeTypeConverterPair(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 := MakeTypeConverterPair(srcType, dstType, fromFn, toFn)
if len(converters) != 2 {
t.Fatalf("expected 2 converters, got %d", len(converters))
}
// 验证第一个转换器
if converters[0].SrcType != srcType || converters[0].DstType != dstType {
t.Errorf("first converter types mismatch")
}
result, err := converters[0].Fn(&time.Time{})
if err != nil {
t.Errorf("first converter function failed: %v", err)
}
if _, ok := result.(*string); !ok {
t.Errorf("first converter result type mismatch")
}
// 验证第二个转换器
if converters[1].SrcType != dstType || converters[1].DstType != srcType {
t.Errorf("second converter types mismatch")
}
result, err = converters[1].Fn(trans.Ptr(""))
if err != nil {
t.Errorf("second converter function failed: %v", err)
}
if _, ok := result.(*time.Time); !ok {
t.Errorf("second converter result type mismatch")
}
}
func TestMakeGenericTypeConverterPair(t *testing.T) {
srcType := &time.Time{}
dstType := trans.Ptr("")
fromFn := timeutil.TimeToTimeString
toFn := timeutil.StringTimeToTime
converters := MakeGenericTypeConverterPair(srcType, dstType, fromFn, toFn)
if len(converters) != 2 {
t.Fatalf("expected 2 converters, got %d", len(converters))
}
// 验证第一个转换器
if converters[0].SrcType != srcType || converters[0].DstType != dstType {
t.Errorf("first converter types mismatch")
}
result, err := converters[0].Fn(&time.Time{})
if err != nil {
t.Errorf("first converter function failed: %v", err)
}
if _, ok := result.(*string); !ok {
t.Errorf("first converter result type mismatch")
}
// 验证第二个转换器
if converters[1].SrcType != dstType || converters[1].DstType != srcType {
t.Errorf("second converter types mismatch")
}
result, err = converters[1].Fn(trans.Ptr(""))
if err != nil {
t.Errorf("second converter function failed: %v", err)
}
if _, ok := result.(*time.Time); !ok {
t.Errorf("second converter result type mismatch")
}
}
func TestMakeErrorHandlingTypeConverterPair(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 := MakeErrorHandlingTypeConverterPair(srcType, dstType, fromFn, toFn)
if len(converters) != 2 {
t.Fatalf("expected 2 converters, got %d", len(converters))
}
// 验证第一个转换器
if converters[0].SrcType != srcType || converters[0].DstType != dstType {
t.Errorf("first converter types mismatch")
}
result, err := converters[0].Fn(&time.Time{})
if err != nil {
t.Errorf("first converter function failed: %v", err)
}
if _, ok := result.(*string); !ok {
t.Errorf("first converter result type mismatch")
}
// 验证第二个转换器
if converters[1].SrcType != dstType || converters[1].DstType != srcType {
t.Errorf("second converter types mismatch")
}
result, err = converters[1].Fn(trans.Ptr(""))
if err != nil {
t.Errorf("second converter function failed: %v", err)
}
if _, ok := result.(*time.Time); !ok {
t.Errorf("second converter result type mismatch")
}
}

14
copierutil/go.mod Normal file
View File

@@ -0,0 +1,14 @@
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.22
)
require google.golang.org/protobuf v1.36.6
replace github.com/tx7do/go-utils => ../

16
copierutil/go.sum Normal file
View File

@@ -0,0 +1,16 @@
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/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=
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/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

@@ -15,16 +15,16 @@ import (
entSql "entgo.io/ent/dialect/sql"
)
type entClientInterface interface {
type EntClientInterface interface {
Close() error
}
type EntClient[T entClientInterface] struct {
type EntClient[T EntClientInterface] struct {
db T
drv *entSql.Driver
}
func NewEntClient[T entClientInterface](db T, drv *entSql.Driver) *EntClient[T] {
func NewEntClient[T EntClientInterface](db T, drv *entSql.Driver) *EntClient[T] {
return &EntClient[T]{
db: db,
drv: drv,

View File

@@ -1,46 +1,49 @@
module github.com/tx7do/go-utils/entgo
go 1.21
go 1.23.0
toolchain go1.22.1
toolchain go1.23.2
require (
entgo.io/contrib v0.5.0
entgo.io/ent v0.13.1
github.com/go-kratos/kratos/v2 v2.7.3
entgo.io/contrib v0.6.0
entgo.io/ent v0.14.4
github.com/XSAM/otelsql v0.38.0
github.com/go-kratos/kratos/v2 v2.8.4
github.com/google/uuid v1.6.0
github.com/stretchr/testify v1.9.0
github.com/tx7do/go-utils v1.1.11
github.com/stretchr/testify v1.10.0
github.com/tx7do/go-utils v1.1.22
go.opentelemetry.io/otel v1.36.0
google.golang.org/protobuf v1.36.6
)
require (
ariga.io/atlas v0.19.1-0.20240203083654-5948b60a8e43 // indirect
github.com/XSAM/otelsql v0.30.0 // indirect
ariga.io/atlas v0.33.1 // indirect
github.com/agext/levenshtein v1.2.3 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/bufbuild/protocompile v0.6.0 // indirect
github.com/bmatcuk/doublestar v1.3.4 // indirect
github.com/bufbuild/protocompile v0.14.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/inflect v0.19.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/hashicorp/hcl/v2 v2.19.1 // indirect
github.com/jhump/protoreflect v1.15.3 // indirect
github.com/go-openapi/inflect v0.21.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/hashicorp/hcl/v2 v2.23.0 // indirect
github.com/jhump/protoreflect v1.17.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sony/sonyflake v1.2.0 // indirect
github.com/zclconf/go-cty v1.14.1 // indirect
go.opentelemetry.io/otel v1.26.0 // indirect
go.opentelemetry.io/otel/metric v1.26.0 // indirect
go.opentelemetry.io/otel/trace v1.26.0 // indirect
github.com/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.36.0 // indirect
go.opentelemetry.io/otel/trace v1.36.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/mod v0.15.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.18.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/sync v0.14.0 // indirect
golang.org/x/text v0.25.0 // indirect
golang.org/x/tools v0.33.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@@ -1,50 +1,48 @@
ariga.io/atlas v0.19.1-0.20240203083654-5948b60a8e43 h1:GwdJbXydHCYPedeeLt4x/lrlIISQ4JTH1mRWuE5ZZ14=
ariga.io/atlas v0.19.1-0.20240203083654-5948b60a8e43/go.mod h1:uj3pm+hUTVN/X5yfdBexHlZv+1Xu5u5ZbZx7+CDavNU=
entgo.io/contrib v0.5.0 h1:M4IqodImfUm327RDwNAITLNz3PsxVeC3rD4DPeVA8Gs=
entgo.io/contrib v0.5.0/go.mod h1:q8dXQCmzqpSlVdT2bWDydjgznGcy3y4zmsYmVFC9V/U=
entgo.io/ent v0.13.1 h1:uD8QwN1h6SNphdCCzmkMN3feSUzNnVvV/WIkHKMbzOE=
entgo.io/ent v0.13.1/go.mod h1:qCEmo+biw3ccBn9OyL4ZK5dfpwg++l1Gxwac5B1206A=
ariga.io/atlas v0.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=
entgo.io/ent v0.14.4/go.mod h1:aDPE/OziPEu8+OWbzy4UlvWmD2/kbRuWfK2A40hcxJM=
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/XSAM/otelsql v0.30.0 h1:yd6Ds3xQkKtP5+JztH2un5Hfy9uyo1eUgv34dGpSIK4=
github.com/XSAM/otelsql v0.30.0/go.mod h1:12ObuENPHhAcc2cU89u7Yr0uT60+FliFCTP9Sd4N68o=
github.com/XSAM/otelsql v0.38.0 h1:zWU0/YM9cJhPE71zJcQ2EBHwQDp+G4AX2tPpljslaB8=
github.com/XSAM/otelsql v0.38.0/go.mod h1:5ePOgcLEkWvZtN9H3GV4BUlPeM3p3pzLDCnRG73X8h8=
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
github.com/bufbuild/protocompile v0.6.0 h1:Uu7WiSQ6Yj9DbkdnOe7U4mNKp58y9WDMKDn28/ZlunY=
github.com/bufbuild/protocompile v0.6.0/go.mod h1:YNP35qEYoYGme7QMtz5SBCoN4kL4g12jTtjuzRNdjpE=
github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0=
github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw=
github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-kratos/kratos/v2 v2.7.3 h1:T9MS69qk4/HkVUuHw5GS9PDVnOfzn+kxyF0CL5StqxA=
github.com/go-kratos/kratos/v2 v2.7.3/go.mod h1:CQZ7V0qyVPwrotIpS5VNNUJNzEbcyRUl5pRtxLOIvn4=
github.com/go-kratos/kratos/v2 v2.8.4 h1:eIJLE9Qq9WSoKx+Buy2uPyrahtF/lPh+Xf4MTpxhmjs=
github.com/go-kratos/kratos/v2 v2.8.4/go.mod h1:mq62W2101a5uYyRxe+7IdWubu7gZCGYqSNKwGFiiRcw=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.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/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4=
github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
github.com/go-openapi/inflect v0.21.2 h1:0gClGlGcxifcJR56zwvhaOulnNgnhc4qTAkob5ObnSM=
github.com/go-openapi/inflect v0.21.2/go.mod h1:INezMuUu7SJQc2AyR3WO0DqqYUJSj8Kb4hBd7WtjlAw=
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/hcl/v2 v2.19.1 h1://i05Jqznmb2EXqa39Nsvyan2o5XyMowW5fnCKW5RPI=
github.com/hashicorp/hcl/v2 v2.19.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE=
github.com/jhump/protoreflect v1.15.3 h1:6SFRuqU45u9hIZPJAoZ8c28T3nK64BNdp9w6jFonzls=
github.com/jhump/protoreflect v1.15.3/go.mod h1:4ORHmSBmlCW8fh3xHmJMGyul1zNqZK4Elxc8qKP+p1k=
github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos=
github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94=
github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
@@ -53,46 +51,51 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sony/sonyflake v1.2.0 h1:Pfr3A+ejSg+0SPqpoAmQgEtNDAhc2G1SUYk205qVMLQ=
github.com/sony/sonyflake v1.2.0/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/zclconf/go-cty v1.14.1 h1:t9fyA35fwjjUMcmL5hLER+e/rEPqrbCK1/OSE4SI9KA=
github.com/zclconf/go-cty v1.14.1/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs=
go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4=
go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30=
go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4=
go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA=
go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/sony/sonyflake v1.2.1 h1:Jzo4abS84qVNbYamXZdrZF1/6TzNJjEogRfXv7TsG48=
github.com/sony/sonyflake v1.2.1/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/zclconf/go-cty v1.16.3 h1:osr++gw2T61A8KVYHoQiFbFd1Lh3JOCXc/jFLJXKTxk=
github.com/zclconf/go-cty v1.16.3/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
github.com/zclconf/go-cty-yaml v1.1.0 h1:nP+jp0qPHv2IhUVqmQSzjvqAWcObN0KBkUl2rWBdig0=
github.com/zclconf/go-cty-yaml v1.1.0/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ=
golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto v0.0.0-20230629202037-9506855d4529 h1:9JucMWR7sPvCxUFd6UsOUNmA5kCcWOfORaT3tpAsKQs=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529 h1:DEH99RbiLZhMxrpEJCZ0A+wdTe0EOgou/poSLx9vWf4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU=
google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY=
google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

View File

@@ -9,8 +9,8 @@
| query | `string` | `json object``json object array` | AND过滤条件 | json字符串: `{"field1":"val1","field2":"val2"}` 或者`[{"field1":"val1"},{"field1":"val2"},{"field2":"val2"}]` | `map``array`都支持,当需要同字段名,不同值的情况下,请使用`array`。具体规则请见:[过滤规则](#过滤规则) |
| or | `string` | `json object``json object array` | OR过滤条件 | 同 AND过滤条件 | |
| orderBy | `string` | `json string array` | 排序条件 | json字符串`["-create_time", "type"]` | json的`string array`,字段名前加`-`是为降序,不加为升序。具体规则请见:[排序规则](#排序规则) |
| nopaging | `boolean` | | 是否不分页 | | 此字段为`true`时,`page``pageSize`字段的传入将无效用。 |
| fieldMask | `string` | `json string array` | 字段掩码 | | 此字段是`SELECT`条件,为空的时候是为`*`。 |
| noPaging | `boolean` | | 是否不分页 | | 此字段为`true`时,`page``pageSize`字段的传入将无效用。 |
| fieldMask | `string` | 其语法为使用逗号分隔字段名 | 字段掩码 | 例如id,realName,userName。 | 此字段是`SELECT`条件,为空的时候是为`*`。 |
## 排序规则
@@ -36,6 +36,7 @@
```text
{字段名}__{查找类型} : {值}
{字段名}.{JSON字段名}__{查找类型} : {值}
```
| 查找类型 | 示例 | SQL | 备注 |

View File

@@ -1,10 +1,11 @@
package entgo
import (
"encoding/json"
"fmt"
"strings"
"encoding/json"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
@@ -63,16 +64,6 @@ var ops = [...]string{
FilterSearch: "search",
}
func hasOperations(str string) bool {
str = strings.ToLower(str)
for _, item := range ops {
if str == item {
return true
}
}
return false
}
type DatePart int
const (
@@ -109,6 +100,38 @@ var dateParts = [...]string{
DatePartMicrosecond: "microsecond",
}
const (
QueryDelimiter = "__" // 分隔符
JsonFieldDelimiter = "." // JSONB字段分隔符
)
// splitQueryKey 分割查询键
func splitQueryKey(key string) []string {
return strings.Split(key, QueryDelimiter)
}
// splitJsonFieldKey 分割JSON字段键
func splitJsonFieldKey(key string) []string {
return strings.Split(key, JsonFieldDelimiter)
}
// isJsonFieldKey 是否为JSON字段键
func isJsonFieldKey(key string) bool {
return strings.Contains(key, JsonFieldDelimiter)
}
// hasOperations 是否有操作
func hasOperations(str string) bool {
str = strings.ToLower(str)
for _, item := range ops {
if str == item {
return true
}
}
return false
}
// hasDatePart 是否有日期部分
func hasDatePart(str string) bool {
str = strings.ToLower(str)
for _, item := range dateParts {
@@ -119,6 +142,32 @@ func hasDatePart(str string) bool {
return false
}
// BuildFilterSelector 构建过滤选择器
func BuildFilterSelector(andFilterJsonString, orFilterJsonString string) (error, []func(s *sql.Selector)) {
var err error
var queryConditions []func(s *sql.Selector)
var andSelector func(s *sql.Selector)
err, andSelector = QueryCommandToWhereConditions(andFilterJsonString, false)
if err != nil {
return err, nil
}
if andSelector != nil {
queryConditions = append(queryConditions, andSelector)
}
var orSelector func(s *sql.Selector)
err, orSelector = QueryCommandToWhereConditions(orFilterJsonString, true)
if err != nil {
return err, nil
}
if orSelector != nil {
queryConditions = append(queryConditions, orSelector)
}
return nil, queryConditions
}
// QueryCommandToWhereConditions 查询命令转换为选择条件
func QueryCommandToWhereConditions(strJson string, isOr bool) (error, func(s *sql.Selector)) {
if len(strJson) == 0 {
@@ -150,14 +199,13 @@ func QueryCommandToWhereConditions(strJson string, isOr bool) (error, func(s *sq
}
}
// processQueryMap 处理查询映射表
func processQueryMap(s *sql.Selector, queryMap map[string]string) []*sql.Predicate {
var ps []*sql.Predicate
for k, v := range queryMap {
key := stringcase.ToSnakeCase(k)
keys := splitQueryKey(k)
keys := strings.Split(key, "__")
if cond := oneFieldFilter(s, keys, v); cond != nil {
if cond := makeFieldFilter(s, keys, v); cond != nil {
ps = append(ps, cond)
}
}
@@ -165,32 +213,8 @@ func processQueryMap(s *sql.Selector, queryMap map[string]string) []*sql.Predica
return ps
}
func BuildFilterSelector(andFilterJsonString, orFilterJsonString string) (error, []func(s *sql.Selector)) {
var err error
var queryConditions []func(s *sql.Selector)
var andSelector func(s *sql.Selector)
err, andSelector = QueryCommandToWhereConditions(andFilterJsonString, false)
if err != nil {
return err, nil
}
if andSelector != nil {
queryConditions = append(queryConditions, andSelector)
}
var orSelector func(s *sql.Selector)
err, orSelector = QueryCommandToWhereConditions(orFilterJsonString, true)
if err != nil {
return err, nil
}
if orSelector != nil {
queryConditions = append(queryConditions, orSelector)
}
return nil, queryConditions
}
func oneFieldFilter(s *sql.Selector, keys []string, value string) *sql.Predicate {
// makeFieldFilter 构建一个字段过滤器
func makeFieldFilter(s *sql.Selector, keys []string, value string) *sql.Predicate {
if len(keys) == 0 {
return nil
}
@@ -207,6 +231,21 @@ func oneFieldFilter(s *sql.Selector, keys []string, value string) *sql.Predicate
switch len(keys) {
case 1:
if isJsonFieldKey(field) {
jsonFields := splitJsonFieldKey(field)
if len(jsonFields) != 2 {
field = stringcase.ToSnakeCase(field)
return filterEqual(s, p, field, value)
}
//value = "'" + value + "'"
return filterJsonb(
s, p,
stringcase.ToSnakeCase(jsonFields[1]),
stringcase.ToSnakeCase(jsonFields[0]),
).
EQ("", value)
}
field = stringcase.ToSnakeCase(field)
return filterEqual(s, p, field, value)
case 2:
@@ -215,6 +254,19 @@ func oneFieldFilter(s *sql.Selector, keys []string, value string) *sql.Predicate
return nil
}
if isJsonFieldKey(field) {
jsonFields := splitJsonFieldKey(field)
if len(jsonFields) == 2 {
field = filterJsonbField(s,
stringcase.ToSnakeCase(jsonFields[1]),
stringcase.ToSnakeCase(jsonFields[0]),
)
//value = "'" + value + "'"
}
} else {
field = stringcase.ToSnakeCase(field)
}
var cond *sql.Predicate
if hasOperations(op) {
return processOp(s, p, op, field, value)
@@ -241,10 +293,22 @@ func oneFieldFilter(s *sql.Selector, keys []string, value string) *sql.Predicate
//var cond *sql.Predicate
if hasDatePart(op1) {
if isJsonFieldKey(field) {
jsonFields := splitJsonFieldKey(field)
if len(jsonFields) == 2 {
field = filterJsonbField(s, jsonFields[1], jsonFields[0])
//value = "'" + value + "'"
}
} else {
field = stringcase.ToSnakeCase(field)
}
str := filterDatePartField(s, op1, field)
if hasOperations(op2) {
return processOp(s, p, op2, str, value)
}
return nil
} else {
str := filterJsonbField(s, op1, field)
@@ -547,6 +611,7 @@ func filterDatePart(s *sql.Selector, p *sql.Predicate, datePart, field string) *
return p
}
// filterDatePartField 日期
func filterDatePartField(s *sql.Selector, datePart, field string) string {
p := sql.P()
switch s.Builder.Dialect() {
@@ -565,12 +630,14 @@ func filterDatePartField(s *sql.Selector, datePart, field string) string {
}
// filterJsonb 提取JSONB字段
// Postgresql: WHERE ("app_profile"."preferences" -> daily_email) = 'true'
// Postgresql: WHERE ("app_profile"."preferences" ->> 'daily_email') = 'true'
func filterJsonb(s *sql.Selector, p *sql.Predicate, jsonbField, field string) *sql.Predicate {
field = stringcase.ToSnakeCase(field)
p.Append(func(b *sql.Builder) {
switch s.Builder.Dialect() {
case dialect.Postgres:
b.Ident(s.C(field)).WriteString(" -> ").WriteString(jsonbField)
b.Ident(s.C(field)).WriteString(" ->> ").WriteString("'" + jsonbField + "'")
//b.Arg(strings.ToLower(value))
break
@@ -584,11 +651,14 @@ func filterJsonb(s *sql.Selector, p *sql.Predicate, jsonbField, field string) *s
return p
}
// filterJsonbField JSONB字段
func filterJsonbField(s *sql.Selector, jsonbField, field string) string {
field = stringcase.ToSnakeCase(field)
p := sql.P()
switch s.Builder.Dialect() {
case dialect.Postgres:
p.Ident(s.C(field)).WriteString(" -> ").WriteString(jsonbField)
p.Ident(s.C(field)).WriteString(" ->> ").WriteString("'" + jsonbField + "'")
//b.Arg(strings.ToLower(value))
break

View File

@@ -681,14 +681,104 @@ func TestFilter(t *testing.T) {
s.Where(p)
query, args := s.Query()
require.Equal(t, "SELECT * FROM \"app_profile\" WHERE \"app_profile\".\"preferences\" -> daily_email = $1", query)
require.Equal(t, "SELECT * FROM \"app_profile\" WHERE \"app_profile\".\"preferences\" ->> 'daily_email' = $1", query)
require.NotEmpty(t, args)
require.Equal(t, args[0], "true")
})
}
func TestFilterJsonbField(t *testing.T) {
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("app_profile"))
str := filterJsonbField(s, "daily_email", "preferences")
fmt.Println(str)
t.Run("filterJsonbField", func(t *testing.T) {
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("app_profile"))
str := filterJsonbField(s, "daily_email", "preferences")
fmt.Println(str)
require.Equal(t, str, "\"app_profile\".\"preferences\" ->> 'daily_email'")
})
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
t.Run("MySQL_FilterEqual", func(t *testing.T) {
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("menus"))
p := sql.P()
p = makeFieldFilter(s, []string{"meta.title"}, "tom")
s.Where(p)
query, args := s.Query()
require.Equal(t, "SELECT * FROM `menus` WHERE JSON_EXTRACT(`menus`.`meta`, '$.title') = ?", query)
require.NotEmpty(t, args)
require.Equal(t, args[0], "'tom'")
})
t.Run("PostgreSQL_FilterEqual", func(t *testing.T) {
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("menus"))
p := sql.P()
p = makeFieldFilter(s, []string{"meta.title"}, "tom")
s.Where(p)
query, args := s.Query()
require.Equal(t, "SELECT * FROM \"menus\" WHERE \"menus\".\"meta\" ->> 'title' = $1", query)
require.NotEmpty(t, args)
require.Equal(t, args[0], "'tom'")
})
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
t.Run("MySQL_FilterNot", func(t *testing.T) {
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
p := sql.P()
p = makeFieldFilter(s, []string{"meta.title", "not"}, "tom")
s.Where(p)
query, args := s.Query()
require.Equal(t, "SELECT * FROM `users` WHERE NOT JSON_EXTRACT(`users`.`meta`, '$.title') = ?", query)
require.NotEmpty(t, args)
require.Equal(t, args[0], "'tom'")
})
t.Run("PostgreSQL_FilterNot", func(t *testing.T) {
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
p := sql.P()
p = makeFieldFilter(s, []string{"meta.title", "not"}, "tom")
s.Where(p)
query, args := s.Query()
require.Equal(t, "SELECT * FROM \"users\" WHERE NOT \"users\".\"meta\" ->> 'title' = $1", query)
require.NotEmpty(t, args)
require.Equal(t, args[0], "'tom'")
})
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
t.Run("MySQL_FilterNot_Date", func(t *testing.T) {
s := sql.Dialect(dialect.MySQL).Select("*").From(sql.Table("users"))
p := sql.P()
p = makeFieldFilter(s, []string{"meta.title", "date", "not"}, "2023-01-01")
s.Where(p)
query, args := s.Query()
require.Equal(t, "SELECT * FROM `users` WHERE NOT DATE(JSON_EXTRACT(`users`.`meta`, '$.title')) = ?", query)
require.NotEmpty(t, args)
require.Equal(t, args[0], "'2023-01-01'")
})
t.Run("PostgreSQL_FilterNot_Date", func(t *testing.T) {
s := sql.Dialect(dialect.Postgres).Select("*").From(sql.Table("users"))
p := sql.P()
p = makeFieldFilter(s, []string{"meta.title", "date", "not"}, "2023-01-01")
s.Where(p)
query, args := s.Query()
require.Equal(t, "SELECT * FROM \"users\" WHERE NOT EXTRACT('DATE' FROM \"users\".\"meta\" ->> 'title') = $1", query)
require.NotEmpty(t, args)
require.Equal(t, args[0], "'2023-01-01'")
})
}

View File

@@ -3,7 +3,6 @@ package entgo
import (
"encoding/json"
"fmt"
"strings"
"testing"
"entgo.io/ent/dialect"
@@ -11,6 +10,7 @@ import (
"github.com/go-kratos/kratos/v2/encoding"
_ "github.com/go-kratos/kratos/v2/encoding/json"
"github.com/stretchr/testify/assert"
)
@@ -85,11 +85,11 @@ func TestJsonCodec(t *testing.T) {
func TestSplitQuery(t *testing.T) {
var keys []string
keys = strings.Split("id", "__")
keys = splitQueryKey("id")
assert.Equal(t, len(keys), 1)
assert.Equal(t, keys[0], "id")
keys = strings.Split("id__not", "__")
keys = splitQueryKey("id__not")
assert.Equal(t, len(keys), 2)
assert.Equal(t, keys[0], "id")
assert.Equal(t, keys[1], "not")
@@ -111,19 +111,22 @@ func TestBuildQuerySelectorDefault(t *testing.T) {
{"PostgreSQL_NoPagination", dialect.Postgres, "", "", true, "SELECT * FROM \"users\" ORDER BY \"users\".\"created_at\" DESC"},
{"MySQL_JsonbQuery", dialect.MySQL, "{\"preferences__daily_email\" : \"true\"}", "", true, "SELECT * FROM `users` WHERE JSON_EXTRACT(`users`.`preferences`, '$.daily_email') = ? ORDER BY `users`.`created_at` DESC"},
{"PostgreSQL_JsonbQuery", dialect.Postgres, "{\"preferences__daily_email\" : \"true\"}", "", true, "SELECT * FROM \"users\" WHERE \"users\".\"preferences\" -> daily_email = $1 ORDER BY \"users\".\"created_at\" DESC"},
{"PostgreSQL_JsonbQuery", dialect.Postgres, "{\"preferences__daily_email\" : \"true\"}", "", true, "SELECT * FROM \"users\" WHERE \"users\".\"preferences\" ->> 'daily_email' = $1 ORDER BY \"users\".\"created_at\" DESC"},
{"MySQL_DatePartQuery", dialect.MySQL, "{\"created_at__date\" : \"2023-01-01\"}", "", true, "SELECT * FROM `users` WHERE DATE(`users`.`created_at`) = ? ORDER BY `users`.`created_at` DESC"},
{"PostgreSQL_DatePartQuery", dialect.Postgres, "{\"created_at__date\" : \"2023-01-01\"}", "", true, "SELECT * FROM \"users\" WHERE EXTRACT('DATE' FROM \"users\".\"created_at\") = $1 ORDER BY \"users\".\"created_at\" DESC"},
{"MySQL_JsonbCombineQuery", dialect.MySQL, "{\"preferences__pub_date__not\" : \"true\"}", "", true, "SELECT * FROM `users` WHERE NOT JSON_EXTRACT(`users`.`preferences`, '$.pub_date') = ? ORDER BY `users`.`created_at` DESC"},
{"PostgreSQL_JsonbCombineQuery", dialect.Postgres, "{\"preferences__pub_date__not\" : \"true\"}", "", true, "SELECT * FROM \"users\" WHERE NOT \"users\".\"preferences\" -> pub_date = $1 ORDER BY \"users\".\"created_at\" DESC"},
{"PostgreSQL_JsonbCombineQuery", dialect.Postgres, "{\"preferences__pub_date__not\" : \"true\"}", "", true, "SELECT * FROM \"users\" WHERE NOT \"users\".\"preferences\" ->> 'pub_date' = $1 ORDER BY \"users\".\"created_at\" DESC"},
{"MySQL_DatePartCombineQuery", dialect.MySQL, "{\"pub_date__date__not\" : \"true\"}", "", true, "SELECT * FROM `users` WHERE NOT DATE(`users`.`pub_date`) = ? ORDER BY `users`.`created_at` DESC"},
{"PostgreSQL_DatePartCombineQuery", dialect.Postgres, "{\"pub_date__date__not\" : \"true\"}", "", true, "SELECT * FROM \"users\" WHERE NOT EXTRACT('DATE' FROM \"users\".\"pub_date\") = $1 ORDER BY \"users\".\"created_at\" DESC"},
{"MySQL_DatePartRangeQuery", dialect.MySQL, "{\"pub_date__date__range\" : \"[\\\"2023-10-25\\\", \\\"2024-10-25\\\"]\"}", "", true, "SELECT * FROM `users` WHERE DATE(`users`.`pub_date`) >= ? AND DATE(`users`.`pub_date`) <= ? ORDER BY `users`.`created_at` DESC"},
{"PostgreSQL_DatePartRangeQuery", dialect.Postgres, "{\"pub_date__date__range\" : \"[\\\"2023-10-25\\\", \\\"2024-10-25\\\"]\"}", "", true, "SELECT * FROM \"users\" WHERE EXTRACT('DATE' FROM \"users\".\"pub_date\") >= $1 AND EXTRACT('DATE' FROM \"users\".\"pub_date\") <= $2 ORDER BY \"users\".\"created_at\" DESC"},
{"MySQL_JsonQuery", dialect.MySQL, "{\"meta.title\" : \"preferences__daily_email\"}", "", true, "SELECT * FROM `users` WHERE JSON_EXTRACT(`users`.`meta`, '$.title') = ? ORDER BY `users`.`created_at` DESC"},
{"PostgreSQL_JsonQuery", dialect.Postgres, "{\"meta.title\" : \"preferences__daily_email\"}", "", true, "SELECT * FROM \"users\" WHERE \"users\".\"meta\" ->> 'title' = $1 ORDER BY \"users\".\"created_at\" DESC"},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {

View File

@@ -1,8 +1,16 @@
package entgo
import (
"fmt"
"strings"
"entgo.io/ent/dialect/sql"
"github.com/tx7do/go-utils/fieldmaskutil"
"github.com/tx7do/go-utils/stringcase"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
)
func BuildSetNullUpdate(u *sql.UpdateBuilder, fields []string) {
@@ -14,12 +22,79 @@ func BuildSetNullUpdate(u *sql.UpdateBuilder, fields []string) {
}
}
func BuildSetNullUpdater(fields []string) (error, func(u *sql.UpdateBuilder)) {
if len(fields) > 0 {
return nil, func(u *sql.UpdateBuilder) {
BuildSetNullUpdate(u, fields)
}
} else {
return nil, nil
// BuildSetNullUpdater 构建一个UpdateBuilder,用于清空字段的值
func BuildSetNullUpdater(fields []string) func(u *sql.UpdateBuilder) {
if len(fields) == 0 {
return nil
}
return func(u *sql.UpdateBuilder) {
BuildSetNullUpdate(u, fields)
}
}
// ExtractJsonFieldKeyValues 提取json字段的键值对
func ExtractJsonFieldKeyValues(msg proto.Message, paths []string, needToSnakeCase bool) []string {
var keyValues []string
rft := msg.ProtoReflect()
for _, path := range paths {
fd := rft.Descriptor().Fields().ByName(protoreflect.Name(path))
if fd == nil {
continue
}
if !rft.Has(fd) {
continue
}
var k string
if needToSnakeCase {
k = stringcase.ToSnakeCase(path)
} else {
k = path
}
keyValues = append(keyValues, fmt.Sprintf("'%s'", k))
v := rft.Get(fd)
switch v.Interface().(type) {
case int32, int64, uint32, uint64, float32, float64, bool:
keyValues = append(keyValues, fmt.Sprintf("%d", v.Interface()))
case string:
keyValues = append(keyValues, fmt.Sprintf("'%s'", v.Interface()))
}
}
return keyValues
}
// SetJsonNullFieldUpdateBuilder 设置json字段的空值
func SetJsonNullFieldUpdateBuilder(fieldName string, msg proto.Message, paths []string) func(u *sql.UpdateBuilder) {
nilPaths := fieldmaskutil.NilValuePaths(msg, paths)
if len(nilPaths) == 0 {
return nil
}
return func(u *sql.UpdateBuilder) {
u.Set(fieldName,
sql.Expr(
fmt.Sprintf("\"%s\" - '{%s}'::text[]", fieldName, strings.Join(nilPaths, ",")),
),
)
}
}
// SetJsonFieldValueUpdateBuilder 设置json字段的值
func SetJsonFieldValueUpdateBuilder(fieldName string, msg proto.Message, paths []string, needToSnakeCase bool) func(u *sql.UpdateBuilder) {
keyValues := ExtractJsonFieldKeyValues(msg, paths, needToSnakeCase)
if len(keyValues) == 0 {
return nil
}
return func(u *sql.UpdateBuilder) {
u.Set(fieldName,
sql.Expr(
fmt.Sprintf("\"%s\" || jsonb_build_object(%s)", fieldName, strings.Join(keyValues, ",")),
),
)
}
}

View File

@@ -1,21 +1,22 @@
module github.com/tx7do/go-utils/geoip
go 1.20
go 1.23.0
toolchain go1.24.1
require (
github.com/go-kratos/kratos/v2 v2.7.3
github.com/oschwald/geoip2-golang v1.9.0
github.com/stretchr/testify v1.9.0
golang.org/x/text v0.14.0
github.com/go-kratos/kratos/v2 v2.8.4
github.com/oschwald/geoip2-golang v1.11.0
github.com/stretchr/testify v1.10.0
golang.org/x/text v0.25.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/oschwald/maxminddb-golang v1.12.0 // indirect
github.com/oschwald/maxminddb-golang v1.13.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/sys v0.33.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@@ -1,27 +1,30 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-kratos/kratos/v2 v2.7.3 h1:T9MS69qk4/HkVUuHw5GS9PDVnOfzn+kxyF0CL5StqxA=
github.com/go-kratos/kratos/v2 v2.7.3/go.mod h1:CQZ7V0qyVPwrotIpS5VNNUJNzEbcyRUl5pRtxLOIvn4=
github.com/go-kratos/kratos/v2 v2.8.4 h1:eIJLE9Qq9WSoKx+Buy2uPyrahtF/lPh+Xf4MTpxhmjs=
github.com/go-kratos/kratos/v2 v2.8.4/go.mod h1:mq62W2101a5uYyRxe+7IdWubu7gZCGYqSNKwGFiiRcw=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/oschwald/geoip2-golang v1.9.0 h1:uvD3O6fXAXs+usU+UGExshpdP13GAqp4GBrzN7IgKZc=
github.com/oschwald/geoip2-golang v1.9.0/go.mod h1:BHK6TvDyATVQhKNbQBdrj9eAvuwOMi2zSFXizL3K81Y=
github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
github.com/oschwald/geoip2-golang v1.11.0 h1:hNENhCn1Uyzhf9PTmquXENiWS6AlxAEnBII6r8krA3w=
github.com/oschwald/geoip2-golang v1.11.0/go.mod h1:P9zG+54KPEFOliZ29i7SeYZ/GM6tfEL+rgSn03hYuUo=
github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

16
go.mod
View File

@@ -1,16 +1,18 @@
module github.com/tx7do/go-utils
go 1.20
go 1.23.0
toolchain go1.24.1
require (
github.com/gobwas/glob v0.2.3
github.com/google/uuid v1.6.0
github.com/gosimple/slug v1.14.0
github.com/sony/sonyflake v1.2.0
github.com/stretchr/testify v1.9.0
golang.org/x/crypto v0.22.0
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0
google.golang.org/protobuf v1.33.0
github.com/gosimple/slug v1.15.0
github.com/sony/sonyflake v1.2.1
github.com/stretchr/testify v1.10.0
golang.org/x/crypto v0.38.0
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6
google.golang.org/protobuf v1.36.6
)
require (

27
go.sum
View File

@@ -3,11 +3,12 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gosimple/slug v1.14.0 h1:RtTL/71mJNDfpUbCOmnf/XFkzKRtD6wL6Uy+3akm4Es=
github.com/gosimple/slug v1.14.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ=
github.com/gosimple/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=
@@ -23,16 +24,16 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/sony/sonyflake v1.2.0 h1:Pfr3A+ejSg+0SPqpoAmQgEtNDAhc2G1SUYk205qVMLQ=
github.com/sony/sonyflake v1.2.0/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 h1:985EYyeCOxTpcgOTJpflJUwOeEz0CQOdPt73OzpE9F8=
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
github.com/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=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

View File

@@ -1,52 +1,54 @@
module github.com/tx7do/go-utils/gorm
go 1.21
go 1.23.0
toolchain go1.22.1
toolchain go1.24.3
require (
gorm.io/driver/clickhouse v0.6.0
gorm.io/driver/mysql v1.5.6
gorm.io/driver/postgres v1.5.7
gorm.io/driver/sqlite v1.5.5
gorm.io/driver/sqlserver v1.5.3
gorm.io/gorm v1.25.10
gorm.io/plugin/opentelemetry v0.1.4
gorm.io/driver/clickhouse v0.6.1
gorm.io/driver/mysql v1.5.7
gorm.io/driver/postgres v1.5.11
gorm.io/driver/sqlite v1.5.7
gorm.io/driver/sqlserver v1.5.4
gorm.io/gorm v1.26.1
gorm.io/plugin/opentelemetry v0.1.14
)
require (
github.com/ClickHouse/ch-go v0.58.2 // indirect
github.com/ClickHouse/clickhouse-go/v2 v2.15.0 // indirect
github.com/andybalholm/brotli v1.0.6 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/ClickHouse/ch-go v0.66.0 // indirect
github.com/ClickHouse/clickhouse-go/v2 v2.34.0 // indirect
github.com/andybalholm/brotli v1.1.1 // indirect
github.com/go-faster/city v1.0.1 // indirect
github.com/go-faster/errors v0.6.1 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-faster/errors v0.7.1 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/go-sql-driver/mysql v1.9.2 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/google/uuid v1.3.1 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.4.3 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.7.5 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/klauspost/compress v1.16.7 // indirect
github.com/mattn/go-sqlite3 v1.14.17 // indirect
github.com/microsoft/go-mssqldb v1.6.0 // indirect
github.com/paulmach/orb v0.10.0 // indirect
github.com/pierrec/lz4/v4 v4.1.18 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/mattn/go-sqlite3 v1.14.28 // indirect
github.com/microsoft/go-mssqldb v1.8.1 // indirect
github.com/paulmach/orb v0.11.1 // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect
github.com/segmentio/asm v1.2.0 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
go.opentelemetry.io/otel v1.25.0 // indirect
go.opentelemetry.io/otel/metric v1.25.0 // indirect
go.opentelemetry.io/otel/sdk v1.25.0 // indirect
go.opentelemetry.io/otel/trace v1.25.0 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.13.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/otel v1.36.0 // indirect
go.opentelemetry.io/otel/metric v1.36.0 // indirect
go.opentelemetry.io/otel/trace v1.36.0 // indirect
golang.org/x/crypto v0.38.0 // indirect
golang.org/x/sync v0.14.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.25.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

File diff suppressed because it is too large Load Diff

View File

@@ -42,3 +42,30 @@ func StandardDeviation(num []float64) float64 {
var variance = Variance(mean, num)
return math.Sqrt(variance)
}
// SumInt 计算整数数组的和
func SumInt[T int | int32 | int64](array []T) int64 {
var sum int64
for _, v := range array {
sum = sum + int64(v)
}
return sum
}
// SumUint 计算整数数组的和
func SumUint[T uint | uint32 | uint64](array []T) uint64 {
var sum uint64
for _, v := range array {
sum = sum + uint64(v)
}
return sum
}
// SumFloat 计算浮点数数组的和
func SumFloat[T float32 | float64](array []T) float64 {
var sum float64
for _, v := range array {
sum = sum + float64(v)
}
return sum
}

View File

@@ -1,18 +1,52 @@
package rand
import (
"github.com/tx7do/go-utils/math"
"math/rand"
"time"
)
var RANDOM = rand.New(rand.NewSource(time.Now().UnixNano()))
var rnd = rand.New(rand.NewSource(Seed(UnixNanoSeed)))
func init() {
if rnd != nil {
rnd = rand.New(rand.NewSource(Seed(UnixNanoSeed)))
}
}
func Float32() float32 {
return rnd.Float32()
}
func Float64() float64 {
return rnd.Float64()
}
func Intn(n int) int {
return rnd.Intn(n)
}
func Int31n(n int32) int32 {
return rnd.Int31n(n)
}
func Int63n(n int64) int64 {
return rnd.Int63n(n)
}
// RandomInt 根据区间产生随机数
func RandomInt(min, max int) int {
if min >= max {
return max
}
return RANDOM.Intn(max-min) + min
return min + Intn(max-min+1)
}
// RandomInt32 根据区间产生随机数
func RandomInt32(min, max int32) int32 {
if min >= max {
return max
}
return min + Int31n(max-min+1)
}
// RandomInt64 根据区间产生随机数
@@ -20,5 +54,95 @@ func RandomInt64(min, max int64) int64 {
if min >= max {
return max
}
return RANDOM.Int63n(max-min) + min
return min + Int63n(max-min+1)
}
// RandomString 随机字符串,包含大小写字母和数字
func RandomString(l int) string {
if l <= 0 {
return ""
}
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
bytes := make([]byte, l)
for i := 0; i < l; i++ {
bytes[i] = charset[Intn(len(charset))]
}
return string(bytes)
}
// RandomChoice 随机选择数组中的元素
func RandomChoice[T any](array []T, n int) []T {
if n <= 0 {
return nil
}
if n == 1 {
return []T{array[Intn(len(array))]}
}
tmp := make([]T, len(array))
copy(tmp, array)
if len(tmp) <= n {
return tmp
}
Shuffle(tmp)
return tmp[:n]
}
// Shuffle 随机打乱数组
func Shuffle[T any](array []T) {
if array == nil {
return
}
for i := range array {
j := Intn(i + 1)
array[i], array[j] = array[j], array[i]
}
}
// WeightedChoice 根据权重随机返回对应选项的索引O(n)
func WeightedChoice(weightArray []int) int {
if weightArray == nil {
return -1
}
total := math.SumInt(weightArray)
rv := Int63n(total)
for i, v := range weightArray {
if rv < int64(v) {
return i
}
rv -= int64(v)
}
return len(weightArray) - 1
}
// NonWeightedChoice 根据权重随机返回对应选项的索引O(n). 权重大于等于0
func NonWeightedChoice(weightArray []int) int {
if weightArray == nil {
return -1
}
for i, weight := range weightArray {
if weight < 0 {
weightArray[i] = 0
}
}
total := math.SumInt(weightArray)
rv := Int63n(total)
for i, v := range weightArray {
if rv < int64(v) {
return i
}
rv -= int64(v)
}
return len(weightArray) - 1
}

View File

@@ -7,11 +7,289 @@ import (
"github.com/stretchr/testify/assert"
)
func TestRandomInt(t *testing.T) {
func TestFloat32_GeneratesValueWithinRange(t *testing.T) {
for i := 0; i < 1000; i++ {
n := RandomInt(1, 100)
fmt.Println(n)
assert.True(t, n >= 1)
assert.True(t, n < 100)
value := Float32()
assert.True(t, value >= 0.0)
assert.True(t, value < 1.0)
}
}
func TestFloat32_GeneratesDifferentValues(t *testing.T) {
values := make(map[float32]bool)
for i := 0; i < 1000; i++ {
value := Float32()
values[value] = true
}
assert.Greater(t, len(values), 1)
}
func TestFloat64_GeneratesValueWithinRange(t *testing.T) {
for i := 0; i < 1000; i++ {
value := Float64()
assert.True(t, value >= 0.0)
assert.True(t, value < 1.0)
}
}
func TestFloat64_GeneratesDifferentValues(t *testing.T) {
values := make(map[float64]bool)
for i := 0; i < 1000; i++ {
value := Float64()
values[value] = true
}
assert.Greater(t, len(values), 1)
}
func TestRandomInt(t *testing.T) {
for i := 0; i < 1000; i++ {
n := RandomInt(1, 10)
fmt.Println(n)
assert.True(t, n >= 1)
assert.True(t, n <= 100)
}
}
func TestRandomInt_MinEqualsMax(t *testing.T) {
n := RandomInt(5, 5)
assert.Equal(t, 5, n)
}
func TestRandomInt_MinGreaterThanMax(t *testing.T) {
n := RandomInt(10, 5)
assert.Equal(t, 5, n)
}
func TestRandomInt_NegativeRange(t *testing.T) {
for i := 0; i < 1000; i++ {
n := RandomInt(-10, -1)
assert.True(t, n >= -10)
assert.True(t, n <= -1)
}
}
func TestRandomInt_ZeroRange(t *testing.T) {
for i := 0; i < 1000; i++ {
n := RandomInt(0, 0)
assert.Equal(t, 0, n)
}
}
func TestShuffle_EmptyArray(t *testing.T) {
var array []int
Shuffle(array)
assert.Equal(t, array, array)
}
func TestShuffle_SingleElementArray(t *testing.T) {
array := []int{1}
Shuffle(array)
assert.Equal(t, []int{1}, array)
}
func TestShuffle_MultipleElementsArray(t *testing.T) {
array := []int{1, 2, 3, 4, 5}
original := make([]int, len(array))
copy(original, array)
Shuffle(array)
assert.ElementsMatch(t, original, array)
}
func TestShuffle_ArrayWithDuplicates(t *testing.T) {
array := []int{1, 2, 2, 3, 3, 3}
original := make([]int, len(array))
copy(original, array)
Shuffle(array)
fmt.Println(array)
assert.ElementsMatch(t, original, array)
}
func TestRandomChoice_EmptyArray(t *testing.T) {
result := RandomChoice([]int{}, 3)
assert.Nil(t, result)
}
func TestRandomChoice_NegativeN(t *testing.T) {
array := []int{1, 2, 3}
result := RandomChoice(array, -1)
assert.Nil(t, result)
}
func TestRandomChoice_ZeroN(t *testing.T) {
array := []int{1, 2, 3}
result := RandomChoice(array, 0)
assert.Nil(t, result)
}
func TestRandomChoice_NGreaterThanArrayLength(t *testing.T) {
array := []int{1, 2, 3}
result := RandomChoice(array, 5)
assert.ElementsMatch(t, array, result)
}
func TestRandomChoice_NEqualToArrayLength(t *testing.T) {
array := []int{1, 2, 3}
result := RandomChoice(array, 3)
assert.ElementsMatch(t, array, result)
}
func TestRandomChoice_NLessThanArrayLength(t *testing.T) {
array := []int{1, 2, 3, 4, 5}
result := RandomChoice(array, 3)
assert.Len(t, result, 3)
}
func TestRandomInt32_MinEqualsMax(t *testing.T) {
result := RandomInt32(5, 5)
assert.Equal(t, int32(5), result)
}
func TestRandomInt32_MinGreaterThanMax(t *testing.T) {
result := RandomInt32(10, 5)
assert.Equal(t, int32(5), result)
}
func TestRandomInt32_PositiveRange(t *testing.T) {
for i := 0; i < 1000; i++ {
result := RandomInt32(1, 10)
assert.True(t, result >= 1)
assert.True(t, result <= 10)
}
}
func TestRandomInt32_NegativeRange(t *testing.T) {
for i := 0; i < 1000; i++ {
result := RandomInt32(-10, -1)
assert.True(t, result >= -10)
assert.True(t, result <= -1)
}
}
func TestRandomInt32_ZeroRange(t *testing.T) {
for i := 0; i < 1000; i++ {
result := RandomInt32(0, 0)
assert.Equal(t, int32(0), result)
}
}
func TestRandomInt64_MinEqualsMax(t *testing.T) {
result := RandomInt64(5, 5)
assert.Equal(t, int64(5), result)
}
func TestRandomInt64_MinGreaterThanMax(t *testing.T) {
result := RandomInt64(10, 5)
assert.Equal(t, int64(5), result)
}
func TestRandomInt64_PositiveRange(t *testing.T) {
for i := 0; i < 1000; i++ {
result := RandomInt64(1, 10)
assert.True(t, result >= 1)
assert.True(t, result <= 10)
}
}
func TestRandomInt64_NegativeRange(t *testing.T) {
for i := 0; i < 1000; i++ {
result := RandomInt64(-10, -1)
assert.True(t, result >= -10)
assert.True(t, result <= -1)
}
}
func TestRandomInt64_ZeroRange(t *testing.T) {
for i := 0; i < 1000; i++ {
result := RandomInt64(0, 0)
assert.Equal(t, int64(0), result)
}
}
func TestRandomString_LengthZero(t *testing.T) {
result := RandomString(0)
assert.Equal(t, "", result)
t.Logf("LengthZero: %s", result)
}
func TestRandomString_PositiveLength(t *testing.T) {
result := RandomString(10)
assert.Len(t, result, 10)
t.Logf("PositiveLength: %s", result)
}
func TestRandomString_ContainsOnlyValidCharacters(t *testing.T) {
result := RandomString(100)
for _, char := range result {
assert.True(t, (char >= 'A' && char <= 'Z') || (char >= 'a' && char <= 'z') || (char >= '0' && char <= '9'))
}
t.Logf("ContainsOnlyValidCharacters: %s", result)
}
func TestRandomString_NegativeLength(t *testing.T) {
result := RandomString(-5)
assert.Equal(t, "", result)
}
func TestWeightedChoice_EmptyArray(t *testing.T) {
result := WeightedChoice([]int{})
assert.Equal(t, -1, result)
}
func TestWeightedChoice_AllZeroWeights(t *testing.T) {
result := WeightedChoice([]int{0, 0, 0})
assert.Equal(t, 2, result)
}
func TestWeightedChoice_NegativeWeights(t *testing.T) {
result := WeightedChoice([]int{-1, -2, -3})
assert.Equal(t, 2, result)
}
func TestWeightedChoice_MixedWeights(t *testing.T) {
weightArray := []int{1, 0, 3, 0, 2}
counts := make([]int, len(weightArray))
for i := 0; i < 1000; i++ {
choice := WeightedChoice(weightArray)
counts[choice]++
}
assert.Greater(t, counts[0], 0)
assert.Greater(t, counts[2], 0)
assert.Greater(t, counts[4], 0)
}
func TestWeightedChoice_SingleElement(t *testing.T) {
result := WeightedChoice([]int{5})
assert.Equal(t, 0, result)
}
func TestNonWeightedChoice_EmptyArray(t *testing.T) {
result := NonWeightedChoice([]int{})
assert.Equal(t, -1, result)
}
func TestNonWeightedChoice_AllZeroWeights(t *testing.T) {
result := NonWeightedChoice([]int{0, 0, 0})
assert.Equal(t, 2, result)
}
func TestNonWeightedChoice_NegativeWeights(t *testing.T) {
result := NonWeightedChoice([]int{-1, -2, -3})
assert.Equal(t, 2, result)
}
func TestNonWeightedChoice_MixedWeights(t *testing.T) {
weightArray := []int{1, 0, 3, 0, 2}
counts := make([]int, len(weightArray))
for i := 0; i < 1000; i++ {
choice := NonWeightedChoice(weightArray)
counts[choice]++
}
assert.Greater(t, counts[0], 0)
assert.Greater(t, counts[2], 0)
assert.Greater(t, counts[4], 0)
}
func TestNonWeightedChoice_SingleElement(t *testing.T) {
result := NonWeightedChoice([]int{5})
assert.Equal(t, 0, result)
}

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

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

12
tag.bat
View File

@@ -1,9 +1,11 @@
git tag v1.1.11
git tag v1.1.22
git tag bank_card/v1.1.1
git tag geoip/v1.1.1
git tag bank_card/v1.1.5
git tag geoip/v1.1.5
git tag translator/v1.1.2
git tag copierutil/v0.0.2
git tag entgo/v1.1.14
git tag gorm/v1.1.1
git tag entgo/v1.1.28
git tag gorm/v1.1.6
git push origin --tags

View File

@@ -1,4 +1,4 @@
package util
package timeutil
import "time"

View File

@@ -1,4 +1,4 @@
package util
package timeutil
import (
"math"

View File

@@ -1,4 +1,4 @@
package util
package timeutil
import (
"testing"

View File

@@ -1,4 +1,4 @@
package util
package timeutil
import (
"fmt"

View File

@@ -1,4 +1,4 @@
package util
package timeutil
import "time"

View File

@@ -1,4 +1,4 @@
package util
package timeutil
import (
"fmt"

View File

@@ -1,17 +1,28 @@
package util
package timeutil
import (
"time"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/tx7do/go-utils/trans"
)
var DefaultTimeLocation *time.Location
var defaultTimeLocation *time.Location
func RefreshDefaultTimeLocation(name string) {
DefaultTimeLocation, _ = time.LoadLocation(name)
func RefreshDefaultTimeLocation(name string) *time.Location {
if defaultTimeLocation == nil {
defaultTimeLocation, _ = time.LoadLocation(name)
}
return defaultTimeLocation
}
func GetDefaultTimeLocation() *time.Location {
if defaultTimeLocation == nil {
RefreshDefaultTimeLocation(DefaultTimeLocationName)
}
return defaultTimeLocation
}
// UnixMilliToStringPtr 毫秒时间戳 -> 字符串
@@ -25,6 +36,10 @@ func UnixMilliToStringPtr(tm *int64) *string {
// StringToUnixMilliInt64Ptr 字符串 -> 毫秒时间戳
func StringToUnixMilliInt64Ptr(tm *string) *int64 {
if tm == nil {
return nil
}
theTime := StringTimeToTime(tm)
if theTime == nil {
return nil
@@ -33,6 +48,46 @@ func StringToUnixMilliInt64Ptr(tm *string) *int64 {
return &unixTime
}
// UnixMilliToTimePtr 毫秒时间戳 -> 时间
func UnixMilliToTimePtr(tm *int64) *time.Time {
if tm == nil {
return nil
}
unixMilli := time.UnixMilli(*tm)
return &unixMilli
}
// TimeToUnixMilliInt64Ptr 时间 -> 毫秒时间戳
func TimeToUnixMilliInt64Ptr(tm *time.Time) *int64 {
if tm == nil {
return nil
}
unixTime := tm.UnixMilli()
return &unixTime
}
// UnixSecondToTimePtr 秒时间戳 -> 时间
func UnixSecondToTimePtr(tm *int64) *time.Time {
if tm == nil {
return nil
}
unixMilli := time.Unix(*tm, 0)
return &unixMilli
}
// TimeToUnixSecondInt64Ptr 时间 -> 秒时间戳
func TimeToUnixSecondInt64Ptr(tm *time.Time) *int64 {
if tm == nil {
return nil
}
unixTime := tm.Unix()
return &unixTime
}
// StringTimeToTime 时间字符串 -> 时间
func StringTimeToTime(str *string) *time.Time {
if str == nil {
@@ -42,24 +97,20 @@ 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)
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
}
@@ -84,24 +135,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
}
@@ -132,3 +179,68 @@ func TimeToTimestamppb(tm *time.Time) *timestamppb.Timestamp {
}
return nil
}
func FloatToDurationpb(duration *float64, timePrecision time.Duration) *durationpb.Duration {
if duration == nil {
return nil
}
return durationpb.New(time.Duration(*duration * float64(timePrecision)))
}
func Float64ToDurationpb(d float64) *durationpb.Duration {
duration := time.Duration(d * float64(time.Second))
return durationpb.New(duration)
}
func SecondToDurationpb(seconds *float64) *durationpb.Duration {
return FloatToDurationpb(seconds, time.Second)
}
func DurationpbToFloat(duration *durationpb.Duration, timePrecision time.Duration) *float64 {
if duration == nil {
return nil
}
seconds := duration.AsDuration().Seconds()
secondsWithPrecision := seconds / timePrecision.Seconds()
return &secondsWithPrecision
}
func NumberToDurationpb[T int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64](duration *T, timePrecision time.Duration) *durationpb.Duration {
if duration == nil {
return nil
}
return durationpb.New(time.Duration(*duration) * timePrecision)
}
func DurationpbToNumber[T int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64](duration *durationpb.Duration, timePrecision time.Duration) *T {
if duration == nil {
return nil
}
seconds := duration.AsDuration().Seconds()
secondsWithPrecision := T(seconds / timePrecision.Seconds())
return &secondsWithPrecision
}
func DurationToDurationpb(duration *time.Duration) *durationpb.Duration {
if duration == nil {
return nil
}
return durationpb.New(*duration)
}
func DurationpbToDuration(duration *durationpb.Duration) *time.Duration {
if duration == nil {
return nil
}
d := duration.AsDuration()
return &d
}
func DurationpbSecond(duration *durationpb.Duration) *float64 {
if duration == nil {
return nil
}
seconds := duration.AsDuration().Seconds()
secondsInt64 := seconds
return &secondsInt64
}

View File

@@ -1,11 +1,14 @@
package util
package timeutil
import (
"fmt"
"google.golang.org/protobuf/types/known/timestamppb"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/tx7do/go-utils/trans"
"google.golang.org/protobuf/types/known/durationpb"
)
func TestUnixMilliToStringPtr(t *testing.T) {
@@ -19,17 +22,334 @@ func TestUnixMilliToStringPtr(t *testing.T) {
fmt.Println(*UnixMilliToStringPtr(trans.Int64(1677647946234)))
fmt.Println(*UnixMilliToStringPtr(trans.Int64(1678245350773)))
fmt.Println("START: ", *StringToUnixMilliInt64Ptr(trans.String("2023-03-09 00:00:00")))
fmt.Println("END: ", *StringToUnixMilliInt64Ptr(trans.String("2023-03-09 23:59:59")))
fmt.Println("START: ", *StringToUnixMilliInt64Ptr(trans.Ptr("2023-03-09 00:00:00")))
fmt.Println("END: ", *StringToUnixMilliInt64Ptr(trans.Ptr("2023-03-09 23:59:59")))
fmt.Println(StringTimeToTime(trans.String("2023-03-01 00:00:00")))
fmt.Println(*StringDateToTime(trans.String("2023-03-01")))
fmt.Println(StringTimeToTime(trans.Ptr("2023-03-01 00:00:00")))
fmt.Println(*StringDateToTime(trans.Ptr("2023-03-01")))
fmt.Println(StringTimeToTime(trans.String("2023-03-08 00:00:00")).UnixMilli())
fmt.Println(StringDateToTime(trans.String("2023-03-07")).UnixMilli())
fmt.Println(StringTimeToTime(trans.Ptr("2023-03-08 00:00:00")).UnixMilli())
fmt.Println(StringDateToTime(trans.Ptr("2023-03-07")).UnixMilli())
// 测试有效输入
now = time.Now().UnixMilli()
result := UnixMilliToStringPtr(&now)
assert.NotNil(t, result)
expected := time.UnixMilli(now).Format(TimeLayout)
assert.Equal(t, expected, *result)
// 测试空输入
result = UnixMilliToStringPtr(nil)
assert.Nil(t, result)
}
func TestStringToUnixMilliInt64Ptr(t *testing.T) {
// 测试有效输入
input := "2023-03-09 00:00:00"
expected := time.Date(2023, 3, 9, 0, 0, 0, 0, GetDefaultTimeLocation()).UnixMilli()
result := StringToUnixMilliInt64Ptr(&input)
assert.NotNil(t, result)
assert.Equal(t, expected, *result)
// 测试无效输入
invalidInput := "invalid-date"
result = StringToUnixMilliInt64Ptr(&invalidInput)
assert.Nil(t, result)
// 测试空字符串输入
emptyInput := ""
result = StringToUnixMilliInt64Ptr(&emptyInput)
assert.Nil(t, result)
// 测试空指针输入
result = StringToUnixMilliInt64Ptr(nil)
assert.Nil(t, result)
}
func TestStringTimeToTime(t *testing.T) {
// 测试有效时间字符串输入
input := "2023-03-09 12:34:56"
expected := time.Date(2023, 3, 9, 12, 34, 56, 0, GetDefaultTimeLocation())
result := StringTimeToTime(&input)
assert.NotNil(t, result)
assert.Equal(t, expected, *result)
// 测试有效日期字符串输入
input = "2023-03-09"
expected = time.Date(2023, 3, 9, 0, 0, 0, 0, GetDefaultTimeLocation())
result = StringTimeToTime(&input)
assert.NotNil(t, result)
assert.Equal(t, expected, *result)
// 测试无效时间字符串输入
invalidInput := "invalid-date"
result = StringTimeToTime(&invalidInput)
assert.Nil(t, result)
// 测试空字符串输入
emptyInput := ""
result = StringTimeToTime(&emptyInput)
assert.Nil(t, result)
// 测试空指针输入
result = StringTimeToTime(nil)
assert.Nil(t, result)
}
func TestTimeToTimeString(t *testing.T) {
// 测试非空输入
now := time.Now()
result := TimeToTimeString(&now)
assert.NotNil(t, result)
expected := now.Format(TimeLayout)
assert.Equal(t, expected, *result)
// 测试空输入
result = TimeToTimeString(nil)
assert.Nil(t, result)
}
func TestStringDateToTime(t *testing.T) {
// 测试有效日期字符串输入
input := "2023-03-09"
expected := time.Date(2023, 3, 9, 0, 0, 0, 0, GetDefaultTimeLocation())
result := StringDateToTime(&input)
assert.NotNil(t, result)
assert.Equal(t, expected, *result)
// 测试无效日期字符串输入
invalidInput := "invalid-date"
result = StringDateToTime(&invalidInput)
assert.Nil(t, result)
// 测试空字符串输入
emptyInput := ""
result = StringDateToTime(&emptyInput)
assert.Nil(t, result)
// 测试空指针输入
result = StringDateToTime(nil)
assert.Nil(t, result)
}
func TestTimeToDateString(t *testing.T) {
fmt.Println(*TimeToTimeString(trans.Time(time.Now())))
fmt.Println(*TimeToDateString(trans.Time(time.Now())))
// 测试非空输入
now := time.Now()
result := TimeToDateString(&now)
assert.NotNil(t, result)
expected := now.Format(DateLayout)
assert.Equal(t, expected, *result)
// 测试空输入
result = TimeToDateString(nil)
assert.Nil(t, result)
}
func TestTimestamppbToTime(t *testing.T) {
// 测试有效输入
timestamp := timestamppb.Now()
result := TimestamppbToTime(timestamp)
assert.NotNil(t, result)
assert.Equal(t, timestamp.AsTime(), *result)
// 测试零时间输入
zeroTimestamp := timestamppb.New(time.Time{})
result = TimestamppbToTime(zeroTimestamp)
assert.NotNil(t, result)
assert.Equal(t, time.Time{}, *result)
// 测试空输入
result = TimestamppbToTime(nil)
assert.Nil(t, result)
}
func TestTimeToTimestamppb(t *testing.T) {
// 测试非空输入
now := time.Now()
result := TimeToTimestamppb(&now)
assert.NotNil(t, result)
assert.Equal(t, timestamppb.New(now), result)
// 测试空输入
result = TimeToTimestamppb(nil)
assert.Nil(t, result)
}
func TestDurationpb(t *testing.T) {
fmt.Println(FloatToDurationpb(trans.Ptr(100.0), time.Nanosecond).String())
fmt.Println(*DurationpbToFloat(durationpb.New(100*time.Nanosecond), time.Nanosecond))
fmt.Println(FloatToDurationpb(trans.Ptr(100.0), time.Second).String())
fmt.Println(*DurationpbToFloat(durationpb.New(100*time.Second), time.Second))
fmt.Println(FloatToDurationpb(trans.Ptr(100.0), time.Minute).String())
fmt.Println(*DurationpbToFloat(durationpb.New(100*time.Minute), time.Minute))
//
fmt.Println(NumberToDurationpb(trans.Ptr(100.0), time.Nanosecond).String())
fmt.Println(*DurationpbToNumber[float64](durationpb.New(100*time.Nanosecond), time.Nanosecond))
fmt.Println(NumberToDurationpb(trans.Ptr(100.0), time.Second).String())
fmt.Println(*DurationpbToNumber[float64](durationpb.New(100*time.Second), time.Second))
fmt.Println(NumberToDurationpb(trans.Ptr(100.0), time.Minute).String())
fmt.Println(*DurationpbToNumber[float64](durationpb.New(100*time.Minute), time.Minute))
}
func TestFloatToDurationpb(t *testing.T) {
// 测试有效输入
input := 1.5 // 1.5秒
timePrecision := time.Second
expected := durationpb.New(1500 * time.Millisecond)
result := FloatToDurationpb(&input, timePrecision)
assert.NotNil(t, result)
assert.Equal(t, expected, result)
// 测试零输入
input = 0.0
expected = durationpb.New(0)
result = FloatToDurationpb(&input, timePrecision)
assert.NotNil(t, result)
assert.Equal(t, expected, result)
// 测试空输入
result = FloatToDurationpb(nil, timePrecision)
assert.Nil(t, result)
}
func TestDurationToDurationpb(t *testing.T) {
// 测试非空输入
duration := time.Duration(5 * time.Second)
result := DurationToDurationpb(&duration)
assert.NotNil(t, result)
assert.Equal(t, durationpb.New(duration), result)
// 测试空输入
result = DurationToDurationpb(nil)
assert.Nil(t, result)
}
func TestDurationpbToDuration(t *testing.T) {
// 测试非空输入
durationpbValue := durationpb.New(5 * time.Second)
result := DurationpbToDuration(durationpbValue)
assert.NotNil(t, result)
assert.Equal(t, 5*time.Second, *result)
// 测试空输入
result = DurationpbToDuration(nil)
assert.Nil(t, result)
}
func TestFloat64ToDurationpb(t *testing.T) {
// 测试有效输入
input := 1.5 // 1.5秒
expected := durationpb.New(1500 * time.Millisecond)
result := Float64ToDurationpb(input)
assert.NotNil(t, result)
assert.Equal(t, expected, result)
// 测试零输入
input = 0.0
expected = durationpb.New(0)
result = Float64ToDurationpb(input)
assert.NotNil(t, result)
assert.Equal(t, expected, result)
// 测试负数输入
input = -2.5 // -2.5秒
expected = durationpb.New(-2500 * time.Millisecond)
result = Float64ToDurationpb(input)
assert.NotNil(t, result)
assert.Equal(t, expected, result)
}
func TestSecondToDurationpb(t *testing.T) {
// 测试有效输入
input := 2.5 // 2.5秒
expected := durationpb.New(2500 * time.Millisecond)
result := SecondToDurationpb(&input)
assert.NotNil(t, result)
assert.Equal(t, expected, result)
// 测试零输入
input = 0.0
expected = durationpb.New(0)
result = SecondToDurationpb(&input)
assert.NotNil(t, result)
assert.Equal(t, expected, result)
// 测试空输入
result = SecondToDurationpb(nil)
assert.Nil(t, result)
}
func TestDurationpbSecond(t *testing.T) {
// 测试非空输入
duration := durationpb.New(5 * time.Second)
result := DurationpbSecond(duration)
assert.NotNil(t, result)
assert.Equal(t, 5.0, *result, "应返回正确的秒数")
// 测试零输入
duration = durationpb.New(0)
result = DurationpbSecond(duration)
assert.NotNil(t, result)
assert.Equal(t, 0.0, *result, "应返回零秒")
// 测试空输入
result = DurationpbSecond(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)
}

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,7 +0,0 @@
package uuid
import "testing"
func TestUUID(t *testing.T) {
}

View File

@@ -2,6 +2,7 @@ package uuid
import (
"github.com/google/uuid"
"github.com/tx7do/go-utils/trans"
)

66
uuid/uuid_test.go Normal file
View File

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