Compare commits
10 Commits
jwtutil/v0
...
slug/v0.0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99c8f6e4c1 | ||
|
|
3f713e489b | ||
|
|
ace6bd4237 | ||
|
|
3ec9bbbd27 | ||
|
|
faa5817857 | ||
|
|
89a69a9d4d | ||
|
|
63789d090d | ||
|
|
06c29c57d2 | ||
|
|
5faa4e06f7 | ||
|
|
5abe88a0d9 |
@@ -42,11 +42,15 @@ var TimestamppbToTimeConverter = copier.TypeConverter{
|
||||
},
|
||||
}
|
||||
|
||||
func TimeToString(tm *time.Time) *string {
|
||||
return timeutil.TimeToString(tm, timeutil.ISO8601)
|
||||
}
|
||||
|
||||
func NewTimeStringConverterPair() []copier.TypeConverter {
|
||||
srcType := &time.Time{}
|
||||
dstType := trans.Ptr("")
|
||||
|
||||
fromFn := timeutil.TimeToTimeString
|
||||
fromFn := TimeToString
|
||||
toFn := timeutil.StringTimeToTime
|
||||
|
||||
return NewGenericTypeConverterPair(srcType, dstType, fromFn, toFn)
|
||||
|
||||
@@ -6,7 +6,7 @@ toolchain go1.24.3
|
||||
|
||||
require (
|
||||
github.com/jinzhu/copier v0.4.0
|
||||
github.com/tx7do/go-utils v1.1.22
|
||||
github.com/tx7do/go-utils v1.1.27
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@@ -11,7 +11,7 @@ require (
|
||||
github.com/go-kratos/kratos/v2 v2.8.4
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/tx7do/go-utils v1.1.22
|
||||
github.com/tx7do/go-utils v1.1.27
|
||||
go.opentelemetry.io/otel v1.36.0
|
||||
google.golang.org/protobuf v1.36.6
|
||||
)
|
||||
@@ -23,7 +23,7 @@ require (
|
||||
github.com/bmatcuk/doublestar v1.3.4 // indirect
|
||||
github.com/bufbuild/protocompile v0.14.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/inflect v0.21.2 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
|
||||
@@ -23,6 +23,8 @@ github.com/go-kratos/kratos/v2 v2.8.4/go.mod h1:mq62W2101a5uYyRxe+7IdWubu7gZCGYq
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-openapi/inflect v0.21.2 h1:0gClGlGcxifcJR56zwvhaOulnNgnhc4qTAkob5ObnSM=
|
||||
|
||||
3
go.mod
3
go.mod
@@ -7,8 +7,6 @@ toolchain go1.24.1
|
||||
require (
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gosimple/slug v1.15.0
|
||||
github.com/sony/sonyflake v1.2.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
golang.org/x/crypto v0.38.0
|
||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6
|
||||
@@ -17,7 +15,6 @@ require (
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/gosimple/unidecode v1.0.1 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
||||
|
||||
6
go.sum
6
go.sum
@@ -7,10 +7,6 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gosimple/slug v1.15.0 h1:wRZHsRrRcs6b0XnxMUBM6WK1U1Vg5B0R7VkIf1Xzobo=
|
||||
github.com/gosimple/slug v1.15.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ=
|
||||
github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o=
|
||||
github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
@@ -24,8 +20,6 @@ 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.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=
|
||||
|
||||
28
id/README.md
Normal file
28
id/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# ID生成器
|
||||
|
||||
## 订单ID
|
||||
|
||||
- 电商平台:202506041234567890(时间戳 + 随机数,19-20 位)。
|
||||
- 支付系统:PAY20250604123456789(业务前缀 + 时间戳 + 序号)。
|
||||
- 微信支付:1589123456789012345(类似 Snowflake 的纯数字 ID)。
|
||||
- 美团订单:202506041234567890123(时间戳 + 商户 ID + 随机数)。
|
||||
|
||||
## UUID
|
||||
|
||||
| 特性 | GUID/UUID | KSUID | ShortUUID | XID | Snowflake |
|
||||
|-------|--------------|------------|-----------|----------|----------------|
|
||||
| 长度 | 36/32字符(不含-) | 27字符 | 22字符 | 20字符 | 19(数字位数) |
|
||||
| 有序性 | 无序(UUIDv4) | 严格时序 | 无序 | 趋势有序 | 严格时序 |
|
||||
| 时间精度 | 无(UUIDv4) | 毫秒级 | 无 | 秒级 | 毫秒级 |
|
||||
| 分布式安全 | 高(随机数) | 高 | 高 | 高 | 高(需配置WorkerID) |
|
||||
| 性能 | 中等 | 中等 | 较低(编码开销) | 极高 | 极高 |
|
||||
| 时钟依赖 | 无 | 有(需处理时钟回拨) | 无 | 有(但影响较小) | 强依赖(需严格同步) |
|
||||
| 适用场景 | 跨系统兼容 | 时序索引 | 短ID、URL | 高并发、短ID | 分布式时序ID |
|
||||
|
||||
## 选择建议
|
||||
|
||||
- **GUID/UUID**: 适用于需要跨系统兼容的场景,特别是当不需要有序性时。
|
||||
- **KSUID**: 适合需要严格时序的应用,如事件日志、时间序列数据。
|
||||
- **ShortUUID**: 当需要短ID且不关心有序性时的理想选择,适用于URL、短链接等。
|
||||
- **XID**: 高并发场景下的短ID选择,适合需要一定有序性的应用。
|
||||
- **Snowflake**: 适合分布式系统,特别是需要严格时序和高性能的场景,如大规模分布式应用。
|
||||
27
id/go.mod
Normal file
27
id/go.mod
Normal file
@@ -0,0 +1,27 @@
|
||||
module github.com/tx7do/go-utils/id
|
||||
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.23.2
|
||||
|
||||
require (
|
||||
github.com/bwmarrin/snowflake v0.3.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/lithammer/shortuuid/v4 v4.2.0
|
||||
github.com/rs/xid v1.6.0
|
||||
github.com/segmentio/ksuid v1.0.4
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/tx7do/go-utils v1.1.27
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||
github.com/sony/sonyflake v1.2.1 // indirect
|
||||
go.mongodb.org/mongo-driver v1.17.3 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
replace github.com/tx7do/go-utils => ../
|
||||
32
id/go.sum
Normal file
32
id/go.sum
Normal file
@@ -0,0 +1,32 @@
|
||||
github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0=
|
||||
github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lithammer/shortuuid/v4 v4.2.0 h1:LMFOzVB3996a7b8aBuEXxqOBflbfPQAiVzkIcHO0h8c=
|
||||
github.com/lithammer/shortuuid/v4 v4.2.0/go.mod h1:D5noHZ2oFw/YaKCfGy0YxyE7M0wMbezmMjPdhyEFe6Y=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=
|
||||
github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
|
||||
github.com/sony/sonyflake v1.2.1 h1:Jzo4abS84qVNbYamXZdrZF1/6TzNJjEogRfXv7TsG48=
|
||||
github.com/sony/sonyflake v1.2.1/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ=
|
||||
go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
84
id/order_id.go
Normal file
84
id/order_id.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package id
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/tx7do/go-utils/trans"
|
||||
)
|
||||
|
||||
type idCounter uint32
|
||||
|
||||
func (c *idCounter) Increase() uint32 {
|
||||
cur := *c
|
||||
atomic.AddUint32((*uint32)(c), 1)
|
||||
atomic.CompareAndSwapUint32((*uint32)(c), 1000, 0)
|
||||
return uint32(cur)
|
||||
}
|
||||
|
||||
var orderIdIndex idCounter
|
||||
|
||||
// GenerateOrderIdWithRandom 生成20位订单号,前缀 + 时间戳 + 随机数
|
||||
func GenerateOrderIdWithRandom(prefix string, tm *time.Time) string {
|
||||
// 前缀 + 时间戳(14位) + 随机数(4位)
|
||||
|
||||
if tm == nil {
|
||||
tm = trans.Time(time.Now())
|
||||
}
|
||||
|
||||
timestamp := tm.Format("20060102150405")
|
||||
|
||||
randNum := rand.Intn(10000) // 生成0-9999之间的随机数
|
||||
|
||||
return fmt.Sprintf("%s%s%d", prefix, timestamp, randNum)
|
||||
}
|
||||
|
||||
// GenerateOrderIdWithIncreaseIndex 生成20位订单号,前缀+时间+自增长索引
|
||||
func GenerateOrderIdWithIncreaseIndex(prefix string, tm *time.Time) string {
|
||||
if tm == nil {
|
||||
tm = trans.Time(time.Now())
|
||||
}
|
||||
|
||||
timestamp := tm.Format("20060102150405")
|
||||
|
||||
index := orderIdIndex.Increase()
|
||||
|
||||
return fmt.Sprintf("%s%s%d", prefix, timestamp, index)
|
||||
}
|
||||
|
||||
// GenerateOrderIdWithTenantId 带商户ID的订单ID生成器:202506041234567890123
|
||||
func GenerateOrderIdWithTenantId(tenantID string) string {
|
||||
// 时间戳(14位) + 商户ID(固定 5 位) + 随机数(4位)
|
||||
|
||||
// 时间戳部分(精确到毫秒)
|
||||
now := time.Now()
|
||||
timestamp := now.Format("20060102150405")
|
||||
|
||||
// 商户ID部分(截取或补零到5位)
|
||||
tenantPart := tenantID
|
||||
if len(tenantPart) > 5 {
|
||||
tenantPart = tenantPart[:5]
|
||||
} else {
|
||||
tenantPart = fmt.Sprintf("%-5s", tenantPart)
|
||||
tenantPart = strings.ReplaceAll(tenantPart, " ", "0")
|
||||
}
|
||||
|
||||
// 随机数部分(4位)
|
||||
n := rand.Int31n(10000)
|
||||
randomPart := fmt.Sprintf("%04d", n)
|
||||
|
||||
return timestamp + tenantPart + randomPart
|
||||
}
|
||||
|
||||
func GenerateOrderIdWithPrefixSonyflake(prefix string) string {
|
||||
id, _ := NewSonyflakeID()
|
||||
return fmt.Sprintf("%s%d", prefix, id)
|
||||
}
|
||||
|
||||
func GenerateOrderIdWithPrefixSnowflake(workerId int64, prefix string) string {
|
||||
id, _ := NewSnowflakeID(workerId)
|
||||
return fmt.Sprintf("%s%d", prefix, id)
|
||||
}
|
||||
158
id/order_id_test.go
Normal file
158
id/order_id_test.go
Normal file
@@ -0,0 +1,158 @@
|
||||
package id
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGenerateOrderIdWithRandom(t *testing.T) {
|
||||
prefix := "PT"
|
||||
|
||||
// 测试生成的订单号是否包含前缀
|
||||
orderID := GenerateOrderIdWithRandom(prefix, nil)
|
||||
assert.Contains(t, orderID, prefix, "订单号应包含前缀")
|
||||
t.Logf("GenerateOrderIdWithRandom: %s", orderID)
|
||||
|
||||
// 测试生成的订单号长度是否正确
|
||||
assert.Equal(t, len(prefix)+14+4, len(orderID), "订单号长度应为前缀+时间戳+随机数")
|
||||
}
|
||||
|
||||
func TestGenerateOrderIdWithIndex(t *testing.T) {
|
||||
prefix := "PT"
|
||||
|
||||
tm := time.Now()
|
||||
|
||||
fmt.Println(GenerateOrderIdWithIncreaseIndex(prefix, &(tm)))
|
||||
|
||||
ids := make(map[string]bool)
|
||||
count := 100
|
||||
for i := 0; i < count; i++ {
|
||||
ids[GenerateOrderIdWithIncreaseIndex(prefix, &(tm))] = true
|
||||
}
|
||||
assert.Equal(t, count, len(ids))
|
||||
}
|
||||
|
||||
func TestGenerateOrderIdWithIndexThread(t *testing.T) {
|
||||
tm := time.Now()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var ids sync.Map
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
for i := 0; i < 100; i++ {
|
||||
id := GenerateOrderIdWithIncreaseIndex("PT", &(tm))
|
||||
ids.Store(id, true)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
aLen := 0
|
||||
ids.Range(func(k, v interface{}) bool {
|
||||
aLen++
|
||||
return true
|
||||
})
|
||||
assert.Equal(t, 1000, aLen)
|
||||
}
|
||||
|
||||
func TestGenerateOrderIdWithTenantId(t *testing.T) {
|
||||
tenantID := "M9876"
|
||||
orderID := GenerateOrderIdWithTenantId(tenantID)
|
||||
|
||||
t.Logf(orderID)
|
||||
|
||||
// 验证订单号长度是否正确
|
||||
assert.Equal(t, 14+5+4, len(orderID))
|
||||
|
||||
// 验证时间戳部分是否正确
|
||||
timestamp := time.Now().Format("20060102150405")
|
||||
assert.Contains(t, orderID, timestamp)
|
||||
t.Logf("timestamp %d", len(timestamp))
|
||||
|
||||
// 验证商户ID部分是否正确
|
||||
assert.Contains(t, orderID, tenantID)
|
||||
|
||||
// 验证随机数部分是否为4位数字
|
||||
randomPart := orderID[len(orderID)-4:]
|
||||
assert.Regexp(t, `^\d{4}$`, randomPart)
|
||||
}
|
||||
|
||||
func TestGenerateOrderIdWithTenantIdCollision(t *testing.T) {
|
||||
tenantID := "M9876"
|
||||
count := 1000 // 生成订单号的数量
|
||||
ids := make(map[string]bool)
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
orderID := GenerateOrderIdWithTenantId(tenantID)
|
||||
if ids[orderID] {
|
||||
t.Errorf("碰撞的订单号: %s", orderID)
|
||||
}
|
||||
ids[orderID] = true
|
||||
}
|
||||
|
||||
t.Logf("生成了 %d 个订单号,没有发生碰撞", count)
|
||||
}
|
||||
|
||||
func TestGenerateOrderIdWithPrefixSonyflake(t *testing.T) {
|
||||
prefix := "ORD"
|
||||
orderID := GenerateOrderIdWithPrefixSonyflake(prefix)
|
||||
t.Logf("order id with prefix sonyflake: %s [%d]", orderID, len(orderID))
|
||||
|
||||
// 验证订单号是否包含前缀
|
||||
assert.Contains(t, orderID, prefix, "订单号应包含前缀")
|
||||
|
||||
// 验证订单号是否为有效的数字字符串
|
||||
assert.Regexp(t, `^ORD\d+$`, orderID, "订单号格式应为前缀加数字")
|
||||
}
|
||||
|
||||
func TestGenerateOrderIdWithPrefixSonyflakeCollision(t *testing.T) {
|
||||
prefix := "ORD"
|
||||
count := 100000 // 生成订单号的数量
|
||||
ids := make(map[string]bool)
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
orderID := GenerateOrderIdWithPrefixSonyflake(prefix)
|
||||
if ids[orderID] {
|
||||
t.Errorf("碰撞的订单号: %s", orderID)
|
||||
}
|
||||
ids[orderID] = true
|
||||
}
|
||||
|
||||
t.Logf("生成了 %d 个订单号,没有发生碰撞", count)
|
||||
}
|
||||
|
||||
func TestGenerateOrderIdWithPrefixSnowflake(t *testing.T) {
|
||||
workerId := int64(1) // 假设使用的 workerId
|
||||
prefix := "ORD"
|
||||
orderID := GenerateOrderIdWithPrefixSnowflake(workerId, prefix)
|
||||
t.Logf("order id with prefix snowflake: %s [%d]", orderID, len(orderID))
|
||||
|
||||
// 验证订单号是否包含前缀
|
||||
assert.Contains(t, orderID, prefix, "订单号应包含前缀")
|
||||
|
||||
// 验证订单号是否为有效的数字字符串
|
||||
assert.Regexp(t, `^ORD\d+$`, orderID, "订单号格式应为前缀加数字")
|
||||
}
|
||||
|
||||
func TestGenerateOrderIdWithPrefixSnowflakeCollision(t *testing.T) {
|
||||
workerId := int64(1) // 假设使用的 workerId
|
||||
prefix := "ORD"
|
||||
count := 1000000 // 生成订单号的数量
|
||||
ids := make(map[string]bool)
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
orderID := GenerateOrderIdWithPrefixSnowflake(workerId, prefix)
|
||||
if ids[orderID] {
|
||||
t.Errorf("碰撞的订单号: %s", orderID)
|
||||
}
|
||||
ids[orderID] = true
|
||||
}
|
||||
|
||||
t.Logf("生成了 %d 个订单号,没有发生碰撞", count)
|
||||
}
|
||||
61
id/snowflake.go
Normal file
61
id/snowflake.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package id
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/bwmarrin/snowflake"
|
||||
)
|
||||
|
||||
var snowflakeNodeMap = sync.Map{}
|
||||
|
||||
type SnowflakeNode struct {
|
||||
workerId int64
|
||||
node *snowflake.Node
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func NewSnowflakeNode(workerId int64) (*SnowflakeNode, error) {
|
||||
node, err := snowflake.NewNode(workerId)
|
||||
return &SnowflakeNode{
|
||||
workerId: workerId,
|
||||
node: node,
|
||||
Mutex: sync.Mutex{},
|
||||
}, err
|
||||
}
|
||||
|
||||
func (sfNode *SnowflakeNode) Generate() int64 {
|
||||
sfNode.Lock()
|
||||
defer sfNode.Unlock()
|
||||
return sfNode.node.Generate().Int64()
|
||||
}
|
||||
|
||||
func (sfNode *SnowflakeNode) GenerateString() string {
|
||||
sfNode.Lock()
|
||||
defer sfNode.Unlock()
|
||||
return sfNode.node.Generate().String()
|
||||
}
|
||||
|
||||
func NewSnowflakeID(workerId int64) (int64, error) {
|
||||
// 64 位 ID = 41 位时间戳 + 10 位工作节点 ID + 12 位序列号
|
||||
|
||||
var node *SnowflakeNode
|
||||
var err error
|
||||
find, ok := snowflakeNodeMap.Load(workerId)
|
||||
if ok {
|
||||
node = find.(*SnowflakeNode)
|
||||
} else {
|
||||
node, err = NewSnowflakeNode(workerId)
|
||||
if err != nil {
|
||||
//log.Println(err)
|
||||
return 0, err
|
||||
}
|
||||
snowflakeNodeMap.Store(workerId, node)
|
||||
}
|
||||
if node == nil {
|
||||
//log.Println("snowflake node is nil")
|
||||
return 0, errors.New("snowflake node is nil")
|
||||
}
|
||||
|
||||
return node.Generate(), nil
|
||||
}
|
||||
25
id/sonyflake.go
Normal file
25
id/sonyflake.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package id
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/sony/sonyflake"
|
||||
)
|
||||
|
||||
var (
|
||||
sf *sonyflake.Sonyflake
|
||||
sfMu sync.Mutex
|
||||
)
|
||||
|
||||
func NewSonyflakeID() (uint64, error) {
|
||||
// 64 位 ID = 39 位时间戳 + 8 位机器 ID + 16 位序列号
|
||||
|
||||
sfMu.Lock()
|
||||
defer sfMu.Unlock()
|
||||
|
||||
if sf == nil {
|
||||
sf = sonyflake.NewSonyflake(sonyflake.Settings{})
|
||||
}
|
||||
|
||||
return sf.NextID()
|
||||
}
|
||||
36
id/uuid.go
Normal file
36
id/uuid.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package id
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/lithammer/shortuuid/v4"
|
||||
"github.com/rs/xid"
|
||||
"github.com/segmentio/ksuid"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
func NewGUIDv4(withHyphen bool) string {
|
||||
id := uuid.NewString()
|
||||
if !withHyphen {
|
||||
id = strings.ReplaceAll(id, "-", "")
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
func NewShortUUID() string {
|
||||
return shortuuid.New()
|
||||
}
|
||||
|
||||
func NewKSUID() string {
|
||||
return ksuid.New().String()
|
||||
}
|
||||
|
||||
func NewXID() string {
|
||||
return xid.New().String()
|
||||
}
|
||||
|
||||
func NewMongoObjectID() string {
|
||||
objID := primitive.NewObjectID()
|
||||
return objID.String()
|
||||
}
|
||||
266
id/uuid_test.go
Normal file
266
id/uuid_test.go
Normal file
@@ -0,0 +1,266 @@
|
||||
package id
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewGUIDv4(t *testing.T) {
|
||||
// 测试带有连字符的 GUID
|
||||
withHyphen := NewGUIDv4(true)
|
||||
assert.NotEmpty(t, withHyphen)
|
||||
assert.Equal(t, 4, strings.Count(withHyphen, "-"), "GUID 应包含 4 个连字符")
|
||||
|
||||
// 测试不带连字符的 GUID
|
||||
withoutHyphen := NewGUIDv4(false)
|
||||
assert.NotEmpty(t, withoutHyphen)
|
||||
assert.Equal(t, 0, strings.Count(withoutHyphen, "-"), "GUID 不应包含连字符")
|
||||
|
||||
// 验证 GUID 的长度
|
||||
assert.Equal(t, 36, len(withHyphen), "带连字符的 GUID 长度应为 36")
|
||||
assert.Equal(t, 32, len(withoutHyphen), "不带连字符的 GUID 长度应为 32")
|
||||
}
|
||||
|
||||
func TestNewGUIDv4CollisionRate(t *testing.T) {
|
||||
const (
|
||||
testCount = 100000 // 测试生成的GUID数量
|
||||
withHyphen = true // 是否带连字符
|
||||
)
|
||||
|
||||
ids := make(map[string]struct{})
|
||||
for i := 0; i < testCount; i++ {
|
||||
id := NewGUIDv4(withHyphen)
|
||||
if _, exists := ids[id]; exists {
|
||||
t.Errorf("碰撞发生: %s 已存在", id)
|
||||
}
|
||||
ids[id] = struct{}{}
|
||||
}
|
||||
|
||||
t.Logf("生成了 %d 个GUID,无碰撞。", testCount)
|
||||
}
|
||||
|
||||
func TestNewShortUUID(t *testing.T) {
|
||||
// 测试生成的 ShortUUID 是否非空
|
||||
id := NewShortUUID()
|
||||
assert.NotEmpty(t, id, "生成的 ShortUUID 应该非空")
|
||||
|
||||
// 测试生成的 ShortUUID 的长度是否符合预期
|
||||
assert.True(t, len(id) > 0, "生成的 ShortUUID 长度应该大于 0")
|
||||
}
|
||||
|
||||
func TestNewShortUUIDCollisionRate(t *testing.T) {
|
||||
const testCount = 100000 // 测试生成的ShortUUID数量
|
||||
|
||||
ids := make(map[string]struct{})
|
||||
for i := 0; i < testCount; i++ {
|
||||
id := NewShortUUID()
|
||||
if _, exists := ids[id]; exists {
|
||||
t.Errorf("碰撞发生: %s 已存在", id)
|
||||
}
|
||||
ids[id] = struct{}{}
|
||||
}
|
||||
|
||||
t.Logf("生成了 %d 个ShortUUID,无碰撞。", testCount)
|
||||
}
|
||||
|
||||
func TestNewKSUID(t *testing.T) {
|
||||
// 测试生成的 KSUID 是否非空
|
||||
id := NewKSUID()
|
||||
assert.NotEmpty(t, id, "生成的 KSUID 应该非空")
|
||||
|
||||
// 测试生成的 KSUID 的长度是否符合预期
|
||||
assert.Equal(t, 27, len(id), "生成的 KSUID 长度应该为 27")
|
||||
}
|
||||
|
||||
func TestNewKSUIDCollisionRate(t *testing.T) {
|
||||
const testCount = 100000 // 测试生成的KSUID数量
|
||||
|
||||
ids := make(map[string]struct{})
|
||||
for i := 0; i < testCount; i++ {
|
||||
id := NewKSUID()
|
||||
if _, exists := ids[id]; exists {
|
||||
t.Errorf("碰撞发生: %s 已存在", id)
|
||||
}
|
||||
ids[id] = struct{}{}
|
||||
}
|
||||
|
||||
t.Logf("生成了 %d 个KSUID,无碰撞。", testCount)
|
||||
}
|
||||
|
||||
func TestNewXID(t *testing.T) {
|
||||
// 测试生成的 XID 是否非空
|
||||
id := NewXID()
|
||||
assert.NotEmpty(t, id, "生成的 XID 应该非空")
|
||||
|
||||
// 测试生成的 XID 的长度是否符合预期
|
||||
assert.Equal(t, 20, len(id), "生成的 XID 长度应该为 20")
|
||||
}
|
||||
|
||||
func TestNewXIDCollisionRate(t *testing.T) {
|
||||
const testCount = 100000 // 测试生成的XID数量
|
||||
|
||||
ids := make(map[string]struct{})
|
||||
for i := 0; i < testCount; i++ {
|
||||
id := NewXID()
|
||||
if _, exists := ids[id]; exists {
|
||||
t.Errorf("碰撞发生: %s 已存在", id)
|
||||
}
|
||||
ids[id] = struct{}{}
|
||||
}
|
||||
|
||||
t.Logf("生成了 %d 个XID,无碰撞。", testCount)
|
||||
}
|
||||
|
||||
func TestNewSnowflakeID(t *testing.T) {
|
||||
tests := []struct {
|
||||
workerId int64
|
||||
expectErr bool
|
||||
}{
|
||||
{0, false}, // 有效的 workerId
|
||||
{31, false}, // 有效的 workerId
|
||||
{32, false}, // 有效的 workerId
|
||||
|
||||
{-1, true}, // 无效的 workerId
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
id, err := NewSnowflakeID(tt.workerId)
|
||||
if (err != nil) != tt.expectErr {
|
||||
t.Errorf("NewSnowflakeID(%d) 错误状态不符合预期: %v", tt.workerId, err)
|
||||
}
|
||||
if err == nil && id <= 0 {
|
||||
t.Errorf("NewSnowflakeID(%d) 生成的ID无效: %d", tt.workerId, id)
|
||||
}
|
||||
t.Logf("NewSnowflakeID(%d) ID: %d", tt.workerId, id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewSnowflakeIDCollisionRate(t *testing.T) {
|
||||
const (
|
||||
workerId = 0
|
||||
testCount = 100000 // 测试生成的ID数量
|
||||
)
|
||||
|
||||
ids := make(map[int64]struct{})
|
||||
for i := 0; i < testCount; i++ {
|
||||
id, err := NewSnowflakeID(workerId)
|
||||
if err != nil {
|
||||
t.Errorf("生成ID时出现错误: %v", err)
|
||||
continue
|
||||
}
|
||||
if _, exists := ids[id]; exists {
|
||||
t.Errorf("碰撞发生: %d 已存在", id)
|
||||
}
|
||||
ids[id] = struct{}{}
|
||||
}
|
||||
|
||||
t.Logf("生成了 %d 个Snowflake ID,无碰撞。", testCount)
|
||||
}
|
||||
|
||||
func TestConcurrentNewSnowflakeIDCollisionRate(t *testing.T) {
|
||||
const (
|
||||
workerId = 0 // Snowflake 工作节点 ID
|
||||
testCount = 100000 // 测试生成的 ID 数量
|
||||
workerCount = 10 // 并发工作线程数
|
||||
)
|
||||
|
||||
var mu sync.Mutex
|
||||
ids := make(map[int64]struct{})
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for w := 0; w < workerCount; w++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < testCount/workerCount; i++ {
|
||||
id, err := NewSnowflakeID(workerId)
|
||||
if err != nil {
|
||||
t.Errorf("生成 Snowflake ID 时出现错误: %v", err)
|
||||
continue
|
||||
}
|
||||
mu.Lock()
|
||||
if _, exists := ids[id]; exists {
|
||||
t.Errorf("碰撞发生: %d 已存在", id)
|
||||
}
|
||||
ids[id] = struct{}{}
|
||||
mu.Unlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
t.Logf("生成了 %d 个 Snowflake ID,无碰撞。", testCount)
|
||||
}
|
||||
|
||||
func TestNewSonyflakeID(t *testing.T) {
|
||||
// 测试生成的 Sonyflake ID 是否有效
|
||||
id, err := NewSonyflakeID()
|
||||
t.Logf("sonyflake id: %v", id)
|
||||
assert.NoError(t, err, "生成 Sonyflake ID 时不应出现错误")
|
||||
assert.True(t, id > 0, "生成的 Sonyflake ID 应该是正数")
|
||||
}
|
||||
|
||||
func TestNewSonyflakeIDCollisionRate(t *testing.T) {
|
||||
const testCount = 100000 // 测试生成的 Sonyflake ID 数量
|
||||
|
||||
ids := make(map[uint64]struct{})
|
||||
for i := 0; i < testCount; i++ {
|
||||
id, err := NewSonyflakeID()
|
||||
if err != nil {
|
||||
t.Errorf("生成 Sonyflake ID 时出现错误: %v", err)
|
||||
continue
|
||||
}
|
||||
if _, exists := ids[id]; exists {
|
||||
t.Errorf("碰撞发生: %d 已存在", id)
|
||||
}
|
||||
ids[id] = struct{}{}
|
||||
}
|
||||
|
||||
t.Logf("生成了 %d 个 Sonyflake ID,无碰撞。", testCount)
|
||||
}
|
||||
|
||||
func TestConcurrentNewSonyflakeIDCollisionRate(t *testing.T) {
|
||||
const (
|
||||
testCount = 100000 // 测试生成的 Sonyflake ID 数量
|
||||
workerCount = 10 // 并发工作线程数
|
||||
)
|
||||
|
||||
var mu sync.Mutex
|
||||
ids := make(map[uint64]struct{})
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for w := 0; w < workerCount; w++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < testCount/workerCount; i++ {
|
||||
id, err := NewSonyflakeID()
|
||||
if err != nil {
|
||||
t.Errorf("生成 Sonyflake ID 时出现错误: %v", err)
|
||||
continue
|
||||
}
|
||||
mu.Lock()
|
||||
if _, exists := ids[id]; exists {
|
||||
t.Errorf("碰撞发生: %d 已存在", id)
|
||||
}
|
||||
ids[id] = struct{}{}
|
||||
mu.Unlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
t.Logf("生成了 %d 个 Sonyflake ID,无碰撞。", testCount)
|
||||
}
|
||||
|
||||
func TestNewMongoObjectID(t *testing.T) {
|
||||
// 测试生成的 ObjectID 是否非空
|
||||
id := NewMongoObjectID()
|
||||
assert.NotEmpty(t, id, "生成的 Mongo ObjectID 应该非空")
|
||||
|
||||
// 测试生成的 ObjectID 的长度是否符合预期
|
||||
assert.Equal(t, 36, len(id), "生成的 Mongo ObjectID 长度应该为 36")
|
||||
}
|
||||
@@ -5,13 +5,16 @@ go 1.23.0
|
||||
toolchain go1.24.1
|
||||
|
||||
require (
|
||||
github.com/go-kratos/kratos/v2 v2.8.4
|
||||
github.com/golang-jwt/jwt/v5 v5.1.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
|
||||
@@ -1,12 +1,28 @@
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-kratos/kratos/v2 v2.8.4 h1:eIJLE9Qq9WSoKx+Buy2uPyrahtF/lPh+Xf4MTpxhmjs=
|
||||
github.com/go-kratos/kratos/v2 v2.8.4/go.mod h1:mq62W2101a5uYyRxe+7IdWubu7gZCGYqSNKwGFiiRcw=
|
||||
github.com/golang-jwt/jwt/v5 v5.1.0 h1:UGKbA/IPjtS6zLcdB7i5TyACMgSbOTiR8qzXgw8HWQU=
|
||||
github.com/golang-jwt/jwt/v5 v5.1.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"encoding/json"
|
||||
"github.com/go-kratos/kratos/v2/encoding"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
@@ -40,14 +40,16 @@ func ParseJWTClaimsToStruct[T any](tokenString string) (*T, error) {
|
||||
return nil, fmt.Errorf("invalid token claims")
|
||||
}
|
||||
|
||||
codec := encoding.GetCodec("json")
|
||||
|
||||
// 将 claims 转换为目标类型
|
||||
claimsBytes, err := json.Marshal(claims)
|
||||
claimsBytes, err := codec.Marshal(claims)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal claims: %v", err)
|
||||
}
|
||||
|
||||
var ret T
|
||||
err = json.Unmarshal(claimsBytes, &ret)
|
||||
err = codec.Unmarshal(claimsBytes, &ret)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal claims: %v", err)
|
||||
}
|
||||
@@ -132,13 +134,15 @@ func GenerateGenericJWT[T any](payload T, secretKey []byte, signingMethod jwt.Si
|
||||
|
||||
// ToMapClaims 将泛型 payload 转换为 jwt.MapClaims
|
||||
func ToMapClaims[T any](payload T) (jwt.MapClaims, error) {
|
||||
payloadBytes, err := json.Marshal(payload)
|
||||
codec := encoding.GetCodec("json")
|
||||
|
||||
payloadBytes, err := codec.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var claims jwt.MapClaims
|
||||
err = json.Unmarshal(payloadBytes, &claims)
|
||||
err = codec.Unmarshal(payloadBytes, &claims)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@ import (
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
_ "github.com/go-kratos/kratos/v2/encoding/json"
|
||||
_ "github.com/go-kratos/kratos/v2/encoding/proto"
|
||||
)
|
||||
|
||||
func TestParseJWTPayload(t *testing.T) {
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
package order_id
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/tx7do/go-utils/trans"
|
||||
)
|
||||
|
||||
type idCounter uint32
|
||||
|
||||
func (c *idCounter) Increase() uint32 {
|
||||
cur := *c
|
||||
atomic.AddUint32((*uint32)(c), 1)
|
||||
atomic.CompareAndSwapUint32((*uint32)(c), 1000, 0)
|
||||
return uint32(cur)
|
||||
}
|
||||
|
||||
var orderIdIndex idCounter
|
||||
|
||||
// GenerateOrderIdWithRandom 生成20位订单号,前缀+时间+随机数
|
||||
func GenerateOrderIdWithRandom(prefix string, split string, tm *time.Time) string {
|
||||
if tm == nil {
|
||||
tm = trans.Time(time.Now())
|
||||
}
|
||||
|
||||
index := rand.Intn(1000)
|
||||
|
||||
return fmt.Sprintf("%s%s%.4d%s%.2d%s%.2d%s%.2d%s%.2d%s%.2d%s%.4d", prefix, split,
|
||||
tm.Year(), split, tm.Month(), split, tm.Day(), split,
|
||||
tm.Hour(), split, tm.Minute(), split, tm.Second(), split, index)
|
||||
}
|
||||
|
||||
// GenerateOrderIdWithIncreaseIndex 生成20位订单号,前缀+时间+自增长索引
|
||||
func GenerateOrderIdWithIncreaseIndex(prefix string, split string, tm *time.Time) string {
|
||||
if tm == nil {
|
||||
tm = trans.Time(time.Now())
|
||||
}
|
||||
|
||||
index := orderIdIndex.Increase()
|
||||
|
||||
return fmt.Sprintf("%s%s%.4d%s%.2d%s%.2d%s%.2d%s%.2d%s%.2d%s%.4d", prefix, split,
|
||||
tm.Year(), split, tm.Month(), split, tm.Day(), split,
|
||||
tm.Hour(), split, tm.Minute(), split, tm.Second(), split, index)
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package order_id
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/tx7do/go-utils/trans"
|
||||
)
|
||||
|
||||
func TestGenerateOrderIdWithRandom(t *testing.T) {
|
||||
fmt.Println(GenerateOrderIdWithRandom("PT", "-", trans.Time(time.Now())))
|
||||
|
||||
tm := time.Now()
|
||||
var ids map[string]bool
|
||||
ids = make(map[string]bool)
|
||||
count := 100
|
||||
for i := 0; i < count; i++ {
|
||||
ids[GenerateOrderIdWithRandom("PT", "", trans.Time(tm))] = true
|
||||
}
|
||||
assert.Equal(t, count, len(ids))
|
||||
}
|
||||
|
||||
func TestGenerateOrderIdWithIndex(t *testing.T) {
|
||||
tm := time.Now()
|
||||
|
||||
fmt.Println(GenerateOrderIdWithIncreaseIndex("PT", "", trans.Time(tm)))
|
||||
|
||||
ids := make(map[string]bool)
|
||||
count := 100
|
||||
for i := 0; i < count; i++ {
|
||||
ids[GenerateOrderIdWithIncreaseIndex("PT", "", trans.Time(tm))] = true
|
||||
}
|
||||
assert.Equal(t, count, len(ids))
|
||||
}
|
||||
|
||||
func TestGenerateOrderIdWithIndexThread(t *testing.T) {
|
||||
tm := time.Now()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var ids sync.Map
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
for i := 0; i < 100; i++ {
|
||||
id := GenerateOrderIdWithIncreaseIndex("PT", "", trans.Time(tm))
|
||||
ids.Store(id, true)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
aLen := 0
|
||||
ids.Range(func(k, v interface{}) bool {
|
||||
aLen++
|
||||
return true
|
||||
})
|
||||
assert.Equal(t, 1000, aLen)
|
||||
}
|
||||
22
slug/go.mod
Normal file
22
slug/go.mod
Normal file
@@ -0,0 +1,22 @@
|
||||
module github.com/tx7do/go-utils/slug
|
||||
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.23.2
|
||||
|
||||
require (
|
||||
github.com/gosimple/slug v1.15.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/gosimple/unidecode v1.0.1 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
replace github.com/tx7do/go-utils => ../
|
||||
27
slug/go.sum
Normal file
27
slug/go.sum
Normal file
@@ -0,0 +1,27 @@
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gosimple/slug v1.15.0 h1:wRZHsRrRcs6b0XnxMUBM6WK1U1Vg5B0R7VkIf1Xzobo=
|
||||
github.com/gosimple/slug v1.15.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ=
|
||||
github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o=
|
||||
github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@@ -1,50 +0,0 @@
|
||||
package sonyflake
|
||||
|
||||
import (
|
||||
"github.com/sony/sonyflake"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
sf *sonyflake.Sonyflake
|
||||
)
|
||||
|
||||
func startTime() time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
func getMachineID() (uint16, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func checkMachineID(uint16) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// InitSonyflake 初始化Sonyflake节点单体
|
||||
func InitSonyflake() {
|
||||
settings := sonyflake.Settings{
|
||||
/*StartTime: startTime(),
|
||||
MachineID: getMachineID,
|
||||
CheckMachineID: checkMachineID,*/
|
||||
}
|
||||
sf = sonyflake.NewSonyflake(settings)
|
||||
if sf == nil {
|
||||
panic("sonyflake not created")
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateSonyflake 生成 Sonyflake ID
|
||||
func GenerateSonyflake() uint64 {
|
||||
if sf == nil {
|
||||
InitSonyflake()
|
||||
}
|
||||
if sf == nil {
|
||||
return 0
|
||||
}
|
||||
id, err := sf.NextID()
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return id
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package sonyflake
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
InitSonyflake()
|
||||
}
|
||||
|
||||
func TestGenerateSonyflake(t *testing.T) {
|
||||
for i := 0; i < 100; i++ {
|
||||
id := GenerateSonyflake()
|
||||
fmt.Println(id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateTime(t *testing.T) {
|
||||
// 秒
|
||||
fmt.Printf("时间戳(秒):%v;\n", time.Now().Unix())
|
||||
fmt.Printf("时间戳(纳秒转换为秒):%v;\n", time.Now().UnixNano()/1e9)
|
||||
|
||||
// 毫秒
|
||||
fmt.Printf("时间戳(毫秒):%v;\n", time.Now().UnixMilli())
|
||||
fmt.Printf("时间戳(纳秒转换为毫秒):%v;\n", time.Now().UnixNano()/1e6)
|
||||
|
||||
// 微秒
|
||||
fmt.Printf("时间戳(微秒):%v;\n", time.Now().UnixMicro())
|
||||
fmt.Printf("时间戳(纳秒转换为微秒):%v;\n", time.Now().UnixNano()/1e3)
|
||||
|
||||
// 纳秒
|
||||
fmt.Printf("时间戳(纳秒):%v;\n", time.Now().UnixNano())
|
||||
}
|
||||
10
tag.bat
10
tag.bat
@@ -1,12 +1,14 @@
|
||||
git tag v1.1.23
|
||||
git tag v1.1.28
|
||||
|
||||
git tag bank_card/v1.1.5
|
||||
git tag geoip/v1.1.5
|
||||
git tag translator/v1.1.2
|
||||
git tag copierutil/v0.0.3
|
||||
git tag jwtutil/v0.0.1
|
||||
git tag copierutil/v0.0.5
|
||||
git tag jwtutil/v0.0.2
|
||||
git tag id/v0.0.1
|
||||
git tag slug/v0.0.1
|
||||
|
||||
git tag entgo/v1.1.28
|
||||
git tag entgo/v1.1.30
|
||||
git tag gorm/v1.1.6
|
||||
|
||||
git push origin --tags
|
||||
|
||||
@@ -10,4 +10,52 @@ const (
|
||||
DefaultTimeLocationName = "Asia/Shanghai"
|
||||
)
|
||||
|
||||
// More predefined layouts for use in Time.Format and time.Parse.
|
||||
const (
|
||||
DT14 = "20060102150405"
|
||||
DT8 = "20060102"
|
||||
DT8MDY = "01022006"
|
||||
DT6 = "200601"
|
||||
MonthDay = "1/2"
|
||||
DIN5008FullDate = "02.01.2006" // German DIN 5008 standard
|
||||
DIN5008Date = "02.01.06"
|
||||
RFC3339FullDate = time.DateOnly
|
||||
RFC3339Milli = "2006-01-02T15:04:05.999Z07:00"
|
||||
RFC3339Dash = "2006-01-02T15-04-05Z07-00"
|
||||
ISO8601 = "2006-01-02T15:04:05Z0700"
|
||||
ISO8601TZHour = "2006-01-02T15:04:05Z07"
|
||||
ISO8601NoTZ = "2006-01-02T15:04:05"
|
||||
ISO8601MilliNoTZ = "2006-01-02T15:04:05.999"
|
||||
ISO8601Milli = "2006-01-02T15:04:05.999Z0700"
|
||||
ISO8601CompactZ = "20060102T150405Z0700"
|
||||
ISO8601CompactNoTZ = "20060102T150405"
|
||||
ISO8601YM = "2006-01"
|
||||
ISO9075 = time.DateTime // ISO/IEC 9075 used by MySQL, BigQuery, etc.
|
||||
ISO9075MicroTZ = "2006-01-02 15:04:05.999999-07" // ISO/IEC 9075 used by PostgreSQL
|
||||
RFC5322 = "Mon, 2 Jan 2006 15:04:05 -0700" // RFC5322 = "Mon Jan 02 15:04:05 -0700 2006"
|
||||
SQLTimestamp = ISO9075
|
||||
SQLTimestampMinutes = "2006-01-02 15:04"
|
||||
Ruby = "2006-01-02 15:04:05 -0700" // Ruby Time.now.to_s
|
||||
InsightlyAPIQuery = "_1/_2/2006 _3:04:05 PM"
|
||||
DateMDY = "1/2/2006" // an underscore results in a space.
|
||||
DateMDYSlash = "01/02/2006"
|
||||
DateDMYDash = "_2-01-2006" // Jira XML Date format
|
||||
DateDMYHM2 = "02:01:06 15:04" // GMT time in format dd:mm:yy hh:mm
|
||||
DateYMD = RFC3339FullDate
|
||||
DateTextUS = "January 2, 2006"
|
||||
DateTextUSAbbr3 = "Jan 2, 2006"
|
||||
DateTextEU = "2 January 2006"
|
||||
DateTextEUAbbr3 = "2 Jan 2006"
|
||||
MonthAbbrYear = "Jan 2006"
|
||||
MonthYear = "January 2006"
|
||||
)
|
||||
|
||||
const (
|
||||
RFC3339Min = "0000-01-01T00:00:00Z"
|
||||
RFC3339Max = "9999-12-31T23:59:59Z"
|
||||
RFC3339Zero = "0001-01-01T00:00:00Z" // Golang zero value
|
||||
RFC3339ZeroUnix = "1970-01-01T00:00:00Z"
|
||||
RFC3339YMDZeroUnix = int64(-62135596800)
|
||||
)
|
||||
|
||||
var ReferenceTimeValue time.Time = time.Date(2006, 1, 2, 15, 4, 5, 999999999, time.FixedZone("MST", -7*60*60))
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package timeutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -43,3 +45,291 @@ func DurationHMS(d time.Duration) (int, int, int) {
|
||||
|
||||
return int(h), int(m), int(s)
|
||||
}
|
||||
|
||||
// Reformat a time string from one format to another
|
||||
// Deprecated...
|
||||
func FromTo(value, fromLayout, toLayout string) (string, error) {
|
||||
t, err := time.Parse(fromLayout, strings.TrimSpace(value))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return t.Format(toLayout), nil
|
||||
}
|
||||
|
||||
// Reformat a time string from one format to another
|
||||
func FromTo2(fromLayout, toLayout, value string) (string, error) {
|
||||
t, err := time.Parse(fromLayout, strings.TrimSpace(value))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return t.Format(toLayout), nil
|
||||
}
|
||||
|
||||
func FromToFirstValueOrEmpty(fromLayout, toLayout string, values []string) string {
|
||||
dtString, err := FromToFirstValue(fromLayout, toLayout, values)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return dtString
|
||||
}
|
||||
|
||||
func FromToFirstValue(fromLayout, toLayout string, values []string) (string, error) {
|
||||
for _, val := range values {
|
||||
dt, err := time.Parse(fromLayout, val)
|
||||
if err == nil {
|
||||
return dt.Format(toLayout), nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("no match")
|
||||
}
|
||||
|
||||
func ParseFirstValueOrZero(layout string, values []string) time.Time {
|
||||
dt, err := ParseFirstValue(layout, values)
|
||||
if err != nil {
|
||||
return TimeZeroRFC3339()
|
||||
}
|
||||
return dt
|
||||
}
|
||||
|
||||
func ParseFirstValue(layout string, values []string) (time.Time, error) {
|
||||
for _, val := range values {
|
||||
try, err := time.Parse(layout, val)
|
||||
if err == nil {
|
||||
return try, nil
|
||||
}
|
||||
}
|
||||
numVals := len(values)
|
||||
if numVals == 0 {
|
||||
return time.Now(), errors.New("no time values supplied")
|
||||
}
|
||||
return time.Now(), fmt.Errorf("no valid string of [%v] supplied values", strconv.Itoa(numVals))
|
||||
}
|
||||
|
||||
// ParseOrZero returns a parsed time.Time or the RFC-3339 zero time.
|
||||
func ParseOrZero(layout, value string) time.Time {
|
||||
t, err := time.Parse(layout, value)
|
||||
if err != nil {
|
||||
return TimeZeroRFC3339()
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// ParseFirst attempts to parse a string with a set of layouts.
|
||||
func ParseFirst(layouts []string, value string) (time.Time, error) {
|
||||
value = strings.TrimSpace(value)
|
||||
if len(value) == 0 || len(layouts) == 0 {
|
||||
return time.Now(), fmt.Errorf(
|
||||
"requires value [%v] and at least one layout [%v]", value, strings.Join(layouts, ","))
|
||||
}
|
||||
for _, layout := range layouts {
|
||||
layout = strings.TrimSpace(layout)
|
||||
if len(layout) == 0 {
|
||||
continue
|
||||
}
|
||||
if dt, err := time.Parse(layout, value); err == nil {
|
||||
return dt, nil
|
||||
}
|
||||
}
|
||||
return time.Now(), fmt.Errorf("cannot parse time [%v] with layouts [%v]",
|
||||
value, strings.Join(layouts, ","))
|
||||
}
|
||||
|
||||
var FormatMap = map[string]string{
|
||||
"RFC3339": time.RFC3339,
|
||||
"RFC3339YMD": RFC3339FullDate,
|
||||
"ISO8601YM": ISO8601YM,
|
||||
}
|
||||
|
||||
func GetFormat(formatName string) (string, error) {
|
||||
format, ok := FormatMap[strings.TrimSpace(formatName)]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("format Not Found: %v", format)
|
||||
}
|
||||
return format, nil
|
||||
}
|
||||
|
||||
func TimeMinRFC3339() time.Time {
|
||||
t0, _ := time.Parse(time.RFC3339, RFC3339Min)
|
||||
return t0
|
||||
}
|
||||
|
||||
func TimeZeroRFC3339() time.Time {
|
||||
t0, _ := time.Parse(time.RFC3339, RFC3339Zero)
|
||||
return t0
|
||||
}
|
||||
|
||||
func TimeZeroUnix() time.Time {
|
||||
t0, _ := time.Parse(time.RFC3339, RFC3339ZeroUnix)
|
||||
return t0
|
||||
}
|
||||
|
||||
func isZeroAny(u time.Time) bool {
|
||||
return u.Equal(TimeZeroRFC3339()) ||
|
||||
u.Equal(TimeMinRFC3339()) ||
|
||||
u.Equal(TimeZeroUnix())
|
||||
}
|
||||
|
||||
type RFC3339YMDTime struct{ time.Time }
|
||||
|
||||
type ISO8601NoTzMilliTime struct{ time.Time }
|
||||
|
||||
func (t *RFC3339YMDTime) UnmarshalJSON(buf []byte) error {
|
||||
tt, isNil, err := timeUnmarshalJSON(buf, RFC3339FullDate)
|
||||
if err != nil || isNil {
|
||||
return err
|
||||
}
|
||||
t.Time = tt
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t RFC3339YMDTime) MarshalJSON() ([]byte, error) {
|
||||
return timeMarshalJSON(t.Time, RFC3339FullDate)
|
||||
}
|
||||
|
||||
func (t *ISO8601NoTzMilliTime) UnmarshalJSON(buf []byte) error {
|
||||
tt, isNil, err := timeUnmarshalJSON(buf, ISO8601MilliNoTZ)
|
||||
if err != nil || isNil {
|
||||
return err
|
||||
}
|
||||
t.Time = tt
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t ISO8601NoTzMilliTime) MarshalJSON() ([]byte, error) {
|
||||
return timeMarshalJSON(t.Time, ISO8601MilliNoTZ)
|
||||
}
|
||||
|
||||
func timeUnmarshalJSON(buf []byte, layout string) (time.Time, bool, error) {
|
||||
str := string(buf)
|
||||
isNil := true
|
||||
if str == "null" || str == "\"\"" {
|
||||
return time.Time{}, isNil, nil
|
||||
}
|
||||
tt, err := time.Parse(layout, strings.Trim(str, `"`))
|
||||
if err != nil {
|
||||
return time.Time{}, false, err
|
||||
}
|
||||
return tt, false, nil
|
||||
}
|
||||
|
||||
func timeMarshalJSON(t time.Time, layout string) ([]byte, error) {
|
||||
return []byte(`"` + t.Format(layout) + `"`), nil
|
||||
}
|
||||
|
||||
func ParseSlice(layout string, strings []string) ([]time.Time, error) {
|
||||
var times []time.Time
|
||||
for _, raw := range strings {
|
||||
t, err := time.Parse(layout, raw)
|
||||
if err != nil {
|
||||
return times, err
|
||||
}
|
||||
times = append(times, t)
|
||||
}
|
||||
return times, nil
|
||||
}
|
||||
|
||||
// FormatTimeMulti formats a `time.Time` object or
|
||||
// an epoch number. It is adapted from `github.com/wcharczuk/go-chart`.
|
||||
func FormatTimeMulti(dateFormat string, v any) string {
|
||||
if typed, isTyped := v.(time.Time); isTyped {
|
||||
return typed.Format(dateFormat)
|
||||
}
|
||||
if typed, isTyped := v.(int64); isTyped {
|
||||
return time.Unix(0, typed).Format(dateFormat)
|
||||
}
|
||||
if typed, isTyped := v.(float64); isTyped {
|
||||
return time.Unix(0, int64(typed)).Format(dateFormat)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func FormatTimeToString(format string) func(time.Time) string {
|
||||
return func(dt time.Time) string {
|
||||
return dt.Format(format)
|
||||
}
|
||||
}
|
||||
|
||||
// OffsetFormat converts an integer offset value to a string
|
||||
// value to use in string time formats. Note: RFC-3339 times
|
||||
// use colons and the UTC "Z" offset.
|
||||
func OffsetFormat(offset int, useColon, useZ bool) string {
|
||||
offsetStr := "+0000"
|
||||
if offset == 0 {
|
||||
if useZ {
|
||||
offsetStr = "Z"
|
||||
} else if useColon {
|
||||
offsetStr = "+00:00"
|
||||
}
|
||||
} else if offset > 0 {
|
||||
if useColon {
|
||||
hr := offset / 100
|
||||
mn := offset - (hr * 100)
|
||||
offsetStr = "+" + fmt.Sprintf("%02d:%02d", hr, mn)
|
||||
} else {
|
||||
offsetStr = "+" + fmt.Sprintf("%04d", offset)
|
||||
}
|
||||
} else if offset < 0 {
|
||||
if useColon {
|
||||
offsetPositive := -1 * offset
|
||||
hr := offsetPositive / 100
|
||||
mn := offsetPositive - (hr * 100)
|
||||
offsetStr = "-" + fmt.Sprintf("%02d:%02d", hr, mn)
|
||||
} else {
|
||||
offsetStr = "-" + fmt.Sprintf("%04d", -1*offset)
|
||||
}
|
||||
}
|
||||
return offsetStr
|
||||
}
|
||||
|
||||
// var rxSQLTimestamp = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}$`)
|
||||
|
||||
func ParseTimeUsingOffset(format, raw, sep string, offset int, useColon, useZ bool) (time.Time, error) {
|
||||
return time.Parse(format, raw+sep+OffsetFormat(offset, useColon, useZ))
|
||||
}
|
||||
|
||||
// ParseTimeSQLTimestampUsingOffset converts a SQL timestamp without timezone
|
||||
// adding in a manual integer timezone.
|
||||
func ParseTimeSQLTimestampUsingOffset(timeStr string, offset int) (time.Time, error) {
|
||||
return ParseTimeUsingOffset(Ruby, timeStr, " ", offset, false, false)
|
||||
/*
|
||||
timeStr = strings.TrimSpace(timeStr)
|
||||
if !rxSQLTimestamp.MatchString(timeStr) {
|
||||
return time.Now(), fmt.Errorf("E_INVALID_SQL_TIMESTAMP [%v]", timeStr)
|
||||
}
|
||||
offsetStr := OffsetFormat(offset, useColon, useZ)
|
||||
timeStr += " " + offsetStr
|
||||
dt, err := time.Parse(Ruby, timeStr)
|
||||
return dt, err
|
||||
*/
|
||||
}
|
||||
|
||||
const (
|
||||
LayoutNameDT4 = "dt4"
|
||||
LayoutNameDT6 = "dt6"
|
||||
LayoutNameDT8 = "dt8"
|
||||
LayoutNameDT14 = "dt14"
|
||||
)
|
||||
|
||||
// IsDTX returns the dtx format if conformant to various DTX values (dt4, dt6, dt8, dt14).
|
||||
func IsDTX(d int) (string, error) {
|
||||
switch len(strconv.Itoa(d)) {
|
||||
case 4:
|
||||
return LayoutNameDT4, nil
|
||||
case 6:
|
||||
m := d - ((d / 100) * 100)
|
||||
if m < 1 || m > 12 {
|
||||
return LayoutNameDT6, errors.New("dt6 length month value is out of bounds")
|
||||
}
|
||||
return LayoutNameDT6, nil
|
||||
case 8:
|
||||
dy := d - ((d / 100) * 100)
|
||||
if dy < 1 || dy > 31 {
|
||||
return LayoutNameDT6, errors.New("dt8 day value is out of bounds")
|
||||
}
|
||||
return LayoutNameDT8, nil
|
||||
case 14:
|
||||
return LayoutNameDT14, nil
|
||||
default:
|
||||
return "", errors.New("length mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
132
timeutil/format_test.go
Normal file
132
timeutil/format_test.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package timeutil
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var dmyhm2ParseTests = []struct {
|
||||
v string
|
||||
want string
|
||||
}{
|
||||
{"02:01:06 15:04", "2006-01-02T15:04:00Z"},
|
||||
}
|
||||
|
||||
// TestDMYHM2ParseTime ensures timeutil.DateDMYHM2 is parsed to GMT timezone.
|
||||
func TestDMYHM2ParseTime(t *testing.T) {
|
||||
for _, tt := range dmyhm2ParseTests {
|
||||
got, err := FromTo(tt.v, DateDMYHM2, time.RFC3339)
|
||||
if err != nil {
|
||||
t.Errorf("time.Parse(DateDMYHM2) Error: with %v, want %v, err %v", tt.v, tt.want, err)
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("time.Parse(\"%v\", DateDMYHM2) Mismatch: want %v, got %v", tt.v, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var rfc3339YMDTimeTests = []struct {
|
||||
v string
|
||||
want string
|
||||
}{
|
||||
{`{"MyTime":"2001-02-03"}`, `{"MyTime":"2001-02-03"}`},
|
||||
{`{"MyTime":"0001-01-01"}`, `{"MyTime":"0001-01-01"}`},
|
||||
{`{"MyTime":""}`, `{"MyTime":"0001-01-01"}`},
|
||||
{`{}`, `{"MyTime":"0001-01-01"}`}}
|
||||
|
||||
type myStruct struct {
|
||||
MyTime RFC3339YMDTime
|
||||
}
|
||||
|
||||
func TestRfc3339YMDTime(t *testing.T) {
|
||||
for _, tt := range rfc3339YMDTimeTests {
|
||||
my := myStruct{}
|
||||
//fmt.Println(tt.v)
|
||||
err := json.Unmarshal([]byte(tt.v), &my)
|
||||
if err != nil {
|
||||
t.Errorf("Rfc3339YMDTime.Unmarshal: with %v, want %v, err %v", tt.v, tt.want, err)
|
||||
}
|
||||
|
||||
bytes, err := json.Marshal(my)
|
||||
if err != nil {
|
||||
t.Errorf("Rfc3339YMDTime.Marshal: with %v, want %v, err %v", tt.v, tt.want, err)
|
||||
}
|
||||
|
||||
got := string(bytes)
|
||||
|
||||
if got != tt.want {
|
||||
t.Errorf("Rfc3339YMDTime(%v): want %v, got %v", tt.v, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var offsetFormatTests = []struct {
|
||||
input int
|
||||
useColon bool
|
||||
useZ bool
|
||||
want string
|
||||
}{
|
||||
{0, false, false, "+0000"},
|
||||
{0, true, false, "+00:00"},
|
||||
{0, true, true, "Z"},
|
||||
{400, false, false, "+0400"},
|
||||
{-400, false, false, "-0400"},
|
||||
{530, false, false, "+0530"},
|
||||
{-530, false, false, "-0530"},
|
||||
{700, true, false, "+07:00"},
|
||||
{-700, true, false, "-07:00"},
|
||||
{845, true, false, "+08:45"},
|
||||
{-845, true, false, "-08:45"},
|
||||
}
|
||||
|
||||
func TestOffsetFormat(t *testing.T) {
|
||||
for _, tt := range offsetFormatTests {
|
||||
got := OffsetFormat(tt.input, tt.useColon, tt.useZ)
|
||||
if got != tt.want {
|
||||
t.Errorf("time.OffsetFormat(\"%v\",%v,%v) Mismatch: want [%v], got [%v]", tt.input, tt.useColon, tt.useZ, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var isDTXTests = []struct {
|
||||
v int
|
||||
want string
|
||||
}{
|
||||
{2004, LayoutNameDT4},
|
||||
{200401, LayoutNameDT6},
|
||||
}
|
||||
|
||||
func TestIsDTX(t *testing.T) {
|
||||
for _, tt := range isDTXTests {
|
||||
got, err := IsDTX(tt.v)
|
||||
if err != nil {
|
||||
t.Errorf("time.IsDTX(%d) Error: want (%s), err (%v)", tt.v, tt.want, err)
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("time.IsDTX(%d) Mismatch: want (%s), got (%s)", tt.v, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var formatsTests = []struct {
|
||||
v string
|
||||
format string
|
||||
want string
|
||||
}{
|
||||
{"2023-06-18T00:00:00Z", DateTextUS, "June 18, 2023"},
|
||||
{"2023-06-18T00:00:00Z", DateTextUSAbbr3, "Jun 18, 2023"},
|
||||
}
|
||||
|
||||
func TestFormats(t *testing.T) {
|
||||
for _, tt := range formatsTests {
|
||||
dt, err := time.Parse(time.RFC3339, tt.v)
|
||||
if err != nil {
|
||||
t.Errorf("time.Parse(time.RFC3339, %s) Error: err (%s)", tt.v, err.Error())
|
||||
}
|
||||
got := dt.Format(tt.format)
|
||||
if got != tt.want {
|
||||
t.Errorf("time.Format(%s) dt (%s) Mismatch: want (%s), got (%s)", tt.format, tt.v, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,11 +26,14 @@ func GetDefaultTimeLocation() *time.Location {
|
||||
}
|
||||
|
||||
// UnixMilliToStringPtr 毫秒时间戳 -> 字符串
|
||||
func UnixMilliToStringPtr(tm *int64) *string {
|
||||
if tm == nil {
|
||||
func UnixMilliToStringPtr(milli *int64) *string {
|
||||
if milli == nil {
|
||||
return nil
|
||||
}
|
||||
str := time.UnixMilli(*tm).Format(TimeLayout)
|
||||
|
||||
tm := time.UnixMilli(*milli)
|
||||
|
||||
str := tm.In(GetDefaultTimeLocation()).Format(TimeLayout)
|
||||
return &str
|
||||
}
|
||||
|
||||
@@ -44,17 +47,18 @@ func StringToUnixMilliInt64Ptr(tm *string) *int64 {
|
||||
if theTime == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
unixTime := theTime.UnixMilli()
|
||||
return &unixTime
|
||||
}
|
||||
|
||||
// UnixMilliToTimePtr 毫秒时间戳 -> 时间
|
||||
func UnixMilliToTimePtr(tm *int64) *time.Time {
|
||||
if tm == nil {
|
||||
func UnixMilliToTimePtr(milli *int64) *time.Time {
|
||||
if milli == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
unixMilli := time.UnixMilli(*tm)
|
||||
unixMilli := time.UnixMilli(*milli)
|
||||
return &unixMilli
|
||||
}
|
||||
|
||||
@@ -69,12 +73,13 @@ func TimeToUnixMilliInt64Ptr(tm *time.Time) *int64 {
|
||||
}
|
||||
|
||||
// UnixSecondToTimePtr 秒时间戳 -> 时间
|
||||
func UnixSecondToTimePtr(tm *int64) *time.Time {
|
||||
if tm == nil {
|
||||
func UnixSecondToTimePtr(second *int64) *time.Time {
|
||||
if second == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
unixMilli := time.Unix(*tm, 0)
|
||||
unixMilli := time.Unix(*second, 0)
|
||||
|
||||
return &unixMilli
|
||||
}
|
||||
|
||||
@@ -100,18 +105,19 @@ func StringTimeToTime(str *string) *time.Time {
|
||||
var err error
|
||||
var theTime time.Time
|
||||
|
||||
theTime, err = time.ParseInLocation(TimeLayout, *str, GetDefaultTimeLocation())
|
||||
if err == nil {
|
||||
if theTime, err = time.ParseInLocation(TimeLayout, *str, GetDefaultTimeLocation()); err == nil {
|
||||
return &theTime
|
||||
}
|
||||
|
||||
theTime, err = time.ParseInLocation(DateLayout, *str, GetDefaultTimeLocation())
|
||||
if err == nil {
|
||||
if theTime, err = time.ParseInLocation(DateLayout, *str, GetDefaultTimeLocation()); err == nil {
|
||||
return &theTime
|
||||
}
|
||||
|
||||
theTime, err = time.ParseInLocation(ClockLayout, *str, GetDefaultTimeLocation())
|
||||
if err == nil {
|
||||
if theTime, err = time.ParseInLocation(ClockLayout, *str, GetDefaultTimeLocation()); err == nil {
|
||||
return &theTime
|
||||
}
|
||||
|
||||
if theTime, err = time.ParseInLocation(ISO9075MicroTZ, *str, GetDefaultTimeLocation()); err == nil {
|
||||
return &theTime
|
||||
}
|
||||
|
||||
@@ -123,7 +129,8 @@ func TimeToTimeString(tm *time.Time) *string {
|
||||
if tm == nil {
|
||||
return nil
|
||||
}
|
||||
return trans.String(tm.Format(TimeLayout))
|
||||
|
||||
return trans.String(tm.In(GetDefaultTimeLocation()).Format(TimeLayout))
|
||||
}
|
||||
|
||||
// StringDateToTime 字符串 -> 时间
|
||||
@@ -161,13 +168,41 @@ func TimeToDateString(tm *time.Time) *string {
|
||||
if tm == nil {
|
||||
return nil
|
||||
}
|
||||
return trans.String(tm.Format(DateLayout))
|
||||
|
||||
return trans.String(tm.In(GetDefaultTimeLocation()).Format(DateLayout))
|
||||
}
|
||||
|
||||
func StringToTime(str *string, layout string) *time.Time {
|
||||
if str == nil {
|
||||
return nil
|
||||
}
|
||||
if len(*str) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
var theTime time.Time
|
||||
|
||||
theTime, err = time.ParseInLocation(layout, *str, GetDefaultTimeLocation())
|
||||
if err == nil {
|
||||
return &theTime
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TimeToString(tm *time.Time, layout string) *string {
|
||||
if tm == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return trans.String(tm.Format(layout))
|
||||
}
|
||||
|
||||
// TimestamppbToTime timestamppb.Timestamp -> time.Time
|
||||
func TimestamppbToTime(tm *timestamppb.Timestamp) *time.Time {
|
||||
if tm != nil {
|
||||
return trans.Ptr(tm.AsTime())
|
||||
func TimestamppbToTime(timestamp *timestamppb.Timestamp) *time.Time {
|
||||
if timestamp != nil {
|
||||
return trans.Ptr(timestamp.AsTime())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,13 +2,14 @@ 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"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
func TestUnixMilliToStringPtr(t *testing.T) {
|
||||
@@ -148,6 +149,49 @@ func TestTimeToDateString(t *testing.T) {
|
||||
assert.Nil(t, result)
|
||||
}
|
||||
|
||||
func TestTimeToString(t *testing.T) {
|
||||
now := time.Now()
|
||||
|
||||
fmt.Println(*TimeToString(&now, DateLayout))
|
||||
fmt.Println(*TimeToString(&now, ISO8601))
|
||||
fmt.Println(*TimeToString(&now, ISO8601TZHour))
|
||||
fmt.Println(*TimeToString(&now, ISO8601NoTZ))
|
||||
|
||||
layout := "2006-01-02"
|
||||
expected := now.Format(layout)
|
||||
result := TimeToString(&now, layout)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, expected, *result)
|
||||
|
||||
// 测试空输入
|
||||
result = TimeToString(nil, layout)
|
||||
assert.Nil(t, result)
|
||||
}
|
||||
|
||||
func TestStringToTime(t *testing.T) {
|
||||
// 测试有效时间字符串输入
|
||||
input := "2023-03-09 12:34:56"
|
||||
layout := "2006-01-02 15:04:05"
|
||||
expected := time.Date(2023, 3, 9, 12, 34, 56, 0, GetDefaultTimeLocation())
|
||||
result := StringToTime(&input, layout)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, expected, *result)
|
||||
|
||||
// 测试无效时间字符串输入
|
||||
invalidInput := "invalid-date"
|
||||
result = StringToTime(&invalidInput, layout)
|
||||
assert.Nil(t, result)
|
||||
|
||||
// 测试空字符串输入
|
||||
emptyInput := ""
|
||||
result = StringToTime(&emptyInput, layout)
|
||||
assert.Nil(t, result)
|
||||
|
||||
// 测试空指针输入
|
||||
result = StringToTime(nil, layout)
|
||||
assert.Nil(t, result)
|
||||
}
|
||||
|
||||
func TestTimestamppbToTime(t *testing.T) {
|
||||
// 测试有效输入
|
||||
timestamp := timestamppb.Now()
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package trans
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func String(a string) *string {
|
||||
return &a
|
||||
@@ -527,3 +531,28 @@ func MapValues[TKey mapKeyValueType, TValue mapKeyValueType](source map[TKey]TVa
|
||||
}
|
||||
return target
|
||||
}
|
||||
|
||||
func ToUuidPtr(str *string) *uuid.UUID {
|
||||
var id *uuid.UUID
|
||||
if str != nil {
|
||||
_id, err := uuid.Parse(*str)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
id = &_id
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
func ToUuid(str string) uuid.UUID {
|
||||
id, _ := uuid.Parse(str)
|
||||
return id
|
||||
}
|
||||
|
||||
func ToStringPtr(id *uuid.UUID) *string {
|
||||
var strUUID *string
|
||||
if id != nil {
|
||||
strUUID = Ptr(id.String())
|
||||
}
|
||||
return strUUID
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -167,3 +168,62 @@ func Test_Trans(t *testing.T) {
|
||||
assert.Equal(t, time.Now(), tmVal)
|
||||
assert.Equal(t, time.Now(), TimeValue(nil))
|
||||
}
|
||||
|
||||
func TestUUID(t *testing.T) {
|
||||
t.Run("ToUuidPtr_NilString", func(t *testing.T) {
|
||||
var str *string
|
||||
result := ToUuidPtr(str)
|
||||
if result != nil {
|
||||
t.Errorf("expected nil, got %v", result)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ToUuidPtr_ValidString", func(t *testing.T) {
|
||||
str := "550e8400-e29b-41d4-a716-446655440000"
|
||||
result := ToUuidPtr(&str)
|
||||
if result == nil || result.String() != str {
|
||||
t.Errorf("expected %v, got %v", str, result)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ToUuidPtr_InvalidString", func(t *testing.T) {
|
||||
str := "invalid-uuid"
|
||||
result := ToUuidPtr(&str)
|
||||
if result != nil {
|
||||
t.Errorf("expected nil, got %v", result)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ToUuid_ValidString", func(t *testing.T) {
|
||||
str := "550e8400-e29b-41d4-a716-446655440000"
|
||||
result := ToUuid(str)
|
||||
if result.String() != str {
|
||||
t.Errorf("expected %v, got %v", str, result)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ToUuid_InvalidString", func(t *testing.T) {
|
||||
str := "invalid-uuid"
|
||||
result := ToUuid(str)
|
||||
if result.String() == str {
|
||||
t.Errorf("expected invalid UUID, got %v", result)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ToStringPtr_NilUUID", func(t *testing.T) {
|
||||
var id *uuid.UUID
|
||||
result := ToStringPtr(id)
|
||||
if result != nil {
|
||||
t.Errorf("expected nil, got %v", result)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ToStringPtr_ValidUUID", func(t *testing.T) {
|
||||
id := uuid.MustParse("550e8400-e29b-41d4-a716-446655440000")
|
||||
result := ToStringPtr(&id)
|
||||
expected := "550e8400-e29b-41d4-a716-446655440000"
|
||||
if result == nil || *result != expected {
|
||||
t.Errorf("expected %v, got %v", expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
32
uuid/uuid.go
32
uuid/uuid.go
@@ -1,32 +0,0 @@
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/tx7do/go-utils/trans"
|
||||
)
|
||||
|
||||
func ToUuidPtr(str *string) *uuid.UUID {
|
||||
var id *uuid.UUID
|
||||
if str != nil {
|
||||
_id, err := uuid.Parse(*str)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
id = &_id
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
func ToUuid(str string) uuid.UUID {
|
||||
id, _ := uuid.Parse(str)
|
||||
return id
|
||||
}
|
||||
|
||||
func ToStringPtr(id *uuid.UUID) *string {
|
||||
var strUUID *string
|
||||
if id != nil {
|
||||
strUUID = trans.String(id.String())
|
||||
}
|
||||
return strUUID
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func TestUUID(t *testing.T) {
|
||||
t.Run("ToUuidPtr_NilString", func(t *testing.T) {
|
||||
var str *string
|
||||
result := ToUuidPtr(str)
|
||||
if result != nil {
|
||||
t.Errorf("expected nil, got %v", result)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ToUuidPtr_ValidString", func(t *testing.T) {
|
||||
str := "550e8400-e29b-41d4-a716-446655440000"
|
||||
result := ToUuidPtr(&str)
|
||||
if result == nil || result.String() != str {
|
||||
t.Errorf("expected %v, got %v", str, result)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ToUuidPtr_InvalidString", func(t *testing.T) {
|
||||
str := "invalid-uuid"
|
||||
result := ToUuidPtr(&str)
|
||||
if result != nil {
|
||||
t.Errorf("expected nil, got %v", result)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ToUuid_ValidString", func(t *testing.T) {
|
||||
str := "550e8400-e29b-41d4-a716-446655440000"
|
||||
result := ToUuid(str)
|
||||
if result.String() != str {
|
||||
t.Errorf("expected %v, got %v", str, result)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ToUuid_InvalidString", func(t *testing.T) {
|
||||
str := "invalid-uuid"
|
||||
result := ToUuid(str)
|
||||
if result.String() == str {
|
||||
t.Errorf("expected invalid UUID, got %v", result)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ToStringPtr_NilUUID", func(t *testing.T) {
|
||||
var id *uuid.UUID
|
||||
result := ToStringPtr(id)
|
||||
if result != nil {
|
||||
t.Errorf("expected nil, got %v", result)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ToStringPtr_ValidUUID", func(t *testing.T) {
|
||||
id := uuid.MustParse("550e8400-e29b-41d4-a716-446655440000")
|
||||
result := ToStringPtr(&id)
|
||||
expected := "550e8400-e29b-41d4-a716-446655440000"
|
||||
if result == nil || *result != expected {
|
||||
t.Errorf("expected %v, got %v", expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user