Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20ebcfa60b | ||
|
|
7a94a208c0 | ||
|
|
a66a38e074 | ||
|
|
e47dd99816 | ||
|
|
272943d238 | ||
|
|
172c1cc506 | ||
|
|
915e1f65ec | ||
|
|
c512327358 | ||
|
|
c091ed4984 | ||
|
|
dc4dd5dc62 | ||
|
|
7e51069595 | ||
|
|
791c9905df |
@@ -0,0 +1 @@
|
|||||||
|
.git
|
||||||
+7
-4
@@ -318,14 +318,16 @@ func selectHook(ctx context.Context, in *gdb.HookSelectInput) (result gdb.Result
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tenantId = user.TenantId
|
tenantId = user.TenantId
|
||||||
// 【关键修复】找到 SQL 中第一个出现的 ORDER BY / GROUP BY / LIMIT 等关键字位置
|
// 【关键修复】找到 SQL 中最靠前出现的关键字位置:SQL 子句顺序固定为
|
||||||
|
// GROUP BY → HAVING → ORDER BY → LIMIT,必须取最早出现的位置,而非关键字列表里先命中的那个。
|
||||||
|
// 否则同时含 GROUP BY 与 ORDER BY 的查询会命中靠后的 ORDER BY,把 tenant_id 条件拼进 GROUP BY
|
||||||
|
// (如 GROUP BY DATE(created_at) AND tenant_id = 94),导致 pq: argument of AND must be type boolean。
|
||||||
sql := in.Sql
|
sql := in.Sql
|
||||||
insertPos := len(sql)
|
insertPos := len(sql)
|
||||||
keywords := []string{" ORDER BY ", " GROUP BY ", " HAVING ", " LIMIT ", " FOR UPDATE"}
|
keywords := []string{" GROUP BY ", " HAVING ", " ORDER BY ", " LIMIT ", " FOR UPDATE"}
|
||||||
for _, kw := range keywords {
|
for _, kw := range keywords {
|
||||||
if idx := gstr.PosI(sql, kw); idx != -1 {
|
if idx := gstr.PosI(sql, kw); idx != -1 && idx < insertPos {
|
||||||
insertPos = idx
|
insertPos = idx
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -439,6 +441,7 @@ var (
|
|||||||
|
|
||||||
type Gfdb interface {
|
type Gfdb interface {
|
||||||
GetAll(ctx context.Context, sql string, args ...any) (gdb.Result, error)
|
GetAll(ctx context.Context, sql string, args ...any) (gdb.Result, error)
|
||||||
|
GetOne(ctx context.Context, sql string, args ...any) (gdb.Record, error)
|
||||||
Exec(ctx context.Context, sql string, args ...any) (sql.Result, error)
|
Exec(ctx context.Context, sql string, args ...any) (sql.Result, error)
|
||||||
Model(ctx context.Context, tableNameOrStruct ...any) *model
|
Model(ctx context.Context, tableNameOrStruct ...any) *model
|
||||||
Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) error
|
Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) error
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
module gitea.redpowerfuture.com/red-future/common
|
module gitea.redpowerfuture.com/red-future/common
|
||||||
|
|
||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -8,12 +9,16 @@ require (
|
|||||||
github.com/gogf/gf/contrib/registry/consul/v2 v2.9.5
|
github.com/gogf/gf/contrib/registry/consul/v2 v2.9.5
|
||||||
github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.9.5
|
github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.9.5
|
||||||
github.com/gogf/gf/v2 v2.9.5
|
github.com/gogf/gf/v2 v2.9.5
|
||||||
|
github.com/google/uuid v1.6.0
|
||||||
|
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674
|
||||||
github.com/hashicorp/consul/api v1.26.1
|
github.com/hashicorp/consul/api v1.26.1
|
||||||
github.com/meilisearch/meilisearch-go v0.36.1
|
github.com/meilisearch/meilisearch-go v0.36.1
|
||||||
github.com/olivere/elastic/v7 v7.0.32
|
github.com/olivere/elastic/v7 v7.0.32
|
||||||
github.com/r3labs/diff/v2 v2.15.1
|
github.com/r3labs/diff/v2 v2.15.1
|
||||||
|
github.com/redis/go-redis/v9 v9.12.1
|
||||||
github.com/rpcxio/rpcx-consul v0.1.1
|
github.com/rpcxio/rpcx-consul v0.1.1
|
||||||
github.com/smallnest/rpcx v1.9.1
|
github.com/smallnest/rpcx v1.9.1
|
||||||
|
github.com/tidwall/sjson v1.2.5
|
||||||
github.com/tiger1103/gfast-token v1.0.10
|
github.com/tiger1103/gfast-token v1.0.10
|
||||||
go.mongodb.org/mongo-driver/v2 v2.4.0
|
go.mongodb.org/mongo-driver/v2 v2.4.0
|
||||||
go.opentelemetry.io/otel v1.38.0
|
go.opentelemetry.io/otel v1.38.0
|
||||||
@@ -69,8 +74,6 @@ require (
|
|||||||
github.com/google/flatbuffers v1.12.1 // indirect
|
github.com/google/flatbuffers v1.12.1 // indirect
|
||||||
github.com/google/gnostic-models v0.7.0 // indirect
|
github.com/google/gnostic-models v0.7.0 // indirect
|
||||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
|
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
|
||||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
|
|
||||||
github.com/grandcat/zeroconf v1.0.0 // indirect
|
github.com/grandcat/zeroconf v1.0.0 // indirect
|
||||||
github.com/grokify/html-strip-tags-go v0.1.0 // indirect
|
github.com/grokify/html-strip-tags-go v0.1.0 // indirect
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
|
||||||
@@ -114,7 +117,6 @@ require (
|
|||||||
github.com/prometheus/procfs v0.12.0 // indirect
|
github.com/prometheus/procfs v0.12.0 // indirect
|
||||||
github.com/quic-go/quic-go v0.49.0 // indirect
|
github.com/quic-go/quic-go v0.49.0 // indirect
|
||||||
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
|
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
|
||||||
github.com/redis/go-redis/v9 v9.12.1 // indirect
|
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/rpcxio/libkv v0.5.1 // indirect
|
github.com/rpcxio/libkv v0.5.1 // indirect
|
||||||
github.com/rs/cors v1.11.1 // indirect
|
github.com/rs/cors v1.11.1 // indirect
|
||||||
@@ -126,6 +128,9 @@ require (
|
|||||||
github.com/spf13/pflag v1.0.9 // indirect
|
github.com/spf13/pflag v1.0.9 // indirect
|
||||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
|
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
|
||||||
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect
|
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect
|
||||||
|
github.com/tidwall/gjson v1.18.0 // indirect
|
||||||
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
|
github.com/tidwall/pretty v1.2.1 // indirect
|
||||||
github.com/tinylib/msgp v1.3.0 // indirect
|
github.com/tinylib/msgp v1.3.0 // indirect
|
||||||
github.com/tjfoc/gmsm v1.4.1 // indirect
|
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.6 // indirect
|
github.com/tklauser/go-sysconf v0.3.6 // indirect
|
||||||
@@ -141,6 +146,7 @@ require (
|
|||||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||||
github.com/xtaci/kcp-go v5.4.20+incompatible // indirect
|
github.com/xtaci/kcp-go v5.4.20+incompatible // indirect
|
||||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||||
|
github.com/yuin/gopher-lua v1.1.1 // indirect
|
||||||
go.opencensus.io v0.23.0 // indirect
|
go.opencensus.io v0.23.0 // indirect
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
|
||||||
@@ -180,3 +186,5 @@ require (
|
|||||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
|
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
|
||||||
sigs.k8s.io/yaml v1.6.0 // indirect
|
sigs.k8s.io/yaml v1.6.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
replace gitea.redpowerfuture.com/red-future/common => ../common
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
|
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
|
||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
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/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||||
|
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0/go.mod h1:Cz6ft6Dkn3Et6l2v2a9/RpN7epQ1GtDlO6lj8bEcOvw=
|
||||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||||
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||||
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||||
|
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
|
||||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
|
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
|
||||||
@@ -15,17 +19,22 @@ github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/
|
|||||||
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
|
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
|
||||||
github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A=
|
github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A=
|
||||||
github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw=
|
github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw=
|
||||||
|
github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||||
|
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
|
||||||
github.com/alibaba/sentinel-golang v1.0.4 h1:i0wtMvNVdy7vM4DdzYrlC4r/Mpk1OKUUBurKKkWhEo8=
|
github.com/alibaba/sentinel-golang v1.0.4 h1:i0wtMvNVdy7vM4DdzYrlC4r/Mpk1OKUUBurKKkWhEo8=
|
||||||
github.com/alibaba/sentinel-golang v1.0.4/go.mod h1:Lag5rIYyJiPOylK8Kku2P+a23gdKMMqzQS7wTnjWEpk=
|
github.com/alibaba/sentinel-golang v1.0.4/go.mod h1:Lag5rIYyJiPOylK8Kku2P+a23gdKMMqzQS7wTnjWEpk=
|
||||||
|
github.com/alicebob/miniredis/v2 v2.35.0 h1:QwLphYqCEAo1eu1TqPRN2jgVMPBweeQcR21jeqDCONI=
|
||||||
|
github.com/alicebob/miniredis/v2 v2.35.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM=
|
||||||
github.com/alitto/pond v1.9.2 h1:9Qb75z/scEZVCoSU+osVmQ0I0JOeLfdTDafrbcJ8CLs=
|
github.com/alitto/pond v1.9.2 h1:9Qb75z/scEZVCoSU+osVmQ0I0JOeLfdTDafrbcJ8CLs=
|
||||||
github.com/alitto/pond v1.9.2/go.mod h1:xQn3P/sHTYcU/1BR3i86IGIrilcrGC2LiS+E2+CJWsI=
|
github.com/alitto/pond v1.9.2/go.mod h1:xQn3P/sHTYcU/1BR3i86IGIrilcrGC2LiS+E2+CJWsI=
|
||||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||||
|
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||||
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||||
github.com/apache/thrift v0.21.0 h1:tdPmh/ptjE1IJnhbhrcl2++TauVjy242rkV/UzJChnE=
|
github.com/apache/thrift v0.21.0 h1:tdPmh/ptjE1IJnhbhrcl2++TauVjy242rkV/UzJChnE=
|
||||||
@@ -37,9 +46,11 @@ github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJ
|
|||||||
github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
|
github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
|
||||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||||
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||||
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
|
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
|
||||||
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
|
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
|
||||||
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||||
|
github.com/aws/aws-sdk-go v1.43.21/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
||||||
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||||
@@ -63,6 +74,10 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
|
|||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
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 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
||||||
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
|
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
|
||||||
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
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/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||||
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
|
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
|
||||||
@@ -70,6 +85,7 @@ github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn
|
|||||||
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
|
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
|
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
|
||||||
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
|
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
|
||||||
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
|
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
|
||||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||||
@@ -112,17 +128,24 @@ github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4s
|
|||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=
|
||||||
|
github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=
|
||||||
|
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
|
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
|
||||||
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw=
|
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw=
|
||||||
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA=
|
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA=
|
||||||
|
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||||
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||||
|
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
|
||||||
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
||||||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||||
|
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
||||||
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
|
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
|
||||||
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
|
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
@@ -140,12 +163,15 @@ github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01
|
|||||||
github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=
|
github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=
|
||||||
github.com/go-ego/gse v1.0.2 h1:+27lYFPhQEhA9igtdOsJPRKYL/k3TwYsxBF5jr6KFv4=
|
github.com/go-ego/gse v1.0.2 h1:+27lYFPhQEhA9igtdOsJPRKYL/k3TwYsxBF5jr6KFv4=
|
||||||
github.com/go-ego/gse v1.0.2/go.mod h1:Fy35G+q7VV7Et1zIKO8o/sW1kkugV3znXap/lF/11zc=
|
github.com/go-ego/gse v1.0.2/go.mod h1:Fy35G+q7VV7Et1zIKO8o/sW1kkugV3znXap/lF/11zc=
|
||||||
|
github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
|
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
|
||||||
|
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||||
|
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
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/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
@@ -164,8 +190,11 @@ github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ
|
|||||||
github.com/go-ping/ping v1.2.0 h1:vsJ8slZBZAXNCK4dPcI2PEE9eM9n9RbXbGouVQ/Y4yQ=
|
github.com/go-ping/ping v1.2.0 h1:vsJ8slZBZAXNCK4dPcI2PEE9eM9n9RbXbGouVQ/Y4yQ=
|
||||||
github.com/go-ping/ping v1.2.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
|
github.com/go-ping/ping v1.2.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
|
||||||
github.com/go-redis/redis/v8 v8.8.2/go.mod h1:F7resOH5Kdug49Otu24RjHWwgK7u9AmtqWMnCV1iP5Y=
|
github.com/go-redis/redis/v8 v8.8.2/go.mod h1:F7resOH5Kdug49Otu24RjHWwgK7u9AmtqWMnCV1iP5Y=
|
||||||
|
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||||
|
github.com/go-redis/redis_rate/v10 v10.0.1/go.mod h1:EMiuO9+cjRkR7UvdvwMO7vbgqJkltQHtwbdIQvaBKIU=
|
||||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||||
@@ -174,6 +203,8 @@ github.com/godzie44/go-uring v0.0.0-20220926161041-69611e8b13d5 h1:5zELAgnSz0gqm
|
|||||||
github.com/godzie44/go-uring v0.0.0-20220926161041-69611e8b13d5/go.mod h1:ermjEDUoT/fS+3Ona5Vd6t6mZkw1eHp99ILO5jGRBkM=
|
github.com/godzie44/go-uring v0.0.0-20220926161041-69611e8b13d5/go.mod h1:ermjEDUoT/fS+3Ona5Vd6t6mZkw1eHp99ILO5jGRBkM=
|
||||||
github.com/gogf/gf/contrib/nosql/redis/v2 v2.9.1 h1:egobo4YfQX3C4NtrEFunBqMX3jsddagklgut9u91+BM=
|
github.com/gogf/gf/contrib/nosql/redis/v2 v2.9.1 h1:egobo4YfQX3C4NtrEFunBqMX3jsddagklgut9u91+BM=
|
||||||
github.com/gogf/gf/contrib/nosql/redis/v2 v2.9.1/go.mod h1:YQ+u5Cs5N2ETCeQaLbv29z/UWYuxw27mJpUkOLj0kJ8=
|
github.com/gogf/gf/contrib/nosql/redis/v2 v2.9.1/go.mod h1:YQ+u5Cs5N2ETCeQaLbv29z/UWYuxw27mJpUkOLj0kJ8=
|
||||||
|
github.com/gogf/gf/contrib/nosql/redis/v2 v2.9.5 h1:Ku7p3CvGchxC7zPSgArf/tZs2w9Yb8tS/gH5ADN+p9g=
|
||||||
|
github.com/gogf/gf/contrib/nosql/redis/v2 v2.9.5/go.mod h1:cjy18NsSLZQf5zaLAzuo7B2gr8GGjCTWDTEPY7T+6FI=
|
||||||
github.com/gogf/gf/contrib/registry/consul/v2 v2.9.5 h1:eUqwJ/qNH8lJ6yssiqskazgp1ACQuNU6zXlLOZVuXTQ=
|
github.com/gogf/gf/contrib/registry/consul/v2 v2.9.5 h1:eUqwJ/qNH8lJ6yssiqskazgp1ACQuNU6zXlLOZVuXTQ=
|
||||||
github.com/gogf/gf/contrib/registry/consul/v2 v2.9.5/go.mod h1:sjQyMry9+0POYZCA6lHXBxO77WoNKkruJpRB4xKqk5k=
|
github.com/gogf/gf/contrib/registry/consul/v2 v2.9.5/go.mod h1:sjQyMry9+0POYZCA6lHXBxO77WoNKkruJpRB4xKqk5k=
|
||||||
github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.9.5 h1:tHUEZYB5GTqEYYVDYnlGobf1xISARKDE4KHVlgjwTec=
|
github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.9.5 h1:tHUEZYB5GTqEYYVDYnlGobf1xISARKDE4KHVlgjwTec=
|
||||||
@@ -197,6 +228,7 @@ github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4er
|
|||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||||
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=
|
||||||
@@ -250,6 +282,7 @@ github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5T
|
|||||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
|
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
|
||||||
github.com/grandcat/zeroconf v1.0.0 h1:uHhahLBKqwWBV6WZUDAT71044vwOTL+McW0mBJvo6kE=
|
github.com/grandcat/zeroconf v1.0.0 h1:uHhahLBKqwWBV6WZUDAT71044vwOTL+McW0mBJvo6kE=
|
||||||
github.com/grandcat/zeroconf v1.0.0/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfcP2PdzytEs=
|
github.com/grandcat/zeroconf v1.0.0/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfcP2PdzytEs=
|
||||||
|
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||||
github.com/grokify/html-strip-tags-go v0.1.0 h1:03UrQLjAny8xci+R+qjCce/MYnpNXCtgzltlQbOBae4=
|
github.com/grokify/html-strip-tags-go v0.1.0 h1:03UrQLjAny8xci+R+qjCce/MYnpNXCtgzltlQbOBae4=
|
||||||
github.com/grokify/html-strip-tags-go v0.1.0/go.mod h1:ZdzgfHEzAfz9X6Xe5eBLVblWIxXfYSQ40S/VKrAOGpc=
|
github.com/grokify/html-strip-tags-go v0.1.0/go.mod h1:ZdzgfHEzAfz9X6Xe5eBLVblWIxXfYSQ40S/VKrAOGpc=
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||||
@@ -322,9 +355,11 @@ github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY
|
|||||||
github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
|
github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
|
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
|
||||||
|
github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
|
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
|
||||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||||
|
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
@@ -371,12 +406,17 @@ github.com/libp2p/go-sockaddr v0.2.0 h1:Alhhj6lGxVAon9O32tOO89T601EugSx6YiGjy5BV
|
|||||||
github.com/libp2p/go-sockaddr v0.2.0/go.mod h1:5NxulaB17yJ07IpzRIleys4un0PJ7WLWgMDLBBWrGw8=
|
github.com/libp2p/go-sockaddr v0.2.0/go.mod h1:5NxulaB17yJ07IpzRIleys4un0PJ7WLWgMDLBBWrGw8=
|
||||||
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
|
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
|
||||||
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
|
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
|
||||||
|
github.com/lucas-clemente/quic-go v0.28.0/go.mod h1:oGz5DKK41cJt5+773+BSO9BXDsREY4HLf7+0odGAPO0=
|
||||||
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
|
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
|
||||||
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
|
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
|
||||||
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||||
|
github.com/marten-seemann/qtls-go1-16 v0.1.5/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
|
||||||
|
github.com/marten-seemann/qtls-go1-17 v0.1.2/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s=
|
||||||
|
github.com/marten-seemann/qtls-go1-18 v0.1.2/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
|
||||||
|
github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI=
|
||||||
github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=
|
github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=
|
||||||
github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=
|
github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
@@ -423,6 +463,7 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
|
|||||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
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/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
|
||||||
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=
|
||||||
@@ -435,6 +476,7 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
|
|||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
|
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||||
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
|
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
|
||||||
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
|
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
|
||||||
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
|
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
|
||||||
@@ -443,6 +485,7 @@ github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi
|
|||||||
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
|
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||||
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
|
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
|
||||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||||
github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM=
|
github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM=
|
||||||
@@ -452,12 +495,14 @@ github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIY
|
|||||||
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||||
github.com/olekukonko/tablewriter v1.1.0 h1:N0LHrshF4T39KvI96fn6GT8HEjXRXYNDrDjKFDB7RIY=
|
github.com/olekukonko/tablewriter v1.1.0 h1:N0LHrshF4T39KvI96fn6GT8HEjXRXYNDrDjKFDB7RIY=
|
||||||
github.com/olekukonko/tablewriter v1.1.0/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo=
|
github.com/olekukonko/tablewriter v1.1.0/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo=
|
||||||
|
github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0/go.mod h1:F/7q8/HZz+TXjlsoZQQKVYvXTZaFH4QRa3y+j1p7MS0=
|
||||||
github.com/olivere/elastic/v7 v7.0.32 h1:R7CXvbu8Eq+WlsLgxmKVKPox0oOwAE/2T9Si5BnvK6E=
|
github.com/olivere/elastic/v7 v7.0.32 h1:R7CXvbu8Eq+WlsLgxmKVKPox0oOwAE/2T9Si5BnvK6E=
|
||||||
github.com/olivere/elastic/v7 v7.0.32/go.mod h1:c7PVmLe3Fxq77PIfY/bZmxY/TAamBhCzZ8xDOE09a9k=
|
github.com/olivere/elastic/v7 v7.0.32/go.mod h1:c7PVmLe3Fxq77PIfY/bZmxY/TAamBhCzZ8xDOE09a9k=
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||||
github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
|
github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
|
||||||
|
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||||
github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
|
github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
|
||||||
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
|
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
|
||||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
@@ -471,6 +516,7 @@ github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go
|
|||||||
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
|
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
|
||||||
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||||
|
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
|
||||||
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
|
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
|
||||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||||
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||||
@@ -481,6 +527,7 @@ github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0Mw
|
|||||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||||
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
|
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
|
||||||
|
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||||
github.com/peterbourgon/g2s v0.0.0-20140925154142-ec76db4c1ac1 h1:5Dl+ADmsGerAqHwWzyLqkNaUBQ+48DQwfDCaW1gHAQM=
|
github.com/peterbourgon/g2s v0.0.0-20140925154142-ec76db4c1ac1 h1:5Dl+ADmsGerAqHwWzyLqkNaUBQ+48DQwfDCaW1gHAQM=
|
||||||
github.com/peterbourgon/g2s v0.0.0-20140925154142-ec76db4c1ac1/go.mod h1:1VcHEd3ro4QMoHfiNl/j7Jkln9+KQuorp0PItHMJYNg=
|
github.com/peterbourgon/g2s v0.0.0-20140925154142-ec76db4c1ac1/go.mod h1:1VcHEd3ro4QMoHfiNl/j7Jkln9+KQuorp0PItHMJYNg=
|
||||||
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
||||||
@@ -492,6 +539,7 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
|||||||
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/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
||||||
|
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
||||||
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/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
@@ -530,6 +578,9 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
|
|||||||
github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||||
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||||
|
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||||
|
github.com/quic-go/qtls-go1-19 v0.3.3/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
|
||||||
|
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||||
github.com/quic-go/quic-go v0.49.0 h1:w5iJHXwHxs1QxyBv1EHKuC50GX5to8mJAxvtnttJp94=
|
github.com/quic-go/quic-go v0.49.0 h1:w5iJHXwHxs1QxyBv1EHKuC50GX5to8mJAxvtnttJp94=
|
||||||
github.com/quic-go/quic-go v0.49.0/go.mod h1:s2wDnmCdooUQBmQfpUSTCYBl1/D4FcqbULMMkASvR6s=
|
github.com/quic-go/quic-go v0.49.0/go.mod h1:s2wDnmCdooUQBmQfpUSTCYBl1/D4FcqbULMMkASvR6s=
|
||||||
github.com/r3labs/diff/v2 v2.15.1 h1:EOrVqPUzi+njlumoqJwiS/TgGgmZo83619FNDB9xQUg=
|
github.com/r3labs/diff/v2 v2.15.1 h1:EOrVqPUzi+njlumoqJwiS/TgGgmZo83619FNDB9xQUg=
|
||||||
@@ -543,6 +594,7 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
|
|||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||||
|
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
@@ -573,15 +625,20 @@ github.com/smallnest/rpcx v1.9.1/go.mod h1:owr4mDCReTn+dy9m5ilof0mBivFBeK0XrkYfZ
|
|||||||
github.com/smallnest/rsocket v0.0.0-20241130031020-4a72eb6ff62a h1:GI6kCNC5AVFbKA6ZKbVd4r+fk+Z7XZCRQm9LURZY4t4=
|
github.com/smallnest/rsocket v0.0.0-20241130031020-4a72eb6ff62a h1:GI6kCNC5AVFbKA6ZKbVd4r+fk+Z7XZCRQm9LURZY4t4=
|
||||||
github.com/smallnest/rsocket v0.0.0-20241130031020-4a72eb6ff62a/go.mod h1:VJeIKKrDEzT4ZNVe87JN9uRLw1XLp/ZnnE9PfsyJ1jY=
|
github.com/smallnest/rsocket v0.0.0-20241130031020-4a72eb6ff62a/go.mod h1:VJeIKKrDEzT4ZNVe87JN9uRLw1XLp/ZnnE9PfsyJ1jY=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
|
github.com/smartystreets/assertions v1.1.1/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
|
||||||
|
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
|
||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
|
github.com/smartystreets/gunit v1.4.2/go.mod h1:ZjM1ozSIMJlAz/ay4SG8PeKF00ckUp+zMHZXV9/bvak=
|
||||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||||
github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js=
|
github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js=
|
||||||
github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
|
github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
|
||||||
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
|
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
|
||||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||||
|
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
|
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
|
||||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=
|
||||||
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||||
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||||
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
|
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
|
||||||
@@ -608,10 +665,12 @@ github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7S
|
|||||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
|
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
|
||||||
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b h1:fj5tQ8acgNUr6O8LEplsxDhUIe2573iLkJc+PqnzZTI=
|
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b h1:fj5tQ8acgNUr6O8LEplsxDhUIe2573iLkJc+PqnzZTI=
|
||||||
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
|
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
|
||||||
|
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||||
@@ -628,8 +687,10 @@ github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefld
|
|||||||
github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM=
|
github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM=
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||||
|
github.com/twpayne/go-jsonstruct/v3 v3.1.0/go.mod h1:2pXzrqn1yuRpon60R7JTPQTxYBiWLMSsrh/CDmk1i0Q=
|
||||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8=
|
github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8=
|
||||||
github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=
|
github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=
|
||||||
github.com/vcaesar/cedar v0.30.0 h1:9fSDpM7FTjjUdPiBUUa0MWYMRGSEcqgFXvppZcZ4d7Y=
|
github.com/vcaesar/cedar v0.30.0 h1:9fSDpM7FTjjUdPiBUUa0MWYMRGSEcqgFXvppZcZ4d7Y=
|
||||||
@@ -650,6 +711,7 @@ github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
|||||||
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||||
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
|
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
|
||||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||||
|
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
|
||||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||||
github.com/xtaci/kcp-go v5.4.20+incompatible h1:TN1uey3Raw0sTz0Fg8GkfM0uH3YwzhnZWQ1bABv5xAg=
|
github.com/xtaci/kcp-go v5.4.20+incompatible h1:TN1uey3Raw0sTz0Fg8GkfM0uH3YwzhnZWQ1bABv5xAg=
|
||||||
github.com/xtaci/kcp-go v5.4.20+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE=
|
github.com/xtaci/kcp-go v5.4.20+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE=
|
||||||
@@ -662,6 +724,9 @@ github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfS
|
|||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
|
||||||
|
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||||
|
github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
|
||||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||||
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
|
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
|
||||||
go.mongodb.org/mongo-driver/v2 v2.4.0 h1:Oq6BmUAAFTzMeh6AonuDlgZMuAuEiUxoAD1koK5MuFo=
|
go.mongodb.org/mongo-driver/v2 v2.4.0 h1:Oq6BmUAAFTzMeh6AonuDlgZMuAuEiUxoAD1koK5MuFo=
|
||||||
@@ -673,6 +738,7 @@ go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
|
|||||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
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/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||||
|
go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k=
|
||||||
go.opentelemetry.io/otel v0.19.0/go.mod h1:j9bF567N9EfomkSidSfmMwIwIBuP37AMAIzVW85OxSg=
|
go.opentelemetry.io/otel v0.19.0/go.mod h1:j9bF567N9EfomkSidSfmMwIwIBuP37AMAIzVW85OxSg=
|
||||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||||
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||||
@@ -835,6 +901,7 @@ golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE=
|
||||||
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/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||||
@@ -873,6 +940,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f
|
|||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||||
|
golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
|
||||||
|
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8=
|
||||||
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-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=
|
||||||
@@ -892,6 +961,7 @@ google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRn
|
|||||||
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
|
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
|
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY=
|
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE=
|
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE=
|
||||||
@@ -962,6 +1032,7 @@ k8s.io/apimachinery v0.35.3 h1:MeaUwQCV3tjKP4bcwWGgZ/cp/vpsRnQzqO6J6tJyoF8=
|
|||||||
k8s.io/apimachinery v0.35.3/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=
|
k8s.io/apimachinery v0.35.3/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=
|
||||||
k8s.io/client-go v0.35.3 h1:s1lZbpN4uI6IxeTM2cpdtrwHcSOBML1ODNTCCfsP1pg=
|
k8s.io/client-go v0.35.3 h1:s1lZbpN4uI6IxeTM2cpdtrwHcSOBML1ODNTCCfsP1pg=
|
||||||
k8s.io/client-go v0.35.3/go.mod h1:RzoXkc0mzpWIDvBrRnD+VlfXP+lRzqQjCmKtiwZ8Q9c=
|
k8s.io/client-go v0.35.3/go.mod h1:RzoXkc0mzpWIDvBrRnD+VlfXP+lRzqQjCmKtiwZ8Q9c=
|
||||||
|
k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU=
|
||||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||||
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE=
|
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE=
|
||||||
|
|||||||
+66
-20
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -69,10 +70,10 @@ func SkipMiddleware(h func(r *ghttp.Request), path string) (handler ghttp.Handle
|
|||||||
}
|
}
|
||||||
|
|
||||||
func RouteRegister(controllers []interface{}) {
|
func RouteRegister(controllers []interface{}) {
|
||||||
//Httpserver.Group("/log", func(group *ghttp.RouterGroup) {
|
Httpserver.Group("/log", func(group *ghttp.RouterGroup) {
|
||||||
// group.Middleware(jaeger.NewTracer)
|
group.Middleware(jaeger.NewTracer)
|
||||||
// group.Bind(controller.OperationLog)
|
//group.Bind(controller.OperationLog)
|
||||||
//})
|
})
|
||||||
re := regexp.MustCompile("[A-Z]")
|
re := regexp.MustCompile("[A-Z]")
|
||||||
for _, t := range controllers {
|
for _, t := range controllers {
|
||||||
sName := reflect.ValueOf(t).Elem().Type().Name()
|
sName := reflect.ValueOf(t).Elem().Type().Name()
|
||||||
@@ -80,19 +81,15 @@ func RouteRegister(controllers []interface{}) {
|
|||||||
return fmt.Sprintf("/%s", strings.ToLower(s))
|
return fmt.Sprintf("/%s", strings.ToLower(s))
|
||||||
})
|
})
|
||||||
Httpserver.Group(convertedStr, func(group *ghttp.RouterGroup) {
|
Httpserver.Group(convertedStr, func(group *ghttp.RouterGroup) {
|
||||||
group.Middleware(jaeger.NewTracer)
|
|
||||||
group.Bind(t)
|
group.Bind(t)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
go Httpserver.Run()
|
go Httpserver.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
// doRequest 统一HTTP请求处理(DELETE用ContentJson发送body,gconv.Struct增加err检查)
|
// doRequestRaw 执行HTTP请求,返回gclient.Response,调用方需自行Close
|
||||||
func doRequest(ctx context.Context, method string, url string, headers map[string]string, target any, data ...any) (err error) {
|
// 统一处理:client克隆、ContentJson设置、请求头注入、GET查询参数转换
|
||||||
err = utils.ValidStructPtr(target)
|
func doRequestRaw(ctx context.Context, method string, url string, headers map[string]string, data ...any) (*gclient.Response, error) {
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
client := Httpclient.Clone()
|
client := Httpclient.Clone()
|
||||||
|
|
||||||
// POST/PUT/DELETE请求都需要显式用ContentJson序列化body
|
// POST/PUT/DELETE请求都需要显式用ContentJson序列化body
|
||||||
@@ -103,16 +100,16 @@ func doRequest(ctx context.Context, method string, url string, headers map[strin
|
|||||||
// 最后设置headers,确保不会被ContentJson覆盖
|
// 最后设置headers,确保不会被ContentJson覆盖
|
||||||
if len(headers) > 0 {
|
if len(headers) > 0 {
|
||||||
client.SetHeaderMap(headers)
|
client.SetHeaderMap(headers)
|
||||||
} else {
|
} else if r := g.RequestFromCtx(ctx); r != nil {
|
||||||
client.SetHeader("Authorization", g.RequestFromCtx(ctx).GetHeader("Authorization"))
|
client.SetHeader("Authorization", r.GetHeader("Authorization"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 修复:避免data...展开导致的双重包装问题
|
// 修复:避免data...展开导致的双重包装问题
|
||||||
// 当只有一个元素时,直接传递该元素,避免被包装成数组
|
// 当只有一个元素时,直接传递该元素,避免被包装成数组
|
||||||
var response *gclient.Response
|
var response *gclient.Response
|
||||||
|
var err error
|
||||||
// 对于GET请求,将参数转换为map
|
// 对于GET请求,将参数转换为map
|
||||||
if method == http.MethodGet && len(data) > 0 && len(data)%2 == 0 {
|
if method == http.MethodGet && len(data) > 0 && len(data)%2 == 0 {
|
||||||
// 构建query参数map
|
|
||||||
queryParams := make(map[string]string)
|
queryParams := make(map[string]string)
|
||||||
for i := 0; i < len(data); i += 2 {
|
for i := 0; i < len(data); i += 2 {
|
||||||
if key, ok := data[i].(string); ok && i+1 < len(data) {
|
if key, ok := data[i].(string); ok && i+1 < len(data) {
|
||||||
@@ -126,17 +123,33 @@ func doRequest(ctx context.Context, method string, url string, headers map[strin
|
|||||||
} else {
|
} else {
|
||||||
response, err = client.DoRequest(ctx, method, url, data...)
|
response, err = client.DoRequest(ctx, method, url, data...)
|
||||||
}
|
}
|
||||||
|
return response, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// doRequest 统一HTTP请求处理(同步/异步,解析内部API响应格式并填充target)
|
||||||
|
func doRequest(ctx context.Context, method string, url string, headers map[string]string, target any, respParse bool, data ...any) (res []byte, err error) {
|
||||||
|
if target != nil || respParse {
|
||||||
|
err = utils.ValidStructPtr(target)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := doRequestRaw(ctx, method, url, headers, data...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer response.Close()
|
defer response.Close()
|
||||||
result := response.ReadAll()
|
result := response.ReadAll()
|
||||||
|
|
||||||
|
if !respParse {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
// 统一处理内部API响应格式:{code:200,message:"",data:{...}}
|
// 统一处理内部API响应格式:{code:200,message:"",data:{...}}
|
||||||
resultStrut := &ghttp.DefaultHandlerResponse{}
|
resultStrut := &ghttp.DefaultHandlerResponse{}
|
||||||
|
|
||||||
if err = gconv.Struct(result, &resultStrut); err != nil { // 修复:增加err检查
|
if err = gconv.Struct(result, &resultStrut); err != nil { // 修复:增加err检查
|
||||||
return errors.New("响应解析失败: " + err.Error())
|
return nil, errors.New("响应解析失败: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加调试日志:打印解析后的结构
|
// 添加调试日志:打印解析后的结构
|
||||||
@@ -145,7 +158,7 @@ func doRequest(ctx context.Context, method string, url string, headers map[strin
|
|||||||
|
|
||||||
if resultStrut.Code == 200 || resultStrut.Code == 0 {
|
if resultStrut.Code == 200 || resultStrut.Code == 0 {
|
||||||
if err = gconv.Struct(resultStrut.Data, target); err != nil { // 修复:增加err检查
|
if err = gconv.Struct(resultStrut.Data, target); err != nil { // 修复:增加err检查
|
||||||
return errors.New("数据解析失败: " + err.Error())
|
return nil, errors.New("数据解析失败: " + err.Error())
|
||||||
}
|
}
|
||||||
// 添加调试日志:打印最终的target
|
// 添加调试日志:打印最终的target
|
||||||
g.Log().Debugf(ctx, "[HTTP] 最终target: %+v", target)
|
g.Log().Debugf(ctx, "[HTTP] 最终target: %+v", target)
|
||||||
@@ -154,19 +167,52 @@ func doRequest(ctx context.Context, method string, url string, headers map[strin
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func Get(ctx context.Context, url string, headers map[string]string, target any, data ...any) (err error) {
|
func Get(ctx context.Context, url string, headers map[string]string, target any, data ...any) (err error) {
|
||||||
err = doRequest(ctx, http.MethodGet, url, headers, target, data...)
|
_, err = doRequest(ctx, http.MethodGet, url, headers, target, true, data...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
func Post(ctx context.Context, url string, headers map[string]string, target any, data ...any) (err error) {
|
func Post(ctx context.Context, url string, headers map[string]string, target any, data ...any) (err error) {
|
||||||
err = doRequest(ctx, http.MethodPost, url, headers, target, data...)
|
_, err = doRequest(ctx, http.MethodPost, url, headers, target, true, data...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
func Put(ctx context.Context, url string, headers map[string]string, target any, data ...any) (err error) {
|
func Put(ctx context.Context, url string, headers map[string]string, target any, data ...any) (err error) {
|
||||||
err = doRequest(ctx, http.MethodPut, url, headers, target, data...)
|
_, err = doRequest(ctx, http.MethodPut, url, headers, target, true, data...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
func Delete(ctx context.Context, url string, headers map[string]string, target any, data ...any) (err error) {
|
func Delete(ctx context.Context, url string, headers map[string]string, target any, data ...any) (err error) {
|
||||||
err = doRequest(ctx, http.MethodDelete, url, headers, target, data...)
|
_, err = doRequest(ctx, http.MethodDelete, url, headers, target, true, data...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetNotParse(ctx context.Context, url string, headers map[string]string, data ...any) (res []byte, err error) {
|
||||||
|
res, err = doRequest(ctx, http.MethodGet, url, headers, nil, false, data...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func PostNotParse(ctx context.Context, url string, headers map[string]string, data ...any) (res []byte, err error) {
|
||||||
|
res, err = doRequest(ctx, http.MethodPost, url, headers, nil, false, data...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DoStream 流式HTTP请求,返回响应体io.ReadCloser,由调用方自行控制读取和关闭
|
||||||
|
// 注意:返回的是原始响应流,不会解析内部API响应格式,调用方必须在使用后Close
|
||||||
|
func doStream(ctx context.Context, method string, url string, headers map[string]string, data ...any) (io.ReadCloser, error) {
|
||||||
|
response, err := doRequestRaw(ctx, method, url, headers, data...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查HTTP状态码,提前返回错误
|
||||||
|
if response.StatusCode < 200 || response.StatusCode >= 300 {
|
||||||
|
bodyBytes, _ := io.ReadAll(response.Body)
|
||||||
|
response.Close()
|
||||||
|
return nil, fmt.Errorf("[HTTP][Stream] 状态码异常: %d, body=%s", response.StatusCode, string(bodyBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Body, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostStream POST流式请求,返回响应体io.ReadCloser
|
||||||
|
func PostStream(ctx context.Context, url string, headers map[string]string, data ...any) (io.ReadCloser, error) {
|
||||||
|
return doStream(ctx, http.MethodPost, url, headers, data...)
|
||||||
|
}
|
||||||
|
|||||||
+197
@@ -0,0 +1,197 @@
|
|||||||
|
// Package oss 提供统一的 OSS 上传封装。
|
||||||
|
//
|
||||||
|
// 收敛全仓 4+ 处逐字复制的 multipart 上传(model-gateway/gateway/ai-agent/black-deacon)
|
||||||
|
// 与 media 的裸 client 手动解析,纯 HTTP 调用 oss 服务的 file/uploadFile 接口,不引入 minio SDK。
|
||||||
|
//
|
||||||
|
// 兼容红线:默认 client 惰性构建并挂载全局注册中心(保留 "oss" 服务名解析),
|
||||||
|
// X-User-Info 注入统一三套旧写法(透传请求头 / ctx 注入 user / 解析 token),
|
||||||
|
// 响应解析对齐 ghttp.DefaultHandlerResponse 的 {code,message,data}。
|
||||||
|
package oss
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitea.redpowerfuture.com/red-future/common/utils"
|
||||||
|
"github.com/gogf/gf/v2/net/gclient"
|
||||||
|
"github.com/gogf/gf/v2/net/ghttp"
|
||||||
|
"github.com/gogf/gf/v2/net/gsvc"
|
||||||
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UploadFileRes 对齐 oss/model/dto/file_dto.go:39 的 UploadFileRes。
|
||||||
|
type UploadFileRes struct {
|
||||||
|
FileURL string `json:"fileURL" dc:"上传地址"`
|
||||||
|
FileSize int `json:"fileSize" dc:"文件大小"`
|
||||||
|
FileName string `json:"fileName" dc:"文件名称"`
|
||||||
|
FileFormat string `json:"fileFormat" dc:"文件格式"`
|
||||||
|
FileAddressPrefix string `json:"fileAddressPrefix"`
|
||||||
|
FileMinioAddressPrefix string `json:"fileMinioAddressPrefix"`
|
||||||
|
Items []*UploadFileRes `json:"files"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultEndpoint oss 服务上传端点("oss" 由注册中心解析为实际地址,多副本负载均衡)。
|
||||||
|
const defaultEndpoint = "oss/file/uploadFile"
|
||||||
|
|
||||||
|
// uploadConfig 单次上传配置(Option 可变参数收集)。
|
||||||
|
type uploadConfig struct {
|
||||||
|
endpoint string
|
||||||
|
client *gclient.Client
|
||||||
|
header map[string]string
|
||||||
|
headerTimeout time.Duration // ResponseHeaderTimeout(大文件上传首字节等待)
|
||||||
|
timeout time.Duration // 整体超时
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultClientOnce 惰性构建默认上传客户端(sync.Once 保证只建一次)。
|
||||||
|
var (
|
||||||
|
defaultClientOnce sync.Once
|
||||||
|
defaultClient *gclient.Client
|
||||||
|
)
|
||||||
|
|
||||||
|
// defaultHTTPClient 返回默认上传客户端:
|
||||||
|
// 生产环境各服务 main 已空导入 common/consul(其 init 内 gsvc.SetRegistry),此处挂上全局 registry,
|
||||||
|
// 使 "oss" 服务名可被解析(等效 commonHttp.Httpclient 的 SetDiscovery 行为);
|
||||||
|
// 测试/无注册中心时 GetRegistry() 为 nil,退化为直连 client。
|
||||||
|
// 不直接复用 commonHttp.Httpclient:其空导入 common/consul,而 consul init 的 g.Cfg().MustGet
|
||||||
|
// 在无配置文件环境(单元测试)会 panic,本实现避开该副作用。
|
||||||
|
func defaultHTTPClient() *gclient.Client {
|
||||||
|
defaultClientOnce.Do(func() {
|
||||||
|
client := gclient.New()
|
||||||
|
if reg := gsvc.GetRegistry(); reg != nil {
|
||||||
|
client.SetDiscovery(reg)
|
||||||
|
}
|
||||||
|
defaultClient = client
|
||||||
|
})
|
||||||
|
return defaultClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultConfig() *uploadConfig {
|
||||||
|
return &uploadConfig{
|
||||||
|
endpoint: defaultEndpoint,
|
||||||
|
client: defaultHTTPClient(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildConfig(opts []Option) *uploadConfig {
|
||||||
|
cfg := defaultConfig()
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(cfg)
|
||||||
|
}
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option 上传可选项。
|
||||||
|
type Option func(*uploadConfig)
|
||||||
|
|
||||||
|
// WithEndpoint 覆盖上传端点(默认 oss/file/uploadFile;测试可指 httptest server)。
|
||||||
|
func WithEndpoint(url string) Option {
|
||||||
|
return func(c *uploadConfig) { c.endpoint = url }
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithHeader 追加/覆盖请求头(如 X-User-Info),优先级高于透传与 ctx 注入。
|
||||||
|
func WithHeader(k, v string) Option {
|
||||||
|
return func(c *uploadConfig) {
|
||||||
|
if c.header == nil {
|
||||||
|
c.header = make(map[string]string)
|
||||||
|
}
|
||||||
|
c.header[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithResponseHeaderTimeout 调大响应头等待超时(大文件上传,media 用 5m)。
|
||||||
|
func WithResponseHeaderTimeout(d time.Duration) Option {
|
||||||
|
return func(c *uploadConfig) { c.headerTimeout = d }
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTimeout 调大整体超时(大文件上传,media 用 10m)。
|
||||||
|
func WithTimeout(d time.Duration) Option {
|
||||||
|
return func(c *uploadConfig) { c.timeout = d }
|
||||||
|
}
|
||||||
|
|
||||||
|
// UploadFileBytes 上传文件字节到 OSS,返回完整响应(FileURL/FileFormat 等)。
|
||||||
|
// multipart field 名固定 "file"(oss 端 dto.UploadFileReq.File *ghttp.UploadFile 按此解析)。
|
||||||
|
func UploadFileBytes(ctx context.Context, fileName string, data []byte, opts ...Option) (*UploadFileRes, error) {
|
||||||
|
return uploadFileBytesWithCfg(ctx, buildConfig(opts), fileName, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// uploadFileBytesWithCfg 内部助手:用已构建的配置上传(TempURLToOSS 复用同一 cfg,保留 header/超时)。
|
||||||
|
func uploadFileBytesWithCfg(ctx context.Context, cfg *uploadConfig, fileName string, data []byte) (*UploadFileRes, error) {
|
||||||
|
body := &bytes.Buffer{}
|
||||||
|
writer := multipart.NewWriter(body)
|
||||||
|
part, err := writer.CreateFormFile("file", fileName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if _, err = part.Write(data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = writer.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return doPost(ctx, cfg, body.Bytes(), writer.FormDataContentType())
|
||||||
|
}
|
||||||
|
|
||||||
|
// doPost 执行 multipart 上传并解析 {code,message,data} 响应。
|
||||||
|
// 复刻 commonHttp.doRequestRaw 的「先 ContentJson、后 SetHeaderMap 覆盖 Content-Type」顺序(生产已验证);
|
||||||
|
// 超时走 transport clone,避免改动全局共享 transport。
|
||||||
|
func doPost(ctx context.Context, cfg *uploadConfig, body []byte, contentType string) (*UploadFileRes, error) {
|
||||||
|
client := cfg.clientWithTimeouts().ContentJson()
|
||||||
|
|
||||||
|
// 透传请求头 + 覆盖
|
||||||
|
headers := utils.HeadersFromCtx(ctx, utils.HeadersOptions{ResolveToken: true})
|
||||||
|
if len(cfg.header) > 0 {
|
||||||
|
for k, v := range cfg.header {
|
||||||
|
headers[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if contentType != "" {
|
||||||
|
headers["Content-Type"] = contentType
|
||||||
|
}
|
||||||
|
if len(headers) > 0 {
|
||||||
|
client.SetHeaderMap(headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := client.DoRequest(ctx, http.MethodPost, cfg.endpoint, body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("[OSS上传] 请求失败: %w", err)
|
||||||
|
}
|
||||||
|
defer response.Close()
|
||||||
|
respBody := response.ReadAll()
|
||||||
|
if response.StatusCode < 200 || response.StatusCode >= 300 {
|
||||||
|
return nil, fmt.Errorf("[OSS上传] 状态码异常: %d, body=%s", response.StatusCode, string(respBody))
|
||||||
|
}
|
||||||
|
|
||||||
|
resultStrut := &ghttp.DefaultHandlerResponse{}
|
||||||
|
if err = gconv.Struct(respBody, &resultStrut); err != nil {
|
||||||
|
return nil, errors.New("响应解析失败: " + err.Error())
|
||||||
|
}
|
||||||
|
if resultStrut.Code != 200 && resultStrut.Code != 0 {
|
||||||
|
return nil, errors.New(gconv.String(resultStrut.Message))
|
||||||
|
}
|
||||||
|
res := &UploadFileRes{}
|
||||||
|
if err = gconv.Struct(resultStrut.Data, res); err != nil {
|
||||||
|
return nil, errors.New("数据解析失败: " + err.Error())
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// clientWithTimeouts 克隆客户端并按 Option 调大超时;transport 独立拷贝避免污染全局共享实例。
|
||||||
|
func (c *uploadConfig) clientWithTimeouts() *gclient.Client {
|
||||||
|
client := c.client.Clone()
|
||||||
|
if tr, ok := client.Transport.(*http.Transport); ok && c.headerTimeout > 0 {
|
||||||
|
tr = tr.Clone()
|
||||||
|
tr.ResponseHeaderTimeout = c.headerTimeout
|
||||||
|
client.Transport = tr
|
||||||
|
}
|
||||||
|
if c.timeout > 0 {
|
||||||
|
client = client.Timeout(c.timeout)
|
||||||
|
}
|
||||||
|
return client
|
||||||
|
}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
package oss
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"gitea.redpowerfuture.com/red-future/common/utils"
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ossObjectPathPattern 匹配 MinIO 上传生成的对象路径(不带 http 前缀的相对路径):
|
||||||
|
// /YYYY-MM-DD/32位uuid.扩展名,如 /2026-08-19/1e9d9e48-3f6b-4a2c-8d5e-1f2a3b4c.png
|
||||||
|
var ossObjectPathPattern = regexp.MustCompile(`^/\d{4}-\d{2}-\d{2}/[0-9a-fA-F-]{32}\.[a-zA-Z0-9]{1,10}$`)
|
||||||
|
|
||||||
|
// IsOSSPath 判断字符串是否为 MinIO 对象路径(无 http(s) 前缀)。
|
||||||
|
// 模型网关把结果转存 OSS 后返回该裸路径,消费方据此识别"已是文件路径"而不再重复上传。
|
||||||
|
// 对象命名规则见 oss/minio 的 ensureBucketAndObjectName,格式变化只需改这一处。
|
||||||
|
func IsOSSPath(s string) bool {
|
||||||
|
return ossObjectPathPattern.MatchString(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBucketName 获取 bucket 名称(tenantid-{tenantId},桶归属租户)。
|
||||||
|
func GetBucketName(ctx context.Context) (bucketName string, err error) {
|
||||||
|
user, err := utils.GetUserInfo(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bucketName = fmt.Sprintf("tenantid-%d", user.TenantId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFileAddressPrefix 拼接图片前缀地址({filePrefix}/{bucketName})。
|
||||||
|
func GetFileAddressPrefix(ctx context.Context) (imageUrl string, err error) {
|
||||||
|
bucketName, err := GetBucketName(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
imageUrl = fmt.Sprintf("%s/%s", g.Cfg().MustGet(ctx, "filePrefix").String(), bucketName)
|
||||||
|
return
|
||||||
|
}
|
||||||
+105
@@ -0,0 +1,105 @@
|
|||||||
|
package oss
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitea.redpowerfuture.com/red-future/common/utils"
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
)
|
||||||
|
|
||||||
|
// defaultDownloadTimeout 临时 URL 下载超时(默认 5m,与 model-gateway tempDownloadTimeout 一致)。
|
||||||
|
const defaultDownloadTimeout = 5 * time.Minute
|
||||||
|
|
||||||
|
// TempURLToOSS 处理响应映射取值:模型返回的临时路径(http/https URL)会过期,
|
||||||
|
// 需下载后转存 OSS,用 OSS 完整路径替换原值(从 model-gateway uploadTempURLToOSS 原样搬入)。
|
||||||
|
// - string 且以 http(s):// 开头 → 下载 → 转存 OSS → 返回 OSS 完整路径
|
||||||
|
// - []any → 逐元素处理,任一元素被替换则返回新切片
|
||||||
|
// - 其余类型 / 下载或上传失败 → 原样返回(失败仅记日志,不阻断任务)
|
||||||
|
func TempURLToOSS(ctx context.Context, value any, opts ...Option) any {
|
||||||
|
cfg := buildConfig(opts)
|
||||||
|
do := func(rawURL string) (string, bool) {
|
||||||
|
return uploadSingleURL(ctx, cfg, rawURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := value.(type) {
|
||||||
|
case string:
|
||||||
|
if s, ok := do(v); ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
case []any:
|
||||||
|
out := make([]any, len(v))
|
||||||
|
changed := false
|
||||||
|
for i, e := range v {
|
||||||
|
if s, isStr := e.(string); isStr {
|
||||||
|
if ns, ok := do(s); ok {
|
||||||
|
out[i] = ns
|
||||||
|
changed = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out[i] = e
|
||||||
|
}
|
||||||
|
if changed {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
// uploadSingleURL 下载单个临时 URL 并转存 OSS;返回 OSS 完整路径 + 是否成功替换。
|
||||||
|
func uploadSingleURL(ctx context.Context, cfg *uploadConfig, rawURL string) (string, bool) {
|
||||||
|
rawURL = strings.TrimSpace(rawURL)
|
||||||
|
if !isHTTPURL(rawURL) {
|
||||||
|
return rawURL, false
|
||||||
|
}
|
||||||
|
data, err := downloadTempURL(ctx, defaultDownloadTimeout, rawURL)
|
||||||
|
if err != nil {
|
||||||
|
g.Log().Errorf(ctx, "临时路径下载失败: url=%s err=%v", rawURL, err)
|
||||||
|
return rawURL, false
|
||||||
|
}
|
||||||
|
ossRes, err := uploadFileBytesWithCfg(ctx, cfg, fmt.Sprintf("modelFile:%v%s", time.Now().UnixMilli(), extOfData(data)), data)
|
||||||
|
if err != nil {
|
||||||
|
g.Log().Errorf(ctx, "临时路径转存OSS失败: url=%s err=%v", rawURL, err)
|
||||||
|
return rawURL, false
|
||||||
|
}
|
||||||
|
return ossRes.FileURL, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func isHTTPURL(s string) bool {
|
||||||
|
return strings.HasPrefix(s, "http://") || strings.HasPrefix(s, "https://")
|
||||||
|
}
|
||||||
|
|
||||||
|
// extOfData 按下载内容嗅探文件后缀(不依赖 URL 路径,模型返回的临时路径可能无后缀)。
|
||||||
|
func extOfData(data []byte) string {
|
||||||
|
_, ext := utils.DetectFileType(data)
|
||||||
|
if ext == "" || ext == ".octet-stream" {
|
||||||
|
return ".bin"
|
||||||
|
}
|
||||||
|
return ext
|
||||||
|
}
|
||||||
|
|
||||||
|
// downloadTempURL 带超时下载 URL 内容。
|
||||||
|
func downloadTempURL(ctx context.Context, timeout time.Duration, rawURL string) ([]byte, error) {
|
||||||
|
if timeout <= 0 {
|
||||||
|
timeout = defaultDownloadTimeout
|
||||||
|
}
|
||||||
|
client := &http.Client{Timeout: timeout}
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, rawURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer func() { _ = resp.Body.Close() }()
|
||||||
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
|
return nil, fmt.Errorf("HTTP状态码异常: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
return io.ReadAll(resp.Body)
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package tools
|
||||||
|
|
||||||
|
import "github.com/gogf/gf/v2/util/gconv"
|
||||||
|
|
||||||
|
// 标准入参读取。LLM/外部传入的 map[string]any 中数值可能是 float64、int 或字符串,
|
||||||
|
// 直接类型断言极易静默失败,统一走 gconv 强转。
|
||||||
|
|
||||||
|
// HasArg 判断参数是否存在且非 nil
|
||||||
|
func HasArg(args map[string]any, key string) bool {
|
||||||
|
v, ok := args[key]
|
||||||
|
return ok && v != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArgString 读取字符串参数
|
||||||
|
func ArgString(args map[string]any, key string) string {
|
||||||
|
return gconv.String(args[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArgInt 读取整数参数(自动兼容 float64/int/字符串)
|
||||||
|
func ArgInt(args map[string]any, key string) int {
|
||||||
|
return gconv.Int(args[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArgFloat64 读取浮点参数
|
||||||
|
func ArgFloat64(args map[string]any, key string) float64 {
|
||||||
|
return gconv.Float64(args[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArgBool 读取布尔参数
|
||||||
|
func ArgBool(args map[string]any, key string) bool {
|
||||||
|
return gconv.Bool(args[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArgSlice 读取切片参数
|
||||||
|
func ArgSlice(args map[string]any, key string) []any {
|
||||||
|
return gconv.SliceAny(args[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArgStrings 读取字符串切片参数
|
||||||
|
func ArgStrings(args map[string]any, key string) []string {
|
||||||
|
return gconv.Strings(args[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArgMap 读取对象参数
|
||||||
|
func ArgMap(args map[string]any, key string) map[string]any {
|
||||||
|
return gconv.Map(args[key])
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// Package tools 提供共享的模型工具框架:工具定义、注册表与结构化返回。
|
||||||
|
//
|
||||||
|
// 数据模型对齐 MCP 的 Tool 定义(name + description + inputSchema)。
|
||||||
|
// 工具是**给模型 function calling 用**的通用能力:模型在推理中动态决定是否调用,
|
||||||
|
// 消费方依赖 Server 接口(List/Call)而非注册表本身,未来可无缝替换为远程 MCP Server 客户端。
|
||||||
|
//
|
||||||
|
// # 分层
|
||||||
|
//
|
||||||
|
// - tools(本包):Tool 定义 + 全局注册表 Register + Server 接口,纯框架,不依赖任何业务包
|
||||||
|
// - 业务侧:各服务在自身代码中定义**通用**工具(含执行实现 Func),通过 init() 注册进共享注册表;
|
||||||
|
// 工具的"使用"(如 ReAct 执行循环)也由业务服务基于 Server.Call 自行编排
|
||||||
|
//
|
||||||
|
// 非通用、绑定到固定业务场景的处理逻辑(如工作流节点的前后置钩子)**不属于工具**,
|
||||||
|
// 应由各业务在自己的编排层维护,不进入本注册表。
|
||||||
|
//
|
||||||
|
// # 消费路径
|
||||||
|
//
|
||||||
|
// 注册表内工具由各业务服务的模型调用方消费:模型 function calling 模式下
|
||||||
|
// 经 Server.List 获取全部工具定义交给模型,按返回的 ToolCall 经 Server.Call 执行。
|
||||||
|
package tools
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package tools
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// ToolResult 工具统一返回结构。Code=0 表示成功,非 0 为业务/内部错误码;
|
||||||
|
// 调用方(工作流前置/后置钩子、模型 function calling)统一按 Code 判断结果。
|
||||||
|
type ToolResult struct {
|
||||||
|
Code int `json:"code"` // 0=成功,非 0=失败
|
||||||
|
Message string `json:"message"` // 成功说明或错误信息
|
||||||
|
Data any `json:"data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标准错误码。业务工具可自定义扩展 >500 的错误码。
|
||||||
|
const (
|
||||||
|
CodeOK = 0 // 成功
|
||||||
|
CodeInvalidArgs = 400 // 入参缺失或格式错误
|
||||||
|
CodeNotFound = 404 // 工具/数据不存在
|
||||||
|
CodeInternal = 500 // 内部执行错误
|
||||||
|
)
|
||||||
|
|
||||||
|
// OK 构造成功结果
|
||||||
|
func OK(data any) ToolResult {
|
||||||
|
return ToolResult{Code: CodeOK, Data: data}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fail 构造失败结果,message 支持 fmt 格式化
|
||||||
|
func Fail(code int, format string, args ...any) ToolResult {
|
||||||
|
return ToolResult{Code: code, Message: fmt.Sprintf(format, args...)}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package tools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Server 提供工具的发现与执行能力,对应 MCP 中 Server 侧的 tools/list 与 tools/call。
|
||||||
|
// 消费方(工作流前置/后置钩子、/tool/list 接口、模型 function calling)依赖该接口而非注册表本身,
|
||||||
|
// 未来可无缝替换为远程 MCP Server 客户端实现。
|
||||||
|
type Server interface {
|
||||||
|
// List 返回全部已注册工具定义,按名称排序
|
||||||
|
List(ctx context.Context) ([]*Tool, error)
|
||||||
|
// Call 按名称调用工具。业务失败通过返回结果的 Code 表达,
|
||||||
|
// error 仅用于基础设施异常(如 ctx 取消)。
|
||||||
|
Call(ctx context.Context, name string, args map[string]any) (ToolResult, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// localServer 基于本地注册表的 Server 实现
|
||||||
|
type localServer struct{}
|
||||||
|
|
||||||
|
// Default 默认本地 Server。未来接远程 MCP 时,替换该变量即可,业务侧零改动。
|
||||||
|
var Default Server = localServer{}
|
||||||
|
|
||||||
|
func (localServer) List(ctx context.Context) ([]*Tool, error) {
|
||||||
|
list := make([]*Tool, 0, len(registry))
|
||||||
|
for _, t := range registry {
|
||||||
|
list = append(list, t)
|
||||||
|
}
|
||||||
|
sort.Slice(list, func(i, j int) bool { return list[i].Name < list[j].Name })
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (localServer) Call(ctx context.Context, name string, args map[string]any) (ToolResult, error) {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return ToolResult{}, err
|
||||||
|
}
|
||||||
|
tool := registry[name]
|
||||||
|
if tool == nil || tool.Func == nil {
|
||||||
|
return Fail(CodeNotFound, "工具[%s]不存在或未实现", name), nil
|
||||||
|
}
|
||||||
|
res, err := tool.Func(ctx, args)
|
||||||
|
if err != nil {
|
||||||
|
return Fail(CodeInternal, "工具[%s]执行失败: %v", name, err), nil
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package tools
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
// Tool 对应 MCP 的 Tool 数据结构:name + description + inputSchema。
|
||||||
|
// 模型 function calling 通过 InputSchema 感知入参,推理中按需调用 Func。
|
||||||
|
// 若未来接远程 MCP Server,Func 由客户端统一实现,本结构不变。
|
||||||
|
type Tool struct {
|
||||||
|
Name string // 工具唯一标识
|
||||||
|
Description string // 用途说明(模型必读,用于决定何时调用)
|
||||||
|
Parameters map[string]any // 入参 JSON Schema
|
||||||
|
Func func(ctx context.Context, args map[string]any) (ToolResult, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// registry 工具注册表
|
||||||
|
var registry = make(map[string]*Tool)
|
||||||
|
|
||||||
|
// Register 注册工具,同名覆盖
|
||||||
|
func Register(list ...*Tool) {
|
||||||
|
for _, t := range list {
|
||||||
|
if t == nil || t.Name == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
registry[t.Name] = t
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DetectFileType 根据二进制内容推断 contentType + 扩展名(尽量稳定)。
|
||||||
|
// 纯 stdlib 实现(http.DetectContentType + 常见类型映射),不依赖模型网关。
|
||||||
|
// 本函数是统一实现;model-gateway/common/util/files.go 的本地副本后续委托到此处。
|
||||||
|
func DetectFileType(data []byte) (contentType string, ext string) {
|
||||||
|
if len(data) == 0 {
|
||||||
|
return "application/octet-stream", ""
|
||||||
|
}
|
||||||
|
ct := http.DetectContentType(data)
|
||||||
|
// DetectContentType 可能带 charset 等参数:text/plain; charset=utf-8
|
||||||
|
if idx := strings.Index(ct, ";"); idx > 0 {
|
||||||
|
ct = strings.TrimSpace(ct[:idx])
|
||||||
|
}
|
||||||
|
switch ct {
|
||||||
|
case "audio/mpeg":
|
||||||
|
return ct, ".mp3"
|
||||||
|
case "audio/wave", "audio/wav", "audio/x-wav":
|
||||||
|
return ct, ".wav"
|
||||||
|
case "video/mp4":
|
||||||
|
return ct, ".mp4"
|
||||||
|
case "image/png":
|
||||||
|
return ct, ".png"
|
||||||
|
case "image/jpeg":
|
||||||
|
return ct, ".jpg"
|
||||||
|
case "application/pdf":
|
||||||
|
return ct, ".pdf"
|
||||||
|
case "text/plain":
|
||||||
|
return ct, ".txt"
|
||||||
|
case "application/json":
|
||||||
|
return ct, ".json"
|
||||||
|
default:
|
||||||
|
// 兜底:尝试从 ct 截取 subtype 作为后缀(例如 application/json)
|
||||||
|
if parts := strings.Split(ct, "/"); len(parts) == 2 {
|
||||||
|
sub := parts[1]
|
||||||
|
// 避免出现 "plain; charset=utf-8" 之类的后缀
|
||||||
|
if idx := strings.Index(sub, ";"); idx > 0 {
|
||||||
|
sub = strings.TrimSpace(sub[:idx])
|
||||||
|
}
|
||||||
|
return ct, "." + sub
|
||||||
|
}
|
||||||
|
return ct, ""
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HeadersOptions 控制请求头透传的差异层。基础两层恒有:
|
||||||
|
// 1. 透传 HTTP 请求头(含 Authorization/X-User-Info)
|
||||||
|
// 2. X-User-Info 为空且 ctx 携带 user(恢复续跑/异步任务注入的合成用户)时补 X-User-Info
|
||||||
|
type HeadersOptions struct {
|
||||||
|
ResolveToken bool // X-User-Info 仍为空时,解析调用方 token 得到用户注入(直连场景)
|
||||||
|
TokenFromQuery bool // Authorization 为空时从 URL query ?token= 补 Bearer(WS 握手)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeadersFromCtx 统一构造调用方请求头透传 map。全项目唯一的拼头入口
|
||||||
|
//
|
||||||
|
// 放这里而非 common/http:本函数只依赖 GetUserInfo(同包),与 gclient/gsvc/consul/jaeger 客户端
|
||||||
|
// 基础设施零耦合;common/http 空导入 common/consul、common/jaeger(init 里 g.Cfg().MustGet 无默认值,
|
||||||
|
// 无配置文件环境会 panic),common/oss 等消费方特意避开它,放 utils 依赖链干净。
|
||||||
|
func HeadersFromCtx(ctx context.Context, opts ...HeadersOptions) map[string]string {
|
||||||
|
op := HeadersOptions{}
|
||||||
|
if len(opts) > 0 {
|
||||||
|
op = opts[0]
|
||||||
|
}
|
||||||
|
headers := make(map[string]string)
|
||||||
|
if r := g.RequestFromCtx(ctx); r != nil {
|
||||||
|
for k, v := range r.Request.Header {
|
||||||
|
if len(v) > 0 {
|
||||||
|
headers[k] = v[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if headers["X-User-Info"] == "" {
|
||||||
|
if u := ctx.Value("user"); !g.IsNil(u) {
|
||||||
|
headers["X-User-Info"] = gconv.String(u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if op.TokenFromQuery && headers["Authorization"] == "" {
|
||||||
|
if r := g.RequestFromCtx(ctx); r != nil {
|
||||||
|
if t := r.Request.URL.Query().Get("token"); t != "" {
|
||||||
|
headers["Authorization"] = "Bearer " + t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if op.ResolveToken && headers["X-User-Info"] == "" {
|
||||||
|
if user, err := GetUserInfo(ctx); err == nil && user != nil {
|
||||||
|
headers["X-User-Info"] = gconv.String(user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return headers
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/tidwall/sjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsFlatMap 递归判断 map 是否扁平化
|
||||||
|
func IsFlatMap(m map[string]interface{}) bool {
|
||||||
|
for _, v := range m {
|
||||||
|
switch val := v.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
return false
|
||||||
|
case []interface{}:
|
||||||
|
for _, item := range val {
|
||||||
|
if _, ok := item.(map[string]interface{}); ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnFlatBySjson 将扁平路径映射还原为嵌套 JSON
|
||||||
|
func UnFlatBySjson(flatMap map[string]interface{}) (map[string]interface{}, error) {
|
||||||
|
raw := "{}"
|
||||||
|
for path, val := range flatMap {
|
||||||
|
var err error
|
||||||
|
raw, err = sjson.Set(raw, path, val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("sjson set path %s failed: %w", path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var result map[string]interface{}
|
||||||
|
if err := json.Unmarshal([]byte(raw), &result); err != nil {
|
||||||
|
return nil, fmt.Errorf("parse final json failed: %w", err)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
// Package utils 提供统一的 Redis 计数原语(与 redislock 的「互斥锁」互补,本包是「计数」一族)。
|
||||||
|
//
|
||||||
|
// 只保留 model-gateway 实际业务场景用到、且全仓有调用方的两个原语:
|
||||||
|
// - Semaphore:INCR 信号量(model-gateway/service/queue/semaphore.go),并发额度计数
|
||||||
|
// —— asyncWorker 按模型并发上限抢信号量,超限放回队列(task/worker.go:51);
|
||||||
|
// - SafeDecr:安全回减(model-gateway/model_call_service.go decrSlotLua),计数防负
|
||||||
|
// —— 并发名额释放、超限回滚时原子夹在 >=0。
|
||||||
|
//
|
||||||
|
// (ZSET 槽位幂等闸门 queue_gate.go、固定窗口限流 rate_limiter.go 全仓无调用方,已删除;后续用到再加。)
|
||||||
|
//
|
||||||
|
// 与 redislock 一致,本包不写 Lua 脚本:
|
||||||
|
// - 多步原子操作(判定 + 写)经 go-redis WATCH/MULTI/EXEC 事务组合原生命令,
|
||||||
|
// 通过 gogf 官方 escape hatch GetAdapter().Client() 取底层客户端执行。
|
||||||
|
//
|
||||||
|
// 组合模式(如 model-gateway reserveSlot 的 锁+计数):
|
||||||
|
// redislock.WithLock + 本包 SafeDecr。
|
||||||
|
//
|
||||||
|
// 兼容红线:只用 gf v2.9.5 已有 API(Set/Get/Incr/Decr、GetAdapter().Client())。
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
goredis "github.com/redis/go-redis/v9"
|
||||||
|
)
|
||||||
|
|
||||||
|
// errSkip 内部信号:WATCH 事务里判定不通过,不执行写。
|
||||||
|
// 调用方据 errors.Is(err, errSkip) 视为「本次操作失败但非异常」。
|
||||||
|
var errSkip = errors.New("rediscount: 条件不满足,跳过写入")
|
||||||
|
|
||||||
|
// rawClient 取底层 go-redis 客户端(gogf 官方 escape hatch)。
|
||||||
|
// 各服务 main.go 空导入 contrib/nosql/redis/v2 后 Adapter 为 go-redis 实现,
|
||||||
|
// Client() 返回 redis.UniversalClient(单节点 / 哨兵 / 集群均实现 Watch)。
|
||||||
|
func rawClient() (goredis.UniversalClient, error) {
|
||||||
|
universal, ok := g.Redis().GetAdapter().Client().(goredis.UniversalClient)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("redis 底层客户端非 UniversalClient,无法执行 WATCH 原子事务")
|
||||||
|
}
|
||||||
|
return universal, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// tx 在 WATCH/MULTI/EXEC 事务里执行 fn(先读后写,无 Lua):
|
||||||
|
// fn 里先发读命令判定,命中条件后经 tx.TxPipelined 排队写命令,EXEC 原子提交。
|
||||||
|
// 判定不通过时 fn 返回 errSkip(不写);WATCH 冲突(TxFailedErr,读与写之间被并发改动)自动重试。
|
||||||
|
func tx(ctx context.Context, key string, fn func(tx *goredis.Tx) error) error {
|
||||||
|
c, err := rawClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
err := c.Watch(ctx, func(tx *goredis.Tx) error { return fn(tx) }, key)
|
||||||
|
if errors.Is(err, goredis.TxFailedErr) {
|
||||||
|
continue // 冲突,重读重试
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pipedInt 取出事务写队列中第 n 条命令(INCR/DECR)的执行结果。
|
||||||
|
func pipedInt(cmds []goredis.Cmder, n int) (int64, error) {
|
||||||
|
return cmds[n].(*goredis.IntCmd).Result()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SemaphoreAcquire 获取并发额度:WATCH key → GET 判满 → MULTI/INCR + 首设 EXPIRE/EXEC。
|
||||||
|
// 原子判定未满才 INCR;首次(计数从 0/不存在起)顺手设 TTL 自动回收防泄漏;超限不写、返回 false。
|
||||||
|
// 对应 model-gateway asyncWorker 按模型并发上限抢信号量。max<=0 表示不限制;ttlSeconds<=0 时按 3600 兜底。
|
||||||
|
func SemaphoreAcquire(ctx context.Context, key string, max int, ttlSeconds int64) (bool, error) {
|
||||||
|
if max <= 0 {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
if ttlSeconds <= 0 {
|
||||||
|
ttlSeconds = 3600
|
||||||
|
}
|
||||||
|
err := tx(ctx, key, func(tx *goredis.Tx) error {
|
||||||
|
current, err := tx.Get(ctx, key).Int64()
|
||||||
|
if errors.Is(err, goredis.Nil) {
|
||||||
|
current = 0
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if current >= int64(max) {
|
||||||
|
return errSkip // 已满
|
||||||
|
}
|
||||||
|
_, err = tx.TxPipelined(ctx, func(pipe goredis.Pipeliner) error {
|
||||||
|
pipe.Incr(ctx, key)
|
||||||
|
if current == 0 {
|
||||||
|
pipe.Expire(ctx, key, time.Duration(ttlSeconds)*time.Second)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if errors.Is(err, errSkip) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("获取并发额度失败: %w", err)
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SemaphoreRelease 释放并发额度(幂等,计数归零自动删除 key)。
|
||||||
|
// WATCH key → GET → MULTI:计数>1 仅 DECR;==1 时 DECR+DEL;<=0 直接 DEL(清理残留,不造负)。
|
||||||
|
// 全部原子,避免「DECR 后发现归零、中间被并发 INCR 抢占、DEL 误删新额度」的丢失更新。
|
||||||
|
func SemaphoreRelease(ctx context.Context, key string) error {
|
||||||
|
err := tx(ctx, key, func(tx *goredis.Tx) error {
|
||||||
|
current, err := tx.Get(ctx, key).Int64()
|
||||||
|
if errors.Is(err, goredis.Nil) {
|
||||||
|
current = 0
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = tx.TxPipelined(ctx, func(pipe goredis.Pipeliner) error {
|
||||||
|
switch {
|
||||||
|
case current <= 0:
|
||||||
|
pipe.Del(ctx, key)
|
||||||
|
case current == 1:
|
||||||
|
pipe.Decr(ctx, key)
|
||||||
|
pipe.Del(ctx, key)
|
||||||
|
default:
|
||||||
|
pipe.Decr(ctx, key)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("释放并发额度失败: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SafeDecr 安全回减:WATCH key → GET,仅当 key 存在且值>0 才 MULTI/DECR/EXEC,
|
||||||
|
// 杜绝 Redis DECR 对缺失 Key 按 0-1 处理把计数造负。
|
||||||
|
// 对应 model-gateway decrSlot:并发名额释放 / 超限回滚时原子夹在 >=0。
|
||||||
|
// 返回回减后的计数值;未回减(key 不存在或已为 0)返回 -1。
|
||||||
|
func SafeDecr(ctx context.Context, key string) (int64, error) {
|
||||||
|
var result int64
|
||||||
|
err := tx(ctx, key, func(tx *goredis.Tx) error {
|
||||||
|
current, err := tx.Get(ctx, key).Int64()
|
||||||
|
if errors.Is(err, goredis.Nil) {
|
||||||
|
return errSkip // 不存在,不写
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if current <= 0 {
|
||||||
|
return errSkip // 已为 0,不造负
|
||||||
|
}
|
||||||
|
cmds, err := tx.TxPipelined(ctx, func(pipe goredis.Pipeliner) error {
|
||||||
|
pipe.Decr(ctx, key)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
result, err = pipedInt(cmds, 0)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if errors.Is(err, errSkip) {
|
||||||
|
return -1, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
// Package utils 提供统一的 Redis 分布式锁(防并发互斥)。
|
||||||
|
//
|
||||||
|
// 收敛全仓「SET key token EX ttl NX + 归属校验释放」范式
|
||||||
|
// (此前 ai-agent / shop-user-trade / model-gateway 三处逐字复制)。
|
||||||
|
// 只暴露一个入口:
|
||||||
|
// - WithLock:抢锁 + 自动续期 + 临界区 fn + 返回时保证释放。
|
||||||
|
//
|
||||||
|
// 正确性要点:
|
||||||
|
// - value 存 uuid token 标识持有者(不存 true),key 标识资源;
|
||||||
|
// - 释放 / 续期均按 token 比对归属:锁 TTL 过期易主后,旧持有者的释放不会误删新持有者的锁,
|
||||||
|
// 续期也不会把锁无限续到他人头上;
|
||||||
|
// - 归属比对经 go-redis WATCH/MULTI/EXEC 原子事务完成(通过 gogf 官方 escape hatch
|
||||||
|
// GetAdapter().Client() 取底层客户端),不手写 Lua 脚本。
|
||||||
|
//
|
||||||
|
// 兼容红线:本包只用 gf v2.9.5 已有 API(Set+SetOption{NX,EX}、GetAdapter().Client())。
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gogf/gf/v2/database/gredis"
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
goredis "github.com/redis/go-redis/v9"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Lock 分布式锁(防并发互斥),委托 common/redislock.WithLock(4 次尝试,间隔 500ms)。
|
||||||
|
// 释放 / 续期按 token 归属比对(WATCH/MULTI/EXEC 原子事务,不写 Lua):
|
||||||
|
// 锁 TTL 过期易主后,旧持有者的释放不会误删新持有者的锁。
|
||||||
|
// fn 返回 err 时 success=false、err 上抛。
|
||||||
|
func Lock(ctx context.Context, key string, expireSeconds int64, fn func(ctx context.Context) error) (success bool, err error) {
|
||||||
|
_, err = WithLock(ctx, key, expireSeconds, fn, 4)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithLock 函数式锁:抢锁(可选重试次数),成功后自动续期(默认超过一半 TTL 就续期),
|
||||||
|
// 执行 fn,返回时停止续期并保证释放(WATCH/MULTI 按 token 归属释放,防误删他人锁)。
|
||||||
|
//
|
||||||
|
// retryTimes 传了循环次数:最多尝试那么多次,仍抢不到返回锁占用错误;
|
||||||
|
// retryTimes 未传:无限等待,一直重试直到抢到锁或 ctx 取消。
|
||||||
|
// 两次尝试间隔固定 500ms。
|
||||||
|
//
|
||||||
|
// 返回值:
|
||||||
|
// - (true, nil):抢到锁并执行成功;
|
||||||
|
// - (true, fn 的 err):抢到锁、fn 执行失败(业务错误由 err 单独表达);
|
||||||
|
// - (false, err):重试次数耗尽(锁占用)、Redis 故障或 ctx 被取消,均以 err 表达。
|
||||||
|
//
|
||||||
|
// 续期与释放均用 context.WithoutCancel(ctx):即使 fn 中途 ctx 被取消,锁仍能正常续期并释放,
|
||||||
|
// 不会残留到 TTL 造成下一个持有者等待。
|
||||||
|
func WithLock(ctx context.Context, key string, ttlSeconds int64, fn func(ctx context.Context) error, retryTimes ...int) (bool, error) {
|
||||||
|
maxRetries := -1
|
||||||
|
if len(retryTimes) > 0 {
|
||||||
|
maxRetries = retryTimes[0]
|
||||||
|
}
|
||||||
|
l := &lock{key: key, token: uuid.NewString(), ttl: ttlSeconds}
|
||||||
|
|
||||||
|
for attempt := 0; ; attempt++ {
|
||||||
|
if maxRetries >= 0 && attempt >= maxRetries {
|
||||||
|
return false, errors.New("redis lock busy")
|
||||||
|
}
|
||||||
|
ok, err := l.acquire(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return false, ctx.Err()
|
||||||
|
case <-time.After(500 * time.Millisecond):
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stop := make(chan struct{})
|
||||||
|
go l.renewLoop(context.WithoutCancel(ctx), stop)
|
||||||
|
defer func() {
|
||||||
|
close(stop)
|
||||||
|
_ = l.release(context.WithoutCancel(ctx))
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := fn(ctx); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// lock 单次锁会话:token 标识持有者身份,释放 / 续期按 token 原子比对。
|
||||||
|
// 不导出——生命周期全部由 WithLock 编排。
|
||||||
|
type lock struct {
|
||||||
|
key string
|
||||||
|
token string
|
||||||
|
ttl int64 // 秒
|
||||||
|
}
|
||||||
|
|
||||||
|
// acquire 抢锁;返回 true 表示抢到(SET NX 成功)。
|
||||||
|
// 注意:gogf 新版 SetNX(ctx,key,value) 不接收 TTL 参数,直接 SETNX 会永不过期(崩溃后死锁);
|
||||||
|
// 故改用 Set + SetOption{NX,TTLOption{EX}},原子地执行 `SET key token EX <ttl> NX`。
|
||||||
|
func (l *lock) acquire(ctx context.Context) (bool, error) {
|
||||||
|
r, err := g.Redis().Set(ctx, l.key, l.token, gredis.SetOption{
|
||||||
|
TTLOption: gredis.TTLOption{EX: &l.ttl},
|
||||||
|
NX: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
// SET NX 失败时 Redis 返回空回复(nil),gogf 转为值 nil 的 gvar;成功时返回 "OK"
|
||||||
|
return !r.IsNil(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// release 释放锁:WATCH key → GET 比对 token → MULTI/DEL/EXEC,仅当仍为本锁 token 时删除。
|
||||||
|
// 非持有者调用是 no-op:锁过期易主后,旧持有者的释放不会删掉新持有者的锁。
|
||||||
|
func (l *lock) release(ctx context.Context) error {
|
||||||
|
return l.compareAndWrite(ctx, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// renew 续期:WATCH key → GET 比对 token → MULTI/EXPIRE/EXEC,仅当仍为本锁 token 时重置 TTL。
|
||||||
|
// 锁已易主时 no-op,避免续到他人锁上(把新持有者的锁无限延长)。
|
||||||
|
func (l *lock) renew(ctx context.Context) error {
|
||||||
|
return l.compareAndWrite(ctx, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// compareAndWrite 归属比对 + 原子写(WATCH/MULTI/EXEC,无 Lua):
|
||||||
|
// - 锁不存在(已过期 / 被删):no-op;
|
||||||
|
// - key 值 ≠ 本锁 token(已易主):no-op;
|
||||||
|
// - key 值 == 本锁 token:renew=true 时重置 TTL,否则删除。
|
||||||
|
//
|
||||||
|
// WATCH 保证:比对与写之间若 key 被其他客户端改动,EXEC 会被服务端中止(返回空),写不生效。
|
||||||
|
// 经 gogf 官方 escape hatch(GetAdapter().Client())取底层 go-redis 客户端执行原子事务;
|
||||||
|
// 各服务 main.go 空导入 contrib/nosql/redis/v2 后 Adapter 为 go-redis 实现,
|
||||||
|
// Client() 返回 redis.UniversalClient(单节点 / 哨兵 / 集群均实现 Watch)。
|
||||||
|
func (l *lock) compareAndWrite(ctx context.Context, renew bool) error {
|
||||||
|
universal, ok := g.Redis().GetAdapter().Client().(goredis.UniversalClient)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("redis 底层客户端非 UniversalClient,无法执行 WATCH 原子事务")
|
||||||
|
}
|
||||||
|
return universal.Watch(ctx, func(tx *goredis.Tx) error {
|
||||||
|
val, err := tx.Get(ctx, l.key).Result()
|
||||||
|
if errors.Is(err, goredis.Nil) {
|
||||||
|
return nil // 锁已过期 / 被删
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if val != l.token {
|
||||||
|
return nil // 锁已易主,不动他人锁
|
||||||
|
}
|
||||||
|
_, err = tx.TxPipelined(ctx, func(pipe goredis.Pipeliner) error {
|
||||||
|
if renew {
|
||||||
|
pipe.Expire(ctx, l.key, time.Duration(l.ttl)*time.Second)
|
||||||
|
} else {
|
||||||
|
pipe.Del(ctx, l.key)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}, l.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// renewLoop 自动续期看门狗:每 ttl/2 续一次(「超过一半就续期」)。
|
||||||
|
// 续期失败仅记日志,不打断临界区(WATCH 事务在锁易主时会因 token 不匹配安全地 no-op)。
|
||||||
|
// WithLock 内部启动,临界区结束即停止。
|
||||||
|
func (l *lock) renewLoop(ctx context.Context, stop <-chan struct{}) {
|
||||||
|
ticker := time.NewTicker(time.Duration(l.ttl) * time.Second / 2)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-stop:
|
||||||
|
return
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
if err := l.renew(context.WithoutCancel(ctx)); err != nil {
|
||||||
|
g.Log().Warningf(ctx, "redislock 续期失败: key=%s err=%v", l.key, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
-61
@@ -3,7 +3,6 @@ package utils
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -14,12 +13,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.redpowerfuture.com/red-future/common/beans"
|
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||||
"github.com/gogf/gf/v2/container/gvar"
|
|
||||||
"github.com/gogf/gf/v2/database/gredis"
|
"github.com/gogf/gf/v2/database/gredis"
|
||||||
"github.com/gogf/gf/v2/errors/gcode"
|
"github.com/gogf/gf/v2/errors/gcode"
|
||||||
"github.com/gogf/gf/v2/errors/gerror"
|
"github.com/gogf/gf/v2/errors/gerror"
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
"github.com/gogf/gf/v2/os/glog"
|
|
||||||
"github.com/gogf/gf/v2/os/gtime"
|
"github.com/gogf/gf/v2/os/gtime"
|
||||||
"github.com/gogf/gf/v2/util/gconv"
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
"github.com/tiger1103/gfast-token/gftoken"
|
"github.com/tiger1103/gfast-token/gftoken"
|
||||||
@@ -107,7 +104,8 @@ func GetUserInfo(ctx context.Context) (user *beans.User, err error) {
|
|||||||
|
|
||||||
if !g.IsNil(ctx.Value("token")) {
|
if !g.IsNil(ctx.Value("token")) {
|
||||||
var tokenData *gftoken.TokenData
|
var tokenData *gftoken.TokenData
|
||||||
tokenData, _, err = gft.GetTokenData(ctx, ctx.Value("token").(string))
|
tk := ctx.Value("token").(string)
|
||||||
|
tokenData, _, err = gft.GetTokenData(ctx, tk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return user, gerror.Wrap(err, "ctx token 解析失败")
|
return user, gerror.Wrap(err, "ctx token 解析失败")
|
||||||
}
|
}
|
||||||
@@ -117,7 +115,8 @@ func GetUserInfo(ctx context.Context) (user *beans.User, err error) {
|
|||||||
}
|
}
|
||||||
} else if g.RequestFromCtx(ctx) != nil {
|
} else if g.RequestFromCtx(ctx) != nil {
|
||||||
// 解析 token
|
// 解析 token
|
||||||
data, err = gft.ParseToken(g.RequestFromCtx(ctx))
|
req := g.RequestFromCtx(ctx)
|
||||||
|
data, err = gft.ParseToken(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return user, gerror.Wrap(err, "token 解析失败")
|
return user, gerror.Wrap(err, "token 解析失败")
|
||||||
}
|
}
|
||||||
@@ -389,61 +388,6 @@ func intPow10(n int) int {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFileAddressPrefix 拼接图片前缀地址
|
|
||||||
func GetFileAddressPrefix(ctx context.Context) (imageUrl string, err error) {
|
|
||||||
// 拼接图片前缀地址
|
|
||||||
bucketName, err := GetBucketName(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
imageUrl = fmt.Sprintf("%s/%s", g.Cfg().MustGet(ctx, "filePrefix").String(), bucketName)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBucketName 获取bucket名称
|
|
||||||
func GetBucketName(ctx context.Context) (bucketName string, err error) {
|
|
||||||
user, err := GetUserInfo(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
bucketName = fmt.Sprintf("tenantid-%d", user.TenantId)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lock 分布式锁
|
|
||||||
func Lock(ctx context.Context, key string, expireSeconds int64, fn func(ctx context.Context) error) (success bool, err error) {
|
|
||||||
limit := 3
|
|
||||||
LOOP:
|
|
||||||
if limit < 0 {
|
|
||||||
return false, errors.New("锁重试次数耗尽")
|
|
||||||
}
|
|
||||||
limit--
|
|
||||||
var val *gvar.Var
|
|
||||||
if val, err = g.Redis().Set(ctx, key, true, gredis.SetOption{
|
|
||||||
TTLOption: gredis.TTLOption{
|
|
||||||
EX: &expireSeconds,
|
|
||||||
},
|
|
||||||
NX: true,
|
|
||||||
}); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if val.Bool() {
|
|
||||||
defer func(ctx context.Context, key string) {
|
|
||||||
if _, err = g.Redis().Del(ctx, key); err != nil {
|
|
||||||
glog.Errorf(ctx, "redis client Del error: %v", err)
|
|
||||||
}
|
|
||||||
}(ctx, key)
|
|
||||||
if err = fn(ctx); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
goto LOOP
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsLocalIP 判断是否是本地IP
|
// IsLocalIP 判断是否是本地IP
|
||||||
func IsLocalIP(ip string) bool {
|
func IsLocalIP(ip string) bool {
|
||||||
addrs, err := net.InterfaceAddrs()
|
addrs, err := net.InterfaceAddrs()
|
||||||
@@ -583,7 +527,8 @@ func GetLocalBaseURL(ctx context.Context) string {
|
|||||||
|
|
||||||
// GetCallbackURL 获取回调地址(完整 URL)
|
// GetCallbackURL 获取回调地址(完整 URL)
|
||||||
func GetCallbackURL(ctx context.Context, path string) string {
|
func GetCallbackURL(ctx context.Context, path string) string {
|
||||||
baseURL := GetLocalBaseURL(ctx)
|
//baseURL := GetLocalBaseURL(ctx)
|
||||||
|
baseURL := "http://" + GetLocalAddress(ctx)
|
||||||
// 确保 path 以 / 开头
|
// 确保 path 以 / 开头
|
||||||
if !strings.HasPrefix(path, "/") {
|
if !strings.HasPrefix(path, "/") {
|
||||||
path = "/" + path
|
path = "/" + path
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gogf/gf/v2/encoding/gjson"
|
||||||
|
"github.com/gogf/gf/v2/os/glog"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WsConnection 单个WebSocket连接,Metadata 存放业务自定义数据
|
||||||
|
type WsConnection struct {
|
||||||
|
SessionId string
|
||||||
|
Conn *websocket.Conn
|
||||||
|
Headers map[string]string
|
||||||
|
Metadata sync.Map // 业务数据:FlowId, execCancel 等
|
||||||
|
|
||||||
|
writeMu sync.Mutex // 保护 websocket.Conn 并发写(WriteMessage + WriteControl)
|
||||||
|
closeCancel context.CancelFunc
|
||||||
|
closed int32
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMeta 设置业务元数据
|
||||||
|
func (c *WsConnection) SetMeta(key string, value interface{}) {
|
||||||
|
c.Metadata.Store(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMeta 获取业务元数据
|
||||||
|
func (c *WsConnection) GetMeta(key string) (interface{}, bool) {
|
||||||
|
return c.Metadata.Load(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMetaT 泛型版 GetMeta,省去外部类型断言
|
||||||
|
func GetMetaT[T any](c *WsConnection, key string) (T, bool) {
|
||||||
|
val, ok := c.Metadata.Load(key)
|
||||||
|
if !ok {
|
||||||
|
var zero T
|
||||||
|
return zero, false
|
||||||
|
}
|
||||||
|
t, ok := val.(T)
|
||||||
|
return t, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsClosed 连接是否已关闭
|
||||||
|
func (c *WsConnection) IsClosed() bool {
|
||||||
|
return atomic.LoadInt32(&c.closed) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteControl 带写锁保护的 WriteControl,用于心跳 Ping / Pong / Close帧
|
||||||
|
func (c *WsConnection) WriteControl(msgType int, data []byte, deadline time.Time) error {
|
||||||
|
c.writeMu.Lock()
|
||||||
|
defer c.writeMu.Unlock()
|
||||||
|
_ = c.Conn.SetWriteDeadline(deadline)
|
||||||
|
return c.Conn.WriteControl(msgType, data, deadline)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteJSON 业务层外部写入入口,共享 writeMu 与心跳/Pong 互斥
|
||||||
|
func (c *WsConnection) WriteJSON(data interface{}) error {
|
||||||
|
jsonBytes, err := gjson.Encode(data)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf(context.Background(), "json encode failed: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.writeMu.Lock()
|
||||||
|
_ = c.Conn.SetWriteDeadline(time.Now().Add(30 * time.Second))
|
||||||
|
err = c.Conn.WriteMessage(websocket.TextMessage, jsonBytes)
|
||||||
|
c.writeMu.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
glog.Debugf(context.Background(), "websocket write failed: %v", err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package websocket
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultReadTimeout = 90 * time.Second
|
||||||
|
DefaultWriteTimeout = 10 * time.Second
|
||||||
|
DefaultHeartbeatInterval = 30 * time.Second
|
||||||
|
DefaultWorkerPoolSize = 50
|
||||||
|
DefaultMaxConnections = 2000
|
||||||
|
DefaultConnKeyPrefix = "ws:"
|
||||||
|
)
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
netHttp "net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MessageHandler 业务消息处理函数
|
||||||
|
type MessageHandler func(ctx context.Context, conn *WsConnection, payload interface{})
|
||||||
|
|
||||||
|
// WsMessage 通用入站消息
|
||||||
|
type WsMessage struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Payload interface{} `json:"payload,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WsPushMsg 通用出站推送消息(业务可自行扩展字段)
|
||||||
|
type WsPushMsg struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
Data interface{} `json:"data,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerOptions 服务配置
|
||||||
|
type ServerOptions struct {
|
||||||
|
readTimeout time.Duration
|
||||||
|
writeTimeout time.Duration
|
||||||
|
heartbeatInterval time.Duration
|
||||||
|
workerPoolSize int
|
||||||
|
maxConnections int
|
||||||
|
connKeyPrefix string
|
||||||
|
checkOrigin func(r *netHttp.Request) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerOption 配置函数
|
||||||
|
type ServerOption func(*ServerOptions)
|
||||||
|
|
||||||
|
func WithReadTimeout(d time.Duration) ServerOption {
|
||||||
|
return func(o *ServerOptions) { o.readTimeout = d }
|
||||||
|
}
|
||||||
|
func WithWriteTimeout(d time.Duration) ServerOption {
|
||||||
|
return func(o *ServerOptions) { o.writeTimeout = d }
|
||||||
|
}
|
||||||
|
func WithHeartbeatInterval(d time.Duration) ServerOption {
|
||||||
|
return func(o *ServerOptions) { o.heartbeatInterval = d }
|
||||||
|
}
|
||||||
|
func WithWorkerPoolSize(n int) ServerOption {
|
||||||
|
return func(o *ServerOptions) { o.workerPoolSize = n }
|
||||||
|
}
|
||||||
|
func WithMaxConnections(n int) ServerOption {
|
||||||
|
return func(o *ServerOptions) { o.maxConnections = n }
|
||||||
|
}
|
||||||
|
func WithConnKeyPrefix(p string) ServerOption {
|
||||||
|
return func(o *ServerOptions) { o.connKeyPrefix = p }
|
||||||
|
}
|
||||||
|
func WithCheckOrigin(fn func(r *netHttp.Request) bool) ServerOption {
|
||||||
|
return func(o *ServerOptions) { o.checkOrigin = fn }
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultOptions() ServerOptions {
|
||||||
|
return ServerOptions{
|
||||||
|
readTimeout: DefaultReadTimeout,
|
||||||
|
writeTimeout: DefaultWriteTimeout,
|
||||||
|
heartbeatInterval: DefaultHeartbeatInterval,
|
||||||
|
workerPoolSize: DefaultWorkerPoolSize,
|
||||||
|
maxConnections: DefaultMaxConnections,
|
||||||
|
connKeyPrefix: DefaultConnKeyPrefix,
|
||||||
|
checkOrigin: func(r *netHttp.Request) bool { return true },
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,326 @@
|
|||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gogf/gf/v2/container/gmap"
|
||||||
|
"github.com/gogf/gf/v2/encoding/gjson"
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
"github.com/gogf/gf/v2/net/ghttp"
|
||||||
|
"github.com/gogf/gf/v2/os/glog"
|
||||||
|
"github.com/gogf/gf/v2/os/grpool"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WsServer 泛化 WebSocket 服务器
|
||||||
|
type WsServer struct {
|
||||||
|
connections *gmap.StrAnyMap
|
||||||
|
upgrader websocket.Upgrader
|
||||||
|
workerPool *grpool.Pool
|
||||||
|
handlers map[string]MessageHandler
|
||||||
|
handlerMu sync.RWMutex
|
||||||
|
opts ServerOptions
|
||||||
|
|
||||||
|
svcClosed int32
|
||||||
|
closeOnce sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWsServer 创建泛化 WebSocket 服务器
|
||||||
|
func NewWsServer(opts ...ServerOption) *WsServer {
|
||||||
|
o := defaultOptions()
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&o)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &WsServer{
|
||||||
|
connections: gmap.NewStrAnyMap(true),
|
||||||
|
upgrader: websocket.Upgrader{
|
||||||
|
ReadBufferSize: 1024,
|
||||||
|
WriteBufferSize: 1024,
|
||||||
|
CheckOrigin: o.checkOrigin,
|
||||||
|
},
|
||||||
|
workerPool: grpool.New(o.workerPoolSize),
|
||||||
|
handlers: make(map[string]MessageHandler),
|
||||||
|
opts: o,
|
||||||
|
svcClosed: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnMessage 注册业务消息处理器
|
||||||
|
func (s *WsServer) OnMessage(msgType string, handler MessageHandler) {
|
||||||
|
s.handlerMu.Lock()
|
||||||
|
defer s.handlerMu.Unlock()
|
||||||
|
s.handlers[msgType] = handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upgrade 将 HTTP 连接升级为 WebSocket 并注册到连接池
|
||||||
|
func (s *WsServer) Upgrade(ctx context.Context, r *ghttp.Request, sessionId string) (*WsConnection, error) {
|
||||||
|
if g.IsEmpty(sessionId) {
|
||||||
|
sessionId = uuid.NewString()
|
||||||
|
}
|
||||||
|
if atomic.LoadInt32(&s.svcClosed) == 1 {
|
||||||
|
return nil, errors.New("websocket server is closed")
|
||||||
|
}
|
||||||
|
if s.connections.Size() >= s.opts.maxConnections {
|
||||||
|
return nil, errors.New("too many online websocket connections")
|
||||||
|
}
|
||||||
|
|
||||||
|
wsConn, err := s.upgrader.Upgrade(r.Response.Writer, r.Request, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("upgrade failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := make(map[string]string)
|
||||||
|
for k, v := range r.Request.Header {
|
||||||
|
if len(v) > 0 {
|
||||||
|
headers[k] = v[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
key := s.opts.connKeyPrefix + sessionId
|
||||||
|
|
||||||
|
// 踢下线旧连接
|
||||||
|
s.kickOld(key)
|
||||||
|
|
||||||
|
baseCtx := context.WithoutCancel(ctx)
|
||||||
|
closeCtx, closeCancel := context.WithCancel(baseCtx)
|
||||||
|
|
||||||
|
wc := &WsConnection{
|
||||||
|
SessionId: sessionId,
|
||||||
|
Conn: wsConn,
|
||||||
|
Headers: headers,
|
||||||
|
closeCancel: closeCancel,
|
||||||
|
closed: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
s.connections.Set(key, wc)
|
||||||
|
|
||||||
|
// 连接成功回执
|
||||||
|
_ = s.writeJSON(closeCtx, wc, &WsPushMsg{Type: "ack", Message: "WebSocket连接成功", Data: map[string]any{
|
||||||
|
"sessionId": sessionId,
|
||||||
|
}})
|
||||||
|
|
||||||
|
// Pong 心跳回调,重置读超时
|
||||||
|
wsConn.SetPongHandler(func(string) error {
|
||||||
|
_ = wsConn.SetReadDeadline(time.Now().Add(s.opts.readTimeout))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
go s.handleConnection(closeCtx, key, wc)
|
||||||
|
return wc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushToSession 向指定会话推送消息
|
||||||
|
func (s *WsServer) PushToSession(ctx context.Context, sessionId string, msg *WsPushMsg) {
|
||||||
|
key := s.opts.connKeyPrefix + sessionId
|
||||||
|
val := s.connections.Get(key)
|
||||||
|
if val == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
wc, ok := val.(*WsConnection)
|
||||||
|
if !ok || wc.IsClosed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = s.writeJSON(ctx, wc, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOnlineSessions 获取在线会话列表
|
||||||
|
func (s *WsServer) GetOnlineSessions() []string {
|
||||||
|
var sessions []string
|
||||||
|
prefixLen := len(s.opts.connKeyPrefix)
|
||||||
|
s.connections.Iterator(func(key string, _ interface{}) bool {
|
||||||
|
if len(key) > prefixLen {
|
||||||
|
sessions = append(sessions, key[prefixLen:])
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return sessions
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close 全局优雅关闭
|
||||||
|
func (s *WsServer) Close() {
|
||||||
|
s.closeOnce.Do(func() {
|
||||||
|
atomic.StoreInt32(&s.svcClosed, 1)
|
||||||
|
s.workerPool.Close()
|
||||||
|
|
||||||
|
s.connections.LockFunc(func(m map[string]interface{}) {
|
||||||
|
for _, val := range m {
|
||||||
|
wc, ok := val.(*WsConnection)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if atomic.CompareAndSwapInt32(&wc.closed, 0, 1) {
|
||||||
|
if wc.closeCancel != nil {
|
||||||
|
wc.closeCancel()
|
||||||
|
}
|
||||||
|
_ = wc.Conn.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
s.connections.Clear()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== 内部方法 ======================
|
||||||
|
|
||||||
|
// kickOld 踢掉同session旧连接,不再主动remove,由旧连接defer清理
|
||||||
|
func (s *WsServer) kickOld(key string) {
|
||||||
|
val := s.connections.Get(key)
|
||||||
|
if val == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
old, ok := val.(*WsConnection)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if atomic.CompareAndSwapInt32(&old.closed, 0, 1) {
|
||||||
|
if old.closeCancel != nil {
|
||||||
|
old.closeCancel()
|
||||||
|
}
|
||||||
|
_ = old.Conn.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// heartbeatLoop 心跳发送协程,入参改为 *WsConnection,复用写锁
|
||||||
|
func (s *WsServer) heartbeatLoop(ctx context.Context, wc *WsConnection, done <-chan struct{}) {
|
||||||
|
ticker := time.NewTicker(s.opts.heartbeatInterval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
conn := wc.Conn
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
wc.writeMu.Lock()
|
||||||
|
_ = conn.SetWriteDeadline(time.Now().Add(s.opts.writeTimeout))
|
||||||
|
err := conn.WriteControl(websocket.PingMessage, nil, time.Now().Add(s.opts.writeTimeout))
|
||||||
|
wc.writeMu.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
glog.Debugf(ctx, "heartbeat ping failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case <-done:
|
||||||
|
return
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *WsServer) handleConnection(ctx context.Context, key string, wc *WsConnection) {
|
||||||
|
conn := wc.Conn
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if atomic.CompareAndSwapInt32(&wc.closed, 0, 1) {
|
||||||
|
if wc.closeCancel != nil {
|
||||||
|
wc.closeCancel()
|
||||||
|
}
|
||||||
|
_ = conn.Close()
|
||||||
|
}
|
||||||
|
// 关键修复:只删除自身实例,防止旧连接误删新连接
|
||||||
|
s.connections.LockFunc(func(m map[string]interface{}) {
|
||||||
|
if v, exist := m[key]; exist && v == wc {
|
||||||
|
delete(m, key)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
|
done := make(chan struct{})
|
||||||
|
defer close(done)
|
||||||
|
go s.heartbeatLoop(ctx, wc, done)
|
||||||
|
|
||||||
|
_ = conn.SetReadDeadline(time.Now().Add(s.opts.readTimeout))
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
msgType, data, err := conn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
// 正常关闭不打error日志
|
||||||
|
if !websocket.IsUnexpectedCloseError(err,
|
||||||
|
websocket.CloseNormalClosure,
|
||||||
|
websocket.CloseGoingAway,
|
||||||
|
websocket.CloseNoStatusReceived,
|
||||||
|
) {
|
||||||
|
glog.Debugf(ctx, "normal close: %s, err: %v", key, err)
|
||||||
|
} else {
|
||||||
|
glog.Infof(ctx, "unexpected close: %s, err: %v", key, err)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = conn.SetReadDeadline(time.Now().Add(s.opts.readTimeout))
|
||||||
|
|
||||||
|
switch msgType {
|
||||||
|
case websocket.PingMessage:
|
||||||
|
wc.writeMu.Lock()
|
||||||
|
_ = conn.SetWriteDeadline(time.Now().Add(s.opts.writeTimeout))
|
||||||
|
_ = conn.WriteMessage(websocket.PongMessage, nil)
|
||||||
|
wc.writeMu.Unlock()
|
||||||
|
continue
|
||||||
|
case websocket.CloseMessage:
|
||||||
|
return
|
||||||
|
case websocket.BinaryMessage, websocket.TextMessage:
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var msg WsMessage
|
||||||
|
if err := gjson.Unmarshal(data, &msg); err != nil {
|
||||||
|
_ = s.writeJSON(ctx, wc, &WsPushMsg{Type: "error", Message: "消息格式错误", Error: err.Error()})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
s.handlerMu.RLock()
|
||||||
|
handler, exists := s.handlers[msg.Type]
|
||||||
|
s.handlerMu.RUnlock()
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
_ = s.writeJSON(ctx, wc, &WsPushMsg{Type: "error", Message: fmt.Sprintf("未知消息类型: %s", msg.Type)})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 【重要修复】投递到workerPool,避免业务阻塞读循环
|
||||||
|
taskCtx := ctx
|
||||||
|
payload := msg.Payload
|
||||||
|
if err := s.workerPool.Add(taskCtx, func(ctx context.Context) {
|
||||||
|
handler(ctx, wc, payload)
|
||||||
|
}); err != nil {
|
||||||
|
_ = s.writeJSON(ctx, wc, &WsPushMsg{
|
||||||
|
Type: "error",
|
||||||
|
Message: "服务繁忙,任务队列已满",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeJSON 统一写入消息,入参改为 *WsConnection,带并发写锁
|
||||||
|
func (s *WsServer) writeJSON(ctx context.Context, wc *WsConnection, data interface{}) error {
|
||||||
|
wc.writeMu.Lock()
|
||||||
|
defer wc.writeMu.Unlock()
|
||||||
|
|
||||||
|
jsonBytes, err := gjson.Encode(data)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf(ctx, "json encode failed: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_ = wc.Conn.SetWriteDeadline(time.Now().Add(s.opts.writeTimeout))
|
||||||
|
if err = wc.Conn.WriteMessage(websocket.TextMessage, jsonBytes); err != nil {
|
||||||
|
glog.Debugf(ctx, "websocket write failed: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user