Compare commits

...

57 Commits

Author SHA1 Message Date
kzkzzzz
e95976a4db update 2026-02-27 00:37:04 +08:00
kzkzzzz
e496e76ee7 update 2026-02-27 00:36:14 +08:00
kzkzzzz
9560131870 update 2026-02-27 00:29:16 +08:00
kzkzzzz
9b20ffc450 update 2026-02-27 00:26:14 +08:00
kzkzzzz
cdee211a66 update 2026-02-27 00:23:10 +08:00
kzkzzzz
7b2f9f90a6 Merge branch 'main' of ssh://git.makemake.in:5566/kzkzzzz/mycommon 2026-02-26 23:23:36 +08:00
lzf
18462953ef update 2026-02-26 23:23:16 +08:00
kzkzzzz
570493a424 update 2026-01-03 19:12:49 +08:00
kzkzzzz
c1faeac608 update 2026-01-01 22:17:47 +08:00
kzkzzzz
dd09c6a7be update 2026-01-01 22:15:32 +08:00
kzkzzzz
8413ccd3ba update 2026-01-01 22:11:42 +08:00
kzkzzzz
521184c19c update 2025-12-15 22:44:02 +08:00
kzkzzzz
8806b49e8f update 2025-12-15 22:41:56 +08:00
kzkzzzz
2b704cc700 update 2025-12-15 22:37:55 +08:00
kzkzzzz
4553fc8466 update 2025-12-13 15:16:31 +08:00
kzkzzzz
07ea247096 update 2025-12-12 23:43:40 +08:00
kzkzzzz
10e037243f update 2025-12-12 23:38:37 +08:00
kzkzzzz
bd62ff8235 update 2025-12-11 22:20:19 +08:00
kzkzzzz
22ba1c8046 Merge branch 'main' of ssh://git.makemake.in:5566/kzkzzzz/mycommon 2025-12-11 22:15:49 +08:00
kzkzzzz
e69a987535 update 2025-12-11 22:15:30 +08:00
lzf
c27804489b Merge branch 'main' of ssh://git.makemake.in:5566/kzkzzzz/mycommon 2025-09-22 12:22:46 +08:00
lzf
7ac8fb1f14 update 2025-09-22 12:22:32 +08:00
kzkzzzz
af5fef67db update 2025-09-14 09:46:13 +08:00
kzkzzzz
43799a9841 Merge branch 'main' of ssh://git.makemake.in:5566/kzkzzzz/mycommon 2025-09-14 00:19:43 +08:00
kzkzzzz
ea6896cf05 update 2025-09-14 00:19:27 +08:00
lzf
898d7144f1 update 2025-08-19 15:31:43 +08:00
kzkzzzz
866b2cf1ed update 2025-08-17 13:00:26 +08:00
kzkzzzz
14afd1bc4d update 2025-08-16 19:09:11 +08:00
kzkzzzz
5e26ddfbcd Merge branch 'main' of ssh://git.makemake.in:5566/kzkzzzz/mycommon 2025-08-09 10:20:59 +08:00
kzkzzzz
1bf5b0eec4 update 2025-08-09 10:20:45 +08:00
lzf
9333af16eb update 2025-08-01 15:19:25 +08:00
kzkzzzz
c0852d839f update 2025-07-27 20:50:37 +08:00
kzkzzzz
510629a424 update 2025-07-27 18:10:06 +08:00
kzkzzzz
95880712b8 update 2025-07-27 12:34:13 +08:00
kzkzzzz
0797ab1b4b Merge branch 'main' of ssh://git.makemake.in:5566/kzkzzzz/mycommon 2025-07-27 10:12:24 +08:00
kzkzzzz
781f79cf7c update 2025-07-27 10:08:41 +08:00
lzf
a545116f52 update 2025-07-22 17:58:46 +08:00
lzf
4ced8ae869 update 2025-07-22 17:48:02 +08:00
lzf
1eb10eb1af update 2025-07-22 17:44:04 +08:00
lzf
063fc82338 update 2025-07-22 17:43:07 +08:00
lzf
9093bf2b79 update 2025-07-22 17:39:34 +08:00
kzkzzzz
eb6557e0d8 update 2025-07-15 22:33:08 +08:00
kzkzzzz
7f65f040c4 update 2025-07-15 22:30:12 +08:00
kzkzzzz
dbb113812d update 2025-07-13 00:19:17 +08:00
kzkzzzz
ab4043f28f update 2025-07-12 11:41:40 +08:00
kzkzzzz
89708b5201 Merge branch 'main' of ssh://git.makemake.in:5566/kzkzzzz/mycommon 2025-07-11 21:52:25 +08:00
lzf
c255a257c3 update 2025-07-11 17:11:28 +08:00
lzf
86b2a17868 update 2025-07-11 17:08:16 +08:00
lzf
7ead69e1b6 update 2025-07-11 16:59:47 +08:00
lzf
1cb7943903 update 2025-07-11 16:57:24 +08:00
kzkzzzz
27ccc2a080 update 2025-07-07 22:40:16 +08:00
kzkzzzz
e42de5e171 update 2025-06-21 17:24:00 +08:00
kzkzzzz
bc2b92c73b update 2025-05-28 23:44:19 +08:00
kzkzzzz
e69b0a4756 update 2025-05-27 23:05:12 +08:00
kzkzzzz
db3d256bd7 update 2025-04-20 18:22:49 +08:00
lzf
4d51d0a1d0 update 2025-03-31 15:25:24 +08:00
lzf
b876f0bfa3 update 2025-03-31 15:24:36 +08:00
26 changed files with 2244 additions and 341 deletions

View File

@@ -4,6 +4,7 @@ import (
"errors" "errors"
"git.makemake.in/kzkzzzz/mycommon/mylog" "git.makemake.in/kzkzzzz/mycommon/mylog"
"log" "log"
"math/rand/v2"
"net" "net"
"os" "os"
"runtime/debug" "runtime/debug"
@@ -26,6 +27,15 @@ func SafeGo(fn func()) {
}() }()
} }
// RandRange [min, max] 左右均包含
func RandRange(min, max int) int {
return rand.IntN(max+1-min) + min
}
func RandRangeFloat(min, max float64) float64 {
return min + rand.Float64()*(max-min+1e-10)
}
var ( var (
getIpOnce = &sync.Once{} getIpOnce = &sync.Once{}
outBoundIp = "127.0.0.1" outBoundIp = "127.0.0.1"
@@ -84,3 +94,38 @@ func ExistFile(file string) bool {
// 其他错误 // 其他错误
return false return false
} }
func UniqueSlice[T comparable](rawSlice []T) []T {
if len(rawSlice) <= 1 {
return rawSlice
}
uniqueSlice := make([]T, 0, len(rawSlice))
has := make(map[T]bool, len(rawSlice))
for _, v := range rawSlice {
if !has[v] {
uniqueSlice = append(uniqueSlice, v)
has[v] = true
}
}
return uniqueSlice
}
// Ptr 获取类型指针
func Ptr[T any](v T) *T {
return &v
}
func StringPtr(v string) *string {
if v == "" {
return nil
}
return &v
}
func StringVal(v *string) string {
if v == nil {
return ""
}
return *v
}

42
go.mod
View File

@@ -1,6 +1,6 @@
module git.makemake.in/kzkzzzz/mycommon module git.makemake.in/kzkzzzz/mycommon
go 1.23.0 go 1.25.3
require ( require (
github.com/gin-contrib/pprof v1.5.2 github.com/gin-contrib/pprof v1.5.2
@@ -10,9 +10,10 @@ require (
github.com/go-playground/universal-translator v0.18.1 github.com/go-playground/universal-translator v0.18.1
github.com/go-playground/validator/v10 v10.25.0 github.com/go-playground/validator/v10 v10.25.0
github.com/go-sql-driver/mysql v1.7.0 github.com/go-sql-driver/mysql v1.7.0
github.com/goccy/go-json v0.10.5
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/hashicorp/consul/api v1.28.2 github.com/hashicorp/consul/api v1.28.2
github.com/jpillora/backoff v1.0.0 github.com/json-iterator/go v1.1.12
github.com/knadh/koanf/parsers/json v0.1.0 github.com/knadh/koanf/parsers/json v0.1.0
github.com/knadh/koanf/parsers/toml v0.1.0 github.com/knadh/koanf/parsers/toml v0.1.0
github.com/knadh/koanf/parsers/yaml v0.1.0 github.com/knadh/koanf/parsers/yaml v0.1.0
@@ -20,34 +21,41 @@ require (
github.com/knadh/koanf/providers/file v1.1.2 github.com/knadh/koanf/providers/file v1.1.2
github.com/knadh/koanf/providers/posflag v0.1.0 github.com/knadh/koanf/providers/posflag v0.1.0
github.com/knadh/koanf/v2 v2.1.2 github.com/knadh/koanf/v2 v2.1.2
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20260226062615-1a8d01d9679e
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.11.1
github.com/redis/go-redis/v9 v9.7.3 github.com/redis/go-redis/v9 v9.7.3
github.com/rs/xid v1.6.0 github.com/rs/xid v1.6.0
github.com/spf13/cast v1.6.0 github.com/spf13/cast v1.6.0
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.19.0 github.com/spf13/viper v1.19.0
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.11.1
github.com/valyala/fasthttp v1.66.0
go.uber.org/zap v1.24.0 go.uber.org/zap v1.24.0
golang.org/x/sync v0.12.0 golang.org/x/sync v0.17.0
google.golang.org/grpc v1.67.1 golang.org/x/time v0.5.0
google.golang.org/grpc v1.75.0
gorm.io/driver/mysql v1.5.7 gorm.io/driver/mysql v1.5.7
gorm.io/gorm v1.25.12 gorm.io/gorm v1.25.12
) )
require ( require (
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/armon/go-metrics v0.4.1 // indirect github.com/armon/go-metrics v0.4.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bytedance/sonic v1.13.2 // indirect github.com/bytedance/sonic v1.13.2 // indirect
github.com/bytedance/sonic/loader v0.2.4 // indirect github.com/bytedance/sonic/loader v0.2.4 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect github.com/cloudwego/base64x v0.1.5 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fatih/color v1.15.0 // indirect github.com/fatih/color v1.16.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gin-contrib/sse v1.0.0 // indirect github.com/gin-contrib/sse v1.0.0 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/goccy/go-json v0.10.5 // indirect github.com/golang/protobuf v1.5.4 // indirect
github.com/hashicorp/consul/sdk v0.16.2 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v1.5.0 // indirect github.com/hashicorp/go-hclog v1.5.0 // indirect
@@ -60,13 +68,14 @@ require (
github.com/hashicorp/serf v0.10.1 // indirect github.com/hashicorp/serf v0.10.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/knadh/koanf/maps v0.1.1 // indirect github.com/knadh/koanf/maps v0.1.1 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
@@ -76,6 +85,9 @@ require (
github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.26.0 // indirect
github.com/prometheus/procfs v0.6.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect
@@ -83,16 +95,18 @@ require (
github.com/subosito/gotenv v1.6.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect github.com/ugorji/go/codec v1.2.12 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
go.uber.org/atomic v1.10.0 // indirect go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect
golang.org/x/arch v0.15.0 // indirect golang.org/x/arch v0.15.0 // indirect
golang.org/x/crypto v0.36.0 // indirect golang.org/x/crypto v0.42.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/net v0.37.0 // indirect golang.org/x/net v0.44.0 // indirect
golang.org/x/sys v0.31.0 // indirect golang.org/x/sys v0.36.0 // indirect
golang.org/x/text v0.23.0 // indirect golang.org/x/text v0.29.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
google.golang.org/protobuf v1.36.5 // indirect google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect

144
go.sum
View File

@@ -1,8 +1,12 @@
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA=
@@ -13,6 +17,7 @@ github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLj
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
@@ -41,8 +46,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cu
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
@@ -57,8 +62,14 @@ github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
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-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/form v3.1.4+incompatible h1:lvKiHVxE2WvzDIoyMnWcjyiBxKt2+uFJyZcPYWsLnjI= github.com/go-playground/form v3.1.4+incompatible h1:lvKiHVxE2WvzDIoyMnWcjyiBxKt2+uFJyZcPYWsLnjI=
@@ -80,20 +91,32 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 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/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/consul/api v1.28.2 h1:mXfkRHrpHN4YY3RqL09nXU1eHKLNiuAN4kHvDQ16k/8= github.com/hashicorp/consul/api v1.28.2 h1:mXfkRHrpHN4YY3RqL09nXU1eHKLNiuAN4kHvDQ16k/8=
github.com/hashicorp/consul/api v1.28.2/go.mod h1:KyzqzgMEya+IZPcD65YFoOVAgPpbfERu4I/tzG6/ueE= github.com/hashicorp/consul/api v1.28.2/go.mod h1:KyzqzgMEya+IZPcD65YFoOVAgPpbfERu4I/tzG6/ueE=
github.com/hashicorp/consul/sdk v0.16.0 h1:SE9m0W6DEfgIVCJX7xU+iv/hUl4m/nxqMTnCdMxDpJ8= github.com/hashicorp/consul/sdk v0.16.2 h1:cGX/djeEe9r087ARiKVWwVWCF64J+yW0G6ftZMZYbj0=
github.com/hashicorp/consul/sdk v0.16.0/go.mod h1:7pxqqhqoaPqnBnzXD1StKed62LqJeClzVsUEy85Zr0A= github.com/hashicorp/consul/sdk v0.16.2/go.mod h1:onxcZjYVsPx5XMveAC/OtoIsdr32fykB7INFltDoRE8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -140,13 +163,17 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
@@ -168,8 +195,10 @@ github.com/knadh/koanf/v2 v2.1.2 h1:I2rtLRqXRy1p01m/utEtpZSSA6dcJbgGVuE27kW2PzQ=
github.com/knadh/koanf/v2 v2.1.2/go.mod h1:Gphfaen0q1Fc1HTgJgSTC4oRX9R2R5ErYMZJy8fLJBo= github.com/knadh/koanf/v2 v2.1.2/go.mod h1:Gphfaen0q1Fc1HTgJgSTC4oRX9R2R5ErYMZJy8fLJBo=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
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 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 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/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -178,6 +207,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20260226062615-1a8d01d9679e h1:1+rVed4OnYgQCg934wWHms6J7aEbyQ7TaR/fSYhJmLA=
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20260226062615-1a8d01d9679e/go.mod h1:+mNMTBuDMdEGhWzoQgc6kBdqeaQpWh5ba8zqmp2MxCU=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
@@ -195,6 +226,7 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY= github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
@@ -217,6 +249,7 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
@@ -236,18 +269,28 @@ github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSg
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.1 h1:+4eQaD7vAZ6DsfsxB15hbE0odUjGI5ARs9yskGu1v4s=
github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM= github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=
github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA= github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
@@ -259,6 +302,7 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUt
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
@@ -284,8 +328,9 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
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/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
@@ -293,6 +338,24 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.66.0 h1:M87A0Z7EayeyNaV6pfO3tUTUiYO0dZfEJnRGXTVNuyU=
github.com/valyala/fasthttp v1.66.0/go.mod h1:Y4eC+zwoocmXSVCB1JmhNbYtS7tZPRI2ztPB72EVObs=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
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.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
@@ -306,70 +369,96 @@ golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
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/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/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/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
@@ -378,6 +467,7 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -225,6 +225,18 @@ func (c *Config) Get(key string) any {
return c.kof.Get(key) return c.kof.Get(key)
} }
func (c *Config) Exists(key string) bool {
c.RLock()
defer c.RUnLock()
return c.kof.Exists(key)
}
func (c *Config) GetSlices(key string) []*koanf.Koanf {
c.RLock()
defer c.RUnLock()
return c.kof.Slices(key)
}
func (c *Config) GetString(key string) string { func (c *Config) GetString(key string) string {
c.RLock() c.RLock()
defer c.RUnLock() defer c.RUnLock()

View File

@@ -17,6 +17,7 @@ const (
const ( const (
ServicePrefix = "grpc@" ServicePrefix = "grpc@"
ExposeHttpTag = "expose-http"
) )
const ( const (

View File

@@ -15,6 +15,7 @@ import (
"google.golang.org/grpc/health" "google.golang.org/grpc/health"
"google.golang.org/grpc/health/grpc_health_v1" "google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/keepalive" "google.golang.org/grpc/keepalive"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/reflection" "google.golang.org/grpc/reflection"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"log" "log"
@@ -39,6 +40,7 @@ type Opt func(server *Server)
type Server struct { type Server struct {
gs *grpc.Server gs *grpc.Server
serviceAddr string
serviceId string serviceId string
serviceName string serviceName string
serverConf *Conf serverConf *Conf
@@ -51,6 +53,7 @@ type Server struct {
useDefaultBufferCfg bool useDefaultBufferCfg bool
delayStopMs int delayStopMs int
exposeHttp bool
serviceRegInfo *myregistry.ServiceInfo serviceRegInfo *myregistry.ServiceInfo
} }
@@ -80,6 +83,12 @@ func WithDelayStopMs(v int) Opt {
} }
} }
func WithExposeHttp(v bool) Opt {
return func(server *Server) {
server.exposeHttp = v
}
}
func SetFlag() { func SetFlag() {
pflag.Int("grpc.port", 0, "listen port, 0 is random port") pflag.Int("grpc.port", 0, "listen port, 0 is random port")
pflag.String("grpc.log", "true", "enable request log") pflag.String("grpc.log", "true", "enable request log")
@@ -204,6 +213,11 @@ func (s *Server) Run(ctx context.Context) error {
ServiceName: s.serviceName, ServiceName: s.serviceName,
Ip: svcIp, Ip: svcIp,
Port: port, Port: port,
Extend: map[string]string{},
}
if s.exposeHttp {
s.serviceRegInfo.Extend["tag"] = mygrpc.ExposeHttpTag
} }
err = s.reg.Register(s.serviceRegInfo) err = s.reg.Register(s.serviceRegInfo)
@@ -221,6 +235,8 @@ func (s *Server) Run(ctx context.Context) error {
addr := fmt.Sprintf("%s:%d", s.serverConf.Addr, port) addr := fmt.Sprintf("%s:%d", s.serverConf.Addr, port)
log.Printf("grpc server listen on %s", addr) log.Printf("grpc server listen on %s", addr)
s.serviceAddr = fmt.Sprintf("%s:%d", svcIp, port)
err = s.gs.Serve(lis) err = s.gs.Serve(lis)
if err != nil { if err != nil {
log.Printf("start grpc server err: %s", err) log.Printf("start grpc server err: %s", err)
@@ -297,6 +313,14 @@ func (s *Server) requestLog() grpc.UnaryServerInterceptor {
func (s *Server) grpcRecover() grpc.UnaryServerInterceptor { func (s *Server) grpcRecover() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
md := metadata.New(map[string]string{
"service-host": mycommon.GetHostName(),
"service-addr": s.serviceAddr,
"service-name": s.serviceName,
"service-method": info.FullMethod,
})
grpc.SetHeader(ctx, md)
defer func() { defer func() {
if err0 := recover(); err0 != nil { if err0 := recover(); err0 != nil {
log.Printf("%s - panic: %v\n%s", info.FullMethod, err0, debug.Stack()) log.Printf("%s - panic: %v\n%s", info.FullMethod, err0, debug.Stack())
@@ -304,7 +328,12 @@ func (s *Server) grpcRecover() grpc.UnaryServerInterceptor {
} }
}() }()
return handler(ctx, req) res, err := handler(ctx, req)
if err != nil {
return nil, err
}
return res, nil
} }
} }

View File

@@ -19,7 +19,7 @@
// Package roundrobin defines a roundrobin balancer. Roundrobin balancer is // Package roundrobin defines a roundrobin balancer. Roundrobin balancer is
// installed as one of the default balancers in gRPC, users don't need to // installed as one of the default balancers in gRPC, users don't need to
// explicitly install this balancer. // explicitly install this balancer.
package random package random_balancer
import ( import (
"google.golang.org/grpc/balancer" "google.golang.org/grpc/balancer"

19
myhttp/fasthttpc/const.go Normal file
View File

@@ -0,0 +1,19 @@
package fasthttpc
const (
HeaderUserAgent = "User-Agent"
HeaderContentType = "Content-Type"
HeaderCookie = "Cookie"
HeaderAccept = "Accept"
HeaderOrigin = "Origin"
HeaderReferer = "Referer"
HeaderAcceptLanguage = "Accept-Language"
ContentTypeJSON = "application/json; charset=utf-8"
)
const (
MobileUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/124.0.6367.68 MobileRequest/15E148 Safari/604.1"
PcUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
AcceptHtml = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"
AcceptCNLanguage = "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7"
)

View File

@@ -0,0 +1,381 @@
package fasthttpc
import (
"context"
"crypto/tls"
"errors"
"fmt"
jsoniter "github.com/json-iterator/go"
"github.com/valyala/fasthttp"
"golang.org/x/time/rate"
"io"
"net/http"
"net/url"
"time"
)
var (
defaultClient *HttpClient
)
func init() {
defaultClient = New()
}
func ReInitDefault(timeout time.Duration) {
defaultClient = New(WithTimout(timeout))
}
func ReInitDefaultOpt(opts ...ConfigOpt) {
defaultClient = New(opts...)
}
func Client() *HttpClient {
return defaultClient
}
type Request struct {
ctx context.Context
header http.Header
body any
mapQuery map[string]string
urlQuery url.Values
httpClient *HttpClient
contentType string
noWaitQps bool
}
type HttpClient struct {
config *Config
client *fasthttp.Client
qpsLimiter *rate.Limiter
}
func New(opts ...ConfigOpt) *HttpClient {
config := &Config{}
for _, opt := range opts {
opt(config)
}
if config.timeout <= 0 {
config.timeout = time.Second * 3
}
if config.fasthttpTimeout <= 0 {
config.fasthttpTimeout = time.Second * 10
}
if config.maxConnPerHost <= 0 {
config.maxConnPerHost = 10000
}
if config.client == nil {
client := &fasthttp.Client{
TLSConfig: &tls.Config{InsecureSkipVerify: true},
DialTimeout: (&fasthttp.TCPDialer{
Concurrency: 0,
DNSCacheDuration: time.Second * 600,
}).DialTimeout,
MaxConnsPerHost: config.maxConnPerHost,
MaxIdleConnDuration: time.Second * 90,
MaxIdemponentCallAttempts: 5,
ReadTimeout: config.fasthttpTimeout + time.Second,
MaxConnWaitTimeout: 0,
RetryIfErr: func(req *fasthttp.Request, attempts int, err error) (bool, bool) {
if errors.Is(err, fasthttp.ErrConnectionClosed) || errors.Is(err, io.EOF) {
return false, true
}
return false, false
},
}
config.client = client
}
hc := &HttpClient{
config: config,
client: config.client,
}
if config.qpsLimiter != nil {
hc.qpsLimiter = config.qpsLimiter
} else if config.qps > 0 {
hc.qpsLimiter = rate.NewLimiter(rate.Every(time.Second/time.Duration(config.qps)), config.qps)
}
return hc
}
func (h *HttpClient) RawClient() *fasthttp.Client {
return h.client
}
func (h *HttpClient) NewRequest(ctx context.Context) *Request {
r := &Request{
ctx: ctx,
header: nil,
mapQuery: nil,
httpClient: h,
urlQuery: url.Values{},
}
return r
}
func (h *HttpClient) NewPcRequest(ctx context.Context) *Request {
r := h.NewRequest(ctx)
r.SetHeaderPcAgent()
return r
}
func (h *HttpClient) NewMobileRequest(ctx context.Context) *Request {
r := h.NewRequest(ctx)
r.SetHeaderMobileAgent()
return r
}
func (r *Request) SetContentType(contentType string) *Request {
r.contentType = contentType
return r
}
func (r *Request) SetBody(body any) *Request {
r.body = body
return r
}
func (r *Request) SetQueryParam(k string, v string) *Request {
if r.mapQuery == nil {
r.mapQuery = make(map[string]string)
}
r.mapQuery[k] = v
return r
}
func (r *Request) SetQueryParams(params map[string]string) *Request {
for k, v := range params {
r.SetQueryParam(k, v)
}
return r
}
func (r *Request) SetUrlQueryParam(k string, v string) *Request {
r.urlQuery.Add(k, v)
return r
}
func (r *Request) SetHeader(k, v string) *Request {
if r.header == nil {
r.header = http.Header{}
}
r.header.Set(k, v)
return r
}
func (r *Request) SetHeaders(headers map[string]string) *Request {
for k, v := range headers {
r.SetHeader(k, v)
}
return r
}
func (r *Request) NoWaitQps() *Request {
r.noWaitQps = true
return r
}
func (r *Request) Get(rawUrl string) (*Response, error) {
return r.Do(http.MethodGet, rawUrl)
}
func (r *Request) Post(rawUrl string) (*Response, error) {
return r.Do(http.MethodPost, rawUrl)
}
var QpsLimitError = fmt.Errorf("qps limit")
func (r *Request) DoTimeout(timeout time.Duration, method, rawUrl string) (*Response, error) {
if timeout <= time.Millisecond {
return nil, fmt.Errorf("timeout is too small <= 1 millisecond")
}
if r.httpClient.config.useCtxTimeout == false {
return r.doRequest(timeout, method, rawUrl)
}
ctx, cancel := context.WithTimeout(r.ctx, timeout)
defer cancel()
type tmpRes struct {
res *Response
err error
}
var resChan = make(chan tmpRes, 1)
go func() {
res, err := r.doRequest(r.httpClient.config.fasthttpTimeout, method, rawUrl)
if err != nil {
resChan <- tmpRes{err: err}
return
}
resChan <- tmpRes{res: res}
}()
select {
case <-ctx.Done():
return nil, fmt.Errorf("request timeout: %s (%s)", timeout, ctx.Err())
case res := <-resChan:
return res.res, res.err
}
}
func (r *Request) Do(method, rawUrl string) (*Response, error) {
return r.DoTimeout(r.httpClient.config.timeout, method, rawUrl)
}
func (r *Request) doRequest(timeout time.Duration, method, rawUrl string) (*Response, error) {
if r.httpClient.qpsLimiter != nil {
if r.noWaitQps {
allow := r.httpClient.qpsLimiter.Allow()
if !allow {
return nil, QpsLimitError
}
} else {
err := r.httpClient.qpsLimiter.Wait(r.ctx)
if err != nil {
return nil, err
}
}
}
req := fasthttp.AcquireRequest()
resp := fasthttp.AcquireResponse()
defer func() {
fasthttp.ReleaseRequest(req)
fasthttp.ReleaseResponse(resp)
}()
var reqBody []byte
if r.body != nil {
switch v := r.body.(type) {
case string:
reqBody = []byte(v)
case []byte:
reqBody = v
default:
marshal, err := jsoniter.Marshal(r.body)
if err != nil {
return nil, err
}
reqBody = marshal
}
req.SetBody(reqBody)
}
reqUrl, err := url.Parse(rawUrl)
if err != nil {
return nil, fmt.Errorf("parse url err: %s (%s)", err.Error(), rawUrl)
}
urlQuery := reqUrl.Query()
for k, v := range r.mapQuery {
urlQuery.Add(k, v)
}
for k, v := range r.urlQuery {
urlQuery[k] = v
}
if len(urlQuery) > 0 {
reqUrl.RawQuery = urlQuery.Encode()
}
req.SetRequestURI(reqUrl.String())
//if len(urlQuery) > 0 {
// req.URI().SetQueryString(urlQuery.Encode())
//}
if r.contentType != "" {
req.Header.Set("Content-Type", r.contentType)
}
for k := range r.header {
req.Header.Set(k, r.header.Get(k))
}
req.Header.SetMethod(method)
err = r.httpClient.client.DoTimeout(req, resp, timeout)
if err != nil {
if errors.Is(err, fasthttp.ErrTimeout) {
return nil, err
}
return nil, fmt.Errorf("req err: %s (%s)", err, reqUrl.String())
}
tmpBody := resp.Body()
body := make([]byte, len(tmpBody))
copy(body, tmpBody)
if r.httpClient.config.noCheckStatus == false {
if resp.StatusCode() >= 400 {
return nil, &StatusErr{
StatusCode: resp.StatusCode(),
Body: body,
}
//return nil, fmt.Errorf("status code err: %d (%s)", resp.StatusCode(), body)
}
}
copyHeader := &fasthttp.ResponseHeader{}
resp.Header.CopyTo(copyHeader)
res := &Response{
Header: copyHeader,
body: body,
}
return res, nil
}
func (r *Request) SetHeaderPcAgent() *Request {
r.SetHeader(HeaderUserAgent, PcUserAgent)
return r
}
func (r *Request) SetHeaderMobileAgent() *Request {
r.SetHeader(HeaderUserAgent, MobileUserAgent)
return r
}
func (r *Request) SetHeaderAcceptHtml() *Request {
r.SetHeader(HeaderAccept, AcceptHtml)
return r
}
type Response struct {
Header *fasthttp.ResponseHeader
body []byte
//Response *http.Response
}
func (r *Response) GetBody() []byte {
return r.body
}
var _ error = (*StatusErr)(nil)
type StatusErr struct {
StatusCode int
Body []byte
}
func (s *StatusErr) Error() string {
return fmt.Sprintf("status code err: %d (%s)", s.StatusCode, s.Body)
}

View File

@@ -0,0 +1,39 @@
package fasthttpc
import (
"context"
"fmt"
"testing"
"time"
)
func TestClient(t *testing.T) {
client := New(WithUseCtxTimeout(true), WithTimout(time.Second*5))
reqUrl := "http://127.0.0.1:18001/swap-instructions"
res, err := client.NewRequest(context.Background()).
SetQueryParams(map[string]string{
"a": "b",
"name": "qwe123",
"time": "100",
}).
SetContentType(ContentTypeJSON).
SetHeaders(map[string]string{
"Api": "ok",
}).
SetBody(map[string]any{
"hello": "world",
"id": 5,
}).
Post(reqUrl)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%+v\n", string(res.GetBody()))
fmt.Println(res.Header.String())
fmt.Println(res.Header.StatusCode())
}

View File

@@ -0,0 +1,84 @@
package fasthttpc
import (
"github.com/valyala/fasthttp"
"golang.org/x/time/rate"
"time"
)
type (
Config struct {
timeout time.Duration
client *fasthttp.Client
noCheckStatus bool
qps int
qpsLimiter *rate.Limiter
maxConnPerHost int
fasthttpTimeout time.Duration
useCtxTimeout bool
}
ConfigOpt func(c *Config)
)
func WithTimout(v time.Duration) ConfigOpt {
return func(c *Config) {
c.timeout = v
}
}
func WithUseCtxTimeout(v bool) ConfigOpt {
return func(c *Config) {
c.useCtxTimeout = v
}
}
func WithFasthttpimout(v time.Duration) ConfigOpt {
return func(c *Config) {
c.fasthttpTimeout = v
}
}
func WithClient(v *fasthttp.Client) ConfigOpt {
return func(c *Config) {
c.client = v
}
}
func WithMaxConnPerHost(v int) ConfigOpt {
return func(c *Config) {
c.maxConnPerHost = v
}
}
//func WithRedirectFn(v func(req *http.Request, via []*http.Request) error) ConfigOpt {
// return func(c *Config) {
// c.redirectFn = v
// }
//}
//
//func WithNoRedirect() ConfigOpt {
// return func(c *Config) {
// c.redirectFn = func(req *http.Request, via []*http.Request) error {
// return http.ErrUseLastResponse
// }
// }
//}
func WithNoCheckStatus(v bool) ConfigOpt {
return func(c *Config) {
c.noCheckStatus = v
}
}
func WithQps(v int) ConfigOpt {
return func(c *Config) {
c.qps = v
}
}
func WithQpsLimiter(v *rate.Limiter) ConfigOpt {
return func(c *Config) {
c.qpsLimiter = v
}
}

90
myhttp/httpc/const.go Normal file
View File

@@ -0,0 +1,90 @@
package httpc
import (
"fmt"
"net/http"
"strings"
)
const (
HeaderUserAgent = "User-Agent"
HeaderContentType = "Content-Type"
HeaderCookie = "Cookie"
HeaderAccept = "Accept"
HeaderOrigin = "Origin"
HeaderReferer = "Referer"
HeaderAcceptLanguage = "Accept-Language"
HeaderAcceptEncoding = "Accept-Encoding"
ContentTypeJSON = "application/json; charset=utf-8"
)
const (
MobileUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/124.0.6367.68 MobileRequest/15E148 Safari/604.1"
PcUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
AcceptHtml = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"
AcceptCNLanguage = "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7"
AcceptEncodingIdentity = "identity"
)
func RedirectAllCookies(req *http.Request, via []*http.Request) error {
_, cookieHeader := GetRedirectCookieHeaders(req, via)
if cookieHeader != "" {
req.Header.Set("Cookie", cookieHeader)
}
return nil
}
func GetRedirectCookieHeaders(req *http.Request, via []*http.Request) (map[string]string, string) {
if len(via) == 0 {
return map[string]string{}, ""
}
lastReq := via[len(via)-1]
cookieMap := parseCookieMap(lastReq.Header.Get("Cookie"))
for _, c := range lastReq.Cookies() {
cookieMap[c.Name] = c.Value
}
if lastReq.Response != nil {
for _, c := range lastReq.Response.Cookies() {
cookieMap[c.Name] = c.Value
}
}
var pairs []string
for name, value := range cookieMap {
pairs = append(pairs, fmt.Sprintf("%s=%s", name, value))
}
if len(pairs) > 0 {
// 直接 Set 最终合并后的完整字符串
return cookieMap, strings.Join(pairs, "; ")
}
return map[string]string{}, ""
}
func parseCookieMap(str string) map[string]string {
res := make(map[string]string)
if len(str) == 0 {
return res
}
sp := strings.Split(str, ";")
for _, v := range sp {
split := strings.SplitN(v, "=", 2)
if len(split) == 2 {
name := strings.TrimSpace(split[0])
value := strings.TrimSpace(split[1])
if name != "" && value != "" {
res[name] = value
}
}
}
return res
}

387
myhttp/httpc/httpclient.go Normal file
View File

@@ -0,0 +1,387 @@
package httpc
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"net/http"
"net/http/cookiejar"
"net/url"
"slices"
"strings"
"time"
"git.makemake.in/kzkzzzz/mycommon/mylog"
jsoniter "github.com/json-iterator/go"
"golang.org/x/time/rate"
)
var (
defaultClient *HttpClient
defaultNoRedirectClient *HttpClient
)
func init() {
defaultClient = New()
defaultNoRedirectClient = New(WithNoRedirect(), WithNoCheckStatus(true))
}
func ReInitDefault(timeout time.Duration) {
defaultClient = New(WithTimout(timeout))
defaultNoRedirectClient = New(WithNoRedirect(), WithNoCheckStatus(true), WithTimout(timeout))
}
func ReInitDefaultOpt(opts ...ConfigOpt) {
defaultClient = New(opts...)
defaultNoRedirectClient = New(slices.Concat([]ConfigOpt{WithNoRedirect(), WithNoCheckStatus(true)}, opts)...)
}
func Client() *HttpClient {
return defaultClient
}
func NoRedirectClient() *HttpClient {
return defaultNoRedirectClient
}
func NewTransport(maxConn int, idleTimeout time.Duration) *http.Transport {
if maxConn <= 0 {
panic("max connection <= 0")
}
if idleTimeout <= 0 {
panic("idle timeout <= 0")
}
tr := http.DefaultTransport.(*http.Transport).Clone()
tr.MaxIdleConns = 0
tr.MaxConnsPerHost = 0
tr.MaxIdleConnsPerHost = maxConn
tr.IdleConnTimeout = idleTimeout
tr.DisableKeepAlives = false
//tr.ResponseHeaderTimeout = time.Second * 10
//tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: false}
return tr
}
type Request struct {
ctx context.Context
header http.Header
body any
mapQuery map[string]string
urlQuery url.Values
httpClient *HttpClient
contentType string
noWaitQps bool
cookies []*http.Cookie
}
type HttpClient struct {
config *Config
client *http.Client
qpsLimiter *rate.Limiter
}
func New(opts ...ConfigOpt) *HttpClient {
config := &Config{}
for _, opt := range opts {
opt(config)
}
if config.timeout <= 0 {
config.timeout = time.Second * 10
}
if config.transport == nil {
config.transport = NewTransport(3072, time.Second*90)
}
//if config.redirectFn == nil {
// config.redirectFn = RedirectAllCookies
//}
if config.client == nil {
cookieJar, _ := cookiejar.New(nil)
client := &http.Client{
Transport: config.transport,
Timeout: config.timeout,
Jar: cookieJar,
}
if config.redirectFn != nil {
client.CheckRedirect = config.redirectFn
}
config.client = client
}
hc := &HttpClient{
config: config,
client: config.client,
}
if config.qpsLimiter != nil {
hc.qpsLimiter = config.qpsLimiter
} else if config.qps > 0 {
hc.qpsLimiter = rate.NewLimiter(rate.Every(time.Second/time.Duration(config.qps)), config.qps)
}
return hc
}
func (h *HttpClient) RawClient() *http.Client {
return h.client
}
func (h *HttpClient) SetProxy(pr string) *HttpClient {
u, err := url.Parse(pr)
if err != nil {
panic(err)
}
h.client.Transport.(*http.Transport).Proxy = http.ProxyURL(u)
return h
}
func (h *HttpClient) NewRequest(ctx context.Context) *Request {
r := &Request{
ctx: ctx,
header: nil,
mapQuery: nil,
httpClient: h,
urlQuery: url.Values{},
}
return r
}
func (h *HttpClient) NewPcRequest(ctx context.Context) *Request {
r := h.NewRequest(ctx)
r.SetHeaderPcAgent()
return r
}
func (h *HttpClient) NewMobileRequest(ctx context.Context) *Request {
r := h.NewRequest(ctx)
r.SetHeaderMobileAgent()
return r
}
func (r *Request) SetContentType(contentType string) *Request {
r.contentType = contentType
return r
}
func (r *Request) SetBody(body any) *Request {
r.body = body
return r
}
func (r *Request) SetQueryParam(k string, v string) *Request {
if r.mapQuery == nil {
r.mapQuery = make(map[string]string)
}
r.mapQuery[k] = v
return r
}
func (r *Request) SetQueryParams(params map[string]string) *Request {
for k, v := range params {
r.SetQueryParam(k, v)
}
return r
}
func (r *Request) SetUrlQueryParam(k string, v string) *Request {
r.urlQuery.Add(k, v)
return r
}
func (r *Request) SetHeader(k, v string) *Request {
if r.header == nil {
r.header = http.Header{}
}
r.header.Set(k, v)
return r
}
func (r *Request) SetCookies(v ...*http.Cookie) *Request {
r.cookies = append(r.cookies, v...)
return r
}
func (r *Request) SetCookieMap(cookiesMap map[string]*http.Cookie) *Request {
for _, v := range cookiesMap {
r.cookies = append(r.cookies, v)
}
return r
}
func (r *Request) SetHtmlHeaders() *Request {
r.SetHeaders(map[string]string{
HeaderAccept: AcceptHtml,
HeaderAcceptLanguage: AcceptCNLanguage,
HeaderAcceptEncoding: AcceptEncodingIdentity,
})
return r
}
func (r *Request) SetHeaders(headers map[string]string) *Request {
for k, v := range headers {
r.SetHeader(k, v)
}
return r
}
func (r *Request) NoWaitQps() *Request {
r.noWaitQps = true
return r
}
func (r *Request) Get(rawUrl string) (*Response, error) {
return r.Do(http.MethodGet, rawUrl)
}
func (r *Request) Post(rawUrl string) (*Response, error) {
return r.Do(http.MethodPost, rawUrl)
}
var QpsLimitError = fmt.Errorf("qps limit")
func (r *Request) Do(method, rawUrl string) (*Response, error) {
if r.httpClient.qpsLimiter != nil {
if r.noWaitQps {
allow := r.httpClient.qpsLimiter.Allow()
if !allow {
return nil, QpsLimitError
}
} else {
err := r.httpClient.qpsLimiter.Wait(r.ctx)
if err != nil {
return nil, err
}
}
}
reqUrl, err := url.Parse(rawUrl)
if err != nil {
return nil, err
}
var reqBody io.Reader = nil
if r.body != nil {
switch v := r.body.(type) {
case string:
reqBody = strings.NewReader(v)
case []byte:
reqBody = bytes.NewReader(v)
default:
marshal, err := jsoniter.Marshal(r.body)
if err != nil {
return nil, err
}
reqBody = bytes.NewReader(marshal)
}
}
req, err := http.NewRequest(method, reqUrl.String(), reqBody)
if err != nil {
return nil, err
}
for _, cookie := range r.cookies {
req.AddCookie(cookie)
}
query := req.URL.Query()
for k, v := range r.mapQuery {
query.Add(k, v)
}
for k, v := range r.urlQuery {
query[k] = v
}
req.URL.RawQuery = query.Encode()
if r.contentType != "" {
req.Header.Set("Content-Type", r.contentType)
}
for k := range r.header {
req.Header.Set(k, r.header.Get(k))
}
res, err := r.httpClient.client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
return nil, err
}
if r.httpClient.config.noCheckStatus == false {
if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("status code err: %d (%s)", res.StatusCode, body)
}
}
resp := &Response{
body: body,
Response: res,
}
return resp, nil
}
func (r *Request) SetHeaderPcAgent() *Request {
r.SetHeader(HeaderUserAgent, PcUserAgent)
return r
}
func (r *Request) SetHeaderMobileAgent() *Request {
r.SetHeader(HeaderUserAgent, MobileUserAgent)
return r
}
func (r *Request) SetHeaderAcceptHtml() *Request {
r.SetHeader(HeaderAccept, AcceptHtml)
return r
}
func (r *Request) GetRedirectUrl(reqUrl string) (
*url.URL, *Response, error) {
//r.SetDebug(true)
r.SetHeaderAcceptHtml()
resp, err := r.Get(reqUrl)
if err != nil && !errors.Is(err, http.ErrUseLastResponse) {
return nil, nil, err
}
location, err := resp.Response.Location()
if err != nil {
mylog.Warnf("location code: %v", resp.Response.StatusCode)
return nil, nil, err
}
return location, resp, nil
}
type Response struct {
body []byte
Response *http.Response
}
func (r *Response) GetBody() []byte {
return r.body
}

72
myhttp/httpc/option.go Normal file
View File

@@ -0,0 +1,72 @@
package httpc
import (
"net/http"
"time"
"golang.org/x/time/rate"
)
type (
Config struct {
timeout time.Duration
client *http.Client
transport *http.Transport
redirectFn func(req *http.Request, via []*http.Request) error
noCheckStatus bool
qps int
qpsLimiter *rate.Limiter
}
ConfigOpt func(c *Config)
)
func WithTimout(v time.Duration) ConfigOpt {
return func(c *Config) {
c.timeout = v
}
}
func WithClient(v *http.Client) ConfigOpt {
return func(c *Config) {
c.client = v
}
}
func WithTransport(v *http.Transport) ConfigOpt {
return func(c *Config) {
c.transport = v
}
}
func WithRedirectFn(v func(req *http.Request, via []*http.Request) error) ConfigOpt {
return func(c *Config) {
c.redirectFn = v
}
}
func WithNoRedirect() ConfigOpt {
return func(c *Config) {
c.redirectFn = func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}
}
}
func WithNoCheckStatus(v bool) ConfigOpt {
return func(c *Config) {
c.noCheckStatus = v
}
}
func WithQps(v int) ConfigOpt {
return func(c *Config) {
c.qps = v
}
}
func WithQpsLimiter(v *rate.Limiter) ConfigOpt {
return func(c *Config) {
c.qpsLimiter = v
}
}

View File

@@ -0,0 +1,32 @@
package httpsr
import (
"git.makemake.in/kzkzzzz/mycommon/mymetric"
"github.com/gin-gonic/gin"
"github.com/prometheus/client_golang/prometheus"
"github.com/spf13/cast"
"time"
)
const CtxCollectRequestFrom = "ctx_collect_request_from"
func QPSCollect(svcName string) gin.HandlerFunc {
hs := mymetric.NewQPSHistogram(svcName, []string{
"svc", "method", "route", "status", "from",
}...)
return func(ctx *gin.Context) {
st := time.Now()
ctx.Next()
hs.With(prometheus.Labels{
"svc": svcName,
"method": ctx.Request.Method,
"route": ctx.Request.URL.Path,
"status": cast.ToString(ctx.Writer.Status()),
"from": ctx.GetString(CtxCollectRequestFrom),
}).Observe(float64(time.Since(st).Milliseconds()))
}
}

73
myip/ip2region.go Normal file
View File

@@ -0,0 +1,73 @@
package myip
import (
_ "embed"
"fmt"
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
"strings"
)
var (
searcher *xdb.Searcher
//go:embed ip2region.xdb
dbData []byte
)
func init() {
var err error
// 2、用全局的 cBuff 创建完全基于内存的查询对象。
searcher, err = xdb.NewWithBuffer(xdb.IPv4, dbData)
if err != nil {
panic(fmt.Errorf("failed to create searcher with content: %s", err))
}
}
type IpInfo struct {
Country string `json:"country"`
Area string `json:"area"`
Province string `json:"province"`
City string `json:"city"`
Isp string `json:"isp"`
}
func FindE(ipStr string) (info *IpInfo, err error) {
info = &IpInfo{}
// 国家|区域|省份|城市|ISP
res, err := searcher.SearchByStr(ipStr)
if err != nil {
return
}
//fmt.Println(res)
sp := strings.Split(res, "|")
if len(sp) < 5 {
return
}
info.Country = convertEmpty(sp[0])
info.Area = convertEmpty(sp[1])
info.Province = convertEmpty(sp[2])
info.City = convertEmpty(sp[3])
info.Isp = convertEmpty(sp[4])
return info, nil
}
func Find(ipStr string) *IpInfo {
info, _ := FindE(ipStr)
return info
}
func convertEmpty(s string) string {
if s == "0" || s == "" {
return "NULL"
}
return s
}
func (i *IpInfo) ToString() string {
return i.Country + "-" + i.Province + "-" + i.City + "-" + i.Isp
}

BIN
myip/ip2region.xdb Normal file

Binary file not shown.

111
mymetric/prometheus.go Normal file
View File

@@ -0,0 +1,111 @@
package mymetric
import (
"github.com/gin-gonic/gin"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"log"
"net/http"
"time"
)
type (
HistogramVec struct {
*prometheus.HistogramVec
Labels []string
}
CounterVec struct {
*prometheus.CounterVec
Labels []string
}
)
func NewQPSHistogram(name string, labels ...string) *HistogramVec {
name = HistogramKey(name)
v := prometheus.NewHistogramVec(prometheus.HistogramOpts{
Name: name,
Buckets: []float64{5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 95, 100, 200, 300, 500, 600, 700, 800, 900, 1000, 1500, 2000, 5000}, // 统计区间 单位毫秒
}, labels)
wrapMetric := &HistogramVec{
HistogramVec: v,
Labels: labels,
}
RegisterPrometheus(wrapMetric)
return wrapMetric
}
func NewHistogram(name string, buckets []float64, labels ...string) *HistogramVec {
name = HistogramKey(name)
v := prometheus.NewHistogramVec(prometheus.HistogramOpts{
Name: name,
Buckets: buckets,
}, labels)
wrapMetric := &HistogramVec{
HistogramVec: v,
Labels: labels,
}
RegisterPrometheus(wrapMetric)
return wrapMetric
}
func NewCounter(name string, labels ...string) *CounterVec {
name = CounterKey(name)
v := prometheus.NewCounterVec(prometheus.CounterOpts{
Name: name,
}, labels)
wrapMetric := &CounterVec{
CounterVec: v,
Labels: labels,
}
RegisterPrometheus(wrapMetric)
return wrapMetric
}
const (
MetricsRoute = "/metrics"
)
// 监控指标路由
func GinExport(authKey string) gin.HandlerFunc {
pm := promhttp.HandlerFor(
prometheus.DefaultGatherer,
promhttp.HandlerOpts{Timeout: time.Second * 3},
)
return func(ctx *gin.Context) {
if authKey != "" {
value := ctx.Query("key")
if authKey != value {
ctx.String(http.StatusForbidden, "metric auth key is empty")
return
}
}
pm.ServeHTTP(ctx.Writer, ctx.Request)
}
}
func HistogramKey(name string) string {
return name + "_h"
}
func CounterKey(name string) string {
return name + "_c"
}
func RegisterPrometheus(c prometheus.Collector) {
err := prometheus.Register(c)
if err != nil {
log.Printf("register err: %s", err)
}
}

292
mymysql/batchwriter.go Normal file
View File

@@ -0,0 +1,292 @@
package mymysql
import (
"context"
"git.makemake.in/kzkzzzz/mycommon"
"git.makemake.in/kzkzzzz/mycommon/mylog"
"github.com/goccy/go-json"
"github.com/rs/xid"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"gorm.io/gorm/logger"
"log"
"sync"
"time"
)
const (
defaultDataBuffer = 1e5 // channel缓冲区
defaultBatchSize = 200 // 多少条数据写一次
defaultIntervalTime = time.Second * 2 // 多久时间写一次
defaultJobNum = 2 // 写入db 任务数量
defaultAsyncWorkerNum = 20 // 异步执行写入事件的最大协程数量
)
type iWriterStop interface {
StopWriter()
}
var (
writerJobMap = &sync.Map{}
)
type (
batchData[T any] struct {
jobIndex int
dataList []T
}
)
type BatchWriterConfig struct {
channelBuffer int
batchSize int
batchInterval time.Duration
jobNum int
asyncWorkerNum int
clauseExpr []clause.Expression
debug bool
noPrepare bool
}
type BatchWriter[T any] struct {
db *MysqlDb
tableName string
jobName string
uniqueId string
config *BatchWriterConfig
dataChan chan T
ctx context.Context
cancel context.CancelFunc
stopChan chan struct{}
asyncWorkerLimitChan chan struct{}
asyncWorkerWg *sync.WaitGroup
}
type BatchWriterOpt func(c *BatchWriterConfig)
func NewBatchWrite[T any](db *MysqlDb, tableName, jobName string, opts ...BatchWriterOpt) *BatchWriter[T] {
config := &BatchWriterConfig{}
for _, opt := range opts {
opt(config)
}
if config.batchInterval <= 0 {
config.batchInterval = defaultIntervalTime
}
if config.channelBuffer <= 0 {
config.channelBuffer = defaultDataBuffer
}
if config.jobNum <= 0 {
config.jobNum = defaultJobNum
}
if config.asyncWorkerNum <= 0 {
config.asyncWorkerNum = defaultAsyncWorkerNum
}
if config.batchSize <= 0 {
config.batchSize = defaultBatchSize
}
bw := &BatchWriter[T]{
db: db,
tableName: tableName,
jobName: jobName,
uniqueId: xid.New().String(),
config: config,
dataChan: make(chan T),
stopChan: make(chan struct{}, 1),
asyncWorkerLimitChan: make(chan struct{}, config.asyncWorkerNum),
asyncWorkerWg: &sync.WaitGroup{},
}
bw.ctx, bw.cancel = context.WithCancel(context.Background())
// 记录实例, 便于退出程序的时候入库
writerJobMap.Store(bw.uniqueId, bw)
go func() {
bw.start()
}()
return bw
}
func (bw *BatchWriter[T]) Write(data ...T) {
if len(data) == 0 {
return
}
if bw.ctx.Err() != nil {
b, _ := json.Marshal(data)
mylog.Errorf("[%s] save to db err: job is close, data: (%s)", bw.tableName, b)
return
}
for _, v := range data {
bw.dataChan <- v
}
}
func (bw *BatchWriter[T]) start() {
wg := &sync.WaitGroup{}
for i := 0; i < bw.config.jobNum; i++ {
wg.Add(1)
go func(i0 int) {
defer wg.Done()
bw.startJob(i)
}(i)
}
wg.Wait()
log.Printf("[table:%s - job:%s] batch write job stop", bw.tableName, bw.jobName)
close(bw.stopChan)
}
func (bw *BatchWriter[T]) startJob(jobIndex int) {
tkTime := bw.config.batchInterval
// 定时器增加随机时间差
randN := float64(mycommon.RandRange(50, 350)) / float64(100)
tkTime = tkTime + time.Duration(float64(time.Second)*randN)
log.Printf("[table:%s - job:%s - %d] batch write job start, ticker time: %s", bw.tableName, bw.jobName, jobIndex, tkTime.String())
tk := time.NewTicker(tkTime)
defer tk.Stop()
bd := &batchData[T]{
jobIndex: jobIndex,
dataList: make([]T, 0, bw.config.batchSize),
}
loop:
for {
select {
case <-bw.ctx.Done():
break loop
case <-tk.C:
bw.writeToDb(bd)
case data, ok := <-bw.dataChan:
if !ok {
break loop
}
bd.dataList = append(bd.dataList, data)
if len(bd.dataList) >= bw.config.batchSize {
bw.writeToDb(bd)
}
}
}
if len(bd.dataList) > 0 {
bw.writeToDb(bd)
}
}
func (bw *BatchWriter[T]) writeToDb(bd *batchData[T]) {
if len(bd.dataList) == 0 {
return
}
defer func() {
// 清空切片
bd.dataList = bd.dataList[:0]
}()
bw.asyncWorkerLimitChan <- struct{}{}
// 复制一份数据, 异步写入
copyDataList := make([]T, len(bd.dataList))
copy(copyDataList, bd.dataList)
bw.asyncWorkerWg.Add(1)
go func() {
defer func() {
<-bw.asyncWorkerLimitChan
bw.asyncWorkerWg.Done()
}()
bw.asyncWriteToDb(bd.jobIndex, copyDataList)
}()
}
var DisableGormLog = logger.Default.LogMode(logger.Silent)
func SessionDisableLog() *gorm.Session {
return &gorm.Session{Logger: DisableGormLog}
}
func (bw *BatchWriter[T]) asyncWriteToDb(jobIndex int, copyDataList []T) {
if len(copyDataList) == 0 {
return
}
query := bw.db.Table(bw.tableName)
if len(bw.config.clauseExpr) > 0 {
query.Clauses(bw.config.clauseExpr...)
}
var err error
if bw.config.noPrepare {
_sql := query.ToSQL(func(tx *gorm.DB) *gorm.DB {
return tx.Session(&gorm.Session{
Logger: DisableGormLog,
}).Create(&copyDataList)
})
err = bw.db.Table(bw.tableName).Exec(_sql).Error
} else {
err = query.Create(copyDataList).Error
}
if err == nil {
return
}
// 批量写入失败, 后续优化重试流程
b, _ := json.Marshal(copyDataList)
mylog.Errorf("[%s - %s] save to db err: %s data: (%s)", bw.tableName, bw.jobName, err, b)
}
func (bw *BatchWriter[T]) StopWriter() {
if bw.ctx.Err() != nil {
return
}
bw.cancel()
close(bw.dataChan)
<-bw.stopChan
bw.asyncWorkerWg.Wait()
}
func StopAllBatchWriter() {
writerJobMap.Range(func(k, v interface{}) bool {
q := v.(iWriterStop)
q.StopWriter()
return true
})
}
// Deprecated: 改成用 StopAllBatchWriter
func StopWriter() {
StopAllBatchWriter()
}

View File

@@ -1,6 +1,10 @@
package mymysql package mymysql
import "gorm.io/gorm" import (
"gorm.io/gorm"
"gorm.io/gorm/clause"
"time"
)
type Opt func(m *MysqlDb) type Opt func(m *MysqlDb)
@@ -15,3 +19,51 @@ func WithGormConfig(v *gorm.Config) Opt {
m.gormConfig = v m.gormConfig = v
} }
} }
func WithWriteJobNum(v int) BatchWriterOpt {
return func(c *BatchWriterConfig) {
c.jobNum = v
}
}
func WithWriteChannelBuffer(v int) BatchWriterOpt {
return func(c *BatchWriterConfig) {
c.channelBuffer = v
}
}
func WithWriteBatchSize(v int) BatchWriterOpt {
return func(c *BatchWriterConfig) {
c.batchSize = v
}
}
func WithWriteIntervalTime(v time.Duration) BatchWriterOpt {
return func(c *BatchWriterConfig) {
c.batchInterval = v
}
}
func WithAsyncWorkerNum(v int) BatchWriterOpt {
return func(c *BatchWriterConfig) {
c.asyncWorkerNum = v
}
}
func WithClause(v ...clause.Expression) BatchWriterOpt {
return func(c *BatchWriterConfig) {
c.clauseExpr = v
}
}
func WithDebug(v bool) BatchWriterOpt {
return func(c *BatchWriterConfig) {
c.debug = v
}
}
func WithNoPrepare() BatchWriterOpt {
return func(c *BatchWriterConfig) {
c.noPrepare = true
}
}

View File

@@ -1,194 +0,0 @@
package consul
import (
"context"
"fmt"
"github.com/jpillora/backoff"
"google.golang.org/grpc/grpclog"
"log"
"sort"
"time"
"github.com/hashicorp/consul/api"
"github.com/pkg/errors"
"google.golang.org/grpc/resolver"
)
// schemeName for the urls
// All target URLs like 'consul://.../...' will be resolved by this resolver
const schemeName = "consul"
// builder implements resolver.Builder and use for constructing all consul resolvers
type builder struct{}
func (b *builder) Build(url resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
tgt, err := parseURL(url.URL.String())
if err != nil {
return nil, errors.Wrap(err, "Wrong consul URL")
}
cli, err := api.NewClient(tgt.consulConfig())
if err != nil {
return nil, errors.Wrap(err, "Couldn't connect to the Consul API")
}
ctx, cancel := context.WithCancel(context.Background())
pipe := make(chan []string)
go watchConsulService(ctx, cli.Health(), tgt, pipe)
go populateEndpoints(ctx, cc, pipe, tgt)
return &resolvr{cancelFunc: cancel}, nil
}
// Scheme returns the scheme supported by this resolver.
// Scheme is defined at https://github.com/grpc/grpc/blob/master/doc/naming.md.
func (b *builder) Scheme() string {
return schemeName
}
// init function needs for auto-register in resolvers registry
func init() {
resolver.Register(&builder{})
}
// resolvr implements resolver.Resolver from the gRPC package.
// It watches for endpoints changes and pushes them to the underlying gRPC connection.
type resolvr struct {
cancelFunc context.CancelFunc
}
// ResolveNow will be skipped due unnecessary in this case
func (r *resolvr) ResolveNow(resolver.ResolveNowOptions) {}
// Close closes the resolver.
func (r *resolvr) Close() {
r.cancelFunc()
}
//go:generate ./bin/moq -out mocks_test.go . servicer
type servicer interface {
Service(string, string, bool, *api.QueryOptions) ([]*api.ServiceEntry, *api.QueryMeta, error)
}
func watchConsulService(ctx context.Context, s servicer, tgt target, out chan<- []string) {
res := make(chan []string)
quit := make(chan struct{})
bck := &backoff.Backoff{
Factor: 2,
Jitter: true,
Min: 10 * time.Millisecond,
Max: tgt.MaxBackoff,
}
go func() {
var lastIndex uint64
for {
ss, meta, err := s.Service(
tgt.Service,
tgt.Tag,
tgt.Healthy,
&api.QueryOptions{
WaitIndex: lastIndex,
Near: tgt.Near,
WaitTime: tgt.Wait,
Datacenter: tgt.Dc,
AllowStale: tgt.AllowStale,
RequireConsistent: tgt.RequireConsistent,
},
)
if err != nil {
// No need to continue if the context is done/cancelled.
// We check that here directly because the check for the closed quit channel
// at the end of the loop is not reached when calling continue here.
select {
case <-quit:
return
default:
grpclog.Errorf("[Consul resolver] Couldn't fetch endpoints. target={%s}; error={%v}", tgt.String(), err)
time.Sleep(bck.Duration())
continue
}
}
bck.Reset()
lastIndex = meta.LastIndex
grpclog.Infof("[Consul resolver] %d endpoints fetched in(+wait) %s for target={%s}",
len(ss),
meta.RequestTime,
tgt.String(),
)
ee := make([]string, 0, len(ss))
for _, s := range ss {
address := s.Service.Address
if s.Service.Address == "" {
address = s.Node.Address
}
ee = append(ee, fmt.Sprintf("%s:%d", address, s.Service.Port))
}
if tgt.Limit != 0 && len(ee) > tgt.Limit {
ee = ee[:tgt.Limit]
}
select {
case res <- ee:
continue
case <-quit:
return
}
}
}()
for {
// If in the below select both channels have values that can be read,
// Go picks one pseudo-randomly.
// But when the context is canceled we want to act upon it immediately.
if ctx.Err() != nil {
// Close quit so the goroutine returns and doesn't leak.
// Do NOT close res because that can lead to panics in the goroutine.
// res will be garbage collected at some point.
close(quit)
return
}
select {
case ee := <-res:
out <- ee
case <-ctx.Done():
close(quit)
return
}
}
}
func populateEndpoints(ctx context.Context, clientConn resolver.ClientConn, input <-chan []string, tgt target) {
for {
select {
case cc := <-input:
connsSet := make(map[string]struct{}, len(cc))
for _, c := range cc {
connsSet[c] = struct{}{}
}
conns := make([]resolver.Address, 0, len(connsSet))
for c := range connsSet {
conns = append(conns, resolver.Address{Addr: c})
}
sort.Sort(byAddressString(conns)) // Don't replace the same address list in the balancer
log.Printf("update conn: %s - %s", tgt.Service, conns)
err := clientConn.UpdateState(resolver.State{Addresses: conns})
if err != nil {
grpclog.Errorf("[Consul resolver] Couldn't update client connection. error={%v}", err)
continue
}
case <-ctx.Done():
grpclog.Info("[Consul resolver] Watch has been finished")
return
}
}
}
// byAddressString sorts resolver.Address by Address Field sorting in increasing order.
type byAddressString []resolver.Address
func (p byAddressString) Len() int { return len(p) }
func (p byAddressString) Less(i, j int) bool { return p[i].Addr < p[j].Addr }
func (p byAddressString) Swap(i, j int) { p[i], p[j] = p[j], p[i] }

View File

@@ -3,25 +3,38 @@ package consul
import ( import (
"fmt" "fmt"
"git.makemake.in/kzkzzzz/mycommon/myconf" "git.makemake.in/kzkzzzz/mycommon/myconf"
"git.makemake.in/kzkzzzz/mycommon/mygrpc"
"git.makemake.in/kzkzzzz/mycommon/mylog"
"git.makemake.in/kzkzzzz/mycommon/myregistry" "git.makemake.in/kzkzzzz/mycommon/myregistry"
"github.com/hashicorp/consul/api" "github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/api/watch"
"github.com/hashicorp/go-hclog"
"github.com/rs/xid" "github.com/rs/xid"
"log" "go.uber.org/zap"
"net" "net"
"net/url" "net/url"
"os" "os"
"sync"
"time" "time"
) )
var _ myregistry.IRegister = (*Consul)(nil) var (
_ myregistry.IRegister = (*Consul)(nil)
defaultLog = mylog.NewLogger(&mylog.Config{
Level: mylog.DebugLevel,
NeedLogFile: false,
ConsoleWriter: os.Stdout,
ZapOpt: []zap.Option{
zap.AddCaller(), zap.AddCallerSkip(1),
},
})
)
type Consul struct { type Consul struct {
client *api.Client client *api.Client
serviceIds map[string][]string
serviceTags []string serviceTags []string
services []*api.AgentServiceRegistration services map[string]*api.AgentServiceRegistration
deregisteredMap *sync.Map
} }
type Opt func(*Consul) type Opt func(*Consul)
@@ -41,44 +54,83 @@ func (c *Consul) Register(service *myregistry.ServiceInfo) error {
serviceId := xid.New().String() serviceId := xid.New().String()
hostname, err := os.Hostname() hostname, err := os.Hostname()
if err != nil { if err != nil {
log.Printf("get hostname err: %s", err) defaultLog.Errorf("get hostname err: %s", err)
} else { } else {
serviceId = fmt.Sprintf("%s-%s", hostname, serviceId) serviceId = fmt.Sprintf("%s-%s", hostname, serviceId)
} }
c.serviceIds[service.ServiceName] = append(c.serviceIds[service.ServiceName], serviceId) if _, ok := c.services[service.ServiceName]; ok {
return fmt.Errorf("service [%s] already registered", service.ServiceName)
}
checkTime := time.Second * 15
check := &api.AgentServiceCheck{ check := &api.AgentServiceCheck{
CheckID: serviceId, CheckID: serviceId,
TCP: fmt.Sprintf("%s:%d", service.Ip, service.Port), TTL: (checkTime*2 + time.Second*10).String(),
Timeout: "5s", // 超时时间 //TCP: fmt.Sprintf("%s:%d", service.Ip, service.Port),
Interval: "30s", // 运行检查的频率 //Timeout: "5s", // 超时时间
//Interval: "30s", // 运行检查的频率
// 指定时间后自动注销不健康的服务节点 // 指定时间后自动注销不健康的服务节点
// 最小超时时间为1分钟收获不健康服务的进程每30秒运行一次因此触发注销的时间可能略长于配置的超时时间。 // 最小超时时间为1分钟收获不健康服务的进程每30秒运行一次因此触发注销的时间可能略长于配置的超时时间。
DeregisterCriticalServiceAfter: "6m", DeregisterCriticalServiceAfter: "5m",
Status: "passing", Status: api.HealthPassing,
} }
regTags := make([]string, len(c.serviceTags))
copy(regTags, c.serviceTags)
if v := service.Extend["tag"]; v != "" {
regTags = append(regTags, v)
}
svc := &api.AgentServiceRegistration{ svc := &api.AgentServiceRegistration{
ID: serviceId, // 服务唯一ID ID: serviceId, // 服务唯一ID
Name: service.ServiceName, // 服务名称 Name: service.ServiceName, // 服务名称
Tags: c.serviceTags, // 为服务打标签 Tags: regTags, // 为服务打标签
Address: service.Ip, Address: service.Ip,
Port: service.Port, Port: service.Port,
Check: check, Check: check,
} }
c.services = append(c.services, svc) c.services[service.ServiceName] = svc
return c.client.Agent().ServiceRegister(svc) err = c.client.Agent().ServiceRegister(svc)
if err != nil {
defaultLog.Errorf("register service err: %s: %s", svc.Name, err)
return err
}
defaultLog.Infof("register service ok: %s - %s:%d", svc.Name, svc.Address, svc.Port)
go func() {
for {
//defaultLog.Debugf("PassTTL ttl service: %s", svc.Name)
err := c.client.Agent().PassTTL(serviceId, "")
if err != nil {
defaultLog.Errorf("PassTTL service err: %s: %s", svc.Name, err)
}
time.Sleep(checkTime)
}
}()
return nil
} }
func (c *Consul) Deregister(service *myregistry.ServiceInfo) error { func (c *Consul) Deregister(service *myregistry.ServiceInfo) error {
for _, svcId := range c.serviceIds[service.ServiceName] { c.deregisteredMap.Store(service.ServiceName, true)
err := c.client.Agent().ServiceDeregister(svcId)
if svc, ok := c.services[service.ServiceName]; ok {
err := c.client.Agent().ServiceDeregister(svc.ID)
if err != nil { if err != nil {
log.Printf("Failed to deregister service %s: %s\n", service, err) defaultLog.Errorf("deregister service %s err: %s", service, err)
} }
} }
return nil return nil
} }
@@ -91,9 +143,59 @@ func MustNew(conf *myconf.Config, opts ...Opt) *Consul {
} }
func New(conf *myconf.Config, opts ...Opt) (*Consul, error) { func New(conf *myconf.Config, opts ...Opt) (*Consul, error) {
client, err := NewClient(&ClientConfig{
Address: conf.GetString("addr"),
Token: conf.GetString("token"),
Username: conf.GetString("username"),
Password: conf.GetString("password"),
})
if err != nil {
return nil, err
}
cl := &Consul{
client: client,
serviceTags: conf.GetStringSlice("serviceTags"),
services: make(map[string]*api.AgentServiceRegistration, 0),
deregisteredMap: &sync.Map{},
}
for _, opt := range opts {
opt(cl)
}
go cl.allHealthCheck()
return cl, nil
}
type ClientConfig struct {
Address string
Token string
Username string
Password string
AutoGrpcPrefix bool
}
var (
clientLock = &sync.Mutex{}
clientMap = make(map[string]*api.Client)
)
func NewClient(clientCfg *ClientConfig) (*api.Client, error) {
clientLock.Lock()
defer clientLock.Unlock()
if clientCfg.Address == "" {
return nil, fmt.Errorf("consul address is empty")
}
if client, ok := clientMap[clientCfg.Address]; ok {
return client, nil
}
cfg := api.DefaultConfig() cfg := api.DefaultConfig()
cfg.Address = conf.GetString("addr") cfg.Address = clientCfg.Address
if cfg.Address == "" { if cfg.Address == "" {
return nil, fmt.Errorf("consul address is empty") return nil, fmt.Errorf("consul address is empty")
@@ -102,12 +204,11 @@ func New(conf *myconf.Config, opts ...Opt) (*Consul, error) {
cfg.Transport.DialContext = (&net.Dialer{ cfg.Transport.DialContext = (&net.Dialer{
Timeout: 3 * time.Second, Timeout: 3 * time.Second,
KeepAlive: 20 * time.Second, KeepAlive: 20 * time.Second,
DualStack: true,
}).DialContext }).DialContext
cfg.Token = conf.GetString("token") cfg.Token = clientCfg.Token
username := conf.GetString("username") username := clientCfg.Username
password := conf.GetString("password") password := clientCfg.Password
if username != "" && password != "" { if username != "" && password != "" {
cfg.HttpAuth = &api.HttpBasicAuth{ cfg.HttpAuth = &api.HttpBasicAuth{
@@ -121,55 +222,94 @@ func New(conf *myconf.Config, opts ...Opt) (*Consul, error) {
return nil, err return nil, err
} }
cl := &Consul{ clientMap[clientCfg.Address] = client
client: client, return client, nil
serviceIds: make(map[string][]string),
serviceTags: conf.GetStringSlice("serviceTags"),
services: make([]*api.AgentServiceRegistration, 0),
} }
for _, opt := range opts { func (c *Consul) allHealthCheck() {
opt(cl) time.Sleep(time.Second * 5)
mylog.Infof("start all health check")
var (
lastIndex uint64
isFirst = true
err error
meta *api.QueryMeta
remoteServices map[string][]string
)
for {
if isFirst == false {
time.Sleep(time.Second * 2)
// 错误的情况重新注册一次
if err != nil {
var isRegisterErr bool
for _, svc := range c.services {
err = c.retryRegister(svc, false)
if err != nil {
defaultLog.Errorf("retry register service %s err:: %s", svc.Name, err)
isRegisterErr = true
break
} else {
defaultLog.Infof("retry register service %s ok", svc.Name)
} }
go cl.healthCheck()
return cl, nil
} }
func (c *Consul) healthCheck() { if isRegisterErr {
wlog := newWatchLogger() continue
}
}
wp, err := watch.Parse(map[string]any{ }
"type": "services",
isFirst = false
remoteServices, meta, err = c.client.Catalog().Services(&api.QueryOptions{
WaitIndex: lastIndex,
WaitTime: time.Second * 90,
}) })
if err != nil { if err != nil {
panic(fmt.Sprintf("parse watch err: %s", err)) defaultLog.Errorf("health check err: %s", err)
continue
}
lastIndex = meta.LastIndex
//mylog.Debugf("%d - %+v\n", lastIndex, remoteServices)
if isFirst == false {
for _, localSvc := range c.services {
if _, ok := remoteServices[localSvc.Name]; !ok {
//c.retryRegister(localSvc, true)
err = fmt.Errorf("need retry register service %s not exist [%+v]", localSvc.Name, remoteServices)
}
}
} }
wp.Handler = func(u uint64, raw any) { }
if wlog.isWatchErr == true { }
for _, svc := range c.services { func (c *Consul) retryRegister(svc *api.AgentServiceRegistration, needDelay bool) error {
//c.client.Agent().ServiceDeregister(svc.ID) if _, ok := c.deregisteredMap.Load(svc.Name); ok {
return nil
}
if needDelay {
go func() {
time.Sleep(time.Second)
err := c.client.Agent().ServiceRegister(svc) err := c.client.Agent().ServiceRegister(svc)
if err != nil { if err != nil {
log.Printf("retry register service err: %s: %s", svc.Name, err) defaultLog.Errorf("delay retry register service %s err: %s", svc.Name, err)
} else { } else {
log.Printf("retry register service ok: %s", svc.Name) defaultLog.Infof("delay retry register service %s ok", svc.Name)
} }
}()
return nil
} }
wlog.isWatchErr = false return c.client.Agent().ServiceRegister(svc)
}
//fmt.Println("watch", u, raw)
}
err = wp.RunWithClientAndHclog(c.client, wlog)
if err != nil {
log.Printf("watch err: %s", err)
}
} }
func (c *Consul) Client() *api.Client { func (c *Consul) Client() *api.Client {
@@ -177,13 +317,27 @@ func (c *Consul) Client() *api.Client {
} }
func GrpcUrl(serviceName string, conf *myconf.Config) string { func GrpcUrl(serviceName string, conf *myconf.Config) string {
return GrpcUrlWithTag("", "grpc@"+serviceName, conf) return GrpcUrlWithTag("", serviceName, conf)
} }
func GrpcUrlWithTag(tag string, serviceName string, conf *myconf.Config) string { func GrpcUrlWithTag(tag string, serviceName string, conf *myconf.Config) string {
return GrpcUrlWithTagByConfig(tag, serviceName, &ClientConfig{
Address: conf.GetString("addr"),
Token: conf.GetString("token"),
Username: conf.GetString("username"),
Password: conf.GetString("password"),
AutoGrpcPrefix: true,
})
}
func GrpcUrlWithTagByConfig(tag string, serviceName string, conf *ClientConfig) string {
if conf.AutoGrpcPrefix {
serviceName = mygrpc.ServicePrefix + serviceName
}
u := &url.URL{ u := &url.URL{
Scheme: schemeName, Scheme: schemeName,
Host: conf.GetString("addr"), Host: conf.Address,
Path: serviceName, Path: serviceName,
} }
@@ -194,44 +348,19 @@ func GrpcUrlWithTag(tag string, serviceName string, conf *myconf.Config) string
query := u.Query() query := u.Query()
query.Set("healthy", "true") query.Set("healthy", "true")
if v := conf.GetString("token"); v != "" { if conf.Token != "" {
query.Set("token", v) query.Set("token", conf.Token)
} }
if tag != "" { if tag != "" {
query.Set("tag", tag) query.Set("tag", tag)
} }
username := conf.GetString("username") if conf.Username != "" && conf.Password != "" {
password := conf.GetString("password") u.User = url.UserPassword(conf.Username, conf.Password)
if username != "" && password != "" {
u.User = url.UserPassword(username, password)
} }
u.RawQuery = query.Encode() u.RawQuery = query.Encode()
return u.String() return u.String()
} }
type watchLogger struct {
hclog.Logger
isWatchErr bool
}
func newWatchLogger() *watchLogger {
return &watchLogger{Logger: hclog.New(&hclog.LoggerOptions{
Name: "watch",
Output: os.Stdout,
})}
}
func (l *watchLogger) Error(msg string, args ...interface{}) {
l.isWatchErr = true
log.Printf("is watch err: %s", msg)
l.Logger.Error(msg, args...)
}
func (l *watchLogger) Named(name string) hclog.Logger {
return l
}

View File

@@ -0,0 +1,7 @@
package consul
import "testing"
func TestWatch(t *testing.T) {
}

View File

@@ -0,0 +1,132 @@
package consul
import (
"fmt"
"github.com/hashicorp/consul/api"
"github.com/pkg/errors"
"google.golang.org/grpc/resolver"
"sort"
"strings"
"time"
)
const schemeName = "consul"
func init() {
resolver.Register(&builder{})
}
var _ resolver.Builder = (*builder)(nil)
type builder struct{}
func (b *builder) Build(url resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
tgt, err := parseURL(url.URL.String())
if err != nil {
return nil, errors.Wrap(err, "Wrong consul URL")
}
client, err := NewClient(&ClientConfig{
Address: tgt.Addr,
Token: tgt.Token,
Username: tgt.User,
Password: tgt.Password,
})
if err != nil {
return nil, errors.Wrap(err, "Couldn't connect to the Consul API")
}
rl := &consulResolver{
client: client,
tgt: tgt,
cc: cc,
}
go rl.watchService()
return rl, nil
}
func (b *builder) Scheme() string {
return schemeName
}
var _ resolver.Resolver = (*consulResolver)(nil)
type consulResolver struct {
client *api.Client
tgt *target
cc resolver.ClientConn
}
func (c *consulResolver) watchService() {
var (
lastIndex uint64
isFirst = true
)
for {
if isFirst == false {
time.Sleep(time.Second * 2)
}
isFirst = false
endpoints, meta, err := c.client.Health().Service(c.tgt.Service, c.tgt.Tag,
true,
&api.QueryOptions{
WaitIndex: lastIndex,
WaitTime: c.tgt.Wait,
Datacenter: c.tgt.Dc,
AllowStale: c.tgt.AllowStale,
RequireConsistent: c.tgt.RequireConsistent,
})
if err != nil {
defaultLog.Errorf("watch service err: %s", err)
continue
}
lastIndex = meta.LastIndex
addrs := make([]string, 0, len(endpoints))
state := resolver.State{
Addresses: make([]resolver.Address, 0, len(endpoints)),
}
for _, endpoint := range endpoints {
tmp := resolver.Address{
Addr: endpoint.Service.Address,
}
if tmp.Addr == "" {
continue
}
tmp.Addr = fmt.Sprintf("%s:%d", tmp.Addr, endpoint.Service.Port)
state.Addresses = append(state.Addresses, tmp)
addrs = append(addrs, tmp.Addr)
}
if len(state.Addresses) == 0 {
defaultLog.Warnf("%s services num == 0", c.tgt.String())
}
sort.SliceStable(state.Addresses, func(i, j int) bool {
return state.Addresses[i].Addr < state.Addresses[j].Addr
})
err = c.cc.UpdateState(state)
if err != nil {
defaultLog.Errorf("%s update service state err: %s", err, c.tgt.String())
} else {
defaultLog.Infof("%s update service num:%d (%s)", c.tgt.String(), len(addrs), strings.Join(addrs, ", "))
}
}
}
func (c *consulResolver) ResolveNow(options resolver.ResolveNowOptions) {
}
func (c *consulResolver) Close() {
}

View File

@@ -29,30 +29,31 @@ type target struct {
Dc string `form:"dc"` Dc string `form:"dc"`
AllowStale bool `form:"allow-stale"` AllowStale bool `form:"allow-stale"`
RequireConsistent bool `form:"require-consistent"` RequireConsistent bool `form:"require-consistent"`
// TODO(mbobakov): custom parameters for the http-transport
// TODO(mbobakov): custom parameters for the TLS subsystem
} }
func (t *target) String() string { func (t *target) String() string {
return fmt.Sprintf("service='%s' healthy='%t' tag='%s'", t.Service, t.Healthy, t.Tag) str := t.Service
if t.Tag != "" {
str = fmt.Sprintf("%s (tag:%s)", str, t.Tag)
} }
// parseURL with parameters return str
// see README.md for the actual format
// URL schema will stay stable in the future for backward compatibility }
func parseURL(u string) (target, error) {
func parseURL(u string) (*target, error) {
rawURL, err := url.Parse(u) rawURL, err := url.Parse(u)
if err != nil { if err != nil {
return target{}, errors.Wrap(err, "Malformed URL") return nil, errors.Wrap(err, "Malformed URL")
} }
if rawURL.Scheme != schemeName || if rawURL.Scheme != schemeName ||
len(rawURL.Host) == 0 || len(strings.TrimLeft(rawURL.Path, "/")) == 0 { len(rawURL.Host) == 0 || len(strings.TrimLeft(rawURL.Path, "/")) == 0 {
return target{}, return nil,
errors.Errorf("Malformed URL('%s'). Must be in the next format: 'consul://[user:passwd]@host/service?param=value'", u) errors.Errorf("Malformed URL('%s'). Must be in the next format: 'consul://[user:passwd]@host/service?param=value'", u)
} }
var tgt target tgt := &target{}
tgt.User = rawURL.User.Username() tgt.User = rawURL.User.Username()
tgt.Password, _ = rawURL.User.Password() tgt.Password, _ = rawURL.User.Password()
tgt.Addr = rawURL.Host tgt.Addr = rawURL.Host
@@ -64,7 +65,7 @@ func parseURL(u string) (target, error) {
err = decoder.Decode(&tgt, rawURL.Query()) err = decoder.Decode(&tgt, rawURL.Query())
if err != nil { if err != nil {
return target{}, errors.Wrap(err, "Malformed URL parameters") return nil, errors.Wrap(err, "Malformed URL parameters")
} }
if len(tgt.Near) == 0 { if len(tgt.Near) == 0 {
tgt.Near = "_agent" tgt.Near = "_agent"

View File

@@ -15,6 +15,11 @@ var (
trans ut.Translator trans ut.Translator
) )
func init() {
vt = validator.New()
trans = RegisterTranslate(vt)
}
func Init() { func Init() {
vt = validator.New() vt = validator.New()
trans = RegisterTranslate(vt) trans = RegisterTranslate(vt)