Compare commits

..

61 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
kzkzzzz
9fd0eaadb8 update 2025-03-23 20:29:29 +08:00
kzkzzzz
7e0bf82418 update 2025-03-22 01:24:57 +08:00
lzf
197476e805 修改日志 2024-09-30 15:55:21 +08:00
lzf
fb4fee6e30 修改日志 2024-09-30 15:41:29 +08:00
40 changed files with 4754 additions and 881 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

109
common.go
View File

@@ -1,8 +1,14 @@
package mycommon package mycommon
import ( import (
"errors"
"git.makemake.in/kzkzzzz/mycommon/mylog" "git.makemake.in/kzkzzzz/mycommon/mylog"
"log"
"math/rand/v2"
"net"
"os"
"runtime/debug" "runtime/debug"
"sync"
) )
func SafeFn(fn func()) { func SafeFn(fn func()) {
@@ -20,3 +26,106 @@ func SafeGo(fn func()) {
SafeFn(fn) SafeFn(fn)
}() }()
} }
// 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 (
getIpOnce = &sync.Once{}
outBoundIp = "127.0.0.1"
)
// GetOutboundIP 获取本机ip
func GetOutboundIP() string {
getIpOnce.Do(func() {
conn, err := net.Dial("udp", "223.5.5.5:53")
if err != nil {
log.Printf("get outbound ip err: %s", err)
return
}
defer conn.Close()
localAddr := conn.LocalAddr().(*net.UDPAddr)
if localAddr.IP != nil {
outBoundIp = localAddr.IP.String()
}
})
return outBoundIp
}
var (
getHostNameOnce = &sync.Once{}
hostName = "unknown"
)
// GetHostName 获取hostname
func GetHostName() string {
getHostNameOnce.Do(func() {
v, err := os.Hostname()
if err != nil {
log.Printf("get host name err: %s", err)
return
}
hostName = v
})
return hostName
}
func ExistFile(file string) bool {
_, err := os.Stat(file)
if err == nil {
return true
}
if errors.Is(err, os.ErrNotExist) {
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
}

112
go.mod
View File

@@ -1,49 +1,113 @@
module git.makemake.in/kzkzzzz/mycommon module git.makemake.in/kzkzzzz/mycommon
go 1.19 go 1.25.3
require ( require (
github.com/gin-contrib/pprof v1.5.2
github.com/gin-gonic/gin v1.10.0
github.com/go-playground/form v3.1.4+incompatible
github.com/go-playground/locales v0.14.1 github.com/go-playground/locales v0.14.1
github.com/go-playground/universal-translator v0.18.0 github.com/go-playground/universal-translator v0.18.1
github.com/go-playground/validator/v10 v10.11.1 github.com/go-playground/validator/v10 v10.25.0
github.com/go-redis/redis/v8 v8.11.5 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/hashicorp/consul/api v1.28.2
github.com/json-iterator/go v1.1.12 github.com/json-iterator/go v1.1.12
github.com/knadh/koanf/parsers/json 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/providers/confmap v0.1.0
github.com/knadh/koanf/providers/file v1.1.2
github.com/knadh/koanf/providers/posflag v0.1.0
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/prometheus/client_golang v1.11.1
github.com/redis/go-redis/v9 v9.7.3
github.com/rs/xid 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.14.0 github.com/spf13/viper v1.19.0
github.com/stretchr/testify v1.8.1 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
gopkg.in/natefinch/lumberjack.v2 v2.2.1 golang.org/x/sync v0.17.0
gorm.io/driver/mysql v1.4.5 golang.org/x/time v0.5.0
gorm.io/gorm v1.24.3 google.golang.org/grpc v1.75.0
gorm.io/driver/mysql v1.5.7
gorm.io/gorm v1.25.12
) )
require ( require (
github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/andybalholm/brotli v1.2.0 // indirect
github.com/davecgh/go-spew v1.1.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/loader v0.2.4 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.5 // 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/fsnotify/fsnotify v1.6.0 // indirect github.com/fatih/color v1.16.0 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gin-contrib/sse v1.0.0 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // 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/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v1.5.0 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // 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/leodido/go-urn v1.2.1 // indirect github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/knadh/koanf/maps v0.1.1 // 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-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/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
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.0.6 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/spf13/afero v1.9.3 // indirect github.com/prometheus/client_model v0.2.0 // indirect
github.com/spf13/cast v1.5.0 // indirect github.com/prometheus/common v0.26.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/prometheus/procfs v0.6.0 // indirect
github.com/subosito/gotenv v1.4.2 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // 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/crypto v0.5.0 // indirect golang.org/x/arch v0.15.0 // indirect
golang.org/x/sys v0.4.0 // indirect golang.org/x/crypto v0.42.0 // indirect
golang.org/x/text v0.6.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/net v0.44.0 // indirect
golang.org/x/sys v0.36.0 // indirect
golang.org/x/text v0.29.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // 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/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

796
go.sum
View File

@@ -1,553 +1,479 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/gin-contrib/pprof v1.5.2 h1:Kcq5W2bA2PBcVtF0MqkQjpvCpwJr+pd7zxcQh2csg7E=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/gin-contrib/pprof v1.5.2/go.mod h1:a1W4CDXwAPm2zql2AKdnT7OVCJdV/oFPhJXVOrDs5Ns=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
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/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/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.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/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/go.mod h1:lhcKXfTuhRtIZCIKUeJ0b5F207aeQCPbZU09ScKjwWg=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8=
github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
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.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 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.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.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-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.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 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.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.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 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.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.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 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/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/hashicorp/consul/api v1.28.2 h1:mXfkRHrpHN4YY3RqL09nXU1eHKLNiuAN4kHvDQ16k/8=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/hashicorp/consul/api v1.28.2/go.mod h1:KyzqzgMEya+IZPcD65YFoOVAgPpbfERu4I/tzG6/ueE=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/hashicorp/consul/sdk v0.16.2 h1:cGX/djeEe9r087ARiKVWwVWCF64J+yW0G6ftZMZYbj0=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/hashicorp/consul/sdk v0.16.2/go.mod h1:onxcZjYVsPx5XMveAC/OtoIsdr32fykB7INFltDoRE8=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM=
github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0=
github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY=
github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
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.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
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/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.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/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 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.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs=
github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/parsers/json v0.1.0 h1:dzSZl5pf5bBcW0Acnu20Djleto19T0CfHcvZ14NJ6fU=
github.com/knadh/koanf/parsers/json v0.1.0/go.mod h1:ll2/MlXcZ2BfXD6YJcjVFzhG9P0TdJ207aIBKQhV2hY=
github.com/knadh/koanf/parsers/toml v0.1.0 h1:S2hLqS4TgWZYj4/7mI5m1CQQcWurxUz6ODgOub/6LCI=
github.com/knadh/koanf/parsers/toml v0.1.0/go.mod h1:yUprhq6eo3GbyVXFFMdbfZSo928ksS+uo0FFqNMnO18=
github.com/knadh/koanf/parsers/yaml v0.1.0 h1:ZZ8/iGfRLvKSaMEECEBPM1HQslrZADk8fP1XFUxVI5w=
github.com/knadh/koanf/parsers/yaml v0.1.0/go.mod h1:cvbUDC7AL23pImuQP0oRw/hPuccrNBS2bps8asS0CwY=
github.com/knadh/koanf/providers/confmap v0.1.0 h1:gOkxhHkemwG4LezxxN8DMOFopOPghxRVp7JbIvdvqzU=
github.com/knadh/koanf/providers/confmap v0.1.0/go.mod h1:2uLhxQzJnyHKfxG927awZC7+fyHFdQkd697K4MdLnIU=
github.com/knadh/koanf/providers/file v1.1.2 h1:aCC36YGOgV5lTtAFz2qkgtWdeQsgfxUkxDOe+2nQY3w=
github.com/knadh/koanf/providers/file v1.1.2/go.mod h1:/faSBcv2mxPVjFrXck95qeoyoZ5myJ6uxN8OOVNJJCI=
github.com/knadh/koanf/providers/posflag v0.1.0 h1:mKJlLrKPcAP7Ootf4pBZWJ6J+4wHYujwipe7Ie3qW6U=
github.com/knadh/koanf/providers/posflag v0.1.0/go.mod h1:SYg03v/t8ISBNrMBRMlojH8OsKowbkXV7giIbBVgbz0=
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/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.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/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.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 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=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 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.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= 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.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
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/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/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/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
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/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= 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/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/prometheus/client_golang v1.11.1 h1:+4eQaD7vAZ6DsfsxB15hbE0odUjGI5ARs9yskGu1v4s=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= 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.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
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.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.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
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/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
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/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
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.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/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
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 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
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/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= 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=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
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-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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/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-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
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-20190501004415-9ce7a6920f09/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-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
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-20190628185345-da137c7871d7/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-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
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-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 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/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/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-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-20190227155943-e225da77a7e6/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-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
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-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-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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/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-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/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-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/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-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/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-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/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-20210806184541-e5e7981a1069/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-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
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.0.0-20170915032832-14c0d48ead0c/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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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.4/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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
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-20190114222345-bf090417da8b/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-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
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-20191011141410-1b5146add898/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=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 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-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 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.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.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/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.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 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/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-20180628173108-788fd7840127/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 h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 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/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
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.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 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.0-20210107192922-496545a6307b/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=
gorm.io/driver/mysql v1.4.5 h1:u1lytId4+o9dDaNcPCFzNv7h6wvmc92UjNk3z8enSBU= gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
gorm.io/driver/mysql v1.4.5/go.mod h1:SxzItlnT1cb6e1e4ZRpgJN2VYtcqJgqnHxWr4wsP8oc= gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.24.3 h1:WL2ifUmzR/SLp85CSURAfybcHnGZ+yLSGSxgYXlFBHg= gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.24.3/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

79
graceful/graeceful.go Normal file
View File

@@ -0,0 +1,79 @@
package graceful
import (
"context"
"golang.org/x/sync/errgroup"
"log"
"os"
"os/signal"
"syscall"
)
func init() {
log.SetOutput(os.Stdout)
}
type IRunner interface {
Run(ctx context.Context) error
Stop()
}
type Graceful struct{}
type Opt func(*Graceful)
func New() *Graceful {
return &Graceful{}
}
func (g *Graceful) Run(r ...IRunner) {
eg := &errgroup.Group{}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
for _, v := range r {
goRun(ctx, eg, v)
}
eg.Go(func() error {
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
select {
case v := <-ch:
log.Printf("catch notfiy stop: %v", v)
}
cancel()
for _, v := range r {
v.Stop()
}
return nil
})
defer func() {
log.Printf("server stop")
}()
err := eg.Wait()
if err != nil {
log.Println(err)
return
}
}
func goRun(ctx context.Context, eg *errgroup.Group, r IRunner) {
eg.Go(func() error {
err := r.Run(ctx)
if err != nil {
log.Printf("run err: %s", err)
return err
}
return nil
})
}

View File

@@ -2,47 +2,348 @@ package myconf
import ( import (
"fmt" "fmt"
"git.makemake.in/kzkzzzz/mycommon"
pjson "github.com/knadh/koanf/parsers/json"
ptoml "github.com/knadh/koanf/parsers/toml"
pyaml "github.com/knadh/koanf/parsers/yaml"
"github.com/knadh/koanf/providers/confmap"
kfile "github.com/knadh/koanf/providers/file"
"github.com/knadh/koanf/providers/posflag"
"github.com/knadh/koanf/v2"
"github.com/spf13/cast"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"github.com/spf13/viper" "github.com/spf13/viper"
"log" "log"
"os"
"path/filepath"
"strings"
"sync"
)
const (
Default = "default"
envConfigFile = "APP_CONFIG_FILE"
)
var (
configInstanceMap = &sync.Map{} // 记录配置实例, 使用conf.Conf("name") 获取
defaultConf *Config
defaultConfigFile = []string{"config.yaml", "config.yml"} // 兼容yaml和yml格式
flagConfigFile string // 命令行传参指定的配置文件
) )
type Config struct { type Config struct {
*viper.Viper InstanceName string
ConfigFile string
kof *koanf.Koanf
lock *sync.RWMutex
koanfOpt []koanf.Option
} }
var ( type Opt func(c *Config)
conf = &Config{Viper: viper.New()}
)
// 加载命令行参数 func WithKoanfOpt(v ...koanf.Option) Opt {
func LoadFlag() { return func(c *Config) {
if !pflag.Parsed() { c.koanfOpt = v
pflag.Parse()
} }
err := conf.BindPFlags(pflag.CommandLine) }
func WithLoadOverwrite(v bool) Opt {
return func(c *Config) {
if v == true {
c.koanfOpt = append(c.koanfOpt, koanf.WithMergeFunc(func(src, dest map[string]interface{}) error {
dest = src
return nil
}))
}
}
}
func init() {
defaultConf = New(Default)
log.SetOutput(os.Stdout)
log.SetFlags(log.LstdFlags | log.Lshortfile)
// --config 指定配置文件
pflag.StringVar(&flagConfigFile, "config", "", "set config file")
}
// New 初始化配置 name 只是一个实例标识区分名字, 不一定是文件名称, 文件名称在loadConf的时候定义
// 由于viper在访问key时不区分大小写(会强制转为小写, https://github.com/spf13/viper/issues/373)
// 如 Get("mysql.Dsn"), Get("mysql.dsn"),
// 或者把 Unmarshal解析到 map[string]string等map格式时, key也是转为小写, 导致部分场景判断可能会有问题
// 改为使用 koanf 这个库 https://github.com/knadh/koanf 可以区分大小写
func New(name string, opts ...Opt) *Config {
cfg := &Config{
InstanceName: name,
kof: koanf.New("."),
lock: &sync.RWMutex{}, // 远程更新用到
koanfOpt: make([]koanf.Option, 0),
}
for _, opt := range opts {
opt(cfg)
}
configInstanceMap.Store(name, cfg)
return cfg
}
// Conf 根据name获取对应的实例
func Conf(name ...string) *Config {
if len(name) == 0 {
v, ok := configInstanceMap.Load(Default)
if !ok {
panic(fmt.Errorf("conf [%s] not exists", Default))
}
//vv, ok := v.(*Config)
return v.(*Config)
}
v, ok := configInstanceMap.Load(name[0])
if !ok {
panic(fmt.Errorf("conf [%s] not exists", name[0]))
}
return v.(*Config)
}
// FromViper 转化viper配置
func FromViper(v *viper.Viper) *Config {
kof := koanf.New(".")
keys := v.AllKeys() // key是 mysql.dsn, http.port 这种展平的格式, 中间默认是.分割
data := make(map[string]any, len(keys))
for _, key := range keys {
data[key] = v.Get(key)
}
// 把viper的数据转化到koanf
err := kof.Load(confmap.Provider(data, "."), nil)
if err != nil {
log.Printf("load viper map err: %s", err)
}
cfg := &Config{
kof: kof,
lock: &sync.RWMutex{}, // 远程更新用到
}
return cfg
}
// Load 加载命令行参数, 以及默认配置文件
func Load() *Config {
parseFlag()
// 命令行参数指定文件 --config 指定配置文件
if flagConfigFile != "" {
LoadFile(flagConfigFile)
} else {
if v := getDefaultConfigFile(); v != "" {
LoadFile(v)
}
}
// 命令行参数后加载, 可以覆盖文件配置
LoadFlag()
return defaultConf
}
// LoadFlag 加载命令行参数
func LoadFlag() {
parseFlag()
err := defaultConf.kof.Load(posflag.Provider(pflag.CommandLine, ".", defaultConf.kof), nil)
//err := defaultConf.kof.BindPFlags(pflag.CommandLine)
if err != nil { if err != nil {
panic(fmt.Errorf("load command line fail: %s", err)) panic(fmt.Errorf("load command line fail: %s", err))
} }
}
// 指定文件加载 // 如果有环境变量定义, 则覆盖掉
func LoadFile(confFile string) { if v := os.Getenv(envConfigFile); v != "" {
log.Printf("read conf file: %s", confFile) flagConfigFile = v
conf.SetConfigFile(confFile)
err := conf.ReadInConfig()
if err != nil {
panic(fmt.Errorf("read file fail: %s", err))
} }
} }
func Conf() *Config { // 解析命令行参数
return conf func parseFlag() {
if pflag.Parsed() {
return
}
pflag.Parse()
}
// LoadFile 加载指定的文件
func LoadFile(configFile string) *Config {
return defaultConf.LoadFile(configFile)
}
func getDefaultConfigFile() string {
var cf = ""
// 兼容yaml, yml
for _, v := range defaultConfigFile {
if mycommon.ExistFile(v) {
cf = v
break
}
}
return cf
}
func (c *Config) LoadFile(configFile string) *Config {
c.ConfigFile = configFile
log.Printf("read local config file: %s", configFile)
err := c.kof.Load(kfile.Provider(configFile), GetKoanfParserByFileExt(configFile), c.koanfOpt...)
if err != nil {
panic(fmt.Errorf("read file fail: %s", err))
}
return c
}
func (c *Config) All() map[string]any {
c.RLock()
defer c.RUnLock()
return c.kof.All()
}
func (c *Config) Raw() map[string]any {
c.RLock()
defer c.RUnLock()
return c.kof.Raw()
}
func (c *Config) Get(key string) any {
c.RLock()
defer c.RUnLock()
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 {
c.RLock()
defer c.RUnLock()
return c.kof.String(key)
}
func (c *Config) GetInt(key string) int {
c.RLock()
defer c.RUnLock()
return c.kof.Int(key)
}
func (c *Config) GetInt64(key string) int64 {
c.RLock()
defer c.RUnLock()
return c.kof.Int64(key)
}
func (c *Config) GetBool(key string) bool {
c.RLock()
defer c.RUnLock()
return c.kof.Bool(key)
}
func (c *Config) GetStringMap(key string) map[string]string {
c.RLock()
defer c.RUnLock()
return c.kof.StringMap(key)
}
func (c *Config) GetStringsMap(key string) map[string][]string {
c.RLock()
defer c.RUnLock()
return c.kof.StringsMap(key)
}
func (c *Config) GetMap(key string) map[string]any {
c.RLock()
defer c.RUnLock()
return cast.ToStringMap(c.kof.Get(key))
}
func (c *Config) GetIntMap(key string) map[string]int {
c.RLock()
defer c.RUnLock()
return c.kof.IntMap(key)
}
func (c *Config) GetStringSlice(key string) []string {
c.RLock()
defer c.RUnLock()
return c.kof.Strings(key)
}
func (c *Config) GetIntSlice(key string) []int {
c.RLock()
defer c.RUnLock()
return c.kof.Ints(key)
}
func (c *Config) WithRead(fn func(k *koanf.Koanf)) {
c.RLock()
defer c.RUnLock()
fn(c.kof)
}
func (c *Config) WithDo(fn func(k *koanf.Koanf)) {
c.Lock()
defer c.UnLock()
fn(c.kof)
}
// Sub 获取子路径的配置
func (c *Config) Sub(key string) *Config {
c.RLock()
defer c.RUnLock()
newCfg := &Config{
InstanceName: fmt.Sprintf("%s.%s", c.InstanceName, key),
kof: c.kof.Cut(key),
lock: &sync.RWMutex{},
}
return newCfg
}
func (c *Config) UnmarshalKey(key string, toVal interface{}) error {
c.RLock()
defer c.RUnLock()
return c.kof.Unmarshal(key, toVal)
}
func (c *Config) Unmarshal(toVal interface{}) error {
c.RLock()
defer c.RUnLock()
return c.kof.Unmarshal("", &toVal)
}
func (c *Config) Set(key string, value interface{}) {
c.Lock()
defer c.UnLock()
c.kof.Set(key, value)
} }
func (c *Config) GetStringDefault(key, defaultVal string) string { func (c *Config) GetStringDefault(key, defaultVal string) string {
v := c.GetString(key) c.RLock()
defer c.RUnLock()
v := c.kof.String(key)
if v == "" { if v == "" {
return defaultVal return defaultVal
} }
@@ -50,17 +351,68 @@ func (c *Config) GetStringDefault(key, defaultVal string) string {
} }
func (c *Config) GetIntDefault(key string, defaultVal int) int { func (c *Config) GetIntDefault(key string, defaultVal int) int {
v := c.GetString(key) // 未设置 空字符串 c.RLock()
defer c.RUnLock()
v := c.kof.String(key) // 未设置 空字符串
if v == "" { if v == "" {
return defaultVal return defaultVal
} }
return c.GetInt(key) return c.kof.Int(key)
} }
func (c *Config) GetBoolDefault(key string, defaultVal bool) bool { func (c *Config) GetBoolDefault(key string, defaultVal bool) bool {
v := c.GetString(key) // 未设置 空字符串 c.RLock()
defer c.RUnLock()
v := c.kof.String(key) // 未设置 空字符串
if v == "" { if v == "" {
return defaultVal return defaultVal
} }
return c.GetBool(key) return c.kof.Bool(key)
}
func (c *Config) RLock() {
if c.lock != nil {
c.lock.RLock()
}
}
func (c *Config) RUnLock() {
if c.lock != nil {
c.lock.RUnlock()
}
}
func (c *Config) Lock() {
if c.lock != nil {
c.lock.Lock()
}
}
func (c *Config) UnLock() {
if c.lock != nil {
c.lock.Unlock()
}
}
// GetKoanfParserByFileExt 根据文件后缀文件类型 .json .yaml .toml 获取 parser
func GetKoanfParserByFileExt(configFile string) koanf.Parser {
ext := strings.TrimLeft(filepath.Ext(configFile), ".")
ext = strings.ToLower(ext)
switch ext {
case "yaml", "yml":
return pyaml.Parser()
case "json":
return pjson.Parser()
case "toml":
return ptoml.Parser()
default:
return pyaml.Parser()
}
} }

View File

@@ -1,8 +0,0 @@
App:
Name: "server"
Addr: 127.0.0.1
Port: 9000
Redis:
Addr: "127.0.0.1:6379"
Db: 15

19
mygrpc/error.go Normal file
View File

@@ -0,0 +1,19 @@
package mygrpc
import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
const (
ServerTimeoutCode codes.Code = 10000
ClientTimeoutCode codes.Code = 10001
)
func GrpcClientTimeout(format string, args ...interface{}) error {
return status.Newf(ClientTimeoutCode, format, args).Err()
}
func GrpcServerTimeout(format string, args ...interface{}) error {
return status.Newf(ServerTimeoutCode, format, args...).Err()
}

46
mygrpc/grpc.go Normal file
View File

@@ -0,0 +1,46 @@
package mygrpc
import (
"context"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer"
"net"
)
const (
DefaultReadBufferSize = 256 * 1024 // 256kb
DefaultWriteBufferSize = 256 * 1024
// DefaultWindowSize 滑动窗口 16mb 手动设置滑动窗口大小, 尝试提升吞吐量, 减少动态计算可能导致的cpu波动
DefaultWindowSize = 16 * 1024 * 1024
)
const (
ServicePrefix = "grpc@"
ExposeHttpTag = "expose-http"
)
const (
HeaderClientIP = "grpc-client-ip"
HeaderServiceName = "grpc-service-name"
)
func ClientIP(ctx context.Context) string {
// 先从自定义的header获取
md, ok := metadata.FromIncomingContext(ctx)
if ok {
if v := md.Get(HeaderClientIP); len(v) > 0 {
return v[0]
}
}
p, ok := peer.FromContext(ctx)
if ok {
switch v := p.Addr.(type) {
case *net.TCPAddr:
return v.IP.String()
}
}
return ""
}

155
mygrpc/grpcc/client.go Normal file
View File

@@ -0,0 +1,155 @@
package grpcc
import (
"context"
"fmt"
"git.makemake.in/kzkzzzz/mycommon/myconf"
"git.makemake.in/kzkzzzz/mycommon/mygrpc"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/keepalive"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"log"
"time"
)
type ClientConf struct {
useDefaultBufferCfg bool
conf *myconf.Config
grpcOpts []grpc.DialOption
unaryMiddlewares []grpc.UnaryClientInterceptor
}
type Opt func(*ClientConf)
func UseDefaultBufferCfg(v bool) Opt {
return func(c *ClientConf) {
c.useDefaultBufferCfg = v
}
}
func WithConf(v *myconf.Config) Opt {
return func(c *ClientConf) {
c.conf = v
}
}
func WithGrpcOpts(v ...grpc.DialOption) Opt {
return func(c *ClientConf) {
c.grpcOpts = v
}
}
func WithUnaryMiddlewares(v ...grpc.UnaryClientInterceptor) Opt {
return func(c *ClientConf) {
c.unaryMiddlewares = append(c.unaryMiddlewares, v...)
}
}
func MustNew(grpcUrl string, opts ...Opt) *grpc.ClientConn {
client, err := New(grpcUrl, opts...)
if err != nil {
panic(err)
}
return client
}
func New(grpcUrl string, opts ...Opt) (*grpc.ClientConn, error) {
log.Printf("new grpc client url: %s", grpcUrl)
c := &ClientConf{
useDefaultBufferCfg: true,
unaryMiddlewares: []grpc.UnaryClientInterceptor{WrapRequestError()}, // 默认加上错误包装
}
for _, opt := range opts {
opt(c)
}
dialOpts := []grpc.DialOption{
grpc.WithKeepaliveParams(keepalive.ClientParameters{
Time: time.Second * 20, // 如果没有activity,则每隔N s发送一个ping包
Timeout: time.Second * 5, // 如果ping ack N s之内未返回则认为连接已断开
PermitWithoutStream: true, // 如果没有active的stream,是否允许发送ping
}),
// 参考 https://github.com/grpc/grpc-go/tree/master/examples/features/load_balancing 设置轮训策略
grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`), // This sets the initial balancing policy.
grpc.WithTransportCredentials(insecure.NewCredentials()),
}
// 使用默认的buffer调整配置
if c.useDefaultBufferCfg {
dialOpts = append(dialOpts,
grpc.WithInitialWindowSize(mygrpc.DefaultWindowSize),
grpc.WithInitialConnWindowSize(mygrpc.DefaultWindowSize),
grpc.WithReadBufferSize(mygrpc.DefaultReadBufferSize),
grpc.WithWriteBufferSize(mygrpc.DefaultWriteBufferSize),
)
}
if len(c.unaryMiddlewares) > 0 {
dialOpts = append(dialOpts, grpc.WithChainUnaryInterceptor(c.unaryMiddlewares...))
}
if len(c.grpcOpts) > 0 {
dialOpts = append(dialOpts, c.grpcOpts...)
}
conn, err := grpc.NewClient(
grpcUrl,
dialOpts...,
)
if err != nil {
return nil, err
}
return conn, nil
}
func WrapRequestError() grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
// 提取服务名称
var serviceName string
if md, ok := metadata.FromOutgoingContext(ctx); ok {
if v := md.Get(mygrpc.HeaderServiceName); len(v) > 0 {
serviceName = v[0]
}
}
err := invoker(ctx, method, req, reply, cc, opts...)
if err != nil {
st, ok := status.FromError(err)
if ok && serviceName != "" {
sp := st.Proto()
sp.Message = fmt.Sprintf("[%s] - %s", serviceName, sp.Message)
return status.ErrorProto(sp)
}
return err
}
return nil
}
}
// Timeout 客户端超时
func Timeout(timeout time.Duration) grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
tCtx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
err := invoker(tCtx, method, req, reply, cc, opts...)
if v, ok := status.FromError(err); ok && v.Code() == codes.DeadlineExceeded {
//return status.Errorf(grpcserver.Timeout, "call %s timeout %s", method, timeout)
return mygrpc.GrpcClientTimeout("request timeout: %s", timeout)
}
return err
}
}

373
mygrpc/grpcsr/server.go Normal file
View File

@@ -0,0 +1,373 @@
package grpcsr
import (
"context"
"fmt"
"git.makemake.in/kzkzzzz/mycommon"
"git.makemake.in/kzkzzzz/mycommon/graceful"
"git.makemake.in/kzkzzzz/mycommon/myconf"
"git.makemake.in/kzkzzzz/mycommon/mygrpc"
"git.makemake.in/kzkzzzz/mycommon/mylog"
"git.makemake.in/kzkzzzz/mycommon/myregistry"
"github.com/spf13/pflag"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/health"
"google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/keepalive"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/reflection"
"google.golang.org/grpc/status"
"log"
"net"
"runtime/debug"
"time"
)
//const DefaultInstanceName = "grpc"
var _ graceful.IRunner = (*Server)(nil)
type Conf struct {
Addr string
Port int
Ip string
Log bool
}
type Opt func(server *Server)
type Server struct {
gs *grpc.Server
serviceAddr string
serviceId string
serviceName string
serverConf *Conf
reg myregistry.IRegister
grpcOpts []grpc.ServerOption
logger mylog.ILogger
registerGrpcFn func(*grpc.Server) // 注册grpc服务, 使用函数延迟调用, 便于先初始化中间件等操作
unaryMiddlewares []grpc.UnaryServerInterceptor // grpc一元服务端中间件
useDefaultBufferCfg bool
delayStopMs int
exposeHttp bool
serviceRegInfo *myregistry.ServiceInfo
}
func UseDefaultBufferCfg(v bool) Opt {
return func(server *Server) {
server.useDefaultBufferCfg = v
}
}
func WithRegistry(serviceName string, reg myregistry.IRegister) Opt {
return func(server *Server) {
server.serviceName = mygrpc.ServicePrefix + serviceName
server.reg = reg
}
}
func WithGrpcOpts(v ...grpc.ServerOption) Opt {
return func(server *Server) {
server.grpcOpts = v
}
}
func WithDelayStopMs(v int) Opt {
return func(server *Server) {
server.delayStopMs = v
}
}
func WithExposeHttp(v bool) Opt {
return func(server *Server) {
server.exposeHttp = v
}
}
func SetFlag() {
pflag.Int("grpc.port", 0, "listen port, 0 is random port")
pflag.String("grpc.log", "true", "enable request log")
}
func New(cfg *myconf.Config, opts ...Opt) *Server {
cf := &Conf{}
err := cfg.UnmarshalKey("grpc", cf)
if err != nil {
panic(err)
}
// 命令行的参数覆盖一次, Unmarshal解析的时候, 不会用命令行的参数覆盖 https://github.com/spf13/viper/issues/190
cf.Port = cfg.GetInt(fmt.Sprintf("grpc.port"))
cf.Log = cfg.GetBool(fmt.Sprintf("grpc.log"))
return NewByConf(cf, opts...)
}
func NewByConf(conf *Conf, opts ...Opt) *Server {
s := &Server{
serverConf: conf,
useDefaultBufferCfg: true,
}
for _, opt := range opts {
opt(s)
}
if s.logger == nil {
s.logger = mylog.GetLoggerSkip(-1)
}
if s.reg != nil && s.serviceName == "" {
panic("service name is empty")
}
s.unaryMiddlewares = []grpc.UnaryServerInterceptor{
s.grpcRecover(), // 默认启用recover中间件
}
if s.serverConf.Log {
s.unaryMiddlewares = append(s.unaryMiddlewares, s.requestLog())
}
return s
}
func (s *Server) Use(middlewares ...grpc.UnaryServerInterceptor) {
s.unaryMiddlewares = append(s.unaryMiddlewares, middlewares...)
}
func (s *Server) RegisterGrpc(fn func(*grpc.Server)) {
s.registerGrpcFn = fn
}
func (s *Server) initServer() {
grpcOpts := []grpc.ServerOption{
grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
MinTime: time.Second * 5, // 如果客户端两次 ping 的间隔小于 N则关闭连接
PermitWithoutStream: true, // 即使没有 active stream, 也允许 ping
}),
grpc.KeepaliveParams(keepalive.ServerParameters{
MaxConnectionIdle: time.Minute * 15, // 空闲连接时间
MaxConnectionAgeGrace: time.Second * 30, // 在强制关闭连接之间, 允许有 N 的时间完成 pending 的 rpc 请求
Time: time.Second * 20, // 如果一个连接空闲超过 N, 则发送一个 ping 请求
Timeout: time.Second * 5, // 如果 ping 请求 N 内未收到回复, 则认为该连接已断开
}),
}
if s.useDefaultBufferCfg {
grpcOpts = append(grpcOpts,
grpc.InitialWindowSize(mygrpc.DefaultWindowSize),
grpc.InitialConnWindowSize(mygrpc.DefaultWindowSize),
grpc.ReadBufferSize(mygrpc.DefaultReadBufferSize),
grpc.WriteBufferSize(mygrpc.DefaultWriteBufferSize),
)
}
if len(s.unaryMiddlewares) > 0 {
grpcOpts = append(grpcOpts, grpc.ChainUnaryInterceptor(s.unaryMiddlewares...))
}
if len(s.grpcOpts) > 0 {
grpcOpts = append(grpcOpts, s.grpcOpts...)
}
s.gs = grpc.NewServer(grpcOpts...)
// 注册grpc服务
s.registerGrpcFn(s.gs)
}
func (s *Server) Run(ctx context.Context) error {
s.initServer()
// 端口如果=0, 监听随机端口
addr0 := fmt.Sprintf("%s:%d", s.serverConf.Addr, s.serverConf.Port)
lis, err := net.Listen("tcp", addr0)
if err != nil {
return err
}
// 获取监听的端口
port := lis.Addr().(*net.TCPAddr).Port
// 健康服务
healthServer := health.NewServer()
grpc_health_v1.RegisterHealthServer(s.gs, healthServer)
// 服务反射, 方便调试
reflection.Register(s.gs)
var svcIp = s.serverConf.Ip
if svcIp == "" {
svcIp = mycommon.GetOutboundIP()
}
// 注册服务
if s.reg != nil {
s.serviceRegInfo = &myregistry.ServiceInfo{
ServiceName: s.serviceName,
Ip: svcIp,
Port: port,
Extend: map[string]string{},
}
if s.exposeHttp {
s.serviceRegInfo.Extend["tag"] = mygrpc.ExposeHttpTag
}
err = s.reg.Register(s.serviceRegInfo)
if err != nil {
return err
}
log.Printf("[%s] register service: %s - %s:%d",
s.reg.Name(),
s.serviceRegInfo.ServiceName,
s.serviceRegInfo.Ip, s.serviceRegInfo.Port,
)
}
addr := fmt.Sprintf("%s:%d", s.serverConf.Addr, port)
log.Printf("grpc server listen on %s", addr)
s.serviceAddr = fmt.Sprintf("%s:%d", svcIp, port)
err = s.gs.Serve(lis)
if err != nil {
log.Printf("start grpc server err: %s", err)
return err
}
return nil
}
func (s *Server) Stop() {
if s.reg != nil {
err := s.reg.Deregister(s.serviceRegInfo)
if err != nil {
s.logger.Errorf("grpc server deregister err: %s", err)
}
log.Printf("[%s] deregister service: %s - %s:%d",
s.reg.Name(),
s.serviceRegInfo.ServiceName,
s.serviceRegInfo.Ip, s.serviceRegInfo.Port,
)
}
// 如果使用k8s service, 关闭pod和往service注销ip是同时进行的, 如果退出服务比注销ip先完成, 可能有流量继续进来, 导致请求失败
// 延迟一段时间, 确保服务已经注销ip, 再关闭服务
// 如何使用注册中心, 先从中心退出ip, 也延迟一段时间, 等上游网关更新ip完成(正常不会太久), 不会有流量进来旧服务, 再退出服务
if s.delayStopMs > 0 {
delayTime := time.Millisecond * time.Duration(s.delayStopMs)
log.Printf("grpc server delay stop: %s", delayTime)
time.Sleep(delayTime)
}
s.gs.GracefulStop()
log.Printf("grpc server stop")
}
type handleResp struct {
resp interface{}
err error
}
func (s *Server) requestLog() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
start := time.Now()
resp, err := handler(ctx, req)
var (
code codes.Code
codeMsg = "OK"
)
if err != nil {
fromError, ok := status.FromError(err)
if ok {
code = fromError.Code()
} else {
code = status.New(codes.Unknown, err.Error()).Code()
}
codeMsg = fmt.Sprintf("%s(%d)", code.String(), uint32(code))
}
s.logger.Infof(
"%s - %s - %s - %s",
codeMsg, time.Since(start), mygrpc.ClientIP(ctx), info.FullMethod,
)
return resp, err
}
}
func (s *Server) grpcRecover() grpc.UnaryServerInterceptor {
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() {
if err0 := recover(); err0 != nil {
log.Printf("%s - panic: %v\n%s", info.FullMethod, err0, debug.Stack())
err = fmt.Errorf("server err %s - %s", info.FullMethod, err0)
}
}()
res, err := handler(ctx, req)
if err != nil {
return nil, err
}
return res, nil
}
}
// Timeout 控制服务端超时
func Timeout(timeout time.Duration) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
tCtx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
ch := make(chan *handleResp, 1)
go func() {
defer func() {
if err0 := recover(); err0 != nil {
log.Printf("%s - panic: %v\n%s", info.FullMethod, err0, debug.Stack())
ch <- &handleResp{nil, fmt.Errorf("server err: %s - system err: %s", info.FullMethod, err0)}
}
}()
//start := time.Now()
r := &handleResp{}
r.resp, r.err = handler(tCtx, req)
//log.Printf("rta server time: %s", time.Since(start))
ch <- r
}()
select {
case <-tCtx.Done():
return nil, mygrpc.GrpcServerTimeout("server err: grpc handle timeout %s %s", timeout, info.FullMethod)
//return nil, status.Errorf(codes.DeadlineExceeded, "grpc handle timeout %s %s", timeout, info.FullMethod)
case res := <-ch:
return res.resp, res.err
}
//return nil, fmt.Errorf("handle err %s.%s", info.Server, info.FullMethod)
}
}

View File

@@ -0,0 +1,80 @@
/*
*
* Copyright 2017 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// Package roundrobin defines a roundrobin balancer. Roundrobin balancer is
// installed as one of the default balancers in gRPC, users don't need to
// explicitly install this balancer.
package random_balancer
import (
"google.golang.org/grpc/balancer"
"google.golang.org/grpc/balancer/base"
"google.golang.org/grpc/grpclog"
"log"
"math/rand"
)
// Name is the name of round_robin balancer.
const Name = "my_random_robin"
var logger = grpclog.Component("myrandomrobin")
// newBuilder creates a new roundrobin balancer builder.
func newBuilder() balancer.Builder {
return base.NewBalancerBuilder(Name, &randomPickerBuilder{}, base.Config{HealthCheck: true})
}
func init() {
balancer.Register(newBuilder())
}
type randomPickerBuilder struct{}
type subConnInfo struct {
conn balancer.SubConn
connInfo base.SubConnInfo
}
func (r *randomPickerBuilder) Build(info base.PickerBuildInfo) balancer.Picker {
logger.Infof("myrandomrobin Picker: Build called with info: %v", info)
log.Printf("myrandomrobin Picker: Build called with info: %v", info)
if len(info.ReadySCs) == 0 {
return base.NewErrPicker(balancer.ErrNoSubConnAvailable)
}
scs := make([]*subConnInfo, 0, len(info.ReadySCs))
for sc, scInfo := range info.ReadySCs {
scs = append(scs, &subConnInfo{
conn: sc,
connInfo: scInfo,
})
}
return &randomPicker{
subConns: scs,
}
}
type randomPicker struct {
subConns []*subConnInfo
}
func (p *randomPicker) Pick(balancer.PickInfo) (balancer.PickResult, error) {
sc := p.subConns[rand.Intn(len(p.subConns))]
log.Printf("randomPicker Pick: SubConn: %s", sc.connInfo.Address.String())
return balancer.PickResult{SubConn: sc.conn}, nil
}

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

78
myhttp/httpsr/pprof.go Normal file
View File

@@ -0,0 +1,78 @@
package httpsr
import (
"context"
"fmt"
"git.makemake.in/kzkzzzz/mycommon/graceful"
"github.com/gin-contrib/pprof"
"github.com/gin-gonic/gin"
"log"
"net"
"net/http"
"time"
)
var _ graceful.IRunner = (*PProf)(nil)
type PProf struct {
conf *pprofConf
hs *http.Server
}
type pprofConf struct {
Port int
}
type PProfOpt func(*pprofConf)
func NewPProf(opts ...PProfOpt) *PProf {
engine := gin.Default()
pprof.Register(engine)
p := &PProf{
conf: &pprofConf{},
hs: &http.Server{
Handler: engine,
},
}
for _, opt := range opts {
opt(p.conf)
}
return p
}
func (p *PProf) Run(ctx context.Context) error {
// 端口如果=0, 监听随机端口
addr0 := fmt.Sprintf(":%d", p.conf.Port)
lis, err := net.Listen("tcp", addr0)
if err != nil {
return err
}
// 获取监听的端口
port := lis.Addr().(*net.TCPAddr).Port
addr := fmt.Sprintf(":%d", port)
log.Printf("http pprof server listen on %s", addr)
p.hs.Addr = addr
err = p.hs.Serve(lis)
if err != nil {
log.Printf("start http pprof server err: %s", err)
return err
}
return nil
}
func (p *PProf) Stop() {
tCtx, tCancel := context.WithTimeout(context.Background(), time.Millisecond*100)
defer tCancel()
p.hs.Shutdown(tCtx)
}
func PProfPort(v int) PProfOpt {
return func(p *pprofConf) {
p.Port = v
}
}

252
myhttp/httpsr/server.go Normal file
View File

@@ -0,0 +1,252 @@
package httpsr
import (
"context"
"fmt"
"git.makemake.in/kzkzzzz/mycommon"
"git.makemake.in/kzkzzzz/mycommon/graceful"
"git.makemake.in/kzkzzzz/mycommon/myconf"
"git.makemake.in/kzkzzzz/mycommon/myhttp"
"git.makemake.in/kzkzzzz/mycommon/mylog"
"git.makemake.in/kzkzzzz/mycommon/myregistry"
"github.com/gin-gonic/gin"
"github.com/spf13/pflag"
"log"
"net"
"net/http"
"runtime/debug"
"time"
)
var _ graceful.IRunner = (*Server)(nil)
type Conf struct {
Addr string
Port int
Ip string
Log bool
ReadTimeoutMs int
WriteTimeoutMs int
IdleTimeoutMs int
ShutdownTimeoutMs int
}
type Opt func(server *Server)
type Server struct {
serviceId string
serviceName string
serverConf *Conf
reg myregistry.IRegister
logger mylog.ILogger
useDefaultBufferCfg bool
delayStopMs int
serviceRegInfo *myregistry.ServiceInfo
hs *http.Server
ginEngine *gin.Engine
shutdownTimeout time.Duration
}
func WithRegistry(serviceName string, reg myregistry.IRegister) Opt {
return func(server *Server) {
server.serviceName = myhttp.ServicePrefix + serviceName
server.reg = reg
}
}
func WithDelayStopMs(v int) Opt {
return func(server *Server) {
server.delayStopMs = v
}
}
func SetFlag() {
pflag.Int("http.port", 0, "listen port, 0 is random port")
pflag.String("http.log", "true", "enable request log")
}
func New(cfg *myconf.Config, opts ...Opt) *Server {
cf := &Conf{}
err := cfg.UnmarshalKey("http", cf)
if err != nil {
panic(err)
}
// 命令行的参数覆盖一次, Unmarshal解析的时候, 不会用命令行的参数覆盖 https://github.com/spf13/viper/issues/190
cf.Port = cfg.GetInt(fmt.Sprintf("http.port"))
cf.Log = cfg.GetBool(fmt.Sprintf("http.log"))
return NewByConf(cf, opts...)
}
func NewByConf(conf *Conf, opts ...Opt) *Server {
s := &Server{
serverConf: conf,
useDefaultBufferCfg: true,
}
for _, opt := range opts {
opt(s)
}
if s.logger == nil {
s.logger = mylog.GetLoggerSkip(-1)
}
if s.reg != nil && s.serviceName == "" {
panic("service name is empty")
}
s.ginEngine = gin.New()
if s.serverConf.Log {
//s.unaryMiddlewares = append(s.unaryMiddlewares, s.requestLog())
s.ginEngine.Use(s.requestLog())
}
s.ginEngine.Use(s.httpRecover())
s.ginEngine.GET("/health", func(ctx *gin.Context) {
ctx.String(http.StatusOK, "ok")
})
s.hs = &http.Server{
Handler: s.ginEngine,
ReadTimeout: time.Second * 5,
WriteTimeout: time.Second * 10,
IdleTimeout: time.Second * 180,
}
s.shutdownTimeout = time.Millisecond * 200
if conf.ReadTimeoutMs > 0 {
s.hs.ReadTimeout = time.Millisecond * time.Duration(conf.ReadTimeoutMs)
}
if conf.WriteTimeoutMs > 0 {
s.hs.WriteTimeout = time.Millisecond * time.Duration(conf.WriteTimeoutMs)
}
if conf.IdleTimeoutMs > 0 {
s.hs.IdleTimeout = time.Millisecond * time.Duration(conf.IdleTimeoutMs)
}
if conf.ShutdownTimeoutMs > 0 {
s.shutdownTimeout = time.Millisecond * time.Duration(conf.ShutdownTimeoutMs)
}
return s
}
func (s *Server) Engine() *gin.Engine {
return s.ginEngine
}
func (s *Server) initServer() {
}
func (s *Server) Run(ctx context.Context) error {
s.initServer()
// 端口如果=0, 监听随机端口
addr0 := fmt.Sprintf("%s:%d", s.serverConf.Addr, s.serverConf.Port)
lis, err := net.Listen("tcp", addr0)
if err != nil {
return err
}
// 获取监听的端口
port := lis.Addr().(*net.TCPAddr).Port
var svcIp = s.serverConf.Ip
if svcIp == "" {
svcIp = mycommon.GetOutboundIP()
}
// 注册服务
if s.reg != nil {
s.serviceRegInfo = &myregistry.ServiceInfo{
ServiceName: s.serviceName,
Ip: svcIp,
Port: port,
}
err = s.reg.Register(s.serviceRegInfo)
if err != nil {
return err
}
}
addr := fmt.Sprintf("%s:%d", s.serverConf.Addr, port)
log.Printf("http server listen on %s", addr)
s.hs.Addr = addr
err = s.hs.Serve(lis)
if err != nil {
log.Printf("start http server err: %s", err)
return err
}
return nil
}
func (s *Server) Stop() {
if s.reg != nil {
err := s.reg.Deregister(s.serviceRegInfo)
if err != nil {
s.logger.Errorf("http server deregister err: %s", err)
}
}
// 如果使用k8s service, 关闭pod和往service注销ip是同时进行的, 如果退出服务比注销ip先完成, 可能有流量继续进来, 导致请求失败
// 延迟一段时间, 确保服务已经注销ip, 再关闭服务
// 如何使用注册中心, 先从中心退出ip, 也延迟一段时间, 等上游网关更新ip完成(正常不会太久), 不会有流量进来旧服务, 再退出服务
if s.delayStopMs > 0 {
delayTime := time.Millisecond * time.Duration(s.delayStopMs)
log.Printf("http server delay stop: %s", delayTime)
time.Sleep(delayTime)
}
tCtx, tCancel := context.WithTimeout(context.Background(), s.shutdownTimeout)
defer tCancel()
s.hs.Shutdown(tCtx)
log.Printf("http server stop")
}
func (s *Server) httpRecover() gin.HandlerFunc {
return func(ctx *gin.Context) {
defer func() {
if err := recover(); err != nil {
log.Printf("%s - panic: %v\n%s", ctx.Request.RequestURI, err, debug.Stack())
ctx.JSON(http.StatusOK, gin.H{
"code": 1,
"message": fmt.Sprintf("server err: %s", err),
})
}
}()
ctx.Next()
}
}
func (s *Server) requestLog() gin.HandlerFunc {
return func(ctx *gin.Context) {
start := time.Now()
ctx.Next()
s.logger.Infof(
"%s - %d - %s - %s - %s",
ctx.Request.Method, ctx.Writer.Status(), time.Since(start),
ctx.ClientIP(), ctx.Request.RequestURI,
)
}
}

5
myhttp/myhttp.go Normal file
View File

@@ -0,0 +1,5 @@
package myhttp
const (
ServicePrefix = "http@"
)

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.

View File

@@ -3,13 +3,18 @@ package mylog
import ( import (
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
"io" "io"
"os" "os"
"path/filepath"
"strings" "strings"
) )
type ILogger interface {
Debugf(format string, v ...any)
Infof(format string, v ...any)
Warnf(format string, v ...any)
Errorf(format string, v ...any)
}
const ( const (
DebugLevel = "DEBUG" DebugLevel = "DEBUG"
InfoLevel = "INFO" InfoLevel = "INFO"
@@ -26,6 +31,9 @@ var (
"ERROR": zapcore.ErrorLevel, "ERROR": zapcore.ErrorLevel,
"FATAL": zapcore.FatalLevel, "FATAL": zapcore.FatalLevel,
} }
defaultLogLevel = DebugLevel
// DefaultConfig 默认配置 // DefaultConfig 默认配置
DefaultConfig = &Config{ DefaultConfig = &Config{
Level: DebugLevel, Level: DebugLevel,
@@ -33,13 +41,7 @@ var (
ConsoleWriter: os.Stdout, ConsoleWriter: os.Stdout,
} }
DefaultLogFile = &LogFile{ globalLog = NewLogger(DefaultConfig)
LogFilePath: "logs",
MaxSize: 200,
MaxAge: 0,
MaxBackups: 0,
}
globalLog = NewLogger("debug", DefaultConfig)
) )
type ( type (
@@ -63,12 +65,24 @@ type (
} }
) )
// Init 覆盖默认日志 func SetLogLevel(level string) {
func Init(serverName string, config *Config) { defaultLogLevel = level
globalLog = NewLogger(serverName, config)
} }
func NewLogger(serverName string, config *Config) *ZapLog { func Init() {
globalLog = NewLogger(&Config{
Level: defaultLogLevel,
NeedLogFile: false,
ConsoleWriter: os.Stdout,
})
}
// InitWithConfig 覆盖默认日志
func InitWithConfig(config *Config) {
globalLog = NewLogger(config)
}
func NewLogger(config *Config) *ZapLog {
if config == nil { if config == nil {
config = DefaultConfig config = DefaultConfig
} }
@@ -81,29 +95,15 @@ func NewLogger(serverName string, config *Config) *ZapLog {
cores := make([]zapcore.Core, 0) cores := make([]zapcore.Core, 0)
// 使用控制台输出 // 使用控制台输出
if config.ConsoleWriter != nil { cfg := zap.NewProductionEncoderConfig()
cfg := zap.NewProductionEncoderConfig() cfg.EncodeLevel = zapcore.CapitalColorLevelEncoder
cfg.EncodeLevel = zapcore.CapitalColorLevelEncoder cfg.ConsoleSeparator = " | "
cfg.ConsoleSeparator = " | " // 指定日志时间格式
// 指定日志时间格式 cfg.EncodeTime = zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05.000")
cfg.EncodeTime = zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05.000") cfg.EncodeCaller = zapcore.ShortCallerEncoder
cfg.EncodeCaller = zapcore.ShortCallerEncoder encoder := zapcore.NewConsoleEncoder(cfg)
encoder := zapcore.NewConsoleEncoder(cfg) core := zapcore.NewCore(encoder, zapcore.AddSync(config.ConsoleWriter), level)
core := zapcore.NewCore(encoder, zapcore.AddSync(config.ConsoleWriter), level) cores = append(cores, core)
cores = append(cores, core)
}
if config.NeedLogFile {
cfg := zap.NewProductionEncoderConfig()
cfg.EncodeLevel = zapcore.CapitalLevelEncoder
cfg.ConsoleSeparator = " | "
// 指定日志时间格式
cfg.EncodeTime = zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05.000")
cfg.EncodeCaller = zapcore.ShortCallerEncoder
encoder := zapcore.NewConsoleEncoder(cfg)
core := zapcore.NewCore(encoder, zapcore.AddSync(getRollingFileWriter(serverName, config)), level)
cores = append(cores, core)
}
opts := make([]zap.Option, 0) opts := make([]zap.Option, 0)
if config.ZapOpt != nil { if config.ZapOpt != nil {
@@ -119,21 +119,6 @@ func NewLogger(serverName string, config *Config) *ZapLog {
} }
} }
func getRollingFileWriter(serverName string, config *Config) *lumberjack.Logger {
if config.LogFile == nil {
config.LogFile = DefaultLogFile
}
return &lumberjack.Logger{
Filename: filepath.Join(config.LogFile.LogFilePath, serverName+".log"),
MaxSize: config.LogFile.MaxSize,
MaxAge: config.LogFile.MaxAge,
MaxBackups: config.LogFile.MaxBackups,
LocalTime: true,
Compress: false,
}
}
func (z *ZapLog) Debug(args ...interface{}) { func (z *ZapLog) Debug(args ...interface{}) {
z.sugarLog.Debug(args...) z.sugarLog.Debug(args...)
} }
@@ -238,6 +223,16 @@ func GetLogger() *ZapLog {
return globalLog return globalLog
} }
func GetLoggerSkip(callSkip int) *ZapLog {
sg := globalLog.sugarLog.WithOptions(
zap.AddCallerSkip(callSkip),
)
return &ZapLog{
sugarLog: sg,
}
}
func Flush() { func Flush() {
globalLog.Sync() globalLog.Sync()
} }

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,150 +1,203 @@
package mymysql package mymysql
import ( import (
"database/sql"
"fmt" "fmt"
"git.makemake.in/kzkzzzz/mycommon/mylog" "git.makemake.in/kzkzzzz/mycommon/myconf"
driverMysql "github.com/go-sql-driver/mysql"
"github.com/google/uuid"
"gorm.io/driver/mysql" "gorm.io/driver/mysql"
"gorm.io/gorm" "gorm.io/gorm"
gormLogger "gorm.io/gorm/logger" "gorm.io/gorm/logger"
"log"
"os"
"sync"
"time" "time"
) )
const DefaultKey = "default" const (
DefaultInstance = "mysql"
)
type MysqlDb struct {
*gorm.DB
SqlDB *sql.DB
gormConfig *gorm.Config
disablePing bool
}
type Conf struct {
Dsn string
MaxOpenConn int // 最大连接数
MaxIdleConn int // 最大空闲连接数
MaxIdleTime string // 空闲时间
MaxLifeTime string // 连接最大有效时间
Debug bool
LogSqlSlowTimeMs int
LogDisableColor bool
}
var ( var (
DefaultConfig = &Config{ instanceMap = &sync.Map{}
Dsn: "root:root@tcp(127.0.0.1:3306)/?loc=Local&charset=utf8mb4&parseTime=true",
MaxOpenConn: 32,
MaxIdleConn: 8,
MaxLifeTime: "4h",
MaxIdleTime: "15m",
Debug: true,
GormLogger: gormLogger.Default.LogMode(gormLogger.Info),
}
instanceMap = make(map[string]*gorm.DB)
) )
type ( func DB(name ...string) *MysqlDb {
Config struct { var instanceName string
Dsn string if len(name) > 0 {
MaxOpenConn int instanceName = name[0]
MaxIdleConn int
MaxIdleTime string
MaxLifeTime string
Debug bool
GormLogger gormLogger.Interface
}
)
func DB(key ...string) *gorm.DB {
var key0 string
if len(key) > 0 {
key0 = key[0]
} else { } else {
key0 = DefaultKey instanceName = DefaultInstance
} }
instance, ok := instanceMap[key0] v, ok := instanceMap.Load(instanceName)
if !ok { if !ok {
panic(fmt.Errorf("mysql %s not config", key0)) panic(fmt.Errorf("mysql instance [%s] not init", instanceName))
} }
return instance
return v.(*MysqlDb)
} }
func InitDefault(config *Config) { // InitDb 初始化全局默认db
Init(DefaultKey, config) func InitDb(config *myconf.Config, opts ...Opt) {
} client, err := NewDb(DefaultInstance, config, opts...)
func Init(key string, config *Config) {
db, err := New(config)
if err != nil { if err != nil {
panic(err) panic(err)
} }
instanceMap[key] = db instanceMap.Store(DefaultInstance, client)
} }
func New(config *Config) (*gorm.DB, error) { // InitDbInstance 初始化全局的db
var ( func InitDbInstance(instanceName string, config *myconf.Config, opts ...Opt) {
maxLifeTime, _ = time.ParseDuration(DefaultConfig.MaxLifeTime) client, err := NewDb(instanceName, config, opts...)
maxIdleTime, _ = time.ParseDuration(DefaultConfig.MaxIdleTime)
logger gormLogger.Interface
)
if config.MaxOpenConn <= 0 {
config.MaxOpenConn = DefaultConfig.MaxOpenConn
}
if config.MaxIdleConn <= 0 {
config.MaxIdleConn = DefaultConfig.MaxIdleConn
}
if config.MaxLifeTime != "" {
t, err := time.ParseDuration(config.MaxLifeTime)
if err != nil {
return nil, fmt.Errorf("parse MaxLifeTime err: %s\n", err)
}
maxLifeTime = t
}
if config.MaxIdleTime != "" {
t, err := time.ParseDuration(config.MaxIdleTime)
if err != nil {
return nil, fmt.Errorf("parse MaxIdleTime err: %s\n", err)
}
maxIdleTime = t
}
if config.GormLogger == nil {
level := gormLogger.Warn
if config.Debug {
level = gormLogger.Info
}
logger = DefaultGormLogger(level)
}
db, err := gorm.Open(mysql.Open(config.Dsn), &gorm.Config{
SkipDefaultTransaction: true,
Logger: logger,
})
if err != nil { if err != nil {
return nil, fmt.Errorf("connect mysql err: %s", err) panic(err)
} }
sqlDb, _ := db.DB() instanceMap.Store(instanceName, client)
}
sqlDb.SetMaxOpenConns(config.MaxOpenConn) func NewDb(instanceName string, config *myconf.Config, opts ...Opt) (*MysqlDb, error) {
sqlDb.SetMaxIdleConns(config.MaxIdleConn) cf := &Conf{Debug: true}
sqlDb.SetConnMaxLifetime(maxLifeTime) err := config.UnmarshalKey(instanceName, &cf)
sqlDb.SetConnMaxIdleTime(maxIdleTime) if err != nil {
return nil, err
}
db, err := NewDbFromConf(cf, opts...)
if err != nil {
return nil, err
}
instanceMap.Store(instanceName, db)
return db, nil
}
func NewDbFromConf(cf *Conf, opts ...Opt) (*MysqlDb, error) {
parseDsn, err := driverMysql.ParseDSN(cf.Dsn)
if err != nil {
return nil, fmt.Errorf("mysql parse dsn error: %s", err)
}
db := &MysqlDb{}
for _, opt := range opts {
opt(db)
}
if db.gormConfig == nil {
db.gormConfig = &gorm.Config{
SkipDefaultTransaction: true,
}
lCfg := logger.Config{
SlowThreshold: 200 * time.Millisecond,
LogLevel: logger.Warn,
IgnoreRecordNotFoundError: false,
Colorful: true,
}
if cf.LogSqlSlowTimeMs > 0 {
lCfg.SlowThreshold = time.Duration(cf.LogSqlSlowTimeMs) * time.Millisecond
}
if cf.LogDisableColor {
lCfg.Colorful = false
}
l := newGormLogger(lCfg)
if cf.Debug {
db.gormConfig.Logger = l.LogMode(logger.Info)
} else {
db.gormConfig.Logger = l
}
}
gormDB, err := gorm.Open(mysql.Open(cf.Dsn), db.gormConfig)
if err != nil {
return nil, err
}
sqlDB, err := gormDB.DB()
if err != nil {
return nil, err
}
if db.disablePing == false {
err = sqlDB.Ping()
if err != nil {
return nil, err
}
}
if cf.MaxOpenConn <= 0 {
cf.MaxOpenConn = 1024
}
if cf.MaxIdleConn <= 0 {
// 默认最大空闲数等于最大连接数
cf.MaxIdleConn = cf.MaxOpenConn
}
if cf.MaxIdleTime == "" {
cf.MaxIdleTime = "10m"
}
sqlDB.SetMaxOpenConns(cf.MaxOpenConn)
sqlDB.SetMaxIdleConns(cf.MaxIdleConn)
if dv, err := time.ParseDuration(cf.MaxIdleTime); err != nil {
return nil, fmt.Errorf("parse MaxIdleTime err: %s", err)
} else {
sqlDB.SetConnMaxIdleTime(dv)
}
// max life time默认暂不设置, 使用idle time控制即可
if cf.MaxLifeTime != "" {
if dv, err := time.ParseDuration(cf.MaxLifeTime); err != nil {
return nil, fmt.Errorf("parse MaxLifeTime err: %s", err)
} else {
sqlDB.SetConnMaxLifetime(dv)
}
}
db.DB = gormDB
db.SqlDB = sqlDB
instanceMap.Store(uuid.New().String(), db)
log.Printf("connect db success [addr:%s - db:%s]", parseDsn.Addr, parseDsn.DBName)
return db, nil return db, nil
} }
func DefaultGormLogger(level gormLogger.LogLevel) gormLogger.Interface { func CloseAll() {
return gormLogger.New(mylog.NewLogger("gorm", mylog.DefaultConfig), gormLogger.Config{ instanceMap.Range(func(k, v any) bool {
SlowThreshold: time.Second * 2, db, err := (v.(*MysqlDb)).DB.DB()
Colorful: true, if err != nil {
IgnoreRecordNotFoundError: false, db.Close()
ParameterizedQueries: false, }
LogLevel: level, return true
}) })
} }
func NewGormLogger(writer gormLogger.Writer, gormLoggerConfig gormLogger.Config) gormLogger.Interface { func newGormLogger(cfg logger.Config) logger.Interface {
return gormLogger.New(writer, gormLoggerConfig) return logger.New(log.New(os.Stdout, "\r\n", log.LstdFlags), cfg)
}
func CloseDB(key string) {
db, _ := DB(key).DB()
db.Close()
}
func CloseAllDB() {
for _, v := range instanceMap {
db, _ := v.DB()
db.Close()
}
} }

View File

@@ -1,33 +0,0 @@
package mymysql
import (
"fmt"
"testing"
)
func TestMysql(t *testing.T) {
err := InitDefault(&Config{
Dsn: "root:Tqa129126@tcp(119.29.187.200:3306)/site?loc=Local&charset=utf8mb4&writeTimeout=3s&readTimeout=3s&timeout=2s&parseTime=true",
MaxOpenConn: 16,
MaxIdleConn: 4,
MaxIdleTime: "5m",
MaxLifeTime: "30m",
Debug: false,
GormLogger: nil,
})
if err != nil {
fmt.Println(err)
return
}
defer CloseAllDB()
var res = make(map[string]interface{})
err = DB().Table("image").Limit(1).Take(&res).Error
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%+v\n", res)
}

69
mymysql/option.go Normal file
View File

@@ -0,0 +1,69 @@
package mymysql
import (
"gorm.io/gorm"
"gorm.io/gorm/clause"
"time"
)
type Opt func(m *MysqlDb)
func WithDisablePing(v bool) Opt {
return func(m *MysqlDb) {
m.disablePing = v
}
}
func WithGormConfig(v *gorm.Config) Opt {
return func(m *MysqlDb) {
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
}
}

19
myredis/option.go Normal file
View File

@@ -0,0 +1,19 @@
package myredis
import (
"github.com/redis/go-redis/v9"
)
type Opt func(*Client)
func WithRedisOpt(v *redis.Options) Opt {
return func(r *Client) {
r.redisOpt = v
}
}
func WithDisablePing(v bool) Opt {
return func(r *Client) {
r.disablePing = v
}
}

View File

@@ -3,178 +3,245 @@ package myredis
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/go-redis/redis/v8" "git.makemake.in/kzkzzzz/mycommon/myconf"
jsoniter "github.com/json-iterator/go" "github.com/google/uuid"
"github.com/redis/go-redis/v9"
"log"
"sync"
"time" "time"
) )
const DefaultKey = "default" const (
DefaultInstance = "redis"
)
type Client struct {
*redis.Client
redisOpt *redis.Options
disablePing bool
}
//type Conf struct {
// Addr string
// Password string
// Db int
// PoolSize int // 连接池数量 如果不够用会继续新建连接 可以用 MaxActiveConns 限制
// MinIdleConn int // 最小空闲连接数 预热可能用到
// MaxIdleConn int // 最大空闲连接数 0不限制
// MaxActiveConn int // 0 不限制, 如果不限制, 超出pool size可以继续新建立连接, 但使用完之后不会放回连接池
// PoolTimeout string // 等待连接池超时时间 Default is ReadTimeout + 1 second.
// DialTimeout string // 拨号超时时间
// ReadTimeout string // 读取超时
// WriteTimeout string // 写入超时
// MaxRetries int // 重试次数
// ConnMaxIdleTime string // 连接空闲时间
//}
var ( var (
DefaultConfig = &Config{ defaultClient *redis.Client
Addr: "127.0.0.1:6379", instanceMap = &sync.Map{}
Password: "",
DB: 0,
PoolSize: 16,
MinIdleConn: 4,
MaxConnAge: "4h",
IdleTimeout: "15m",
}
instanceMap = make(map[string]*MyRedis)
) )
type ( func GetClient(name ...string) *Client {
MyRedis struct { var instanceName string
*redis.Client if len(name) > 0 {
} instanceName = name[0]
Config struct {
Addr string
Password string
DB int
PoolSize int
MinIdleConn int
MaxConnAge string
IdleTimeout string
}
)
func DB(key ...string) *MyRedis {
var key0 string
if len(key) > 0 {
key0 = key[0]
} else { } else {
key0 = DefaultKey instanceName = DefaultInstance
} }
instance, ok := instanceMap[key0] v, ok := instanceMap.Load(instanceName)
if !ok { if !ok {
panic(fmt.Errorf("redis %s not config", key0)) panic(fmt.Errorf("redis instance [%s] not init", instanceName))
} }
return instance
return v.(*Client)
} }
func InitDefault(config *Config) error { // InitClient 初始化全局默认client
return Init(DefaultKey, config) func InitClient(config *myconf.Config, opts ...Opt) {
} client, err := NewClient(DefaultInstance, config, opts...)
func Init(key string, config *Config) error {
rd, err := New(config)
if err != nil { if err != nil {
return err panic(err)
} }
instanceMap[key] = rd instanceMap.Store(DefaultInstance, client)
}
// InitClientInstance 初始化全局的client
func InitClientInstance(instanceName string, config *myconf.Config, opts ...Opt) {
client, err := NewClient(instanceName, config, opts...)
if err != nil {
panic(err)
}
instanceMap.Store(instanceName, client)
}
func NewClient(instanceName string, config *myconf.Config, opts ...Opt) (*Client, error) {
cf := &Conf{}
err := config.UnmarshalKey(instanceName, &cf)
if err != nil {
return nil, err
}
client, err := NewClientFromConf(cf, opts...)
if err != nil {
return nil, err
}
instanceMap.Store(instanceMap, client)
return client, nil
}
type Conf struct {
Addr string
Password string
Db int
PoolSize int // 连接池数量 如果不够用会继续新建连接 可以用 MaxActiveConns 限制
MinIdleConn int // 最小空闲连接数 预热可能用到
MaxIdleConn int // 最大空闲连接数 0不限制
MaxActiveConn int // 0 不限制, 如果不限制, 超出pool size可以继续新建立连接, 但使用完之后不会放回连接池
DialTimeout string // 拨号超时时间, 时间单位格式 10ms, 60s, 15m, 2h...
ReadTimeout string // 读取超时
WriteTimeout string // 写入超时
ConnMaxIdleTime string // 连接空闲时间
PoolTimeout string // 等待连接池超时时间 Default is ReadTimeout + 1 second.
MaxRetries int // 重试次数
}
func NewClientFromConf(cf *Conf, opts ...Opt) (*Client, error) {
c := &Client{
redisOpt: DefaultRedisOpt(),
}
for _, opt := range opts {
opt(c)
}
err := c.parseConfToRedisOpt(cf)
if err != nil {
return nil, err
}
client := redis.NewClient(c.redisOpt)
if c.disablePing == false {
_, err := client.Ping(context.Background()).Result()
if err != nil {
return nil, fmt.Errorf("redis ping err: %s", err)
}
}
c.Client = client
instanceMap.Store(uuid.New().String(), c)
log.Printf("connect redis success [addr:%s - db:%d]", cf.Addr, cf.Db)
return c, nil
}
func DefaultRedisOpt() *redis.Options {
return &redis.Options{
Addr: "",
Password: "",
DB: 0,
MaxRetries: 3, // 重试次数
DialTimeout: time.Millisecond * 500, // 拨号超时时间
ReadTimeout: time.Second, // 读取超时
WriteTimeout: time.Second * 3, // 写入超时
PoolSize: 1024, // 连接池数量 如果不够用会继续新建连接 可以用 MaxActiveConns 限制
PoolTimeout: time.Second + time.Second, // 等待连接池超时时间 Default is ReadTimeout + 1 second.
MinIdleConns: 0, // 最小空闲连接数 预热可能用到
MaxIdleConns: 0, // 最大空闲连接数 0不限制
MaxActiveConns: 0, // 0 不限制, 如果不限制, 超出pool size可以继续新建立连接, 但使用完之后不会放回连接池
ConnMaxIdleTime: time.Minute * 10, // 连接空闲时间
ContextTimeoutEnabled: true, // context控制超时用到
}
}
func (c *Client) parseTime(v string) (time.Duration, error) {
if v == "" {
return 0, nil
}
d, err := time.ParseDuration(v)
if err != nil {
return 0, fmt.Errorf("parse time %s err: %s", v, err)
}
return d, nil
}
func (c *Client) parseConfToRedisOpt(cf *Conf) error {
c.redisOpt.Addr = cf.Addr
c.redisOpt.Password = cf.Password
c.redisOpt.DB = cf.Db
if v := cf.PoolSize; v > 0 {
c.redisOpt.PoolSize = v
}
if v := cf.MinIdleConn; v > 0 {
c.redisOpt.MinIdleConns = v
}
if v := cf.MaxIdleConn; v > 0 {
c.redisOpt.MaxIdleConns = v
}
if v := cf.MaxActiveConn; v > 0 {
c.redisOpt.MaxActiveConns = v
}
if v := cf.MaxRetries; v > 0 {
c.redisOpt.MaxRetries = v
}
if v, err := c.parseTime(cf.DialTimeout); err != nil {
return err
} else if v > 0 {
c.redisOpt.DialTimeout = v
}
if v, err := c.parseTime(cf.ReadTimeout); err != nil {
return err
} else if v > 0 {
c.redisOpt.ReadTimeout = v
}
if v, err := c.parseTime(cf.WriteTimeout); err != nil {
return err
} else if v > 0 {
c.redisOpt.WriteTimeout = v
}
if v, err := c.parseTime(cf.ConnMaxIdleTime); err != nil {
return err
} else if v > 0 {
c.redisOpt.ConnMaxIdleTime = v
}
if v, err := c.parseTime(cf.PoolTimeout); err != nil {
return err
} else if v > 0 {
c.redisOpt.PoolTimeout = v
}
return nil return nil
} }
func New(config *Config) (*MyRedis, error) { const luaSetOnce = `if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then redis.call('expire',KEYS[1],ARGV[2]) return 1 else return 0 end`
var (
maxConnAge, _ = time.ParseDuration(DefaultConfig.MaxConnAge)
idleTimeout, _ = time.ParseDuration(DefaultConfig.IdleTimeout)
)
if config.PoolSize <= 0 { // SetOnce 设置一次并设置过期时间, key不存在则设置成功返回1, key已存在返回0
config.MinIdleConn = DefaultConfig.PoolSize func (c *Client) SetOnce(ctx context.Context, key, value string, t time.Duration) (int, error) {
if t < time.Second {
return 0, fmt.Errorf("time must >= 1s")
} }
return c.Client.Eval(ctx, luaSetOnce, []string{key}, value, t).Int()
}
if config.MinIdleConn <= 0 { func CloseAllClient() {
config.MinIdleConn = DefaultConfig.MinIdleConn instanceMap.Range(func(k, v any) bool {
} v.(*Client).Close()
return true
if config.MaxConnAge != "" {
t, err := time.ParseDuration(config.MaxConnAge)
if err != nil {
return nil, fmt.Errorf("parse MaxConnAge err: %s\n", err)
}
maxConnAge = t
}
if config.IdleTimeout != "" {
t, err := time.ParseDuration(config.IdleTimeout)
if err != nil {
return nil, fmt.Errorf("parse IdleTimeout err: %s\n", err)
}
idleTimeout = t
}
client := redis.NewClient(&redis.Options{
Addr: config.Addr,
Password: config.Password,
DB: config.DB,
PoolSize: config.PoolSize,
MinIdleConns: config.MinIdleConn,
MaxConnAge: maxConnAge,
IdleTimeout: idleTimeout,
}) })
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
defer cancel()
rd := &MyRedis{}
rd.Client = client
ping := rd.Client.Ping(ctx)
if ping.Err() != nil {
return nil, fmt.Errorf("connet redis err: %s", ping.Err())
}
return rd, nil
}
// GetSimple 通用get
func (r *MyRedis) GetSimple(key string) (string, error) {
ctx := context.Background()
return r.Client.Get(ctx, key).Result()
}
// SetSimple 通用set
func (r *MyRedis) SetSimple(key string, value interface{}, t ...time.Duration) (string, error) {
ctx := context.Background()
var t2 time.Duration
if len(t) > 0 {
t2 = t[0]
}
return r.Client.Set(ctx, key, value, t2).Result()
}
// GetJson json序列化
func (r *MyRedis) GetJson(key string, result interface{}) error {
ctx := context.Background()
res, err := r.Client.Get(ctx, key).Bytes()
if err != nil {
return err
}
err = jsoniter.Unmarshal(res, &result)
if err != nil {
return fmt.Errorf("get key:%s 反序列化json失败(-2)", key)
}
return nil
}
// SetJson json序列化set
func (r *MyRedis) SetJson(key string, value interface{}, t ...time.Duration) (string, error) {
ctx := context.Background()
var t2 time.Duration
if len(t) > 0 {
t2 = t[0]
}
v, err := jsoniter.Marshal(value)
if err != nil {
return "", fmt.Errorf("set key:%s 序列化json失败", key)
}
return r.Client.Set(ctx, key, v, t2).Result()
}
func CloseDB(key string) {
DB(key).Client.Close()
}
func CloseAllDB() {
for _, v := range instanceMap {
v.Client.Close()
}
} }

View File

@@ -1,40 +0,0 @@
package myredis
import (
"context"
"fmt"
"testing"
"time"
)
func TestRedis(t *testing.T) {
err := InitDefault(&Config{
Addr: "192.168.244.128:6379",
Password: "",
DB: 15,
PoolSize: 16,
MinIdleConn: 4,
MaxConnAge: "1h",
IdleTimeout: "10m",
})
if err != nil {
fmt.Println(err)
return
}
defer CloseAllDB()
set, err := DB().Set(context.Background(), "name", "qwe123", time.Minute).Result()
if err != nil {
fmt.Println(err)
return
}
fmt.Println(set)
get, err := DB().Get(context.Background(), "name").Result()
if err != nil {
fmt.Println(err)
return
}
fmt.Println(get)
}

366
myregistry/consul/consul.go Normal file
View File

@@ -0,0 +1,366 @@
package consul
import (
"fmt"
"git.makemake.in/kzkzzzz/mycommon/myconf"
"git.makemake.in/kzkzzzz/mycommon/mygrpc"
"git.makemake.in/kzkzzzz/mycommon/mylog"
"git.makemake.in/kzkzzzz/mycommon/myregistry"
"github.com/hashicorp/consul/api"
"github.com/rs/xid"
"go.uber.org/zap"
"net"
"net/url"
"os"
"sync"
"time"
)
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 {
client *api.Client
serviceTags []string
services map[string]*api.AgentServiceRegistration
deregisteredMap *sync.Map
}
type Opt func(*Consul)
func WithServiceTags(tags ...string) Opt {
return func(c *Consul) {
c.serviceTags = tags
}
}
func (c *Consul) Name() string {
return "consul"
}
func (c *Consul) Register(service *myregistry.ServiceInfo) error {
// 健康检查
serviceId := xid.New().String()
hostname, err := os.Hostname()
if err != nil {
defaultLog.Errorf("get hostname err: %s", err)
} else {
serviceId = fmt.Sprintf("%s-%s", hostname, serviceId)
}
if _, ok := c.services[service.ServiceName]; ok {
return fmt.Errorf("service [%s] already registered", service.ServiceName)
}
checkTime := time.Second * 15
check := &api.AgentServiceCheck{
CheckID: serviceId,
TTL: (checkTime*2 + time.Second*10).String(),
//TCP: fmt.Sprintf("%s:%d", service.Ip, service.Port),
//Timeout: "5s", // 超时时间
//Interval: "30s", // 运行检查的频率
// 指定时间后自动注销不健康的服务节点
// 最小超时时间为1分钟收获不健康服务的进程每30秒运行一次因此触发注销的时间可能略长于配置的超时时间。
DeregisterCriticalServiceAfter: "5m",
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{
ID: serviceId, // 服务唯一ID
Name: service.ServiceName, // 服务名称
Tags: regTags, // 为服务打标签
Address: service.Ip,
Port: service.Port,
Check: check,
}
c.services[service.ServiceName] = 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 {
c.deregisteredMap.Store(service.ServiceName, true)
if svc, ok := c.services[service.ServiceName]; ok {
err := c.client.Agent().ServiceDeregister(svc.ID)
if err != nil {
defaultLog.Errorf("deregister service %s err: %s", service, err)
}
}
return nil
}
func MustNew(conf *myconf.Config, opts ...Opt) *Consul {
consul, err := New(conf, opts...)
if err != nil {
panic(err)
}
return consul
}
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.Address = clientCfg.Address
if cfg.Address == "" {
return nil, fmt.Errorf("consul address is empty")
}
cfg.Transport.DialContext = (&net.Dialer{
Timeout: 3 * time.Second,
KeepAlive: 20 * time.Second,
}).DialContext
cfg.Token = clientCfg.Token
username := clientCfg.Username
password := clientCfg.Password
if username != "" && password != "" {
cfg.HttpAuth = &api.HttpBasicAuth{
Username: username,
Password: password,
}
}
client, err := api.NewClient(cfg)
if err != nil {
return nil, err
}
clientMap[clientCfg.Address] = client
return client, nil
}
func (c *Consul) allHealthCheck() {
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)
}
}
if isRegisterErr {
continue
}
}
}
isFirst = false
remoteServices, meta, err = c.client.Catalog().Services(&api.QueryOptions{
WaitIndex: lastIndex,
WaitTime: time.Second * 90,
})
if err != nil {
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)
}
}
}
}
}
func (c *Consul) retryRegister(svc *api.AgentServiceRegistration, needDelay bool) error {
if _, ok := c.deregisteredMap.Load(svc.Name); ok {
return nil
}
if needDelay {
go func() {
time.Sleep(time.Second)
err := c.client.Agent().ServiceRegister(svc)
if err != nil {
defaultLog.Errorf("delay retry register service %s err: %s", svc.Name, err)
} else {
defaultLog.Infof("delay retry register service %s ok", svc.Name)
}
}()
return nil
}
return c.client.Agent().ServiceRegister(svc)
}
func (c *Consul) Client() *api.Client {
return c.client
}
func GrpcUrl(serviceName string, conf *myconf.Config) string {
return GrpcUrlWithTag("", serviceName, conf)
}
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{
Scheme: schemeName,
Host: conf.Address,
Path: serviceName,
}
if u.Host == "" {
panic("consul address is empty")
}
query := u.Query()
query.Set("healthy", "true")
if conf.Token != "" {
query.Set("token", conf.Token)
}
if tag != "" {
query.Set("tag", tag)
}
if conf.Username != "" && conf.Password != "" {
u.User = url.UserPassword(conf.Username, conf.Password)
}
u.RawQuery = query.Encode()
return u.String()
}

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

102
myregistry/consul/target.go Normal file
View File

@@ -0,0 +1,102 @@
package consul
import (
"fmt"
"net/http"
"net/url"
"strings"
"time"
"github.com/go-playground/form"
"github.com/hashicorp/consul/api"
"github.com/pkg/errors"
)
type target struct {
Addr string `form:"-"`
User string `form:"-"`
Password string `form:"-"`
Service string `form:"-"`
Wait time.Duration `form:"wait"`
Timeout time.Duration `form:"timeout"`
MaxBackoff time.Duration `form:"max-backoff"`
Tag string `form:"tag"`
Near string `form:"near"`
Limit int `form:"limit"`
Healthy bool `form:"healthy"`
TLSInsecure bool `form:"insecure"`
Token string `form:"token"`
Dc string `form:"dc"`
AllowStale bool `form:"allow-stale"`
RequireConsistent bool `form:"require-consistent"`
}
func (t *target) String() string {
str := t.Service
if t.Tag != "" {
str = fmt.Sprintf("%s (tag:%s)", str, t.Tag)
}
return str
}
func parseURL(u string) (*target, error) {
rawURL, err := url.Parse(u)
if err != nil {
return nil, errors.Wrap(err, "Malformed URL")
}
if rawURL.Scheme != schemeName ||
len(rawURL.Host) == 0 || len(strings.TrimLeft(rawURL.Path, "/")) == 0 {
return nil,
errors.Errorf("Malformed URL('%s'). Must be in the next format: 'consul://[user:passwd]@host/service?param=value'", u)
}
tgt := &target{}
tgt.User = rawURL.User.Username()
tgt.Password, _ = rawURL.User.Password()
tgt.Addr = rawURL.Host
tgt.Service = strings.TrimLeft(rawURL.Path, "/")
decoder := form.NewDecoder()
decoder.RegisterCustomTypeFunc(func(vals []string) (interface{}, error) {
return time.ParseDuration(vals[0])
}, time.Duration(0))
err = decoder.Decode(&tgt, rawURL.Query())
if err != nil {
return nil, errors.Wrap(err, "Malformed URL parameters")
}
if len(tgt.Near) == 0 {
tgt.Near = "_agent"
}
if tgt.MaxBackoff == 0 {
tgt.MaxBackoff = time.Second
}
return tgt, nil
}
// consulConfig returns config based on the parsed target.
// It uses custom http-client.
func (t *target) consulConfig() *api.Config {
var creds *api.HttpBasicAuth
if len(t.User) > 0 && len(t.Password) > 0 {
creds = new(api.HttpBasicAuth)
creds.Password = t.Password
creds.Username = t.User
}
// custom http.Client
c := &http.Client{
Timeout: t.Timeout,
}
return &api.Config{
Address: t.Addr,
HttpAuth: creds,
WaitTime: t.Wait,
HttpClient: c,
TLSConfig: api.TLSConfig{
InsecureSkipVerify: t.TLSInsecure,
},
Token: t.Token,
}
}

21
myregistry/reigster.go Normal file
View File

@@ -0,0 +1,21 @@
package myregistry
import "fmt"
type ServiceInfo struct {
ServiceName string
Ip string
Port int
Extend map[string]string
}
func (s *ServiceInfo) String() string {
return fmt.Sprintf("%s - %s:%d", s.ServiceName, s.Ip, s.Port)
}
// IRegister 注册中心 服务注册发现
type IRegister interface {
Name() string
Register(service *ServiceInfo) error
Deregister(service *ServiceInfo) error
}

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)