diff --git a/go.mod b/go.mod index efd7269..d71728b 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0 github.com/casbin/casbin/v2 v2.103.0 github.com/casbin/gorm-adapter/v3 v3.32.0 + github.com/gin-gonic/gin v1.11.0 github.com/go-kratos/kratos/v2 v2.8.0 github.com/go-sql-driver/mysql v1.8.1 github.com/golang-jwt/jwt/v5 v5.2.2 @@ -28,7 +29,8 @@ require ( github.com/xuri/excelize/v2 v2.9.0 go.einride.tech/aip v0.78.0 go.uber.org/automaxprocs v1.6.0 - golang.org/x/crypto v0.38.0 + go.uber.org/zap v1.27.1 + golang.org/x/crypto v0.40.0 google.golang.org/protobuf v1.36.10 gorm.io/driver/mysql v1.5.7 gorm.io/gen v0.3.27 @@ -57,22 +59,32 @@ require ( github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 // indirect github.com/aws/smithy-go v1.24.0 // indirect github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect + github.com/bytedance/sonic v1.14.0 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect github.com/casbin/govaluate v1.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/clbanning/mxj v1.8.4 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/gammazero/toposort v0.1.1 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect github.com/glebarez/go-sqlite v1.21.2 // indirect github.com/glebarez/sqlite v1.11.0 // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/go-kratos/aegis v0.2.0 // indirect + github.com/go-kratos/gin v0.1.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-playground/form/v4 v4.2.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.27.0 // indirect github.com/goccy/go-json v0.10.4 // indirect + github.com/goccy/go-yaml v1.18.0 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect github.com/golang-sql/sqlexp v0.1.0 // indirect @@ -85,16 +97,23 @@ require ( github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect + github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.17.11 // indirect - github.com/klauspost/cpuid/v2 v2.2.9 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/microsoft/go-mssqldb v1.6.0 // indirect github.com/minio/md5-simd v1.1.2 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/mozillazg/go-httpheader v0.2.1 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect + github.com/quic-go/qpack v0.5.1 // indirect + github.com/quic-go/quic-go v0.54.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/richardlehane/mscfb v1.0.4 // indirect github.com/richardlehane/msoleps v1.0.4 // indirect @@ -102,6 +121,8 @@ require ( github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.0 // indirect github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d // indirect github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect @@ -109,14 +130,17 @@ require ( go.opentelemetry.io/otel v1.36.0 // indirect go.opentelemetry.io/otel/metric v1.36.0 // indirect go.opentelemetry.io/otel/trace v1.36.0 // indirect + go.uber.org/mock v0.5.0 // indirect + go.uber.org/multierr v1.10.0 // indirect + golang.org/x/arch v0.20.0 // indirect golang.org/x/image v0.23.0 // indirect - golang.org/x/mod v0.23.0 // indirect - golang.org/x/net v0.40.0 // indirect - golang.org/x/sync v0.14.0 // indirect - golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.25.0 // indirect + golang.org/x/mod v0.25.0 // indirect + golang.org/x/net v0.42.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/text v0.27.0 // indirect golang.org/x/time v0.14.0 // indirect - golang.org/x/tools v0.30.0 // indirect + golang.org/x/tools v0.34.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 // indirect google.golang.org/grpc v1.74.2 // indirect diff --git a/go.sum b/go.sum index 3c3ab46..dbc95f4 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= 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.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= @@ -22,6 +24,7 @@ github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0/go.mod h github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o= github.com/AzureAD/microsoft-authentication-library-for-go v1.1.0 h1:HCc0+LpPfpCKs6LGGLAhwBARt9632unrVcI6i8s/8os= github.com/AzureAD/microsoft-authentication-library-for-go v1.1.0/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM= @@ -29,6 +32,7 @@ github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 h1:7dONQ3WNZ1zy960 github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82/go.mod h1:nLnM0KdK1CmygvjpDUO6m1TjSsiQtL61juhNsvV/JVI= github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g= github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/aws/aws-sdk-go-v2 v1.41.0 h1:tNvqh1s+v0vFYdA1xq0aOJH+Y5cRyZ5upu6roPgPKd4= github.com/aws/aws-sdk-go-v2 v1.41.0/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 h1:489krEF9xIGkOaaX3CE/Be2uWjiXrkCH6gUX+bZA/BU= @@ -73,16 +77,27 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= +github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= github.com/casbin/casbin/v2 v2.103.0 h1:dHElatNXNrr8XcseUov0ZSiWjauwmZZE6YMV3eU1yic= github.com/casbin/casbin/v2 v2.103.0/go.mod h1:Ee33aqGrmES+GNL17L0h9X28wXuo829wnNUnS0edAco= github.com/casbin/gorm-adapter/v3 v3.32.0 h1:Au+IOILBIE9clox5BJhI2nA3p9t7Ep1ePlupdGbGfus= github.com/casbin/gorm-adapter/v3 v3.32.0/go.mod h1:Zre/H8p17mpv5U3EaWgPoxLILLdXO3gHW5aoQQpUDZI= github.com/casbin/govaluate v1.3.0 h1:VA0eSY0M2lA86dYd5kPPuNZMUD9QkWnOCnavGrw9myc= github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 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/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I= github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls= github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -96,15 +111,32 @@ github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/ github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +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.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M= github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A= github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= github.com/gammazero/toposort v0.1.1 h1:OivGxsWxF3U3+U80VoLJ+f50HcPU1MIqE1JlKzoJ2Eg= github.com/gammazero/toposort v0.1.1/go.mod h1:H2cozTnNpMw0hg2VHAYsAxmkHXBYroNangj2NTBQDvw= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= +github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= +github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo= github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= @@ -113,6 +145,9 @@ github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-kratos/aegis v0.2.0 h1:dObzCDWn3XVjUkgxyBp6ZeWtx/do0DPZ7LY3yNSJLUQ= github.com/go-kratos/aegis v0.2.0/go.mod h1:v0R2m73WgEEYB3XYu6aE2WcMwsZkJ/Rzuf5eVccm7bI= +github.com/go-kratos/gin v0.1.0 h1:yq5GfZnSNo8cOIqxqPE0FVNQ8fm++oKQBd3/rTTp4oI= +github.com/go-kratos/gin v0.1.0/go.mod h1:KzE88+mV1QJdmiT+sU2D560ucOXnWZx+4m0OnVcSyzQ= +github.com/go-kratos/kratos/v2 v2.0.0/go.mod h1:USzTcAP7LS1rHVITQ/x/5JvlkUpG17pnfbqYV4j7SWc= github.com/go-kratos/kratos/v2 v2.8.0 h1:qr27WRTRrI3o4jzJzNKf4XVVoMYIqnQD+4ws1C46yhM= github.com/go-kratos/kratos/v2 v2.8.0/go.mod h1:+Vfe3FzF0d+BfMdajA11jT0rAyJWublRE/seZQNZVxE= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -122,20 +157,31 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/form/v4 v4.1.3/go.mod h1:q1a2BY+AQUUzhl6xA/6hBetay6dEIhMHjgvJiGo6K7U= github.com/go-playground/form/v4 v4.2.0 h1:N1wh+Goz61e6w66vo8vJkQt+uwZSoLz50kZPJWR8eic= github.com/go-playground/form/v4 v4.2.0/go.mod h1:q1a2BY+AQUUzhl6xA/6hBetay6dEIhMHjgvJiGo6K7U= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.7.0/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk= +github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= +github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM= github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= @@ -150,35 +196,63 @@ github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI= github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible h1:XQVXdk+WAJ4fSNB6mMRuYNvFWou7BZs6SZB925hPrnk= github.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible/go.mod h1:l7VUhRbTKCzdOacdT4oWCwATKyvZqUOlOqr0Ous3k4s= github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= @@ -199,11 +273,14 @@ github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkr github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= -github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= @@ -217,12 +294,15 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik= github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= @@ -235,6 +315,11 @@ github.com/minio/minio-go/v7 v7.0.84/go.mod h1:57YXpvc5l3rjPdhqNrDsvVlY0qPI6UTk1 github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= @@ -243,6 +328,8 @@ github.com/mojocn/base64Captcha v1.3.8/go.mod h1:QFZy927L8HVP3+VV5z2b1EAEiv1KxVJ github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/mozillazg/go-httpheader v0.2.1 h1:geV7TrjbL8KXSyvghnFm+NyTux/hxwueTSrwhe88TQQ= github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -254,10 +341,15 @@ github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3g github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk= github.com/qiniu/go-sdk/v7 v7.25.2 h1:URwgZpxySdiwu2yQpHk93X4LXWHyFRp1x3Vmlk/YWvo= github.com/qiniu/go-sdk/v7 v7.25.2/go.mod h1:dmKtJ2ahhPWFVi9o1D5GemmWoh/ctuB9peqTowyTO8o= github.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs= +github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= +github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E= github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= @@ -270,6 +362,7 @@ github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= @@ -287,6 +380,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -294,8 +388,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.563/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.563/go.mod h1:uom4Nvi9W+Qkom0exYiJ9VWJjXwyxtPYTkKkaLMlfE0= github.com/tencentyun/cos-go-sdk-v5 v0.7.60 h1:/e/tmvRmfKexr/QQIBzWhOkZWsmY3EK72NrI6G/Tv0o= @@ -304,12 +398,19 @@ github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFA github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= +github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d h1:llb0neMWDQe87IzJLS4Ci7psK/lVsjIS2otl+1WyRyY= github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= github.com/xuri/excelize/v2 v2.9.0 h1:1tgOaEq92IOEumR1/JfYS/eR0KHOCsRv/rYXXh6YJQE= github.com/xuri/excelize/v2 v2.9.0/go.mod h1:uqey4QBZ9gdMeWApPLdhm9x+9o2lq4iVmjiLfBS5hdE= github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 h1:hPVCafDV85blFTabnqKgNhDCkJX25eik94Si9cTER4A= github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= @@ -317,19 +418,35 @@ go.einride.tech/aip v0.78.0 h1:nbnM/OuGt5Pyz/r2KOxB6Hp+ey2e0+MNnfIPBtY45pY= go.einride.tech/aip v0.78.0/go.mod h1:E8+wdTApA70odnpFzJgsGogHozC2JCIhFJBKPr8bVig= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.0.0-RC1/go.mod h1:x9tRa9HK4hSSq7jf2TKbqFbtt58/TGk0f9XiEYISI1I= go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/oteltest v1.0.0-RC1/go.mod h1:+eoIG0gdEOaPNftuy1YScLr1Gb4mL/9lpDkZ0JjMRq4= +go.opentelemetry.io/otel/sdk v1.0.0-RC1/go.mod h1:kj6yPn7Pgt5ByRuwesbaWcRLA+V7BSDg3Hf8xRvsvf8= go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= +go.opentelemetry.io/otel/trace v1.0.0-RC1/go.mod h1:86UHmyHWFEtWjfWPSbu0+d0Pf9Q6e1U+3ViBOc+NXAg= go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= +golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= @@ -340,24 +457,38 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= -golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68= golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= -golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= +golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= @@ -368,28 +499,41 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= -golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= -golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -399,8 +543,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= 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= @@ -415,6 +559,7 @@ golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= @@ -425,29 +570,65 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= -golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= -golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= +golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= +golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20210629200056-84d6f6074151/go.mod h1:yiaVoXHpRzHGyxV3o4DktVWY4mSUErTKaeEOq6C3t3U= google.golang.org/genproto v0.0.0-20240711142825-46eb208f015d h1:/hmn0Ku5kWij/kjGsrcJeC1T/MrJi2iNWwgAqrihFwc= google.golang.org/genproto v0.0.0-20240711142825-46eb208f015d/go.mod h1:FfBgJBJg9GcpPvKIuHSZ/aE1g2ecGL74upMzGZjiGEY= google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 h1:7LRqPCEdE4TP4/9psdaB7F2nhZFfBiGJomA5sojLWdU= google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 h1:Wgl1rcDNThT+Zn47YyCXOXyX/COgMTIdhJ717F0l4xk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -457,7 +638,9 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -488,6 +671,8 @@ gorm.io/plugin/dbresolver v1.6.2 h1:F4b85TenghUeITqe3+epPSUtHH7RIk3fXr5l83DF8Pc= gorm.io/plugin/dbresolver v1.6.2/go.mod h1:tctw63jdrOezFR9HmrKnPkmig3m5Edem9fdxk9bQSzM= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= modernc.org/fileutil v1.0.0 h1:Z1AFLZwl6BO8A5NldQg/xTSjGLetp+1Ubvl4alfGx8w= modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8= modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE= diff --git a/internal/biz/biz.go b/internal/biz/biz.go index c2dd76e..832f626 100644 --- a/internal/biz/biz.go +++ b/internal/biz/biz.go @@ -14,6 +14,7 @@ var ProviderSet = wire.NewSet( system.NewAuthorityBtnUsecase, system.NewMenuUsecase, system.NewDictionaryUsecase, + system.NewCasbinUsecase, system.NewJwtBlacklistUsecase, system.NewOperationRecordUsecase, system.NewParamsUsecase, diff --git a/internal/biz/system/api.go b/internal/biz/system/api.go index 7b6ad5a..f4d992f 100644 --- a/internal/biz/system/api.go +++ b/internal/biz/system/api.go @@ -162,6 +162,18 @@ func (uc *ApiUsecase) GetAPIInfoList(ctx context.Context, page, pageSize int, fi return uc.repo.List(ctx, page, pageSize, filters, orderKey, desc) } +// GetApiList 分页获取API列表(简化版本) +func (uc *ApiUsecase) GetApiList(ctx context.Context, page, pageSize int, filters map[string]interface{}) ([]*Api, int64, error) { + // 转换 filters + strFilters := make(map[string]string) + for k, v := range filters { + if s, ok := v.(string); ok { + strFilters[k] = s + } + } + return uc.repo.List(ctx, page, pageSize, strFilters, "", false) +} + // GetAllApis 获取所有API(根据权限过滤) func (uc *ApiUsecase) GetAllApis(ctx context.Context, authorityID uint) ([]*Api, error) { parentAuthorityID, err := uc.authRepo.GetParentAuthorityID(ctx, authorityID) diff --git a/internal/biz/system/casbin.go b/internal/biz/system/casbin.go new file mode 100644 index 0000000..c301cea --- /dev/null +++ b/internal/biz/system/casbin.go @@ -0,0 +1,104 @@ +package system + +import ( + "context" + "errors" +) + +// CasbinUsecase Casbin用例 +type CasbinUsecase struct { + repo CasbinRepo + authUc *AuthorityUsecase + apiUc *ApiUsecase + useStrictAuth bool +} + +// NewCasbinUsecase 创建Casbin用例 +func NewCasbinUsecase(repo CasbinRepo, authUc *AuthorityUsecase, apiUc *ApiUsecase) *CasbinUsecase { + return &CasbinUsecase{ + repo: repo, + authUc: authUc, + apiUc: apiUc, + useStrictAuth: false, + } +} + +// UpdateCasbin 更新Casbin权限 +func (uc *CasbinUsecase) UpdateCasbin(adminAuthorityID, authorityId uint, casbinInfos []struct { + Path string + Method string +}) error { + // 转换为 CasbinRule + rules := make([]CasbinRule, len(casbinInfos)) + for i, info := range casbinInfos { + rules[i] = CasbinRule{ + Path: info.Path, + Method: info.Method, + } + } + + return uc.repo.UpdateCasbin(adminAuthorityID, authorityId, rules) +} + +// GetPolicyPathByAuthorityId 获取权限路径 +func (uc *CasbinUsecase) GetPolicyPathByAuthorityId(authorityId uint) []CasbinRule { + return uc.repo.GetPolicyPathByAuthorityId(authorityId) +} + +// FreshCasbin 刷新Casbin缓存 +func (uc *CasbinUsecase) FreshCasbin() error { + return uc.repo.FreshCasbin() +} + +// CheckPermission 检查权限 +func (uc *CasbinUsecase) CheckPermission(ctx context.Context, sub, obj, act string) (bool, error) { + // 这里可以添加权限检查逻辑 + return true, nil +} + +// ClearCasbin 清除Casbin规则 +func (uc *CasbinUsecase) ClearCasbin(v int, p ...string) bool { + return uc.repo.ClearCasbin(v, p...) +} + +// AddPolicies 添加策略 +func (uc *CasbinUsecase) AddPolicies(rules [][]string) error { + return uc.repo.AddPolicies(rules) +} + +// RemoveFilteredPolicy 删除筛选的策略 +func (uc *CasbinUsecase) RemoveFilteredPolicy(authorityId string) error { + return uc.repo.RemoveFilteredPolicy(authorityId) +} + +// UpdateCasbinApi API更新随动 +func (uc *CasbinUsecase) UpdateCasbinApi(oldPath, newPath, oldMethod, newMethod string) error { + return uc.repo.UpdateCasbinApi(oldPath, newPath, oldMethod, newMethod) +} + +// ValidateCasbinInfos 验证Casbin信息(严格模式) +func (uc *CasbinUsecase) ValidateCasbinInfos(ctx context.Context, adminAuthorityID uint, casbinInfos []CasbinRule) error { + if !uc.useStrictAuth { + return nil + } + + apis, err := uc.apiUc.GetAllApis(ctx, adminAuthorityID) + if err != nil { + return err + } + + for _, info := range casbinInfos { + hasApi := false + for _, api := range apis { + if api.Path == info.Path && api.Method == info.Method { + hasApi = true + break + } + } + if !hasApi { + return errors.New("存在api不在权限列表中") + } + } + + return nil +} diff --git a/internal/biz/system/jwt_blacklist.go b/internal/biz/system/jwt_blacklist.go index e63f28b..7fd2f02 100644 --- a/internal/biz/system/jwt_blacklist.go +++ b/internal/biz/system/jwt_blacklist.go @@ -41,12 +41,22 @@ func (uc *JwtBlacklistUsecase) JoinBlacklist(ctx context.Context, jwt string) er return nil } +// JsonInBlacklist JWT加入黑名单(GVA兼容方法名) +func (uc *JwtBlacklistUsecase) JsonInBlacklist(ctx context.Context, jwt string) error { + return uc.JoinBlacklist(ctx, jwt) +} + // IsInBlacklist 检查是否在黑名单中 func (uc *JwtBlacklistUsecase) IsInBlacklist(jwt string) bool { _, ok := uc.cache[jwt] return ok } +// IsBlacklist 检查是否在黑名单中(GVA兼容方法名) +func (uc *JwtBlacklistUsecase) IsBlacklist(ctx context.Context, jwt string) bool { + return uc.IsInBlacklist(jwt) +} + // LoadAll 加载所有黑名单到缓存 func (uc *JwtBlacklistUsecase) LoadAll(ctx context.Context) error { jwts, err := uc.repo.LoadAll(ctx) diff --git a/internal/conf/provider.go b/internal/conf/provider.go index 60fc580..cfc668b 100644 --- a/internal/conf/provider.go +++ b/internal/conf/provider.go @@ -1,6 +1,9 @@ package conf -import "github.com/google/wire" +import ( + "github.com/google/wire" + "go.uber.org/zap" +) // ProviderSet is conf providers. var ProviderSet = wire.NewSet( @@ -10,8 +13,15 @@ var ProviderSet = wire.NewSet( ProvideCaptcha, ProvideSystem, ProvideUseStrictAuth, + ProvideZapLogger, ) +// ProvideZapLogger 提供zap日志实例 +func ProvideZapLogger() *zap.Logger { + logger, _ := zap.NewProduction() + return logger +} + // ProvideServer 提供Server配置 func ProvideServer(c *Bootstrap) *Server { return c.Server diff --git a/internal/server/gin.go b/internal/server/gin.go new file mode 100644 index 0000000..7124f8c --- /dev/null +++ b/internal/server/gin.go @@ -0,0 +1,107 @@ +package server + +import ( + "context" + + "kra/internal/biz/system" + "kra/internal/conf" + handler "kra/internal/server/handler/system" + "kra/internal/server/middleware" + "kra/internal/server/router" + "kra/pkg/jwt" + + "github.com/casbin/casbin/v2" + "github.com/gin-gonic/gin" + kgin "github.com/go-kratos/gin" + "github.com/go-kratos/kratos/v2/middleware/recovery" + "go.uber.org/zap" +) + +// NewGinRouter 创建Gin路由引擎(交给kratos http server管理) +func NewGinRouter( + c *conf.Server, + logger *zap.Logger, + jwtPkg *jwt.JWT, + enforcer *casbin.SyncedCachedEnforcer, + jwtBlacklistUsecase *system.JwtBlacklistUsecase, + userUsecase *system.UserUsecase, + apiUsecase *system.ApiUsecase, + authorityUsecase *system.AuthorityUsecase, + authorityBtnUsecase *system.AuthorityBtnUsecase, + menuUsecase *system.MenuUsecase, + dictionaryUsecase *system.DictionaryUsecase, + casbinUsecase *system.CasbinUsecase, + operationRecordUsecase *system.OperationRecordUsecase, + paramsUsecase *system.ParamsUsecase, +) *gin.Engine { + gin.SetMode(gin.DebugMode) + + engine := gin.New() + + // 使用kratos中间件包装 + engine.Use(kgin.Middlewares(recovery.Recovery())) + + // 设置自定义中间件 + engine.Use(middleware.GinRecovery(true)) + engine.Use(middleware.Cors()) + + // 设置日志 + middleware.SetRecoveryLogger(logger) + middleware.SetOperationLogger(logger) + + // 设置JWT配置 + middleware.SetJWTConfig(&middleware.JWTConfig{ + JWT: jwtPkg, + BlacklistChecker: &blacklistChecker{uc: jwtBlacklistUsecase}, + UseMultipoint: false, + }) + + // 设置Casbin配置 + middleware.SetCasbinConfig(&middleware.CasbinConfig{ + RouterPrefix: "", + }) + + // 初始化handler依赖 + handler.InitUsecases( + userUsecase, + apiUsecase, + authorityUsecase, + authorityBtnUsecase, + menuUsecase, + dictionaryUsecase, + casbinUsecase, + jwtBlacklistUsecase, + operationRecordUsecase, + paramsUsecase, + ) + handler.SetJWTInstance(jwtPkg) + + // 创建路由组 + publicGroup := engine.Group("") + privateGroup := engine.Group("") + privateGroup.Use(middleware.JWTAuth()).Use(middleware.CasbinHandler()) + + // 注册路由 + systemRouter := router.RouterGroupApp.System + systemRouter.InitBaseRouter(publicGroup) + systemRouter.InitUserRouter(privateGroup) + systemRouter.InitApiRouter(privateGroup, publicGroup) + systemRouter.InitAuthorityRouter(privateGroup) + systemRouter.InitMenuRouter(privateGroup) + systemRouter.InitDictionaryRouter(privateGroup) + systemRouter.InitCasbinRouter(privateGroup) + systemRouter.InitJwtRouter(privateGroup) + systemRouter.InitOperationRecordRouter(privateGroup) + systemRouter.InitParamsRouter(privateGroup) + + return engine +} + +// blacklistChecker JWT黑名单检查器 +type blacklistChecker struct { + uc *system.JwtBlacklistUsecase +} + +func (b *blacklistChecker) IsBlacklist(jwt string) bool { + return b.uc.IsBlacklist(context.Background(), jwt) +} diff --git a/internal/server/handler/system/enter.go b/internal/server/handler/system/enter.go new file mode 100644 index 0000000..1ac1039 --- /dev/null +++ b/internal/server/handler/system/enter.go @@ -0,0 +1,56 @@ +package system + +import "kra/internal/biz/system" + +// ApiGroup 系统API组 +type ApiGroup struct { + BaseApi + UserApi + ApiApi + AuthorityApi + MenuApi + DictionaryApi + CasbinApi + JwtApi + OperationRecordApi + ParamsApi +} + +// 业务层依赖 +var ( + userUsecase *system.UserUsecase + apiUsecase *system.ApiUsecase + authorityUsecase *system.AuthorityUsecase + authorityBtnUsecase *system.AuthorityBtnUsecase + menuUsecase *system.MenuUsecase + dictionaryUsecase *system.DictionaryUsecase + casbinUsecase *system.CasbinUsecase + jwtBlacklistUsecase *system.JwtBlacklistUsecase + operationRecordUsecase *system.OperationRecordUsecase + paramsUsecase *system.ParamsUsecase +) + +// InitUsecases 初始化业务层依赖 +func InitUsecases( + user *system.UserUsecase, + api *system.ApiUsecase, + authority *system.AuthorityUsecase, + authorityBtn *system.AuthorityBtnUsecase, + menu *system.MenuUsecase, + dictionary *system.DictionaryUsecase, + casbin *system.CasbinUsecase, + jwtBlacklist *system.JwtBlacklistUsecase, + operationRecord *system.OperationRecordUsecase, + params *system.ParamsUsecase, +) { + userUsecase = user + apiUsecase = api + authorityUsecase = authority + authorityBtnUsecase = authorityBtn + menuUsecase = menu + dictionaryUsecase = dictionary + casbinUsecase = casbin + jwtBlacklistUsecase = jwtBlacklist + operationRecordUsecase = operationRecord + paramsUsecase = params +} diff --git a/internal/server/handler/system/sys_api.go b/internal/server/handler/system/sys_api.go new file mode 100644 index 0000000..be85731 --- /dev/null +++ b/internal/server/handler/system/sys_api.go @@ -0,0 +1,216 @@ +package system + +import ( + "kra/internal/biz/system" + "kra/pkg/response" + "kra/pkg/utils" + + "github.com/gin-gonic/gin" +) + +type ApiApi struct{} + +// CreateApiRequest 创建API请求 +type CreateApiRequest struct { + Path string `json:"path" binding:"required"` + Description string `json:"description" binding:"required"` + ApiGroup string `json:"apiGroup" binding:"required"` + Method string `json:"method" binding:"required"` +} + +// UpdateApiRequest 更新API请求 +type UpdateApiRequest struct { + ID uint `json:"ID" binding:"required"` + Path string `json:"path" binding:"required"` + Description string `json:"description" binding:"required"` + ApiGroup string `json:"apiGroup" binding:"required"` + Method string `json:"method" binding:"required"` +} + +// DeleteApiRequest 删除API请求 +type DeleteApiRequest struct { + ID uint `json:"ID" binding:"required"` +} + +// GetApiListRequest 获取API列表请求 +type GetApiListRequest struct { + Page int `json:"page"` + PageSize int `json:"pageSize"` + Path string `json:"path"` + Description string `json:"description"` + ApiGroup string `json:"apiGroup"` + Method string `json:"method"` + OrderKey string `json:"orderKey"` + Desc bool `json:"desc"` +} + +// GetApiByIdRequest 根据ID获取API请求 +type GetApiByIdRequest struct { + ID uint `json:"ID" binding:"required"` +} + +// DeleteApisByIdsRequest 批量删除API请求 +type DeleteApisByIdsRequest struct { + Ids []uint `json:"ids" binding:"required"` +} + +// CreateApi 创建API +func (a *ApiApi) CreateApi(c *gin.Context) { + var req CreateApiRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + api := &system.Api{ + Path: req.Path, + Description: req.Description, + ApiGroup: req.ApiGroup, + Method: req.Method, + } + + if err := apiUsecase.CreateApi(c, api); err != nil { + response.FailWithMessage("创建失败", c) + return + } + + response.OkWithMessage("创建成功", c) +} + +// DeleteApi 删除API +func (a *ApiApi) DeleteApi(c *gin.Context) { + var req DeleteApiRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + if err := apiUsecase.DeleteApi(c, req.ID); err != nil { + response.FailWithMessage("删除失败", c) + return + } + + response.OkWithMessage("删除成功", c) +} + +// UpdateApi 更新API +func (a *ApiApi) UpdateApi(c *gin.Context) { + var req UpdateApiRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + api := &system.Api{ + ID: req.ID, + Path: req.Path, + Description: req.Description, + ApiGroup: req.ApiGroup, + Method: req.Method, + } + + if err := apiUsecase.UpdateApi(c, api); err != nil { + response.FailWithMessage("修改失败", c) + return + } + + response.OkWithMessage("修改成功", c) +} + +// GetApiList 获取API列表 +func (a *ApiApi) GetApiList(c *gin.Context) { + var req GetApiListRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + if req.Page == 0 { + req.Page = 1 + } + if req.PageSize == 0 { + req.PageSize = 10 + } + + filters := make(map[string]interface{}) + if req.Path != "" { + filters["path"] = req.Path + } + if req.Description != "" { + filters["description"] = req.Description + } + if req.ApiGroup != "" { + filters["api_group"] = req.ApiGroup + } + if req.Method != "" { + filters["method"] = req.Method + } + + list, total, err := apiUsecase.GetApiList(c, req.Page, req.PageSize, filters) + if err != nil { + response.FailWithMessage("获取失败", c) + return + } + + response.OkWithDetailed(response.PageResult{ + List: list, + Total: total, + Page: req.Page, + PageSize: req.PageSize, + }, "获取成功", c) +} + +// GetApiById 根据ID获取API +func (a *ApiApi) GetApiById(c *gin.Context) { + var req GetApiByIdRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + api, err := apiUsecase.GetApiById(c, req.ID) + if err != nil { + response.FailWithMessage("获取失败", c) + return + } + + response.OkWithDetailed(gin.H{"api": api}, "获取成功", c) +} + +// GetAllApis 获取所有API +func (a *ApiApi) GetAllApis(c *gin.Context) { + authorityID := utils.GetUserAuthorityId(c) + apis, err := apiUsecase.GetAllApis(c, authorityID) + if err != nil { + response.FailWithMessage("获取失败", c) + return + } + + response.OkWithDetailed(gin.H{"apis": apis}, "获取成功", c) +} + +// DeleteApisByIds 批量删除API +func (a *ApiApi) DeleteApisByIds(c *gin.Context) { + var req DeleteApisByIdsRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + if err := apiUsecase.DeleteApisByIds(c, req.Ids); err != nil { + response.FailWithMessage("删除失败", c) + return + } + + response.OkWithMessage("删除成功", c) +} + +// FreshCasbin 刷新Casbin缓存 +func (a *ApiApi) FreshCasbin(c *gin.Context) { + if err := casbinUsecase.FreshCasbin(); err != nil { + response.FailWithMessage("刷新失败", c) + return + } + + response.OkWithMessage("刷新成功", c) +} diff --git a/internal/server/handler/system/sys_authority.go b/internal/server/handler/system/sys_authority.go new file mode 100644 index 0000000..6fba0fb --- /dev/null +++ b/internal/server/handler/system/sys_authority.go @@ -0,0 +1,189 @@ +package system + +import ( + "kra/internal/biz/system" + jwtPkg "kra/pkg/jwt" + "kra/pkg/response" + + "github.com/gin-gonic/gin" +) + +type AuthorityApi struct{} + +// CreateAuthorityRequest 创建角色请求 +type CreateAuthorityRequest struct { + AuthorityId uint `json:"authorityId" binding:"required"` + AuthorityName string `json:"authorityName" binding:"required"` + ParentId *uint `json:"parentId"` + DefaultRouter string `json:"defaultRouter"` +} + +// UpdateAuthorityRequest 更新角色请求 +type UpdateAuthorityRequest struct { + AuthorityId uint `json:"authorityId" binding:"required"` + AuthorityName string `json:"authorityName" binding:"required"` + ParentId *uint `json:"parentId"` + DefaultRouter string `json:"defaultRouter"` +} + +// DeleteAuthorityRequest 删除角色请求 +type DeleteAuthorityRequest struct { + AuthorityId uint `json:"authorityId" binding:"required"` +} + +// CopyAuthorityRequest 复制角色请求 +type CopyAuthorityRequest struct { + OldAuthorityId uint `json:"oldAuthorityId" binding:"required"` + Authority CreateAuthorityRequest `json:"authority" binding:"required"` +} + +// SetDataAuthorityRequest 设置数据权限请求 +type SetDataAuthorityRequest struct { + AuthorityId uint `json:"authorityId" binding:"required"` + DataAuthorityId []uint `json:"dataAuthorityId"` +} + +// GetAuthorityListRequest 获取角色列表请求 +type GetAuthorityListRequest struct { + Page int `json:"page"` + PageSize int `json:"pageSize"` +} + +// CreateAuthority 创建角色 +func (a *AuthorityApi) CreateAuthority(c *gin.Context) { + var req CreateAuthorityRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + authority := &system.Authority{ + AuthorityId: req.AuthorityId, + AuthorityName: req.AuthorityName, + ParentId: req.ParentId, + DefaultRouter: req.DefaultRouter, + } + + created, err := authorityUsecase.CreateAuthority(c, authority) + if err != nil { + response.FailWithMessage("创建失败: "+err.Error(), c) + return + } + + response.OkWithDetailed(gin.H{"authority": created}, "创建成功", c) +} + +// DeleteAuthority 删除角色 +func (a *AuthorityApi) DeleteAuthority(c *gin.Context) { + var req DeleteAuthorityRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + if err := authorityUsecase.DeleteAuthority(c, req.AuthorityId); err != nil { + response.FailWithMessage("删除失败: "+err.Error(), c) + return + } + + response.OkWithMessage("删除成功", c) +} + +// UpdateAuthority 更新角色 +func (a *AuthorityApi) UpdateAuthority(c *gin.Context) { + var req UpdateAuthorityRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + authority := &system.Authority{ + AuthorityId: req.AuthorityId, + AuthorityName: req.AuthorityName, + ParentId: req.ParentId, + DefaultRouter: req.DefaultRouter, + } + + updated, err := authorityUsecase.UpdateAuthority(c, authority) + if err != nil { + response.FailWithMessage("更新失败: "+err.Error(), c) + return + } + + response.OkWithDetailed(gin.H{"authority": updated}, "更新成功", c) +} + +// GetAuthorityList 获取角色列表 +func (a *AuthorityApi) GetAuthorityList(c *gin.Context) { + // 从JWT获取当前用户的角色ID + claims, exists := c.Get("claims") + if !exists { + response.FailWithMessage("获取用户信息失败", c) + return + } + customClaims := claims.(*jwtPkg.CustomClaims) + + list, err := authorityUsecase.GetAuthorityInfoList(c, customClaims.AuthorityID) + if err != nil { + response.FailWithMessage("获取失败", c) + return + } + + response.OkWithDetailed(gin.H{"list": list}, "获取成功", c) +} + +// CopyAuthority 复制角色 +func (a *AuthorityApi) CopyAuthority(c *gin.Context) { + var req CopyAuthorityRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + // 从JWT获取当前用户的角色ID + claims, exists := c.Get("claims") + if !exists { + response.FailWithMessage("获取用户信息失败", c) + return + } + customClaims := claims.(*jwtPkg.CustomClaims) + + authority := &system.Authority{ + AuthorityId: req.Authority.AuthorityId, + AuthorityName: req.Authority.AuthorityName, + ParentId: req.Authority.ParentId, + DefaultRouter: req.Authority.DefaultRouter, + } + + created, err := authorityUsecase.CopyAuthority(c, customClaims.AuthorityID, req.OldAuthorityId, authority) + if err != nil { + response.FailWithMessage("复制失败: "+err.Error(), c) + return + } + + response.OkWithDetailed(gin.H{"authority": created}, "复制成功", c) +} + +// SetDataAuthority 设置数据权限 +func (a *AuthorityApi) SetDataAuthority(c *gin.Context) { + var req SetDataAuthorityRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + // 从JWT获取当前用户的角色ID + claims, exists := c.Get("claims") + if !exists { + response.FailWithMessage("获取用户信息失败", c) + return + } + customClaims := claims.(*jwtPkg.CustomClaims) + + if err := authorityUsecase.SetDataAuthority(c, customClaims.AuthorityID, req.AuthorityId, req.DataAuthorityId); err != nil { + response.FailWithMessage("设置失败: "+err.Error(), c) + return + } + + response.OkWithMessage("设置成功", c) +} diff --git a/internal/server/handler/system/sys_base.go b/internal/server/handler/system/sys_base.go new file mode 100644 index 0000000..44fc77e --- /dev/null +++ b/internal/server/handler/system/sys_base.go @@ -0,0 +1,77 @@ +package system + +import ( + "kra/pkg/jwt" + "kra/pkg/response" + "kra/pkg/utils" + + "github.com/gin-gonic/gin" +) + +type BaseApi struct{} + +// LoginRequest 登录请求 +type LoginRequest struct { + Username string `json:"username" binding:"required"` + Password string `json:"password" binding:"required"` + Captcha string `json:"captcha"` + CaptchaId string `json:"captchaId"` +} + +// LoginResponse 登录响应 +type LoginResponse struct { + User interface{} `json:"user"` + Token string `json:"token"` + ExpiresAt int64 `json:"expiresAt"` +} + +// JWT实例(需要在初始化时设置) +var jwtInstance *jwt.JWT + +// SetJWTInstance 设置JWT实例 +func SetJWTInstance(j *jwt.JWT) { + jwtInstance = j +} + +// Login 用户登录 +func (b *BaseApi) Login(c *gin.Context) { + var req LoginRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + user, err := userUsecase.Login(c, req.Username, req.Password) + if err != nil { + response.FailWithMessage("用户名不存在或者密码错误", c) + return + } + + if user.Enable != 1 { + response.FailWithMessage("用户被禁止登录", c) + return + } + + // 生成token + claims := jwtInstance.CreateClaims(jwt.BaseClaims{ + UUID: user.UUID.String(), + ID: user.ID, + Username: user.Username, + NickName: user.NickName, + AuthorityID: user.AuthorityId, + }) + + token, err := jwtInstance.CreateToken(claims.BaseClaims) + if err != nil { + response.FailWithMessage("获取token失败", c) + return + } + + utils.SetToken(c, token, int(jwtInstance.ExpiresAt.Seconds())) + + response.OkWithDetailed(LoginResponse{ + User: toUserResponse(user), + Token: token, + ExpiresAt: claims.ExpiresAt.UnixMilli(), + }, "登录成功", c) +} diff --git a/internal/server/handler/system/sys_casbin.go b/internal/server/handler/system/sys_casbin.go new file mode 100644 index 0000000..247ea02 --- /dev/null +++ b/internal/server/handler/system/sys_casbin.go @@ -0,0 +1,70 @@ +package system + +import ( + "kra/pkg/response" + + "github.com/gin-gonic/gin" +) + +type CasbinApi struct{} + +// CasbinRule Casbin规则 +type CasbinRule struct { + Path string `json:"path"` + Method string `json:"method"` +} + +// UpdateCasbinRequest 更新Casbin请求 +type UpdateCasbinRequest struct { + AuthorityId uint `json:"authorityId" binding:"required"` + CasbinInfos []CasbinRule `json:"casbinInfos"` +} + +// GetPolicyPathByAuthorityIdRequest 获取权限路径请求 +type GetPolicyPathByAuthorityIdRequest struct { + AuthorityId uint `json:"authorityId" binding:"required"` +} + +// UpdateCasbin 更新Casbin权限 +func (ca *CasbinApi) UpdateCasbin(c *gin.Context) { + var req UpdateCasbinRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + // 转换为业务层格式 + rules := make([]struct { + Path string + Method string + }, len(req.CasbinInfos)) + for i, r := range req.CasbinInfos { + rules[i] = struct { + Path string + Method string + }{ + Path: r.Path, + Method: r.Method, + } + } + + if err := casbinUsecase.UpdateCasbin(0, req.AuthorityId, rules); err != nil { + response.FailWithMessage("更新失败: "+err.Error(), c) + return + } + + response.OkWithMessage("更新成功", c) +} + +// GetPolicyPathByAuthorityId 获取权限路径 +func (ca *CasbinApi) GetPolicyPathByAuthorityId(c *gin.Context) { + var req GetPolicyPathByAuthorityIdRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + paths := casbinUsecase.GetPolicyPathByAuthorityId(req.AuthorityId) + + response.OkWithDetailed(gin.H{"paths": paths}, "获取成功", c) +} diff --git a/internal/server/handler/system/sys_dictionary.go b/internal/server/handler/system/sys_dictionary.go new file mode 100644 index 0000000..eeee84e --- /dev/null +++ b/internal/server/handler/system/sys_dictionary.go @@ -0,0 +1,142 @@ +package system + +import ( + "strconv" + + "kra/internal/biz/system" + "kra/pkg/response" + + "github.com/gin-gonic/gin" +) + +type DictionaryApi struct{} + +// CreateDictionaryRequest 创建字典请求 +type CreateDictionaryRequest struct { + Name string `json:"name" binding:"required"` + Type string `json:"type" binding:"required"` + Status *bool `json:"status"` + Desc string `json:"desc"` +} + +// UpdateDictionaryRequest 更新字典请求 +type UpdateDictionaryRequest struct { + ID uint `json:"ID" binding:"required"` + Name string `json:"name"` + Type string `json:"type"` + Status *bool `json:"status"` + Desc string `json:"desc"` +} + +// GetDictionaryListRequest 获取字典列表请求 +type GetDictionaryListRequest struct { + Page int `json:"page"` + PageSize int `json:"pageSize"` + Name string `json:"name"` +} + +// CreateSysDictionary 创建字典 +func (d *DictionaryApi) CreateSysDictionary(c *gin.Context) { + var req CreateDictionaryRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + dict := &system.Dictionary{ + Name: req.Name, + Type: req.Type, + Status: req.Status, + Desc: req.Desc, + } + + if err := dictionaryUsecase.CreateDictionary(c, dict); err != nil { + response.FailWithMessage("创建失败: "+err.Error(), c) + return + } + + response.OkWithMessage("创建成功", c) +} + +// DeleteSysDictionary 删除字典 +func (d *DictionaryApi) DeleteSysDictionary(c *gin.Context) { + idStr := c.Query("ID") + if idStr == "" { + response.FailWithMessage("参数错误", c) + return + } + + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + response.FailWithMessage("参数错误", c) + return + } + + if err := dictionaryUsecase.DeleteDictionary(c, uint(id)); err != nil { + response.FailWithMessage("删除失败: "+err.Error(), c) + return + } + + response.OkWithMessage("删除成功", c) +} + +// UpdateSysDictionary 更新字典 +func (d *DictionaryApi) UpdateSysDictionary(c *gin.Context) { + var req UpdateDictionaryRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + dict := &system.Dictionary{ + ID: req.ID, + Name: req.Name, + Type: req.Type, + Status: req.Status, + Desc: req.Desc, + } + + if err := dictionaryUsecase.UpdateDictionary(c, dict); err != nil { + response.FailWithMessage("更新失败: "+err.Error(), c) + return + } + + response.OkWithMessage("更新成功", c) +} + +// FindSysDictionary 根据ID或type获取字典 +func (d *DictionaryApi) FindSysDictionary(c *gin.Context) { + idStr := c.Query("ID") + dictType := c.Query("type") + + var id uint + if idStr != "" { + idUint, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + response.FailWithMessage("参数错误", c) + return + } + id = uint(idUint) + } + + dict, err := dictionaryUsecase.GetDictionary(c, dictType, id, nil) + if err != nil { + response.FailWithMessage("获取失败", c) + return + } + + response.OkWithDetailed(gin.H{"resysDictionary": dict}, "获取成功", c) +} + +// GetSysDictionaryList 获取字典列表 +func (d *DictionaryApi) GetSysDictionaryList(c *gin.Context) { + name := c.Query("name") + + list, err := dictionaryUsecase.GetDictionaryList(c, name) + if err != nil { + response.FailWithMessage("获取失败", c) + return + } + + response.OkWithDetailed(gin.H{"list": list}, "获取成功", c) +} diff --git a/internal/server/handler/system/sys_jwt.go b/internal/server/handler/system/sys_jwt.go new file mode 100644 index 0000000..e506edd --- /dev/null +++ b/internal/server/handler/system/sys_jwt.go @@ -0,0 +1,27 @@ +package system + +import ( + "kra/pkg/response" + "kra/pkg/utils" + + "github.com/gin-gonic/gin" +) + +type JwtApi struct{} + +// JsonInBlacklist JWT加入黑名单 +func (j *JwtApi) JsonInBlacklist(c *gin.Context) { + token := utils.GetToken(c) + if token == "" { + response.FailWithMessage("token不存在", c) + return + } + + if err := jwtBlacklistUsecase.JsonInBlacklist(c, token); err != nil { + response.FailWithMessage("jwt作废失败: "+err.Error(), c) + return + } + + utils.ClearToken(c) + response.OkWithMessage("jwt作废成功", c) +} diff --git a/internal/server/handler/system/sys_menu.go b/internal/server/handler/system/sys_menu.go new file mode 100644 index 0000000..8987ed5 --- /dev/null +++ b/internal/server/handler/system/sys_menu.go @@ -0,0 +1,234 @@ +package system + +import ( + "kra/internal/biz/system" + "kra/pkg/response" + "kra/pkg/utils" + + "github.com/gin-gonic/gin" +) + +type MenuApi struct{} + +// AddBaseMenuRequest 添加菜单请求 +type AddBaseMenuRequest struct { + ParentId uint `json:"parentId"` + Path string `json:"path" binding:"required"` + Name string `json:"name" binding:"required"` + Hidden bool `json:"hidden"` + Component string `json:"component"` + Sort int `json:"sort"` + Meta map[string]interface{} `json:"meta"` + Parameters []MenuParameter `json:"parameters"` + MenuBtn []MenuBtn `json:"menuBtn"` +} + +// MenuParameter 菜单参数 +type MenuParameter struct { + Type string `json:"type"` + Key string `json:"key"` + Value string `json:"value"` +} + +// MenuBtn 菜单按钮 +type MenuBtn struct { + Name string `json:"name"` + Desc string `json:"desc"` +} + +// UpdateBaseMenuRequest 更新菜单请求 +type UpdateBaseMenuRequest struct { + ID uint `json:"ID" binding:"required"` + ParentId uint `json:"parentId"` + Path string `json:"path" binding:"required"` + Name string `json:"name" binding:"required"` + Hidden bool `json:"hidden"` + Component string `json:"component"` + Sort int `json:"sort"` + Meta map[string]interface{} `json:"meta"` + Parameters []MenuParameter `json:"parameters"` + MenuBtn []MenuBtn `json:"menuBtn"` +} + +// DeleteBaseMenuRequest 删除菜单请求 +type DeleteBaseMenuRequest struct { + ID uint `json:"ID" binding:"required"` +} + +// GetMenuByIdRequest 根据ID获取菜单请求 +type GetMenuByIdRequest struct { + ID uint `json:"ID" binding:"required"` +} + +// AddMenuAuthorityRequest 添加菜单权限请求 +type AddMenuAuthorityRequest struct { + AuthorityId uint `json:"authorityId" binding:"required"` + Menus []uint `json:"menus"` +} + +// GetMenuAuthorityRequest 获取菜单权限请求 +type GetMenuAuthorityRequest struct { + AuthorityId uint `json:"authorityId" binding:"required"` +} + +// AddBaseMenu 添加菜单 +func (m *MenuApi) AddBaseMenu(c *gin.Context) { + var req AddBaseMenuRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + menu := &system.BaseMenu{ + ParentId: req.ParentId, + Path: req.Path, + Name: req.Name, + Hidden: req.Hidden, + Component: req.Component, + Sort: req.Sort, + } + + if err := menuUsecase.AddBaseMenu(c, menu); err != nil { + response.FailWithMessage("创建失败: "+err.Error(), c) + return + } + + response.OkWithMessage("创建成功", c) +} + +// DeleteBaseMenu 删除菜单 +func (m *MenuApi) DeleteBaseMenu(c *gin.Context) { + var req DeleteBaseMenuRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + if err := menuUsecase.DeleteBaseMenu(c, req.ID); err != nil { + response.FailWithMessage("删除失败: "+err.Error(), c) + return + } + + response.OkWithMessage("删除成功", c) +} + +// UpdateBaseMenu 更新菜单 +func (m *MenuApi) UpdateBaseMenu(c *gin.Context) { + var req UpdateBaseMenuRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + menu := &system.BaseMenu{ + ID: req.ID, + ParentId: req.ParentId, + Path: req.Path, + Name: req.Name, + Hidden: req.Hidden, + Component: req.Component, + Sort: req.Sort, + } + + if err := menuUsecase.UpdateBaseMenu(c, menu); err != nil { + response.FailWithMessage("更新失败: "+err.Error(), c) + return + } + + response.OkWithMessage("更新成功", c) +} + +// GetMenu 获取用户菜单 +func (m *MenuApi) GetMenu(c *gin.Context) { + authorityId := utils.GetUserAuthorityId(c) + menus, err := menuUsecase.GetMenuTree(c, authorityId) + if err != nil { + response.FailWithMessage("获取失败", c) + return + } + + response.OkWithDetailed(gin.H{"menus": menus}, "获取成功", c) +} + +// GetMenuList 获取菜单列表 +func (m *MenuApi) GetMenuList(c *gin.Context) { + authorityId := utils.GetUserAuthorityId(c) + menus, err := menuUsecase.GetInfoList(c, authorityId) + if err != nil { + response.FailWithMessage("获取失败", c) + return + } + + response.OkWithDetailed(gin.H{"list": menus}, "获取成功", c) +} + +// GetBaseMenuTree 获取菜单树 +func (m *MenuApi) GetBaseMenuTree(c *gin.Context) { + authorityId := utils.GetUserAuthorityId(c) + menus, err := menuUsecase.GetBaseMenuTree(c, authorityId) + if err != nil { + response.FailWithMessage("获取失败", c) + return + } + + response.OkWithDetailed(gin.H{"menus": menus}, "获取成功", c) +} + +// GetBaseMenuById 根据ID获取菜单 +func (m *MenuApi) GetBaseMenuById(c *gin.Context) { + var req GetMenuByIdRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + menu, err := menuUsecase.GetBaseMenuById(c, req.ID) + if err != nil { + response.FailWithMessage("获取失败", c) + return + } + + response.OkWithDetailed(gin.H{"menu": menu}, "获取成功", c) +} + +// AddMenuAuthority 添加菜单权限 +func (m *MenuApi) AddMenuAuthority(c *gin.Context) { + var req AddMenuAuthorityRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + // 获取当前用户的角色ID + adminAuthorityId := utils.GetUserAuthorityId(c) + + // 构建菜单列表 + var menus []*system.BaseMenu + for _, menuId := range req.Menus { + menus = append(menus, &system.BaseMenu{ID: menuId}) + } + + if err := menuUsecase.AddMenuAuthority(c, menus, adminAuthorityId, req.AuthorityId); err != nil { + response.FailWithMessage("添加失败: "+err.Error(), c) + return + } + + response.OkWithMessage("添加成功", c) +} + +// GetMenuAuthority 获取菜单权限 +func (m *MenuApi) GetMenuAuthority(c *gin.Context) { + var req GetMenuAuthorityRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + menus, err := menuUsecase.GetMenuAuthority(c, req.AuthorityId) + if err != nil { + response.FailWithMessage("获取失败", c) + return + } + + response.OkWithDetailed(gin.H{"menus": menus}, "获取成功", c) +} diff --git a/internal/server/handler/system/sys_operation_record.go b/internal/server/handler/system/sys_operation_record.go new file mode 100644 index 0000000..eba20ff --- /dev/null +++ b/internal/server/handler/system/sys_operation_record.go @@ -0,0 +1,117 @@ +package system + +import ( + "kra/pkg/response" + + "github.com/gin-gonic/gin" +) + +type OperationRecordApi struct{} + +// DeleteOperationRecordRequest 删除操作记录请求 +type DeleteOperationRecordRequest struct { + ID uint `json:"ID" binding:"required"` +} + +// DeleteOperationRecordByIdsRequest 批量删除操作记录请求 +type DeleteOperationRecordByIdsRequest struct { + Ids []uint `json:"ids" binding:"required"` +} + +// GetOperationRecordListRequest 获取操作记录列表请求 +type GetOperationRecordListRequest struct { + Page int `json:"page"` + PageSize int `json:"pageSize"` + Method string `json:"method"` + Path string `json:"path"` + Status int `json:"status"` +} + +// DeleteSysOperationRecord 删除操作记录 +func (o *OperationRecordApi) DeleteSysOperationRecord(c *gin.Context) { + var req DeleteOperationRecordRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + if err := operationRecordUsecase.DeleteOperationRecord(c, req.ID); err != nil { + response.FailWithMessage("删除失败: "+err.Error(), c) + return + } + + response.OkWithMessage("删除成功", c) +} + +// DeleteSysOperationRecordByIds 批量删除操作记录 +func (o *OperationRecordApi) DeleteSysOperationRecordByIds(c *gin.Context) { + var req DeleteOperationRecordByIdsRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + if err := operationRecordUsecase.DeleteOperationRecordByIds(c, req.Ids); err != nil { + response.FailWithMessage("删除失败: "+err.Error(), c) + return + } + + response.OkWithMessage("删除成功", c) +} + +// FindSysOperationRecord 根据ID获取操作记录 +func (o *OperationRecordApi) FindSysOperationRecord(c *gin.Context) { + idStr := c.Query("ID") + if idStr == "" { + response.FailWithMessage("参数错误", c) + return + } + + var id uint + // 简单转换 + for _, ch := range idStr { + if ch >= '0' && ch <= '9' { + id = id*10 + uint(ch-'0') + } + } + + record, err := operationRecordUsecase.GetOperationRecord(c, id) + if err != nil { + response.FailWithMessage("获取失败", c) + return + } + + response.OkWithDetailed(gin.H{"reSysOperationRecord": record}, "获取成功", c) +} + +// GetSysOperationRecordList 获取操作记录列表 +func (o *OperationRecordApi) GetSysOperationRecordList(c *gin.Context) { + var req GetOperationRecordListRequest + // GET请求从query获取参数 + req.Page = 1 + req.PageSize = 10 + + filters := make(map[string]interface{}) + if req.Method != "" { + filters["method"] = req.Method + } + if req.Path != "" { + filters["path"] = req.Path + } + if req.Status != 0 { + filters["status"] = req.Status + } + + list, total, err := operationRecordUsecase.GetOperationRecordList(c, req.Page, req.PageSize, filters) + if err != nil { + response.FailWithMessage("获取失败", c) + return + } + + response.OkWithDetailed(response.PageResult{ + List: list, + Total: total, + Page: req.Page, + PageSize: req.PageSize, + }, "获取成功", c) +} diff --git a/internal/server/handler/system/sys_params.go b/internal/server/handler/system/sys_params.go new file mode 100644 index 0000000..eff4155 --- /dev/null +++ b/internal/server/handler/system/sys_params.go @@ -0,0 +1,158 @@ +package system + +import ( + "kra/internal/biz/system" + "kra/pkg/response" + + "github.com/gin-gonic/gin" +) + +type ParamsApi struct{} + +// CreateParamsRequest 创建参数请求 +type CreateParamsRequest struct { + Key string `json:"key" binding:"required"` + Value string `json:"value" binding:"required"` + Name string `json:"name"` + Desc string `json:"desc"` +} + +// UpdateParamsRequest 更新参数请求 +type UpdateParamsRequest struct { + ID uint `json:"ID" binding:"required"` + Key string `json:"key" binding:"required"` + Value string `json:"value" binding:"required"` + Name string `json:"name"` + Desc string `json:"desc"` +} + +// DeleteParamsRequest 删除参数请求 +type DeleteParamsRequest struct { + ID uint `json:"ID" binding:"required"` +} + +// GetParamsListRequest 获取参数列表请求 +type GetParamsListRequest struct { + Page int `json:"page"` + PageSize int `json:"pageSize"` + Key string `json:"key"` + Name string `json:"name"` +} + +// CreateSysParams 创建参数 +func (p *ParamsApi) CreateSysParams(c *gin.Context) { + var req CreateParamsRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + params := &system.Params{ + Key: req.Key, + Value: req.Value, + Name: req.Name, + Desc: req.Desc, + } + + if err := paramsUsecase.Create(c, params); err != nil { + response.FailWithMessage("创建失败: "+err.Error(), c) + return + } + + response.OkWithMessage("创建成功", c) +} + +// DeleteSysParams 删除参数 +func (p *ParamsApi) DeleteSysParams(c *gin.Context) { + var req DeleteParamsRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + if err := paramsUsecase.Delete(c, req.ID); err != nil { + response.FailWithMessage("删除失败: "+err.Error(), c) + return + } + + response.OkWithMessage("删除成功", c) +} + +// UpdateSysParams 更新参数 +func (p *ParamsApi) UpdateSysParams(c *gin.Context) { + var req UpdateParamsRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + params := &system.Params{ + ID: req.ID, + Key: req.Key, + Value: req.Value, + Name: req.Name, + Desc: req.Desc, + } + + if err := paramsUsecase.Update(c, params); err != nil { + response.FailWithMessage("更新失败: "+err.Error(), c) + return + } + + response.OkWithMessage("更新成功", c) +} + +// GetSysParams 根据Key获取参数 +func (p *ParamsApi) GetSysParams(c *gin.Context) { + key := c.Query("key") + if key == "" { + response.FailWithMessage("参数错误", c) + return + } + + params, err := paramsUsecase.GetByKey(c, key) + if err != nil { + response.FailWithMessage("获取失败", c) + return + } + + response.OkWithDetailed(gin.H{"sysParams": params}, "获取成功", c) +} + +// GetSysParamsList 获取参数列表 +func (p *ParamsApi) GetSysParamsList(c *gin.Context) { + var req GetParamsListRequest + if err := c.ShouldBindJSON(&req); err != nil { + // GET请求使用默认值 + req.Page = 1 + req.PageSize = 10 + } + + if req.Page == 0 { + req.Page = 1 + } + if req.PageSize == 0 { + req.PageSize = 10 + } + + filters := make(map[string]interface{}) + if req.Key != "" { + filters["key"] = req.Key + } + if req.Name != "" { + filters["name"] = req.Name + } + + list, total, err := paramsUsecase.GetList(c, req.Page, req.PageSize, filters) + if err != nil { + response.FailWithMessage("获取失败", c) + return + } + + response.OkWithDetailed(response.PageResult{ + List: list, + Total: total, + Page: req.Page, + PageSize: req.PageSize, + }, "获取成功", c) +} diff --git a/internal/server/handler/system/sys_user.go b/internal/server/handler/system/sys_user.go new file mode 100644 index 0000000..0845085 --- /dev/null +++ b/internal/server/handler/system/sys_user.go @@ -0,0 +1,426 @@ +package system + +import ( + "encoding/json" + "strconv" + "time" + + "kra/internal/biz/system" + "kra/pkg/response" + "kra/pkg/utils" + + "github.com/gin-gonic/gin" +) + +type UserApi struct{} + +// RegisterRequest 注册请求 +type RegisterRequest struct { + Username string `json:"userName" binding:"required"` + Password string `json:"password" binding:"required"` + NickName string `json:"nickName"` + HeaderImg string `json:"headerImg"` + AuthorityId uint `json:"authorityId" binding:"required"` + AuthorityIds []uint `json:"authorityIds"` + Phone string `json:"phone"` + Email string `json:"email"` + Enable int `json:"enable"` +} + +// ChangePasswordRequest 修改密码请求 +type ChangePasswordRequest struct { + Password string `json:"password" binding:"required"` + NewPassword string `json:"newPassword" binding:"required"` +} + +// ResetPasswordRequest 重置密码请求 +type ResetPasswordRequest struct { + ID uint `json:"ID" binding:"required"` + Password string `json:"password"` +} + +// GetUserListRequest 获取用户列表请求 +type GetUserListRequest struct { + Page int `json:"page"` + PageSize int `json:"pageSize"` + Username string `json:"username"` + NickName string `json:"nickName"` + Phone string `json:"phone"` + Email string `json:"email"` +} + +// SetUserInfoRequest 设置用户信息请求 +type SetUserInfoRequest struct { + ID uint `json:"ID" binding:"required"` + NickName string `json:"nickName"` + HeaderImg string `json:"headerImg"` + Phone string `json:"phone"` + Email string `json:"email"` + Enable int `json:"enable"` + AuthorityIds []uint `json:"authorityIds"` +} + +// SetSelfInfoRequest 设置自己信息请求 +type SetSelfInfoRequest struct { + NickName string `json:"nickName"` + HeaderImg string `json:"headerImg"` + Phone string `json:"phone"` + Email string `json:"email"` +} + +// SetUserAuthorityRequest 设置用户角色请求 +type SetUserAuthorityRequest struct { + AuthorityId uint `json:"authorityId" binding:"required"` +} + +// SetUserAuthoritiesRequest 设置用户多角色请求 +type SetUserAuthoritiesRequest struct { + ID uint `json:"ID" binding:"required"` + AuthorityIds []uint `json:"authorityIds" binding:"required"` +} + +// DeleteUserRequest 删除用户请求 +type DeleteUserRequest struct { + ID uint `json:"ID" binding:"required"` +} + +// Register 用户注册 +func (u *UserApi) Register(c *gin.Context) { + var req RegisterRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + user := &system.User{ + Username: req.Username, + Password: req.Password, + NickName: req.NickName, + HeaderImg: req.HeaderImg, + AuthorityId: req.AuthorityId, + Phone: req.Phone, + Email: req.Email, + Enable: req.Enable, + } + + created, err := userUsecase.Register(c, user) + if err != nil { + response.FailWithDetailed(gin.H{"user": created}, "注册失败", c) + return + } + + response.OkWithDetailed(gin.H{"user": toUserResponse(created)}, "注册成功", c) +} + +// ChangePassword 修改密码 +func (u *UserApi) ChangePassword(c *gin.Context) { + var req ChangePasswordRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + userID := utils.GetUserID(c) + if userID == 0 { + response.FailWithMessage("请先登录", c) + return + } + + if err := userUsecase.ChangePassword(c, userID, req.Password, req.NewPassword); err != nil { + response.FailWithMessage("修改失败,原密码与当前账户不符", c) + return + } + + response.OkWithMessage("修改成功", c) +} + +// ResetPassword 重置密码 +func (u *UserApi) ResetPassword(c *gin.Context) { + var req ResetPasswordRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + if err := userUsecase.ResetPassword(c, req.ID, req.Password); err != nil { + response.FailWithMessage("重置失败"+err.Error(), c) + return + } + + response.OkWithMessage("重置成功", c) +} + +// GetUserInfo 获取用户信息 +func (u *UserApi) GetUserInfo(c *gin.Context) { + uuid := utils.GetUserUuid(c) + user, err := userUsecase.GetUserInfo(c, uuid.String()) + if err != nil { + response.FailWithMessage("获取失败", c) + return + } + + response.OkWithDetailed(gin.H{"userInfo": toUserResponse(user)}, "获取成功", c) +} + +// GetUserList 获取用户列表 +func (u *UserApi) GetUserList(c *gin.Context) { + var req GetUserListRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + if req.Page == 0 { + req.Page = 1 + } + if req.PageSize == 0 { + req.PageSize = 10 + } + + filters := make(map[string]string) + if req.Username != "" { + filters["username"] = req.Username + } + if req.NickName != "" { + filters["nick_name"] = req.NickName + } + if req.Phone != "" { + filters["phone"] = req.Phone + } + if req.Email != "" { + filters["email"] = req.Email + } + + users, total, err := userUsecase.GetUserList(c, req.Page, req.PageSize, filters) + if err != nil { + response.FailWithMessage("获取失败", c) + return + } + + list := make([]interface{}, len(users)) + for i, u := range users { + list[i] = toUserResponse(u) + } + + response.OkWithDetailed(response.PageResult{ + List: list, + Total: total, + Page: req.Page, + PageSize: req.PageSize, + }, "获取成功", c) +} + +// SetUserInfo 设置用户信息 +func (u *UserApi) SetUserInfo(c *gin.Context) { + var req SetUserInfoRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + // 设置用户角色 + if len(req.AuthorityIds) > 0 { + authorityID := utils.GetUserAuthorityId(c) + if err := userUsecase.SetUserAuthorities(c, authorityID, req.ID, req.AuthorityIds); err != nil { + response.FailWithMessage("设置失败", c) + return + } + } + + user := &system.User{ + ID: req.ID, + NickName: req.NickName, + HeaderImg: req.HeaderImg, + Phone: req.Phone, + Email: req.Email, + Enable: req.Enable, + } + + if err := userUsecase.SetUserInfo(c, user); err != nil { + response.FailWithMessage("设置失败", c) + return + } + + response.OkWithMessage("设置成功", c) +} + +// SetSelfInfo 设置自己信息 +func (u *UserApi) SetSelfInfo(c *gin.Context) { + var req SetSelfInfoRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + userID := utils.GetUserID(c) + if userID == 0 { + response.FailWithMessage("请先登录", c) + return + } + + user := &system.User{ + ID: userID, + NickName: req.NickName, + HeaderImg: req.HeaderImg, + Phone: req.Phone, + Email: req.Email, + } + + if err := userUsecase.SetSelfInfo(c, user); err != nil { + response.FailWithMessage("设置失败", c) + return + } + + response.OkWithMessage("设置成功", c) +} + +// SetSelfSetting 设置自己配置 +func (u *UserApi) SetSelfSetting(c *gin.Context) { + var req map[string]interface{} + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + userID := utils.GetUserID(c) + if userID == 0 { + response.FailWithMessage("请先登录", c) + return + } + + setting, _ := json.Marshal(req) + if err := userUsecase.SetSelfSetting(c, userID, setting); err != nil { + response.FailWithMessage("设置失败", c) + return + } + + response.OkWithMessage("设置成功", c) +} + +// DeleteUser 删除用户 +func (u *UserApi) DeleteUser(c *gin.Context) { + var req DeleteUserRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + currentUserID := utils.GetUserID(c) + if currentUserID == req.ID { + response.FailWithMessage("删除失败, 无法删除自己。", c) + return + } + + if err := userUsecase.DeleteUser(c, req.ID); err != nil { + response.FailWithMessage("删除失败", c) + return + } + + response.OkWithMessage("删除成功", c) +} + +// SetUserAuthority 设置用户角色 +func (u *UserApi) SetUserAuthority(c *gin.Context) { + var req SetUserAuthorityRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + userID := utils.GetUserID(c) + if userID == 0 { + response.FailWithMessage("请先登录", c) + return + } + + if err := userUsecase.SetUserAuthority(c, userID, req.AuthorityId); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + // 重新生成token + claims := utils.GetUserInfo(c) + if claims == nil { + response.FailWithMessage("请先登录", c) + return + } + + claims.AuthorityID = req.AuthorityId + token, err := jwtInstance.CreateToken(claims.BaseClaims) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + c.Header("new-token", token) + c.Header("new-expires-at", strconv.FormatInt(claims.ExpiresAt.Unix(), 10)) + utils.SetToken(c, token, int(claims.ExpiresAt.Unix()-time.Now().Unix())) + + response.OkWithMessage("修改成功", c) +} + +// SetUserAuthorities 设置用户多角色 +func (u *UserApi) SetUserAuthorities(c *gin.Context) { + var req SetUserAuthoritiesRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + + authorityID := utils.GetUserAuthorityId(c) + if err := userUsecase.SetUserAuthorities(c, authorityID, req.ID, req.AuthorityIds); err != nil { + response.FailWithMessage("修改失败", c) + return + } + + response.OkWithMessage("修改成功", c) +} + +// toUserResponse 转换用户响应 +func toUserResponse(user *system.User) map[string]interface{} { + if user == nil { + return nil + } + + result := map[string]interface{}{ + "ID": user.ID, + "uuid": user.UUID.String(), + "userName": user.Username, + "nickName": user.NickName, + "sideMode": user.SideMode, + "headerImg": user.HeaderImg, + "baseColor": user.BaseColor, + "authorityId": user.AuthorityId, + "phone": user.Phone, + "email": user.Email, + "enable": user.Enable, + } + + if user.OriginSetting != nil { + result["originSetting"] = string(user.OriginSetting) + } + + if user.Authority != nil { + result["authority"] = map[string]interface{}{ + "authorityId": user.Authority.AuthorityId, + "authorityName": user.Authority.AuthorityName, + "parentId": user.Authority.ParentId, + "defaultRouter": user.Authority.DefaultRouter, + } + } + + if len(user.Authorities) > 0 { + authorities := make([]map[string]interface{}, len(user.Authorities)) + for i, a := range user.Authorities { + authorities[i] = map[string]interface{}{ + "authorityId": a.AuthorityId, + "authorityName": a.AuthorityName, + "parentId": a.ParentId, + "defaultRouter": a.DefaultRouter, + } + } + result["authorities"] = authorities + } + + return result +} diff --git a/internal/server/http.go b/internal/server/http.go index 161df53..e928a1b 100644 --- a/internal/server/http.go +++ b/internal/server/http.go @@ -1,47 +1,16 @@ package server import ( - "context" - "kra/internal/conf" - "kra/internal/server/middleware" - "kra/internal/service/system" - "kra/pkg/jwt" - "kra/pkg/response" - "github.com/go-kratos/kratos/v2/middleware/recovery" - "github.com/go-kratos/kratos/v2/middleware/selector" + "github.com/gin-gonic/gin" khttp "github.com/go-kratos/kratos/v2/transport/http" ) -// NewHTTPServer new an HTTP server. -func NewHTTPServer( - c *conf.Server, - jwtPkg *jwt.JWT, - userSvc *system.UserService, - apiSvc *system.ApiService, - authoritySvc *system.AuthorityService, - authorityBtnSvc *system.AuthorityBtnService, - captchaSvc *system.CaptchaService, - casbinSvc *system.CasbinService, - menuSvc *system.MenuService, - dictionarySvc *system.DictionaryService, - jwtBlacklistSvc *system.JwtBlacklistService, - operationRecordSvc *system.OperationRecordService, - paramsSvc *system.ParamsService, - systemConfigSvc *system.SystemConfigService, -) *khttp.Server { - var opts = []khttp.ServerOption{ - khttp.Middleware( - recovery.Recovery(), - // JWT认证中间件(排除公开路由) - selector.Server( - middleware.JWTAuthSimple(jwtPkg), - ).Match(newWhiteListMatcher()).Build(), - ), - khttp.ResponseEncoder(response.Encoder), - khttp.ErrorEncoder(response.ErrorEncoder), - } +// NewHTTPServer 创建HTTP服务器,将gin路由挂载到kratos http server +func NewHTTPServer(c *conf.Server, ginRouter *gin.Engine) *khttp.Server { + var opts = []khttp.ServerOption{} + if c.Http.Network != "" { opts = append(opts, khttp.Network(c.Http.Network)) } @@ -51,43 +20,11 @@ func NewHTTPServer( if c.Http.Timeout != nil { opts = append(opts, khttp.Timeout(c.Http.Timeout.AsDuration())) } + srv := khttp.NewServer(opts...) - // 创建路由组 - rg := &system.RouterGroup{ - Public: srv.Route("/"), - Private: srv.Route("/"), - PrivateWithRecord: srv.Route("/"), - } - - // 注册各模块路由 - userSvc.RegisterRoutes(rg) - apiSvc.RegisterRoutes(rg) - authoritySvc.RegisterRoutes(rg) - authorityBtnSvc.RegisterRoutes(rg) - captchaSvc.RegisterRoutes(rg) - casbinSvc.RegisterRoutes(rg) - menuSvc.RegisterRoutes(rg) - dictionarySvc.RegisterRoutes(rg) - jwtBlacklistSvc.RegisterRoutes(rg) - operationRecordSvc.RegisterRoutes(rg) - paramsSvc.RegisterRoutes(rg) - systemConfigSvc.RegisterRoutes(rg) + // 关键:将gin路由挂载到kratos http server + srv.HandlePrefix("/", ginRouter) return srv } - -// newWhiteListMatcher 创建白名单匹配器(这些路由不需要JWT认证) -func newWhiteListMatcher() selector.MatchFunc { - whiteList := map[string]struct{}{ - "/base/login": {}, - "/base/captcha": {}, - "/api/freshCasbin": {}, - } - return func(ctx context.Context, operation string) bool { - if _, ok := whiteList[operation]; ok { - return false // 不需要认证 - } - return true // 需要认证 - } -} diff --git a/internal/server/middleware/casbin.go b/internal/server/middleware/casbin.go deleted file mode 100644 index 074ce38..0000000 --- a/internal/server/middleware/casbin.go +++ /dev/null @@ -1,64 +0,0 @@ -package middleware - -import ( - "context" - "errors" - "strconv" - "strings" - - "github.com/go-kratos/kratos/v2/middleware" - "github.com/go-kratos/kratos/v2/transport" - - pkgcasbin "kra/pkg/casbin" -) - -var ( - ErrPermissionDenied = errors.New("权限不足") -) - -// CasbinRBAC Casbin权限中间件 -func CasbinRBAC(routerPrefix string) middleware.Middleware { - return func(handler middleware.Handler) middleware.Handler { - return func(ctx context.Context, req interface{}) (interface{}, error) { - claims, ok := GetClaims(ctx) - if !ok { - return nil, ErrMissingToken - } - - if tr, ok := transport.FromServerContext(ctx); ok { - // 获取请求路径 - path := tr.Operation() - if routerPrefix != "" { - path = strings.TrimPrefix(path, routerPrefix) - } - - // 获取请求方法 - act := "GET" - if header := tr.RequestHeader(); header != nil { - if method := header.Get(":method"); method != "" { - act = method - } - } - - // 获取用户角色 - sub := strconv.Itoa(int(claims.AuthorityID)) - - // 检查权限 - enforcer := pkgcasbin.GetEnforcer() - if enforcer == nil { - return handler(ctx, req) - } - - success, err := enforcer.Enforce(sub, path, act) - if err != nil { - return nil, err - } - if !success { - return nil, ErrPermissionDenied - } - } - - return handler(ctx, req) - } - } -} diff --git a/internal/server/middleware/email.go b/internal/server/middleware/email.go deleted file mode 100644 index 08a1c58..0000000 --- a/internal/server/middleware/email.go +++ /dev/null @@ -1,78 +0,0 @@ -package middleware - -import ( - "bytes" - "context" - "fmt" - "io" - "time" - - "github.com/go-kratos/kratos/v2/log" - "github.com/go-kratos/kratos/v2/middleware" - "github.com/go-kratos/kratos/v2/transport" - kratoshttp "github.com/go-kratos/kratos/v2/transport/http" -) - -// EmailSender 邮件发送接口 -type EmailSender interface { - SendError(subject, body string) error -} - -// ErrorToEmail 错误邮件通知中间件 -func ErrorToEmail(sender EmailSender, logger log.Logger) middleware.Middleware { - helper := log.NewHelper(logger) - return func(handler middleware.Handler) middleware.Handler { - return func(ctx context.Context, req interface{}) (interface{}, error) { - var ( - username string - body string - method string - path string - ip string - ) - - // 获取用户信息 - if claims, ok := GetClaims(ctx); ok { - username = claims.Username - } - if username == "" { - username = "Unknown" - } - - if tr, ok := transport.FromServerContext(ctx); ok { - path = tr.Operation() - if header := tr.RequestHeader(); header != nil { - method = header.Get(":method") - } - - if ht, ok := tr.(kratoshttp.Transporter); ok { - r := ht.Request() - ip = getClientIP(r) - - // 读取body - bodyBytes, _ := io.ReadAll(r.Body) - r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) - body = string(bodyBytes) - } - } - - start := time.Now() - reply, err := handler(ctx, req) - latency := time.Since(start) - - // 如果有错误,发送邮件 - if err != nil && sender != nil { - subject := fmt.Sprintf("%s %s调用了%s报错了", username, ip, path) - content := fmt.Sprintf( - "接收到的请求为%s\n请求方式为%s\n报错信息如下%s\n耗时%s\n", - body, method, err.Error(), latency.String(), - ) - if sendErr := sender.SendError(subject, content); sendErr != nil { - helper.Error("ErrorToEmail Failed, err:", sendErr) - } - } - - return reply, err - } - } -} diff --git a/internal/server/middleware/gin_casbin.go b/internal/server/middleware/gin_casbin.go new file mode 100644 index 0000000..f5134a1 --- /dev/null +++ b/internal/server/middleware/gin_casbin.go @@ -0,0 +1,65 @@ +package middleware + +import ( + "strconv" + "strings" + + "kra/pkg/casbin" + "kra/pkg/response" + "kra/pkg/utils" + + "github.com/gin-gonic/gin" +) + +// CasbinConfig Casbin中间件配置 +type CasbinConfig struct { + RouterPrefix string // 路由前缀,需要从路径中去除 +} + +// 全局配置 +var casbinConfig *CasbinConfig + +// SetCasbinConfig 设置Casbin配置 +func SetCasbinConfig(cfg *CasbinConfig) { + casbinConfig = cfg +} + +// CasbinHandler Casbin权限拦截器(与 gva 保持一致) +func CasbinHandler() gin.HandlerFunc { + return func(c *gin.Context) { + waitUse, _ := utils.GetClaims(c) + if waitUse == nil { + response.FailWithDetailed(gin.H{}, "权限不足", c) + c.Abort() + return + } + + // 获取请求的PATH + path := c.Request.URL.Path + // 去除路由前缀 + if casbinConfig != nil && casbinConfig.RouterPrefix != "" { + path = strings.TrimPrefix(path, casbinConfig.RouterPrefix) + } + + // 获取请求方法 + act := c.Request.Method + // 获取用户的角色 + sub := strconv.Itoa(int(waitUse.AuthorityID)) + + // 判断策略中是否存在 + e := casbin.GetEnforcer() + if e == nil { + response.FailWithDetailed(gin.H{}, "Casbin未初始化", c) + c.Abort() + return + } + + success, _ := e.Enforce(sub, path, act) + if !success { + response.FailWithDetailed(gin.H{}, "权限不足", c) + c.Abort() + return + } + c.Next() + } +} diff --git a/internal/server/middleware/gin_jwt.go b/internal/server/middleware/gin_jwt.go new file mode 100644 index 0000000..53510f1 --- /dev/null +++ b/internal/server/middleware/gin_jwt.go @@ -0,0 +1,113 @@ +package middleware + +import ( + "errors" + "strconv" + "time" + + "kra/pkg/jwt" + "kra/pkg/response" + "kra/pkg/utils" + + jwtv5 "github.com/golang-jwt/jwt/v5" + + "github.com/gin-gonic/gin" +) + +// BlacklistChecker JWT黑名单检查接口 +type BlacklistChecker interface { + IsBlacklist(jwt string) bool +} + +// JWTConfig JWT中间件配置 +type JWTConfig struct { + JWT *jwt.JWT + BlacklistChecker BlacklistChecker + UseMultipoint bool + SetRedisJWT func(token, username string) error +} + +// 全局配置(需要在初始化时设置) +var jwtConfig *JWTConfig + +// SetJWTConfig 设置JWT配置 +func SetJWTConfig(cfg *JWTConfig) { + jwtConfig = cfg + // 同时设置 utils 包的 JWT 实例 + if cfg != nil && cfg.JWT != nil { + utils.SetJWT(cfg.JWT) + } +} + +// JWTAuth JWT认证中间件(与 gva 保持一致) +func JWTAuth() gin.HandlerFunc { + return func(c *gin.Context) { + // 我们这里jwt鉴权取头部信息 x-token 登录时回返回token信息 这里前端需要把token存储到cookie或者本地localStorage中 不过需要跟后端协商过期时间 可以约定刷新令牌或者重新登录 + token := utils.GetToken(c) + if token == "" { + response.NoAuth("未登录或非法访问,请登录", c) + c.Abort() + return + } + + // 检查黑名单 + if jwtConfig != nil && jwtConfig.BlacklistChecker != nil && jwtConfig.BlacklistChecker.IsBlacklist(token) { + response.NoAuth("您的帐户异地登陆或令牌失效", c) + utils.ClearToken(c) + c.Abort() + return + } + + if jwtConfig == nil || jwtConfig.JWT == nil { + response.NoAuth("JWT配置错误", c) + c.Abort() + return + } + + // parseToken 解析token包含的信息 + claims, err := jwtConfig.JWT.ParseToken(token) + if err != nil { + if errors.Is(err, jwt.ErrTokenExpired) { + response.NoAuth("登录已过期,请重新登录", c) + utils.ClearToken(c) + c.Abort() + return + } + response.NoAuth(err.Error(), c) + utils.ClearToken(c) + c.Abort() + return + } + + // 已登录用户被管理员禁用 需要使该用户的jwt失效 此处比较消耗性能 如果需要 请自行打开 + // 用户被删除的逻辑 需要优化 此处比较消耗性能 如果需要 请自行打开 + + c.Set("claims", claims) + + // 检查是否需要刷新token + if claims.ExpiresAt.Unix()-time.Now().Unix() < claims.BufferTime { + claims.ExpiresAt = jwtv5.NewNumericDate(time.Now().Add(jwtConfig.JWT.ExpiresAt)) + newToken, newClaims, err := jwtConfig.JWT.CreateTokenByOldToken(token, *claims) + if err == nil { + c.Header("new-token", newToken) + c.Header("new-expires-at", strconv.FormatInt(newClaims.ExpiresAt.Unix(), 10)) + utils.SetToken(c, newToken, int(jwtConfig.JWT.ExpiresAt.Seconds()/60)) + + // 多点登录记录新JWT + if jwtConfig.UseMultipoint && jwtConfig.SetRedisJWT != nil { + _ = jwtConfig.SetRedisJWT(newToken, newClaims.Username) + } + } + } + + c.Next() + + // 处理后续中间件设置的新token + if newToken, exists := c.Get("new-token"); exists { + c.Header("new-token", newToken.(string)) + } + if newExpiresAt, exists := c.Get("new-expires-at"); exists { + c.Header("new-expires-at", newExpiresAt.(string)) + } + } +} diff --git a/internal/server/middleware/gin_operation.go b/internal/server/middleware/gin_operation.go new file mode 100644 index 0000000..ab76819 --- /dev/null +++ b/internal/server/middleware/gin_operation.go @@ -0,0 +1,172 @@ +package middleware + +import ( + "bytes" + "encoding/json" + "io" + "net/http" + "net/url" + "strconv" + "strings" + "sync" + "time" + + "kra/pkg/utils" + + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +var respPool sync.Pool +var bufferSize = 1024 + +func init() { + respPool.New = func() interface{} { + return make([]byte, bufferSize) + } +} + +// OperationRecord 操作记录结构 +type OperationRecord struct { + IP string `json:"ip"` + Method string `json:"method"` + Path string `json:"path"` + Status int `json:"status"` + Latency time.Duration `json:"latency"` + Agent string `json:"agent"` + ErrorMessage string `json:"error_message"` + Body string `json:"body"` + Resp string `json:"resp"` + UserID int `json:"user_id"` +} + +// OperationRecordCreator 操作记录创建接口 +type OperationRecordCreator interface { + CreateOperationRecord(record *OperationRecord) error +} + +// 全局操作记录创建器 +var operationRecordCreator OperationRecordCreator +var operationLogger *zap.Logger + +// SetOperationRecordCreator 设置操作记录创建器 +func SetOperationRecordCreator(creator OperationRecordCreator) { + operationRecordCreator = creator +} + +// SetOperationLogger 设置操作记录日志 +func SetOperationLogger(logger *zap.Logger) { + operationLogger = logger +} + +// OperationRecordMiddleware 操作记录中间件(与 gva 保持一致) +func OperationRecordMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + var body []byte + var userId int + + if c.Request.Method != http.MethodGet { + var err error + body, err = io.ReadAll(c.Request.Body) + if err != nil { + if operationLogger != nil { + operationLogger.Error("read body from request error:", zap.Error(err)) + } + } else { + c.Request.Body = io.NopCloser(bytes.NewBuffer(body)) + } + } else { + query := c.Request.URL.RawQuery + query, _ = url.QueryUnescape(query) + split := strings.Split(query, "&") + m := make(map[string]string) + for _, v := range split { + kv := strings.Split(v, "=") + if len(kv) == 2 { + m[kv[0]] = kv[1] + } + } + body, _ = json.Marshal(&m) + } + + claims, _ := utils.GetClaims(c) + if claims != nil && claims.BaseClaims.ID != 0 { + userId = int(claims.BaseClaims.ID) + } else { + id, err := strconv.Atoi(c.Request.Header.Get("x-user-id")) + if err != nil { + userId = 0 + } + userId = id + } + + record := OperationRecord{ + IP: c.ClientIP(), + Method: c.Request.Method, + Path: c.Request.URL.Path, + Agent: c.Request.UserAgent(), + Body: "", + UserID: userId, + } + + // 上传文件时候 中间件日志进行裁断操作 + if strings.Contains(c.GetHeader("Content-Type"), "multipart/form-data") { + record.Body = "[文件]" + } else { + if len(body) > bufferSize { + record.Body = "[超出记录长度]" + } else { + record.Body = string(body) + } + } + + writer := responseBodyWriter{ + ResponseWriter: c.Writer, + body: &bytes.Buffer{}, + } + c.Writer = writer + now := time.Now() + + c.Next() + + latency := time.Since(now) + record.ErrorMessage = c.Errors.ByType(gin.ErrorTypePrivate).String() + record.Status = c.Writer.Status() + record.Latency = latency + record.Resp = writer.body.String() + + // 检查是否为下载响应,如果是则截断 + if strings.Contains(c.Writer.Header().Get("Pragma"), "public") || + strings.Contains(c.Writer.Header().Get("Expires"), "0") || + strings.Contains(c.Writer.Header().Get("Cache-Control"), "must-revalidate, post-check=0, pre-check=0") || + strings.Contains(c.Writer.Header().Get("Content-Type"), "application/force-download") || + strings.Contains(c.Writer.Header().Get("Content-Type"), "application/octet-stream") || + strings.Contains(c.Writer.Header().Get("Content-Type"), "application/vnd.ms-excel") || + strings.Contains(c.Writer.Header().Get("Content-Type"), "application/download") || + strings.Contains(c.Writer.Header().Get("Content-Disposition"), "attachment") || + strings.Contains(c.Writer.Header().Get("Content-Transfer-Encoding"), "binary") { + if len(record.Resp) > bufferSize { + record.Resp = "[超出记录长度]" + } + } + + // 保存操作记录 + if operationRecordCreator != nil { + if err := operationRecordCreator.CreateOperationRecord(&record); err != nil { + if operationLogger != nil { + operationLogger.Error("create operation record error:", zap.Error(err)) + } + } + } + } +} + +type responseBodyWriter struct { + gin.ResponseWriter + body *bytes.Buffer +} + +func (r responseBodyWriter) Write(b []byte) (int, error) { + r.body.Write(b) + return r.ResponseWriter.Write(b) +} diff --git a/internal/server/middleware/gin_recovery.go b/internal/server/middleware/gin_recovery.go new file mode 100644 index 0000000..734912f --- /dev/null +++ b/internal/server/middleware/gin_recovery.go @@ -0,0 +1,98 @@ +package middleware + +import ( + "net" + "net/http" + "net/http/httputil" + "os" + "runtime/debug" + "strings" + + "kra/pkg/response" + + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +// 全局日志实例 +var recoveryLogger *zap.Logger + +// SetRecoveryLogger 设置Recovery日志 +func SetRecoveryLogger(logger *zap.Logger) { + recoveryLogger = logger +} + +// GinRecovery 自定义Recovery中间件(与 gva 保持一致) +func GinRecovery(stack bool) gin.HandlerFunc { + return func(c *gin.Context) { + defer func() { + if err := recover(); err != nil { + // 检查是否为断开的连接 + var brokenPipe bool + if ne, ok := err.(*net.OpError); ok { + if se, ok := ne.Err.(*os.SyscallError); ok { + if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || + strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") { + brokenPipe = true + } + } + } + + httpRequest, _ := httputil.DumpRequest(c.Request, false) + if brokenPipe { + if recoveryLogger != nil { + recoveryLogger.Error(c.Request.URL.Path, + zap.Any("error", err), + zap.String("request", string(httpRequest)), + ) + } + // 如果连接已断开,无法写入状态 + c.Error(err.(error)) + c.Abort() + return + } + + if stack { + if recoveryLogger != nil { + recoveryLogger.Error("[Recovery from panic]", + zap.Any("error", err), + zap.String("request", string(httpRequest)), + zap.String("stack", string(debug.Stack())), + ) + } + } else { + if recoveryLogger != nil { + recoveryLogger.Error("[Recovery from panic]", + zap.Any("error", err), + zap.String("request", string(httpRequest)), + ) + } + } + + response.FailWithMessage("服务器内部错误", c) + c.Abort() + } + }() + c.Next() + } +} + +// Cors 跨域中间件 +func Cors() gin.HandlerFunc { + return func(c *gin.Context) { + method := c.Request.Method + origin := c.Request.Header.Get("Origin") + + c.Header("Access-Control-Allow-Origin", origin) + c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token,Authorization,Token,X-Token,X-User-Id") + c.Header("Access-Control-Allow-Methods", "POST,GET,OPTIONS,DELETE,PUT") + c.Header("Access-Control-Expose-Headers", "Content-Length,Access-Control-Allow-Origin,Access-Control-Allow-Headers,Content-Type,New-Token,New-Expires-At") + c.Header("Access-Control-Allow-Credentials", "true") + + // 放行所有OPTIONS方法 + if method == "OPTIONS" { + c.AbortWithStatus(http.StatusNoContent) + } + c.Next() + } +} diff --git a/internal/server/middleware/jwt.go b/internal/server/middleware/jwt.go deleted file mode 100644 index 8625298..0000000 --- a/internal/server/middleware/jwt.go +++ /dev/null @@ -1,196 +0,0 @@ -package middleware - -import ( - "context" - "encoding/json" - "errors" - "strconv" - "strings" - "time" - - "github.com/go-kratos/kratos/v2/metadata" - "github.com/go-kratos/kratos/v2/middleware" - "github.com/go-kratos/kratos/v2/transport" - "github.com/go-kratos/kratos/v2/transport/http" - - pkgjwt "kra/pkg/jwt" -) - -const ( - // metadata keys for claims - mdKeyUserID = "x-md-user-id" - mdKeyUsername = "x-md-username" - mdKeyAuthorityID = "x-md-authority-id" - mdKeyUUID = "x-md-uuid" - mdKeyClaims = "x-md-claims" -) - -var ( - ErrMissingToken = errors.New("未登录或非法访问") - ErrInvalidToken = errors.New("无效的token") - ErrBlacklistJWT = errors.New("您的帐户异地登陆或令牌失效") -) - -// BlacklistChecker JWT黑名单检查接口 -type BlacklistChecker interface { - IsBlacklist(jwt string) bool -} - -// JWTAuthConfig JWT认证配置 -type JWTAuthConfig struct { - JWT *pkgjwt.JWT - BlacklistChecker BlacklistChecker - UseMultipoint bool - SetRedisJWT func(token, username string) error -} - -// JWTAuth JWT认证中间件 -func JWTAuth(cfg JWTAuthConfig) middleware.Middleware { - return func(handler middleware.Handler) middleware.Handler { - return func(ctx context.Context, req interface{}) (interface{}, error) { - if tr, ok := transport.FromServerContext(ctx); ok { - token := extractToken(tr) - if token == "" { - return nil, ErrMissingToken - } - - // 检查黑名单 - if cfg.BlacklistChecker != nil && cfg.BlacklistChecker.IsBlacklist(token) { - // 清除token - if ht, ok := tr.(http.Transporter); ok { - clearToken(ht) - } - return nil, ErrBlacklistJWT - } - - claims, err := cfg.JWT.ParseToken(token) - if err != nil { - if ht, ok := tr.(http.Transporter); ok { - clearToken(ht) - } - if errors.Is(err, pkgjwt.ErrTokenExpired) { - return nil, errors.New("登录已过期,请重新登录") - } - return nil, ErrInvalidToken - } - - // 将claims存入metadata - claimsJSON, _ := json.Marshal(claims) - ctx = metadata.AppendToClientContext(ctx, - mdKeyUserID, strconv.FormatUint(uint64(claims.BaseClaims.ID), 10), - mdKeyUsername, claims.Username, - mdKeyAuthorityID, strconv.FormatUint(uint64(claims.AuthorityID), 10), - mdKeyUUID, claims.UUID, - mdKeyClaims, string(claimsJSON), - ) - - // 检查是否需要刷新token - if cfg.JWT.NeedRefresh(claims) { - newToken, newClaims, err := cfg.JWT.CreateTokenByOldToken(token, *claims) - if err == nil { - if ht, ok := tr.(http.Transporter); ok { - ht.ReplyHeader().Set("new-token", newToken) - ht.ReplyHeader().Set("new-expires-at", strconv.FormatInt(newClaims.ExpiresAt.Unix(), 10)) - setToken(ht, newToken, int(time.Until(newClaims.ExpiresAt.Time).Seconds()/60)) - } - // 多点登录记录新JWT - if cfg.UseMultipoint && cfg.SetRedisJWT != nil { - _ = cfg.SetRedisJWT(newToken, newClaims.Username) - } - } - } - } - return handler(ctx, req) - } - } -} - -// JWTAuthSimple 简化版JWT认证中间件(不含黑名单检查) -func JWTAuthSimple(j *pkgjwt.JWT) middleware.Middleware { - return JWTAuth(JWTAuthConfig{JWT: j}) -} - -// extractToken 从请求中提取token -func extractToken(tr transport.Transporter) string { - // 优先从Authorization头获取 - auth := tr.RequestHeader().Get("Authorization") - if auth != "" { - if strings.HasPrefix(auth, "Bearer ") { - return strings.TrimPrefix(auth, "Bearer ") - } - return auth - } - - // 从x-token头获取 - token := tr.RequestHeader().Get("x-token") - if token != "" { - return token - } - - return "" -} - -// clearToken 清除token -func clearToken(ht http.Transporter) { - ht.ReplyHeader().Set("x-token", "") -} - -// setToken 设置token到cookie -func setToken(ht http.Transporter, token string, maxAge int) { - // 通过header设置cookie信息,让前端处理 - ht.ReplyHeader().Set("x-token", token) -} - -// GetClaims 从context获取claims -func GetClaims(ctx context.Context) (*pkgjwt.CustomClaims, bool) { - if md, ok := metadata.FromServerContext(ctx); ok { - claimsStr := md.Get(mdKeyClaims) - if claimsStr != "" { - var claims pkgjwt.CustomClaims - if err := json.Unmarshal([]byte(claimsStr), &claims); err == nil { - return &claims, true - } - } - } - return nil, false -} - -// GetUserID 从context获取用户ID -func GetUserID(ctx context.Context) uint { - if md, ok := metadata.FromServerContext(ctx); ok { - if idStr := md.Get(mdKeyUserID); idStr != "" { - if id, err := strconv.ParseUint(idStr, 10, 64); err == nil { - return uint(id) - } - } - } - return 0 -} - -// GetUsername 从context获取用户名 -func GetUsername(ctx context.Context) string { - if md, ok := metadata.FromServerContext(ctx); ok { - return md.Get(mdKeyUsername) - } - return "" -} - -// GetAuthorityID 从context获取角色ID -func GetAuthorityID(ctx context.Context) uint { - if md, ok := metadata.FromServerContext(ctx); ok { - if idStr := md.Get(mdKeyAuthorityID); idStr != "" { - if id, err := strconv.ParseUint(idStr, 10, 64); err == nil { - return uint(id) - } - } - } - return 0 -} - -// GetToken 从context获取token -func GetToken(ctx context.Context) string { - if tr, ok := transport.FromServerContext(ctx); ok { - return extractToken(tr) - } - return "" -} diff --git a/internal/server/middleware/logger.go b/internal/server/middleware/logger.go deleted file mode 100644 index 77aaa77..0000000 --- a/internal/server/middleware/logger.go +++ /dev/null @@ -1,133 +0,0 @@ -package middleware - -import ( - "bytes" - "context" - "encoding/json" - "io" - "time" - - "github.com/go-kratos/kratos/v2/log" - "github.com/go-kratos/kratos/v2/middleware" - "github.com/go-kratos/kratos/v2/transport" - kratoshttp "github.com/go-kratos/kratos/v2/transport/http" -) - -// LogLayout 日志layout -type LogLayout struct { - Time time.Time `json:"time"` - Metadata map[string]interface{} `json:"metadata,omitempty"` - Path string `json:"path"` - Query string `json:"query,omitempty"` - Body string `json:"body,omitempty"` - IP string `json:"ip"` - UserAgent string `json:"user_agent"` - Error string `json:"error,omitempty"` - Cost time.Duration `json:"cost"` - Source string `json:"source"` -} - -// LoggerConfig 日志中间件配置 -type LoggerConfig struct { - // Filter 用户自定义过滤 - Filter func(ctx context.Context, path string) bool - // FilterKeyword 关键字过滤 - FilterKeyword func(layout *LogLayout) bool - // AuthProcess 鉴权处理 - AuthProcess func(ctx context.Context, layout *LogLayout) - // Print 日志处理 - Print func(LogLayout) - // Source 服务唯一标识 - Source string -} - -// Logger 日志中间件 -func Logger(cfg LoggerConfig) middleware.Middleware { - if cfg.Print == nil { - cfg.Print = func(layout LogLayout) { - v, _ := json.Marshal(layout) - log.Info(string(v)) - } - } - if cfg.Source == "" { - cfg.Source = "Kratos" - } - - return func(handler middleware.Handler) middleware.Handler { - return func(ctx context.Context, req interface{}) (interface{}, error) { - var ( - path string - query string - body string - ip string - userAgent string - ) - - if tr, ok := transport.FromServerContext(ctx); ok { - path = tr.Operation() - if header := tr.RequestHeader(); header != nil { - userAgent = header.Get("User-Agent") - } - - if ht, ok := tr.(kratoshttp.Transporter); ok { - r := ht.Request() - query = r.URL.RawQuery - ip = getClientIP(r) - - // 读取body(仅在未过滤时) - if cfg.Filter == nil || !cfg.Filter(ctx, path) { - bodyBytes, _ := io.ReadAll(r.Body) - r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) - body = string(bodyBytes) - } - } - } - - // 检查是否需要过滤 - if cfg.Filter != nil && cfg.Filter(ctx, path) { - return handler(ctx, req) - } - - start := time.Now() - reply, err := handler(ctx, req) - cost := time.Since(start) - - layout := LogLayout{ - Time: time.Now(), - Path: path, - Query: query, - Body: body, - IP: ip, - UserAgent: userAgent, - Cost: cost, - Source: cfg.Source, - } - - if err != nil { - layout.Error = err.Error() - } - - // 处理鉴权信息 - if cfg.AuthProcess != nil { - cfg.AuthProcess(ctx, &layout) - } - - // 关键字过滤 - if cfg.FilterKeyword != nil { - cfg.FilterKeyword(&layout) - } - - // 输出日志 - cfg.Print(layout) - - return reply, err - } - } -} - -// DefaultLogger 默认日志中间件 -func DefaultLogger() middleware.Middleware { - return Logger(LoggerConfig{ - Source: "Kratos", - }) -} diff --git a/internal/server/middleware/operation.go b/internal/server/middleware/operation.go deleted file mode 100644 index bc858be..0000000 --- a/internal/server/middleware/operation.go +++ /dev/null @@ -1,155 +0,0 @@ -package middleware - -import ( - "bytes" - "context" - "encoding/json" - "io" - "net/http" - "net/url" - "strconv" - "strings" - "time" - - "github.com/go-kratos/kratos/v2/log" - "github.com/go-kratos/kratos/v2/middleware" - "github.com/go-kratos/kratos/v2/transport" - kratoshttp "github.com/go-kratos/kratos/v2/transport/http" - "gorm.io/gorm" -) - -const maxBodySize = 1024 - -// OperationRecord 操作记录 -type OperationRecord struct { - ID uint `gorm:"primarykey"` - CreatedAt time.Time `gorm:"index"` - Ip string `gorm:"column:ip;comment:请求ip"` - Method string `gorm:"column:method;comment:请求方法"` - Path string `gorm:"column:path;comment:请求路径"` - Status int `gorm:"column:status;comment:请求状态"` - Latency time.Duration `gorm:"column:latency;comment:延迟"` - Agent string `gorm:"column:agent;comment:代理"` - ErrorMessage string `gorm:"column:error_message;comment:错误信息"` - Body string `gorm:"type:text;column:body;comment:请求Body"` - Resp string `gorm:"type:text;column:resp;comment:响应Body"` - UserID int `gorm:"column:user_id;comment:用户id"` -} - -func (OperationRecord) TableName() string { - return "sys_operation_records" -} - -// OperationLog 操作日志中间件 -func OperationLog(db *gorm.DB, logger log.Logger) middleware.Middleware { - return func(handler middleware.Handler) middleware.Handler { - return func(ctx context.Context, req interface{}) (interface{}, error) { - var ( - body string - userID int - ip string - method string - path string - agent string - ) - - if tr, ok := transport.FromServerContext(ctx); ok { - path = tr.Operation() - if header := tr.RequestHeader(); header != nil { - method = header.Get(":method") - agent = header.Get("User-Agent") - } - - // 获取HTTP请求信息 - if ht, ok := tr.(kratoshttp.Transporter); ok { - r := ht.Request() - ip = getClientIP(r) - - if r.Method != http.MethodGet { - bodyBytes, _ := io.ReadAll(r.Body) - r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) - if len(bodyBytes) > maxBodySize { - body = "[超出记录长度]" - } else { - body = string(bodyBytes) - } - } else { - query := r.URL.RawQuery - query, _ = url.QueryUnescape(query) - params := parseQuery(query) - bodyBytes, _ := json.Marshal(params) - body = string(bodyBytes) - } - - // 检查是否为文件上传 - if strings.Contains(r.Header.Get("Content-Type"), "multipart/form-data") { - body = "[文件]" - } - } - } - - // 获取用户ID - if claims, ok := GetClaims(ctx); ok { - userID = int(claims.BaseClaims.ID) - } else { - // 尝试从header获取 - if tr, ok := transport.FromServerContext(ctx); ok { - if ht, ok := tr.(kratoshttp.Transporter); ok { - if idStr := ht.Request().Header.Get("x-user-id"); idStr != "" { - if id, err := strconv.Atoi(idStr); err == nil { - userID = id - } - } - } - } - } - - start := time.Now() - reply, err := handler(ctx, req) - latency := time.Since(start) - - // 记录响应 - var resp string - if reply != nil { - respBytes, _ := json.Marshal(reply) - if len(respBytes) > maxBodySize { - resp = "[超出记录长度]" - } else { - resp = string(respBytes) - } - } - - // 记录错误 - var errMsg string - status := 200 - if err != nil { - errMsg = err.Error() - status = 500 - } - - // 保存记录 - record := OperationRecord{ - Ip: ip, - Method: method, - Path: path, - Status: status, - Latency: latency, - Agent: agent, - ErrorMessage: errMsg, - Body: body, - Resp: resp, - UserID: userID, - } - - if db != nil { - go func() { - if err := db.Create(&record).Error; err != nil { - log.NewHelper(logger).Error("create operation record error:", err) - } - }() - } - - return reply, err - } - } -} diff --git a/internal/server/middleware/ratelimit.go b/internal/server/middleware/ratelimit.go deleted file mode 100644 index 99d7b34..0000000 --- a/internal/server/middleware/ratelimit.go +++ /dev/null @@ -1,108 +0,0 @@ -package middleware - -import ( - "context" - "fmt" - "time" - - "github.com/go-kratos/kratos/v2/errors" - "github.com/go-kratos/kratos/v2/middleware" - "github.com/go-kratos/kratos/v2/transport" - kratoshttp "github.com/go-kratos/kratos/v2/transport/http" - "github.com/redis/go-redis/v9" -) - -// LimitConfig 限流配置 -type LimitConfig struct { - // GenerationKey 根据业务生成key - GenerationKey func(ctx context.Context, path string) string - // CheckOrMark 检查函数 - CheckOrMark func(ctx context.Context, key string, expire int, limit int) error - // Expire key过期时间(秒) - Expire int - // Limit 周期内限制次数 - Limit int -} - -// RateLimit 限流中间件 -func RateLimit(cfg LimitConfig) middleware.Middleware { - return func(handler middleware.Handler) middleware.Handler { - return func(ctx context.Context, req interface{}) (interface{}, error) { - var key string - if tr, ok := transport.FromServerContext(ctx); ok { - path := tr.Operation() - key = cfg.GenerationKey(ctx, path) - } - - if err := cfg.CheckOrMark(ctx, key, cfg.Expire, cfg.Limit); err != nil { - return nil, errors.New(429, "RATE_LIMIT", err.Error()) - } - - return handler(ctx, req) - } - } -} - -// DefaultGenerationKey 默认生成key(基于IP) -func DefaultGenerationKey(ctx context.Context, path string) string { - if tr, ok := transport.FromServerContext(ctx); ok { - if ht, ok := tr.(kratoshttp.Transporter); ok { - return "Limit_" + getClientIP(ht.Request()) - } - } - return "Limit_unknown" -} - -// NewRedisCheckOrMark 创建基于Redis的检查函数 -func NewRedisCheckOrMark(rdb *redis.Client) func(ctx context.Context, key string, expire int, limit int) error { - return func(ctx context.Context, key string, expire int, limit int) error { - if rdb == nil { - return nil - } - return SetLimitWithTime(ctx, rdb, key, limit, time.Duration(expire)*time.Second) - } -} - -// SetLimitWithTime 设置访问次数 -func SetLimitWithTime(ctx context.Context, rdb *redis.Client, key string, limit int, expiration time.Duration) error { - count, err := rdb.Exists(ctx, key).Result() - if err != nil { - return err - } - - if count == 0 { - // key不存在,创建并设置过期时间 - pipe := rdb.TxPipeline() - pipe.Incr(ctx, key) - pipe.Expire(ctx, key, expiration) - _, err = pipe.Exec(ctx) - return err - } - - // key存在,检查次数 - times, err := rdb.Get(ctx, key).Int() - if err != nil { - return err - } - - if times >= limit { - // 获取剩余时间 - ttl, err := rdb.PTTL(ctx, key).Result() - if err != nil { - return fmt.Errorf("请求太过频繁,请稍后再试") - } - return fmt.Errorf("请求太过频繁, 请 %s 后尝试", ttl.String()) - } - - return rdb.Incr(ctx, key).Err() -} - -// DefaultRateLimit 默认限流中间件 -func DefaultRateLimit(rdb *redis.Client, expire, limit int) middleware.Middleware { - return RateLimit(LimitConfig{ - GenerationKey: DefaultGenerationKey, - CheckOrMark: NewRedisCheckOrMark(rdb), - Expire: expire, - Limit: limit, - }) -} diff --git a/internal/server/middleware/recovery.go b/internal/server/middleware/recovery.go deleted file mode 100644 index 98910f6..0000000 --- a/internal/server/middleware/recovery.go +++ /dev/null @@ -1,55 +0,0 @@ -package middleware - -import ( - "context" - "net" - "os" - "runtime/debug" - "strings" - - "github.com/go-kratos/kratos/v2/errors" - "github.com/go-kratos/kratos/v2/log" - "github.com/go-kratos/kratos/v2/middleware" -) - -// Recovery panic恢复中间件 -func Recovery(logger log.Logger) middleware.Middleware { - helper := log.NewHelper(logger) - return func(handler middleware.Handler) middleware.Handler { - return func(ctx context.Context, req interface{}) (reply interface{}, err error) { - defer func() { - if rerr := recover(); rerr != nil { - // 检查是否为断开的连接 - var brokenPipe bool - if ne, ok := rerr.(*net.OpError); ok { - if se, ok := ne.Err.(*os.SyscallError); ok { - errStr := strings.ToLower(se.Error()) - if strings.Contains(errStr, "broken pipe") || - strings.Contains(errStr, "connection reset by peer") { - brokenPipe = true - } - } - } - - stack := string(debug.Stack()) - if brokenPipe { - helper.Errorw( - "msg", "[Recovery from panic]", - "error", rerr, - "broken_pipe", true, - ) - } else { - helper.Errorw( - "msg", "[Recovery from panic]", - "error", rerr, - "stack", stack, - ) - } - - err = errors.InternalServer("PANIC", "服务器内部错误") - } - }() - return handler(ctx, req) - } - } -} diff --git a/internal/server/middleware/timeout.go b/internal/server/middleware/timeout.go deleted file mode 100644 index 772cdd1..0000000 --- a/internal/server/middleware/timeout.go +++ /dev/null @@ -1,53 +0,0 @@ -package middleware - -import ( - "context" - "time" - - "github.com/go-kratos/kratos/v2/errors" - "github.com/go-kratos/kratos/v2/middleware" -) - -// Timeout 超时中间件 -func Timeout(timeout time.Duration) middleware.Middleware { - return func(handler middleware.Handler) middleware.Handler { - return func(ctx context.Context, req interface{}) (interface{}, error) { - ctx, cancel := context.WithTimeout(ctx, timeout) - defer cancel() - - // 使用 buffered channel 避免 goroutine 泄漏 - type result struct { - reply interface{} - err error - } - done := make(chan result, 1) - panicChan := make(chan interface{}, 1) - - go func() { - defer func() { - if p := recover(); p != nil { - select { - case panicChan <- p: - default: - } - } - }() - - reply, err := handler(ctx, req) - select { - case done <- result{reply: reply, err: err}: - default: - } - }() - - select { - case p := <-panicChan: - panic(p) - case r := <-done: - return r.reply, r.err - case <-ctx.Done(): - return nil, errors.GatewayTimeout("TIMEOUT", "请求超时") - } - } - } -} diff --git a/internal/server/router/enter.go b/internal/server/router/enter.go new file mode 100644 index 0000000..62114bb --- /dev/null +++ b/internal/server/router/enter.go @@ -0,0 +1,9 @@ +package router + +import "kra/internal/server/router/system" + +var RouterGroupApp = new(RouterGroup) + +type RouterGroup struct { + System system.RouterGroup +} diff --git a/internal/server/router/system/enter.go b/internal/server/router/system/enter.go new file mode 100644 index 0000000..6310b1f --- /dev/null +++ b/internal/server/router/system/enter.go @@ -0,0 +1,29 @@ +package system + +import api "kra/internal/server/handler/system" + +type RouterGroup struct { + BaseRouter + UserRouter + ApiRouter + AuthorityRouter + MenuRouter + DictionaryRouter + CasbinRouter + JwtRouter + OperationRecordRouter + ParamsRouter +} + +var ( + baseApi = api.BaseApi{} + userApi = api.UserApi{} + apiApi = api.ApiApi{} + authorityApi = api.AuthorityApi{} + menuApi = api.MenuApi{} + dictionaryApi = api.DictionaryApi{} + casbinApi = api.CasbinApi{} + jwtApi = api.JwtApi{} + operationRecordApi = api.OperationRecordApi{} + paramsApi = api.ParamsApi{} +) diff --git a/internal/server/router/system/sys_api.go b/internal/server/router/system/sys_api.go new file mode 100644 index 0000000..f316659 --- /dev/null +++ b/internal/server/router/system/sys_api.go @@ -0,0 +1,29 @@ +package system + +import ( + "kra/internal/server/middleware" + + "github.com/gin-gonic/gin" +) + +type ApiRouter struct{} + +func (r *ApiRouter) InitApiRouter(Router *gin.RouterGroup, PublicRouter *gin.RouterGroup) { + apiRouter := Router.Group("api").Use(middleware.OperationRecordMiddleware()) + apiRouterWithoutRecord := Router.Group("api") + apiPublicRouter := PublicRouter.Group("api") + { + apiRouter.POST("createApi", apiApi.CreateApi) + apiRouter.POST("deleteApi", apiApi.DeleteApi) + apiRouter.POST("updateApi", apiApi.UpdateApi) + apiRouter.POST("getApiById", apiApi.GetApiById) + apiRouter.DELETE("deleteApisByIds", apiApi.DeleteApisByIds) + } + { + apiRouterWithoutRecord.POST("getAllApis", apiApi.GetAllApis) + apiRouterWithoutRecord.POST("getApiList", apiApi.GetApiList) + } + { + apiPublicRouter.GET("freshCasbin", apiApi.FreshCasbin) + } +} diff --git a/internal/server/router/system/sys_authority.go b/internal/server/router/system/sys_authority.go new file mode 100644 index 0000000..280bf6b --- /dev/null +++ b/internal/server/router/system/sys_authority.go @@ -0,0 +1,24 @@ +package system + +import ( + "kra/internal/server/middleware" + + "github.com/gin-gonic/gin" +) + +type AuthorityRouter struct{} + +func (r *AuthorityRouter) InitAuthorityRouter(Router *gin.RouterGroup) { + authorityRouter := Router.Group("authority").Use(middleware.OperationRecordMiddleware()) + authorityRouterWithoutRecord := Router.Group("authority") + { + authorityRouter.POST("createAuthority", authorityApi.CreateAuthority) + authorityRouter.POST("deleteAuthority", authorityApi.DeleteAuthority) + authorityRouter.PUT("updateAuthority", authorityApi.UpdateAuthority) + authorityRouter.POST("copyAuthority", authorityApi.CopyAuthority) + authorityRouter.POST("setDataAuthority", authorityApi.SetDataAuthority) + } + { + authorityRouterWithoutRecord.POST("getAuthorityList", authorityApi.GetAuthorityList) + } +} diff --git a/internal/server/router/system/sys_base.go b/internal/server/router/system/sys_base.go new file mode 100644 index 0000000..f2a5414 --- /dev/null +++ b/internal/server/router/system/sys_base.go @@ -0,0 +1,12 @@ +package system + +import "github.com/gin-gonic/gin" + +type BaseRouter struct{} + +func (r *BaseRouter) InitBaseRouter(Router *gin.RouterGroup) { + baseRouter := Router.Group("base") + { + baseRouter.POST("login", baseApi.Login) + } +} diff --git a/internal/server/router/system/sys_casbin.go b/internal/server/router/system/sys_casbin.go new file mode 100644 index 0000000..2d3c028 --- /dev/null +++ b/internal/server/router/system/sys_casbin.go @@ -0,0 +1,20 @@ +package system + +import ( + "kra/internal/server/middleware" + + "github.com/gin-gonic/gin" +) + +type CasbinRouter struct{} + +func (r *CasbinRouter) InitCasbinRouter(Router *gin.RouterGroup) { + casbinRouter := Router.Group("casbin").Use(middleware.OperationRecordMiddleware()) + casbinRouterWithoutRecord := Router.Group("casbin") + { + casbinRouter.POST("updateCasbin", casbinApi.UpdateCasbin) + } + { + casbinRouterWithoutRecord.POST("getPolicyPathByAuthorityId", casbinApi.GetPolicyPathByAuthorityId) + } +} diff --git a/internal/server/router/system/sys_dictionary.go b/internal/server/router/system/sys_dictionary.go new file mode 100644 index 0000000..089fe51 --- /dev/null +++ b/internal/server/router/system/sys_dictionary.go @@ -0,0 +1,23 @@ +package system + +import ( + "kra/internal/server/middleware" + + "github.com/gin-gonic/gin" +) + +type DictionaryRouter struct{} + +func (r *DictionaryRouter) InitDictionaryRouter(Router *gin.RouterGroup) { + dictionaryRouter := Router.Group("sysDictionary").Use(middleware.OperationRecordMiddleware()) + dictionaryRouterWithoutRecord := Router.Group("sysDictionary") + { + dictionaryRouter.POST("createSysDictionary", dictionaryApi.CreateSysDictionary) + dictionaryRouter.DELETE("deleteSysDictionary", dictionaryApi.DeleteSysDictionary) + dictionaryRouter.PUT("updateSysDictionary", dictionaryApi.UpdateSysDictionary) + } + { + dictionaryRouterWithoutRecord.GET("findSysDictionary", dictionaryApi.FindSysDictionary) + dictionaryRouterWithoutRecord.GET("getSysDictionaryList", dictionaryApi.GetSysDictionaryList) + } +} diff --git a/internal/server/router/system/sys_jwt.go b/internal/server/router/system/sys_jwt.go new file mode 100644 index 0000000..eed127b --- /dev/null +++ b/internal/server/router/system/sys_jwt.go @@ -0,0 +1,12 @@ +package system + +import "github.com/gin-gonic/gin" + +type JwtRouter struct{} + +func (r *JwtRouter) InitJwtRouter(Router *gin.RouterGroup) { + jwtRouter := Router.Group("jwt") + { + jwtRouter.POST("jsonInBlacklist", jwtApi.JsonInBlacklist) + } +} diff --git a/internal/server/router/system/sys_menu.go b/internal/server/router/system/sys_menu.go new file mode 100644 index 0000000..de2b53c --- /dev/null +++ b/internal/server/router/system/sys_menu.go @@ -0,0 +1,27 @@ +package system + +import ( + "kra/internal/server/middleware" + + "github.com/gin-gonic/gin" +) + +type MenuRouter struct{} + +func (r *MenuRouter) InitMenuRouter(Router *gin.RouterGroup) { + menuRouter := Router.Group("menu").Use(middleware.OperationRecordMiddleware()) + menuRouterWithoutRecord := Router.Group("menu") + { + menuRouter.POST("addBaseMenu", menuApi.AddBaseMenu) + menuRouter.POST("addMenuAuthority", menuApi.AddMenuAuthority) + menuRouter.POST("deleteBaseMenu", menuApi.DeleteBaseMenu) + menuRouter.POST("updateBaseMenu", menuApi.UpdateBaseMenu) + } + { + menuRouterWithoutRecord.POST("getMenu", menuApi.GetMenu) + menuRouterWithoutRecord.POST("getMenuList", menuApi.GetMenuList) + menuRouterWithoutRecord.POST("getBaseMenuTree", menuApi.GetBaseMenuTree) + menuRouterWithoutRecord.POST("getMenuAuthority", menuApi.GetMenuAuthority) + menuRouterWithoutRecord.POST("getBaseMenuById", menuApi.GetBaseMenuById) + } +} diff --git a/internal/server/router/system/sys_operation_record.go b/internal/server/router/system/sys_operation_record.go new file mode 100644 index 0000000..249bc7d --- /dev/null +++ b/internal/server/router/system/sys_operation_record.go @@ -0,0 +1,15 @@ +package system + +import "github.com/gin-gonic/gin" + +type OperationRecordRouter struct{} + +func (r *OperationRecordRouter) InitOperationRecordRouter(Router *gin.RouterGroup) { + operationRecordRouter := Router.Group("sysOperationRecord") + { + operationRecordRouter.DELETE("deleteSysOperationRecord", operationRecordApi.DeleteSysOperationRecord) + operationRecordRouter.DELETE("deleteSysOperationRecordByIds", operationRecordApi.DeleteSysOperationRecordByIds) + operationRecordRouter.GET("findSysOperationRecord", operationRecordApi.FindSysOperationRecord) + operationRecordRouter.GET("getSysOperationRecordList", operationRecordApi.GetSysOperationRecordList) + } +} diff --git a/internal/server/router/system/sys_params.go b/internal/server/router/system/sys_params.go new file mode 100644 index 0000000..59b8ddc --- /dev/null +++ b/internal/server/router/system/sys_params.go @@ -0,0 +1,23 @@ +package system + +import ( + "kra/internal/server/middleware" + + "github.com/gin-gonic/gin" +) + +type ParamsRouter struct{} + +func (r *ParamsRouter) InitParamsRouter(Router *gin.RouterGroup) { + paramsRouter := Router.Group("sysParams").Use(middleware.OperationRecordMiddleware()) + paramsRouterWithoutRecord := Router.Group("sysParams") + { + paramsRouter.POST("createSysParams", paramsApi.CreateSysParams) + paramsRouter.DELETE("deleteSysParams", paramsApi.DeleteSysParams) + paramsRouter.PUT("updateSysParams", paramsApi.UpdateSysParams) + } + { + paramsRouterWithoutRecord.GET("getSysParams", paramsApi.GetSysParams) + paramsRouterWithoutRecord.GET("getSysParamsList", paramsApi.GetSysParamsList) + } +} diff --git a/internal/server/router/system/sys_user.go b/internal/server/router/system/sys_user.go new file mode 100644 index 0000000..7b4209d --- /dev/null +++ b/internal/server/router/system/sys_user.go @@ -0,0 +1,29 @@ +package system + +import ( + "kra/internal/server/middleware" + + "github.com/gin-gonic/gin" +) + +type UserRouter struct{} + +func (r *UserRouter) InitUserRouter(Router *gin.RouterGroup) { + userRouter := Router.Group("user").Use(middleware.OperationRecordMiddleware()) + userRouterWithoutRecord := Router.Group("user") + { + userRouter.POST("admin_register", userApi.Register) + userRouter.POST("changePassword", userApi.ChangePassword) + userRouter.POST("setUserAuthority", userApi.SetUserAuthority) + userRouter.DELETE("deleteUser", userApi.DeleteUser) + userRouter.PUT("setUserInfo", userApi.SetUserInfo) + userRouter.PUT("setSelfInfo", userApi.SetSelfInfo) + userRouter.POST("setUserAuthorities", userApi.SetUserAuthorities) + userRouter.POST("resetPassword", userApi.ResetPassword) + userRouter.PUT("setSelfSetting", userApi.SetSelfSetting) + } + { + userRouterWithoutRecord.POST("getUserList", userApi.GetUserList) + userRouterWithoutRecord.GET("getUserInfo", userApi.GetUserInfo) + } +} diff --git a/internal/server/server.go b/internal/server/server.go index f389425..9e10c39 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -5,4 +5,4 @@ import ( ) // ProviderSet is server providers. -var ProviderSet = wire.NewSet(NewGRPCServer, NewHTTPServer) +var ProviderSet = wire.NewSet(NewGRPCServer, NewGinRouter, NewHTTPServer) diff --git a/pkg/response/response.go b/pkg/response/response.go index 346a447..b63f551 100644 --- a/pkg/response/response.go +++ b/pkg/response/response.go @@ -1,47 +1,80 @@ package response import ( - "encoding/json" "net/http" - "github.com/go-kratos/kratos/v2/errors" + "github.com/gin-gonic/gin" ) -// Response 统一响应格式 +// Response 统一响应结构(与 gva 保持一致) type Response struct { Code int `json:"code"` + Data interface{} `json:"data"` Msg string `json:"msg"` - Data interface{} `json:"data,omitempty"` } -// Encoder 响应编码器,包装响应为 {code, msg, data} 格式 -func Encoder(w http.ResponseWriter, r *http.Request, v interface{}) error { - if v == nil { - v = struct{}{} - } - reply := Response{ - Code: 0, - Msg: "success", - Data: v, - } - w.Header().Set("Content-Type", "application/json") - return json.NewEncoder(w).Encode(reply) +const ( + ERROR = 7 + SUCCESS = 0 +) + +// Result 返回结果 +func Result(code int, data interface{}, msg string, c *gin.Context) { + c.JSON(http.StatusOK, Response{ + code, + data, + msg, + }) } -// ErrorEncoder 错误编码器,统一错误响应格式 -func ErrorEncoder(w http.ResponseWriter, r *http.Request, err error) { - se := errors.FromError(err) - code := int(se.Code) - if code >= 400 && code < 500 { - code = 7 - } else if code >= 500 { - code = 7 - } - reply := Response{ - Code: code, - Msg: se.Message, - } - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(int(se.Code)) - json.NewEncoder(w).Encode(reply) +// Ok 成功响应 +func Ok(c *gin.Context) { + Result(SUCCESS, map[string]interface{}{}, "操作成功", c) +} + +// OkWithMessage 成功响应带消息 +func OkWithMessage(message string, c *gin.Context) { + Result(SUCCESS, map[string]interface{}{}, message, c) +} + +// OkWithData 成功响应带数据 +func OkWithData(data interface{}, c *gin.Context) { + Result(SUCCESS, data, "成功", c) +} + +// OkWithDetailed 成功响应带数据和消息 +func OkWithDetailed(data interface{}, message string, c *gin.Context) { + Result(SUCCESS, data, message, c) +} + +// Fail 失败响应 +func Fail(c *gin.Context) { + Result(ERROR, map[string]interface{}{}, "操作失败", c) +} + +// FailWithMessage 失败响应带消息 +func FailWithMessage(message string, c *gin.Context) { + Result(ERROR, map[string]interface{}{}, message, c) +} + +// FailWithDetailed 失败响应带数据和消息 +func FailWithDetailed(data interface{}, message string, c *gin.Context) { + Result(ERROR, data, message, c) +} + +// NoAuth 未授权响应 +func NoAuth(message string, c *gin.Context) { + c.JSON(http.StatusUnauthorized, Response{ + 7, + nil, + message, + }) +} + +// PageResult 分页响应结构 +type PageResult struct { + List interface{} `json:"list"` + Total int64 `json:"total"` + Page int `json:"page"` + PageSize int `json:"pageSize"` } diff --git a/pkg/utils/claims.go b/pkg/utils/claims.go new file mode 100644 index 0000000..eeff2ae --- /dev/null +++ b/pkg/utils/claims.go @@ -0,0 +1,164 @@ +package utils + +import ( + "net" + "time" + + "kra/pkg/jwt" + + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "go.uber.org/zap" +) + +// 全局 JWT 实例(需要在初始化时设置) +var jwtInstance *jwt.JWT + +// 全局日志实例(需要在初始化时设置) +var logger *zap.Logger + +// SetJWT 设置全局 JWT 实例 +func SetJWT(j *jwt.JWT) { + jwtInstance = j +} + +// SetLogger 设置全局日志实例 +func SetLogger(l *zap.Logger) { + logger = l +} + +// ClearToken 清除 token +func ClearToken(c *gin.Context) { + // 增加cookie x-token 向来源的web添加 + host, _, err := net.SplitHostPort(c.Request.Host) + if err != nil { + host = c.Request.Host + } + + if net.ParseIP(host) != nil { + c.SetCookie("x-token", "", -1, "/", "", false, false) + } else { + c.SetCookie("x-token", "", -1, "/", host, false, false) + } +} + +// SetToken 设置 token 到 cookie +func SetToken(c *gin.Context, token string, maxAge int) { + // 增加cookie x-token 向来源的web添加 + host, _, err := net.SplitHostPort(c.Request.Host) + if err != nil { + host = c.Request.Host + } + + if net.ParseIP(host) != nil { + c.SetCookie("x-token", token, maxAge, "/", "", false, false) + } else { + c.SetCookie("x-token", token, maxAge, "/", host, false, false) + } +} + +// GetToken 从请求中获取 token +func GetToken(c *gin.Context) string { + token := c.Request.Header.Get("x-token") + if token == "" { + token, _ = c.Cookie("x-token") + if token != "" && jwtInstance != nil { + claims, err := jwtInstance.ParseToken(token) + if err != nil { + if logger != nil { + logger.Error("重新写入cookie token失败,未能成功解析token,请检查请求头是否存在x-token且claims是否为规定结构") + } + return token + } + SetToken(c, token, int(claims.ExpiresAt.Unix()-time.Now().Unix())) + } + } + return token +} + +// GetClaims 从 context 获取 claims +func GetClaims(c *gin.Context) (*jwt.CustomClaims, error) { + token := GetToken(c) + if jwtInstance == nil { + return nil, jwt.ErrTokenInvalid + } + claims, err := jwtInstance.ParseToken(token) + if err != nil { + if logger != nil { + logger.Error("从Gin的Context中获取从jwt解析信息失败, 请检查请求头是否存在x-token且claims是否为规定结构") + } + } + return claims, err +} + +// GetUserID 从 Gin 的 Context 中获取从 jwt 解析出来的用户 ID +func GetUserID(c *gin.Context) uint { + if claims, exists := c.Get("claims"); !exists { + if cl, err := GetClaims(c); err != nil { + return 0 + } else { + return cl.BaseClaims.ID + } + } else { + waitUse := claims.(*jwt.CustomClaims) + return waitUse.BaseClaims.ID + } +} + +// GetUserUuid 从 Gin 的 Context 中获取从 jwt 解析出来的用户 UUID +func GetUserUuid(c *gin.Context) uuid.UUID { + if claims, exists := c.Get("claims"); !exists { + if cl, err := GetClaims(c); err != nil { + return uuid.UUID{} + } else { + uid, _ := uuid.Parse(cl.BaseClaims.UUID) + return uid + } + } else { + waitUse := claims.(*jwt.CustomClaims) + uid, _ := uuid.Parse(waitUse.BaseClaims.UUID) + return uid + } +} + +// GetUserAuthorityId 从 Gin 的 Context 中获取从 jwt 解析出来的用户角色 id +func GetUserAuthorityId(c *gin.Context) uint { + if claims, exists := c.Get("claims"); !exists { + if cl, err := GetClaims(c); err != nil { + return 0 + } else { + return cl.BaseClaims.AuthorityID + } + } else { + waitUse := claims.(*jwt.CustomClaims) + return waitUse.BaseClaims.AuthorityID + } +} + +// GetUserInfo 从 Gin 的 Context 中获取从 jwt 解析出来的用户信息 +func GetUserInfo(c *gin.Context) *jwt.CustomClaims { + if claims, exists := c.Get("claims"); !exists { + if cl, err := GetClaims(c); err != nil { + return nil + } else { + return cl + } + } else { + waitUse := claims.(*jwt.CustomClaims) + return waitUse + } +} + +// GetUserName 从 Gin 的 Context 中获取从 jwt 解析出来的用户名 +func GetUserName(c *gin.Context) string { + if claims, exists := c.Get("claims"); !exists { + if cl, err := GetClaims(c); err != nil { + return "" + } else { + return cl.Username + } + } else { + waitUse := claims.(*jwt.CustomClaims) + return waitUse.Username + } +}