Compare commits

..

31 Commits

Author SHA1 Message Date
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
36 changed files with 1544 additions and 229 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.24
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.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
github.com/mattn/go-sqlite3 v1.14.24/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=

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

@@ -6,17 +6,18 @@ toolchain go1.23.2
require (
entgo.io/contrib v0.6.0
entgo.io/ent v0.14.1
github.com/XSAM/otelsql v0.35.0
github.com/go-kratos/kratos/v2 v2.8.1
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.12
go.opentelemetry.io/otel v1.31.0
github.com/stretchr/testify v1.10.0
github.com/tx7do/go-utils v1.1.19
go.opentelemetry.io/otel v1.35.0
google.golang.org/protobuf v1.36.6
)
require (
ariga.io/atlas v0.28.1 // indirect
ariga.io/atlas v0.32.1 // indirect
github.com/agext/levenshtein v1.2.3 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/bmatcuk/doublestar v1.3.4 // indirect
@@ -24,24 +25,25 @@ require (
github.com/davecgh/go-spew v1.1.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.21.0 // indirect
github.com/go-openapi/inflect v0.21.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/hashicorp/hcl/v2 v2.22.0 // 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.15.0 // indirect
go.opentelemetry.io/otel/metric v1.31.0 // indirect
go.opentelemetry.io/otel/trace v1.31.0 // indirect
github.com/sony/sonyflake v1.2.1 // indirect
github.com/zclconf/go-cty v1.16.2 // indirect
github.com/zclconf/go-cty-yaml v1.1.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/otel/metric v1.35.0 // indirect
go.opentelemetry.io/otel/trace v1.35.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/mod v0.21.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/text v0.19.0 // indirect
golang.org/x/tools v0.26.0 // indirect
google.golang.org/protobuf v1.35.1 // 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,13 +1,13 @@
ariga.io/atlas v0.28.1 h1:cNE0FYmoYs1u4KF+FGnp2on1srhM6FDpjaCgL7Rd8/c=
ariga.io/atlas v0.28.1/go.mod h1:LOOp18LCL9r+VifvVlJqgYJwYl271rrXD9/wIyzJ8sw=
ariga.io/atlas v0.32.1 h1:cVCxz6D2SRvlKn/1Yne+WaZEFBjji7lR4DklgP+6Py0=
ariga.io/atlas v0.32.1/go.mod h1:Oe1xWPuu5q9LzyrWfbZmEZxFYeu4BHTyzfjeW2aZp/w=
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.1 h1:fUERL506Pqr92EPHJqr8EYxbPioflJo6PudkrEA8a/s=
entgo.io/ent v0.14.1/go.mod h1:MH6XLG0KXpkcDQhKiHfANZSzR55TJyPL5IGNpI8wpco=
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.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4=
github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc=
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=
@@ -18,25 +18,25 @@ github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/
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.8.1 h1:nK+NRp8C+wQk7tr55K9Er7nBjmBLYGbnyNz7UIy37qw=
github.com/go-kratos/kratos/v2 v2.8.1/go.mod h1:+Vfe3FzF0d+BfMdajA11jT0rAyJWublRE/seZQNZVxE=
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.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.21.0 h1:FoBjBTQEcbg2cJUWX6uwL9OyIW8eqc9k4KhN4lfbeYk=
github.com/go-openapi/inflect v0.21.0/go.mod h1:INezMuUu7SJQc2AyR3WO0DqqYUJSj8Kb4hBd7WtjlAw=
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.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.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.22.0 h1:hkZ3nCtqeJsDhPRFz5EA9iwcG1hNWGePOTw6oyul12M=
github.com/hashicorp/hcl/v2 v2.22.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
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=
@@ -51,47 +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.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
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.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ=
github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
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.2 h1:LAJSwc3v81IRBZyUVQDUdZ7hs3SYs9jv0eZJDWHD/70=
github.com/zclconf/go-cty v1.16.2/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=
go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk=
go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0=
go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc=
go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8=
go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys=
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
github.com/zclconf/go-cty-yaml v1.1.0 h1:nP+jp0qPHv2IhUVqmQSzjvqAWcObN0KBkUl2rWBdig0=
github.com/zclconf/go-cty-yaml v1.1.0/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
go.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.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
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.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
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,14 +1,14 @@
module github.com/tx7do/go-utils/geoip
go 1.21
go 1.23.0
toolchain go1.23.2
toolchain go1.24.1
require (
github.com/go-kratos/kratos/v2 v2.8.1
github.com/go-kratos/kratos/v2 v2.8.4
github.com/oschwald/geoip2-golang v1.11.0
github.com/stretchr/testify v1.9.0
golang.org/x/text v0.19.0
github.com/stretchr/testify v1.10.0
golang.org/x/text v0.25.0
)
require (
@@ -16,7 +16,7 @@ require (
github.com/kr/text v0.2.0 // indirect
github.com/oschwald/maxminddb-golang v1.13.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/sys v0.33.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@@ -1,8 +1,8 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-kratos/kratos/v2 v2.8.1 h1:nK+NRp8C+wQk7tr55K9Er7nBjmBLYGbnyNz7UIy37qw=
github.com/go-kratos/kratos/v2 v2.8.1/go.mod h1:+Vfe3FzF0d+BfMdajA11jT0rAyJWublRE/seZQNZVxE=
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=
@@ -15,14 +15,14 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/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.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.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
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=

16
go.mod
View File

@@ -1,18 +1,18 @@
module github.com/tx7do/go-utils
go 1.22.0
go 1.23.0
toolchain go1.23.2
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.28.0
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c
google.golang.org/protobuf v1.35.1
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 (

24
go.sum
View File

@@ -7,8 +7,8 @@ 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=
@@ -24,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.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
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,16 +1,16 @@
package rand
import (
"math/rand"
"time"
"github.com/tx7do/go-utils/math"
"math/rand"
)
var rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
var rnd = rand.New(rand.NewSource(Seed(UnixNanoSeed)))
func init() {
rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
if rnd != nil {
rnd = rand.New(rand.NewSource(Seed(UnixNanoSeed)))
}
}
func Float32() float32 {
@@ -59,18 +59,17 @@ func RandomInt64(min, max int64) int64 {
// RandomString 随机字符串,包含大小写字母和数字
func RandomString(l int) string {
if l <= 0 {
return ""
}
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
bytes := make([]byte, l)
for i := 0; i < l; i++ {
x := Intn(3)
switch x {
case 0:
bytes[i] = byte(RandomInt(65, 90)) //大写字母
case 1:
bytes[i] = byte(RandomInt(97, 122))
case 2:
bytes[i] = byte(Intn(10))
}
bytes[i] = charset[Intn(len(charset))]
}
return string(bytes)
}

View File

@@ -208,11 +208,13 @@ func TestRandomInt64_ZeroRange(t *testing.T) {
func TestRandomString_LengthZero(t *testing.T) {
result := RandomString(0)
assert.Equal(t, "", result)
t.Logf("LengthZero: %s", result)
}
func TestRandomString_PositiveLength(t *testing.T) {
result := RandomString(10)
assert.Len(t, result, 10)
t.Logf("PositiveLength: %s", result)
}
func TestRandomString_ContainsOnlyValidCharacters(t *testing.T) {
@@ -220,6 +222,7 @@ func TestRandomString_ContainsOnlyValidCharacters(t *testing.T) {
for _, char := range result {
assert.True(t, (char >= 'A' && char <= 'Z') || (char >= 'a' && char <= 'z') || (char >= '0' && char <= '9'))
}
t.Logf("ContainsOnlyValidCharacters: %s", result)
}
func TestRandomString_NegativeLength(t *testing.T) {

179
rand/randomizer.go Normal file
View File

@@ -0,0 +1,179 @@
package rand
import (
"github.com/tx7do/go-utils/math"
"math/rand"
)
type Randomizer struct {
rnd *rand.Rand
}
func NewRandomizer(seedType SeedType) *Randomizer {
return &Randomizer{
rnd: rand.New(rand.NewSource(Seed(seedType))),
}
}
func (r *Randomizer) Float32() float32 {
return r.rnd.Float32()
}
func (r *Randomizer) Float64() float64 {
return r.rnd.Float64()
}
func (r *Randomizer) Int() int {
return r.rnd.Int()
}
func (r *Randomizer) Int31() int32 {
return r.rnd.Int31()
}
func (r *Randomizer) Int63() int64 {
return r.rnd.Int63()
}
func (r *Randomizer) Uint32() uint32 {
return r.rnd.Uint32()
}
func (r *Randomizer) Uint64() uint64 {
return r.rnd.Uint64()
}
func (r *Randomizer) Intn(n int) int {
return r.rnd.Intn(n)
}
func (r *Randomizer) Int31n(n int32) int32 {
return r.rnd.Int31n(n)
}
func (r *Randomizer) Int63n(n int64) int64 {
return r.rnd.Int63n(n)
}
// RangeInt 根据区间产生随机数
func (r *Randomizer) RangeInt(min, max int) int {
if min >= max {
return max
}
return min + r.Intn(max-min+1)
}
// RangeInt32 根据区间产生随机数
func (r *Randomizer) RangeInt32(min, max int32) int32 {
if min >= max {
return max
}
return min + r.Int31n(max-min+1)
}
// RangeInt64 根据区间产生随机数
func (r *Randomizer) RangeInt64(min, max int64) int64 {
if min >= max {
return max
}
return min + r.Int63n(max-min+1)
}
// RangeUint 根据区间产生随机数
func (r *Randomizer) RangeUint(min, max uint) uint {
if min >= max {
return max
}
return min + uint(r.Intn(int(max-min+1)))
}
// RangeUint32 根据区间产生随机数
func (r *Randomizer) RangeUint32(min, max uint32) uint32 {
if min >= max {
return max
}
return min + uint32(r.Int31n(int32(max-min+1)))
}
// RangeUint64 根据区间产生随机数
func (r *Randomizer) RangeUint64(min, max uint64) uint64 {
if min >= max {
return max
}
return min + uint64(r.Int63n(int64(max-min+1)))
}
// RangeFloat32 根据区间产生随机数
func (r *Randomizer) RangeFloat32(min, max float32) float32 {
if min >= max {
return max
}
return min + r.Float32()*(max-min)
}
// RangeFloat64 根据区间产生随机数
func (r *Randomizer) RangeFloat64(min, max float64) float64 {
if min >= max {
return max
}
return min + r.Float64()*(max-min)
}
// RandomString 随机字符串,包含大小写字母和数字
func (r *Randomizer) RandomString(l int) string {
bytes := make([]byte, l)
for i := 0; i < l; i++ {
x := r.Intn(3)
switch x {
case 0:
bytes[i] = byte(r.RangeInt(65, 90)) //大写字母
case 1:
bytes[i] = byte(r.RangeInt(97, 122))
case 2:
bytes[i] = byte(r.Intn(10))
}
}
return string(bytes)
}
// WeightedChoice 根据权重随机返回对应选项的索引O(n)
func (r *Randomizer) WeightedChoice(weightArray []int) int {
if weightArray == nil {
return -1
}
total := math.SumInt(weightArray)
rv := r.Int63n(total)
for i, v := range weightArray {
if rv < int64(v) {
return i
}
rv -= int64(v)
}
return len(weightArray) - 1
}
// NonWeightedChoice 根据权重随机返回对应选项的索引O(n). 权重大于等于0
func (r *Randomizer) NonWeightedChoice(weightArray []int) int {
if weightArray == nil {
return -1
}
for i, weight := range weightArray {
if weight < 0 {
weightArray[i] = 0
}
}
total := math.SumInt(weightArray)
rv := r.Int63n(total)
for i, v := range weightArray {
if rv < int64(v) {
return i
}
rv -= int64(v)
}
return len(weightArray) - 1
}

86
rand/randomizer_test.go Normal file
View File

@@ -0,0 +1,86 @@
package rand
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestRandomizer_RangeUint32_MinEqualsMax(t *testing.T) {
r := NewRandomizer(UnixNanoSeed)
result := r.RangeUint32(5, 5)
assert.Equal(t, uint32(5), result)
}
func TestRandomizer_RangeUint32_MinGreaterThanMax(t *testing.T) {
r := NewRandomizer(UnixNanoSeed)
result := r.RangeUint32(10, 5)
assert.Equal(t, uint32(5), result)
}
func TestRandomizer_RangeUint32_PositiveRange(t *testing.T) {
r := NewRandomizer(UnixNanoSeed)
for i := 0; i < 1000; i++ {
result := r.RangeUint32(1, 10)
assert.True(t, result >= 1)
assert.True(t, result <= 10)
}
}
func TestRandomizer_RangeUint64_MinEqualsMax(t *testing.T) {
r := NewRandomizer(UnixNanoSeed)
result := r.RangeUint64(5, 5)
assert.Equal(t, uint64(5), result)
}
func TestRandomizer_RangeUint64_MinGreaterThanMax(t *testing.T) {
r := NewRandomizer(UnixNanoSeed)
result := r.RangeUint64(10, 5)
assert.Equal(t, uint64(5), result)
}
func TestRandomizer_RangeUint64_PositiveRange(t *testing.T) {
r := NewRandomizer(UnixNanoSeed)
for i := 0; i < 1000; i++ {
result := r.RangeUint64(1, 10)
assert.True(t, result >= 1)
assert.True(t, result <= 10)
}
}
func TestRandomizer_WeightedChoice_EmptyArray(t *testing.T) {
r := NewRandomizer(UnixNanoSeed)
result := r.WeightedChoice([]int{})
assert.Equal(t, -1, result)
}
func TestRandomizer_WeightedChoice_AllZeroWeights(t *testing.T) {
r := NewRandomizer(UnixNanoSeed)
result := r.WeightedChoice([]int{0, 0, 0})
assert.Equal(t, 2, result)
}
func TestRandomizer_WeightedChoice_MixedWeights(t *testing.T) {
r := NewRandomizer(UnixNanoSeed)
weightArray := []int{1, 0, 3, 0, 2}
counts := make([]int, len(weightArray))
for i := 0; i < 1000; i++ {
choice := r.WeightedChoice(weightArray)
counts[choice]++
}
assert.Greater(t, counts[0], 0)
assert.Greater(t, counts[2], 0)
assert.Greater(t, counts[4], 0)
}
func TestRandomizer_RandomString_LengthZero(t *testing.T) {
r := NewRandomizer(UnixNanoSeed)
result := r.RandomString(0)
assert.Equal(t, "", result)
}
func TestRandomizer_RandomString_CorrectLength(t *testing.T) {
r := NewRandomizer(UnixNanoSeed)
length := 50
result := r.RandomString(length)
assert.Equal(t, length, len(result))
}

99
rand/seeder.go Normal file
View File

@@ -0,0 +1,99 @@
package rand
import (
cryptoRand "crypto/rand"
"encoding/binary"
"golang.org/x/exp/rand"
"hash/maphash"
mathRand "math/rand"
"runtime"
"time"
)
type SeedType string
const (
UnixNanoSeed SeedType = "UnixNano"
MapHashSeed SeedType = "MapHash"
CryptoRandSeed SeedType = "CryptoRand"
RandomStringSeed SeedType = "RandomString"
)
type Seeder struct {
seedType SeedType
}
func NewSeeder(seedType SeedType) *Seeder {
return &Seeder{
seedType: seedType,
}
}
func (r *Seeder) UnixNano() int64 {
// 获取当前时间戳
timestamp := time.Now().UnixNano()
// 生成一个随机数
var randomBytes [8]byte
_, err := rand.Read(randomBytes[:])
if err != nil {
panic("failed to generate random bytes")
}
randomPart := int64(binary.LittleEndian.Uint64(randomBytes[:]))
// 获取 Goroutine ID或其他唯一标识
goroutineID := int64(runtime.NumGoroutine())
// 结合时间戳、随机数和 Goroutine ID
seed := timestamp ^ randomPart ^ goroutineID
return seed
}
func (r *Seeder) MapHash() int64 {
return int64(new(maphash.Hash).Sum64())
}
func (r *Seeder) CryptoRand() int64 {
var b [8]byte
_, err := cryptoRand.Read(b[:])
if err != nil {
panic("cannot seed math/rand package with cryptographically secure random number generator")
}
seed := int64(binary.LittleEndian.Uint64(b[:]))
return seed
}
var Alpha = "abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789"
func (r *Seeder) RandomString() int64 {
const size = 8
buf := make([]byte, size)
for i := 0; i < size; i++ {
buf[i] = Alpha[mathRand.Intn(len(Alpha))]
}
seed := int64(binary.LittleEndian.Uint64(buf[:]))
return seed
}
func (r *Seeder) Seed() int64 {
switch r.seedType {
default:
fallthrough
case UnixNanoSeed:
return r.UnixNano()
case MapHashSeed:
return r.MapHash()
case CryptoRandSeed:
return r.CryptoRand()
case RandomStringSeed:
return r.RandomString()
}
}
// Seed generates a seed based on the specified SeedType.
func Seed(seedType SeedType) int64 {
randomizer := NewSeeder(seedType)
return randomizer.Seed()
}

80
rand/seeder_test.go Normal file
View File

@@ -0,0 +1,80 @@
package rand
import (
"fmt"
"testing"
)
func TestUnixNanoSeed(t *testing.T) {
seeder := NewSeeder(UnixNanoSeed)
var seeds = make(map[int64]bool)
for i := 0; i < 100000; i++ {
seed := seeder.Seed()
seeds[seed] = true
}
fmt.Println("UnixNano Seed", len(seeds))
}
func TestMapHashSeed(t *testing.T) {
seeder := NewSeeder(MapHashSeed)
var seeds = make(map[int64]bool)
for i := 0; i < 100000; i++ {
seed := seeder.Seed()
seeds[seed] = true
}
fmt.Println("MapHash Seed", len(seeds))
}
func TestCryptoRandSeed(t *testing.T) {
seeder := NewSeeder(CryptoRandSeed)
var seeds = make(map[int64]bool)
for i := 0; i < 100000; i++ {
seed := seeder.Seed()
seeds[seed] = true
}
fmt.Println("CryptoRand Seed", len(seeds))
}
func TestRandomStringSeed(t *testing.T) {
seeder := NewSeeder(RandomStringSeed)
var seeds = make(map[int64]bool)
for i := 0; i < 100000; i++ {
seed := seeder.Seed()
seeds[seed] = true
}
fmt.Println("RandomString Seed", len(seeds))
}
func TestSeed(t *testing.T) {
var seeds = make(map[int64]bool)
for i := 0; i < 100000; i++ {
seed := Seed(UnixNanoSeed)
seeds[seed] = true
}
fmt.Println("UnixNano Seed", len(seeds))
seeds = make(map[int64]bool)
for i := 0; i < 100000; i++ {
seed := Seed(MapHashSeed)
seeds[seed] = true
}
fmt.Println("MapHash Seed", len(seeds))
seeds = make(map[int64]bool)
for i := 0; i < 100000; i++ {
seed := Seed(CryptoRandSeed)
seeds[seed] = true
}
fmt.Println("CryptoRand Seed", len(seeds))
seeds = make(map[int64]bool)
for i := 0; i < 100000; i++ {
seed := Seed(RandomStringSeed)
seeds[seed] = true
}
fmt.Println("RandomString Seed", len(seeds))
}

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

10
tag.bat
View File

@@ -1,9 +1,9 @@
git tag v1.1.12
git tag v1.1.21
git tag bank_card/v1.1.2
git tag geoip/v1.1.2
git tag bank_card/v1.1.5
git tag geoip/v1.1.5
git tag entgo/v1.1.15
git tag gorm/v1.1.2
git tag entgo/v1.1.27
git tag gorm/v1.1.5
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
@@ -42,24 +57,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 +95,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 +139,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,286 @@ 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")
}