wip: 登录和认证
This commit is contained in:
28
go.mod
28
go.mod
@ -4,8 +4,19 @@ go 1.24.2
|
||||
|
||||
require (
|
||||
gitea.loveuer.com/yizhisec/packages v0.0.12
|
||||
github.com/fatih/color v1.18.0
|
||||
github.com/gin-gonic/gin v1.10.1
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/go-playground/validator/v10 v10.20.0
|
||||
github.com/go-redis/redis/v8 v8.11.0
|
||||
github.com/gofiber/fiber/v3 v3.0.0-beta.4
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/jedib0t/go-pretty/v6 v6.6.7
|
||||
github.com/spf13/cast v1.9.2
|
||||
github.com/spf13/cobra v1.9.1
|
||||
golang.org/x/crypto v0.31.0
|
||||
gorm.io/driver/mysql v1.6.0
|
||||
gorm.io/driver/postgres v1.6.0
|
||||
gorm.io/gorm v1.30.0
|
||||
)
|
||||
|
||||
@ -14,36 +25,32 @@ require (
|
||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||
github.com/bytedance/sonic v1.11.6 // indirect
|
||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/dgraph-io/ristretto/v2 v2.2.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/gin-gonic/gin v1.10.1 // indirect
|
||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||
github.com/glebarez/sqlite v1.11.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.20.0 // indirect
|
||||
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/gofiber/schema v1.2.0 // indirect
|
||||
github.com/gofiber/utils/v2 v2.0.0-beta.7 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.6.0 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/jedib0t/go-pretty/v6 v6.6.7 // 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.7 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
@ -62,18 +69,13 @@ require (
|
||||
github.com/valyala/fasthttp v1.58.0 // indirect
|
||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/arch v0.8.0 // indirect
|
||||
golang.org/x/crypto v0.31.0 // indirect
|
||||
golang.org/x/net v0.31.0 // indirect
|
||||
golang.org/x/sync v0.11.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gorm.io/driver/mysql v1.6.0 // indirect
|
||||
gorm.io/driver/postgres v1.6.0 // indirect
|
||||
modernc.org/libc v1.22.5 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.5.0 // indirect
|
||||
|
100
go.sum
100
go.sum
@ -8,19 +8,33 @@ github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc
|
||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM=
|
||||
github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI=
|
||||
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
|
||||
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/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/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
@ -41,6 +55,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
||||
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-redis/redis/v8 v8.11.0 h1:O1Td0mQ8UFChQ3N9zFQqo6kTU2cJ+/it88gDB+zg0wo=
|
||||
github.com/go-redis/redis/v8 v8.11.0/go.mod h1:DLomh7y2e3ggQXQLd1YgmvIfecPJoFl7WU5SOQ/r06M=
|
||||
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.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
@ -51,13 +67,25 @@ github.com/gofiber/schema v1.2.0 h1:j+ZRrNnUa/0ZuWrn/6kAtAufEr4jCJ+JuTURAMxNSZg=
|
||||
github.com/gofiber/schema v1.2.0/go.mod h1:YYwj01w3hVfaNjhtJzaqetymL56VW642YS3qZPhuE6c=
|
||||
github.com/gofiber/utils/v2 v2.0.0-beta.7 h1:NnHFrRHvhrufPABdWajcKZejz9HnCWmT/asoxRsiEbQ=
|
||||
github.com/gofiber/utils/v2 v2.0.0-beta.7/go.mod h1:J/M03s+HMdZdvhAeyh76xT72IfVqBzuz/OJkrMa7cwU=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/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.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 h1:y3N7Bm7Y9/CtpiVkw/ZWj6lSlDF3F74SfKwfTCer72Q=
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
@ -82,8 +110,8 @@ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
@ -100,6 +128,16 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
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/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4=
|
||||
github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ=
|
||||
github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY=
|
||||
@ -115,6 +153,8 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
|
||||
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
@ -148,33 +188,73 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
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.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
|
||||
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/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-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/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-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/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=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
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/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
@ -2,20 +2,33 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"loveuer/utodo/internal/handler"
|
||||
"loveuer/utodo/internal/opt"
|
||||
g_handler "loveuer/utodo/pkg/handler"
|
||||
"loveuer/utodo/pkg/middleware/logger"
|
||||
"loveuer/utodo/pkg/middleware/trace"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
l3 "github.com/gofiber/fiber/v3/middleware/logger"
|
||||
r3 "github.com/gofiber/fiber/v3/middleware/recover"
|
||||
)
|
||||
|
||||
type structValidator struct {
|
||||
validate *validator.Validate
|
||||
}
|
||||
|
||||
func (v *structValidator) Validate(out any) error {
|
||||
return v.validate.Struct(out)
|
||||
}
|
||||
|
||||
func New(ctx context.Context) *fiber.App {
|
||||
app := fiber.New()
|
||||
app := fiber.New(fiber.Config{
|
||||
BodyLimit: 5 * 1024 * 1024,
|
||||
StructValidator: &structValidator{validate: validator.New()},
|
||||
})
|
||||
app.Use(trace.New())
|
||||
app.Use(l3.New())
|
||||
// app.Use(l3.New())
|
||||
app.Use(logger.New())
|
||||
app.Use(r3.New())
|
||||
|
||||
app.Get("/healthz", g_handler.Healthz(opt.Cfg.Name, opt.Cfg.Version))
|
||||
|
@ -6,9 +6,10 @@ import (
|
||||
"loveuer/utodo/internal/model"
|
||||
"loveuer/utodo/internal/opt"
|
||||
g_api "loveuer/utodo/pkg/api"
|
||||
"loveuer/utodo/pkg/database/cache"
|
||||
"loveuer/utodo/pkg/tool"
|
||||
|
||||
"gitea.loveuer.com/yizhisec/packages/database/db"
|
||||
"gitea.loveuer.com/yizhisec/packages/tool"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -21,6 +22,7 @@ func svcCmd() *cobra.Command {
|
||||
|
||||
cmd.Flags().StringVar(&opt.Cfg.Svc.Address, "address", ":9119", "address to listen on")
|
||||
cmd.Flags().StringVar(&opt.Cfg.Svc.DBFile, "db", "/data/data.db", "database file")
|
||||
cmd.Flags().StringVar(&opt.Cfg.Svc.Cache, "cache", "", "cache uri: empty for memory cache; for redis(rdp://user:password@host:port)")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@ -31,7 +33,14 @@ func svcRun(cmd *cobra.Command, args []string) error {
|
||||
stopApi func(context.Context) error
|
||||
)
|
||||
|
||||
if err = db.Init(db.WithSqlite(opt.Cfg.Svc.DBFile)); err != nil {
|
||||
if err = db.Init(db.WithCtx(cmd.Context()), db.WithSqlite(opt.Cfg.Svc.DBFile)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = cache.Init(
|
||||
cache.WithCtx(cmd.Context()),
|
||||
tool.If(opt.Cfg.Svc.Cache == "", cache.WithMemory(), cache.WithRedisURI(opt.Cfg.Svc.Cache)),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,30 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"loveuer/utodo/pkg/logger"
|
||||
"loveuer/utodo/pkg/resp"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
|
||||
func Login() fiber.Handler {
|
||||
return func(c fiber.Ctx) error {
|
||||
logger.InfoCtx(c.Context(), "login")
|
||||
return c.SendString("Hello, World!")
|
||||
type Req struct {
|
||||
Username string `json:"username"`
|
||||
Phone string `json:"phone"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password" validate:"required,min=8"`
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
req Req
|
||||
)
|
||||
|
||||
if err = c.Bind().JSON(&req); err != nil {
|
||||
return resp.R400(c, "", nil, err.Error())
|
||||
}
|
||||
|
||||
// TODO: 完成登录
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ type config struct {
|
||||
Svc struct {
|
||||
Address string
|
||||
DBFile string
|
||||
Cache string
|
||||
}
|
||||
}
|
||||
|
||||
|
98
pkg/database/cache/cache.go
vendored
Normal file
98
pkg/database/cache/cache.go
vendored
Normal file
@ -0,0 +1,98 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type encoded_value interface {
|
||||
MarshalBinary() ([]byte, error)
|
||||
}
|
||||
|
||||
type decoded_value interface {
|
||||
UnmarshalBinary(bs []byte) error
|
||||
}
|
||||
|
||||
type Scanner interface {
|
||||
Scan(model any) error
|
||||
}
|
||||
|
||||
type scan struct {
|
||||
err error
|
||||
bs []byte
|
||||
}
|
||||
|
||||
func newScan(bs []byte, err error) *scan {
|
||||
return &scan{bs: bs, err: err}
|
||||
}
|
||||
|
||||
func (s *scan) Scan(model any) error {
|
||||
if s.err != nil {
|
||||
return s.err
|
||||
}
|
||||
|
||||
return unmarshaler(s.bs, model)
|
||||
}
|
||||
|
||||
type Cache interface {
|
||||
Get(ctx context.Context, key string) ([]byte, error)
|
||||
Gets(ctx context.Context, keys ...string) ([][]byte, error)
|
||||
GetScan(ctx context.Context, key string) Scanner
|
||||
GetEx(ctx context.Context, key string, duration time.Duration) ([]byte, error)
|
||||
GetExScan(ctx context.Context, key string, duration time.Duration) Scanner
|
||||
// Set value 会被序列化, 优先使用 MarshalBinary 方法, 没有则执行 json.Marshal
|
||||
Set(ctx context.Context, key string, value any) error
|
||||
Sets(ctx context.Context, vm map[string]any) error
|
||||
// SetEx value 会被序列化, 优先使用 MarshalBinary 方法, 没有则执行 json.Marshal
|
||||
SetEx(ctx context.Context, key string, value any, duration time.Duration) error
|
||||
Del(ctx context.Context, keys ...string) error
|
||||
GetDel(ctx context.Context, key string) ([]byte, error)
|
||||
GetDelScan(ctx context.Context, key string) Scanner
|
||||
Close()
|
||||
Client() any
|
||||
}
|
||||
|
||||
var (
|
||||
lock = &sync.Mutex{}
|
||||
marshaler func(data any) ([]byte, error) = json.Marshal
|
||||
unmarshaler func(data []byte, model any) error = json.Unmarshal
|
||||
ErrorKeyNotFound = errors.New("key not found")
|
||||
ErrorStoreFailed = errors.New("store failed")
|
||||
Default Cache
|
||||
)
|
||||
|
||||
func handleValue(value any) ([]byte, error) {
|
||||
var (
|
||||
bs []byte
|
||||
err error
|
||||
)
|
||||
|
||||
switch val := value.(type) {
|
||||
case []byte:
|
||||
return val, nil
|
||||
}
|
||||
|
||||
if imp, ok := value.(encoded_value); ok {
|
||||
bs, err = imp.MarshalBinary()
|
||||
} else {
|
||||
bs, err = marshaler(value)
|
||||
}
|
||||
|
||||
return bs, err
|
||||
}
|
||||
|
||||
func SetMarshaler(fn func(data any) ([]byte, error)) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
marshaler = fn
|
||||
}
|
||||
|
||||
func SetUnmarshaler(fn func(data []byte, model any) error) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
unmarshaler = fn
|
||||
}
|
155
pkg/database/cache/memory.go
vendored
Normal file
155
pkg/database/cache/memory.go
vendored
Normal file
@ -0,0 +1,155 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/dgraph-io/ristretto/v2"
|
||||
)
|
||||
|
||||
var _ Cache = (*_mem)(nil)
|
||||
|
||||
type _mem struct {
|
||||
ctx context.Context
|
||||
cache *ristretto.Cache[string, []byte]
|
||||
}
|
||||
|
||||
func newMemory(ctx context.Context, ins *ristretto.Cache[string, []byte]) Cache {
|
||||
return &_mem{
|
||||
ctx: ctx,
|
||||
cache: ins,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *_mem) Client() any {
|
||||
return m.cache
|
||||
}
|
||||
|
||||
func (c *_mem) Close() {
|
||||
c.cache.Close()
|
||||
}
|
||||
|
||||
func (c *_mem) Del(ctx context.Context, keys ...string) error {
|
||||
for _, key := range keys {
|
||||
c.cache.Del(key)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *_mem) Get(ctx context.Context, key string) ([]byte, error) {
|
||||
val, ok := c.cache.Get(key)
|
||||
if !ok {
|
||||
return val, ErrorKeyNotFound
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (c *_mem) GetDel(ctx context.Context, key string) ([]byte, error) {
|
||||
val, err := c.Get(ctx, key)
|
||||
if err != nil {
|
||||
return val, err
|
||||
}
|
||||
|
||||
c.cache.Del(key)
|
||||
|
||||
return val, err
|
||||
}
|
||||
|
||||
func (c *_mem) GetDelScan(ctx context.Context, key string) Scanner {
|
||||
val, err := c.GetDel(ctx, key)
|
||||
return newScan(val, err)
|
||||
}
|
||||
|
||||
func (c *_mem) GetEx(ctx context.Context, key string, duration time.Duration) ([]byte, error) {
|
||||
val, err := c.Get(ctx, key)
|
||||
if err != nil {
|
||||
return val, err
|
||||
}
|
||||
|
||||
c.cache.SetWithTTL(key, val, 1, duration)
|
||||
|
||||
return val, err
|
||||
}
|
||||
|
||||
func (m *_mem) GetExScan(ctx context.Context, key string, duration time.Duration) Scanner {
|
||||
val, err := m.GetEx(ctx, key, duration)
|
||||
return newScan(val, err)
|
||||
}
|
||||
|
||||
func (m *_mem) GetScan(ctx context.Context, key string) Scanner {
|
||||
val, err := m.Get(ctx, key)
|
||||
return newScan(val, err)
|
||||
}
|
||||
|
||||
func (m *_mem) Gets(ctx context.Context, keys ...string) ([][]byte, error) {
|
||||
vals := make([][]byte, 0, len(keys))
|
||||
|
||||
for _, key := range keys {
|
||||
val, err := m.Get(ctx, key)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrorKeyNotFound) {
|
||||
continue
|
||||
}
|
||||
|
||||
return vals, err
|
||||
}
|
||||
|
||||
vals = append(vals, val)
|
||||
}
|
||||
|
||||
if len(vals) != len(keys) {
|
||||
return vals, ErrorKeyNotFound
|
||||
}
|
||||
|
||||
return vals, nil
|
||||
}
|
||||
|
||||
func (m *_mem) Set(ctx context.Context, key string, value any) error {
|
||||
val, err := handleValue(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ok := m.cache.Set(key, val, 1); !ok {
|
||||
return ErrorStoreFailed
|
||||
}
|
||||
|
||||
m.cache.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *_mem) SetEx(ctx context.Context, key string, value any, duration time.Duration) error {
|
||||
val, err := handleValue(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ok := m.cache.SetWithTTL(key, val, 1, duration); !ok {
|
||||
return ErrorStoreFailed
|
||||
}
|
||||
|
||||
m.cache.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *_mem) Sets(ctx context.Context, vm map[string]any) error {
|
||||
for key, value := range vm {
|
||||
val, err := handleValue(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ok := m.cache.Set(key, val, 1); !ok {
|
||||
return ErrorStoreFailed
|
||||
}
|
||||
}
|
||||
|
||||
m.cache.Wait()
|
||||
|
||||
return nil
|
||||
}
|
84
pkg/database/cache/new.go
vendored
Normal file
84
pkg/database/cache/new.go
vendored
Normal file
@ -0,0 +1,84 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"gitea.loveuer.com/yizhisec/packages/tool"
|
||||
"github.com/dgraph-io/ristretto/v2"
|
||||
"github.com/go-redis/redis/v8"
|
||||
)
|
||||
|
||||
func New(opts ...Option) (Cache, error) {
|
||||
var (
|
||||
err error
|
||||
cfg = &option{
|
||||
ctx: context.Background(),
|
||||
}
|
||||
)
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(cfg)
|
||||
}
|
||||
|
||||
if cfg.redis != nil {
|
||||
var (
|
||||
ins *url.URL
|
||||
client *redis.Client
|
||||
)
|
||||
|
||||
if ins, err = url.Parse(*cfg.redis); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
username := ins.User.Username()
|
||||
password, _ := ins.User.Password()
|
||||
|
||||
client = redis.NewClient(&redis.Options{
|
||||
Addr: ins.Host,
|
||||
Username: username,
|
||||
Password: password,
|
||||
})
|
||||
|
||||
if err = client.Ping(tool.CtxTimeout(cfg.ctx, 5)).Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newRedis(cfg.ctx, client), nil
|
||||
}
|
||||
|
||||
if cfg.memory {
|
||||
var (
|
||||
ins *ristretto.Cache[string, []byte]
|
||||
)
|
||||
|
||||
if ins, err = ristretto.NewCache(&ristretto.Config[string, []byte]{
|
||||
NumCounters: 1e7, // number of keys to track frequency of (10M).
|
||||
MaxCost: 1 << 30, // maximum cost of cache (1GB).
|
||||
BufferItems: 64, // number of keys per Get buffer.
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newMemory(cfg.ctx, ins), nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("invalid cache option")
|
||||
}
|
||||
|
||||
func Init(opts ...Option) (err error) {
|
||||
opt := &option{}
|
||||
|
||||
for _, optFn := range opts {
|
||||
optFn(opt)
|
||||
}
|
||||
|
||||
if opt.memory {
|
||||
Default, err = New(opts...)
|
||||
return err
|
||||
}
|
||||
|
||||
Default, err = New(opts...)
|
||||
return err
|
||||
}
|
108
pkg/database/cache/new_test.go
vendored
Normal file
108
pkg/database/cache/new_test.go
vendored
Normal file
@ -0,0 +1,108 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
/* if err := Init(WithRedis("127.0.0.1", 6379, "", "MyPassw0rd")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
}
|
||||
|
||||
if err := Default.Set(t.Context(), "zyp:haha", &User{
|
||||
Name: "cache",
|
||||
Age: 18,
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
s := Default.GetDelScan(t.Context(), "zyp:haha")
|
||||
u := new(User)
|
||||
|
||||
if err := s.Scan(u); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Logf("%#v", *u)
|
||||
|
||||
if err := Default.SetEx(t.Context(), "zyp:haha", &User{
|
||||
Name: "redis",
|
||||
Age: 2,
|
||||
}, time.Hour); err != nil {
|
||||
t.Fatal(err)
|
||||
}*/
|
||||
}
|
||||
|
||||
func TestNoAuth(t *testing.T) {
|
||||
//if err := Init(WithRedis("10.125.1.28", 6379, "", "")); err != nil {
|
||||
// t.Fatal(err)
|
||||
//}
|
||||
//
|
||||
//type User struct {
|
||||
// Name string `json:"name"`
|
||||
// Age int `json:"age"`
|
||||
//}
|
||||
//
|
||||
//if err := Default.Set(t.Context(), "zyp:haha", &User{
|
||||
// Name: "cache",
|
||||
// Age: 18,
|
||||
//}); err != nil {
|
||||
// t.Fatal(err)
|
||||
//}
|
||||
//
|
||||
//s := Default.GetDelScan(t.Context(), "zyp:haha")
|
||||
//u := new(User)
|
||||
//
|
||||
//if err := s.Scan(u); err != nil {
|
||||
// t.Fatal(err)
|
||||
//}
|
||||
//
|
||||
//t.Logf("%#v", *u)
|
||||
//
|
||||
//if err := Default.SetEx(t.Context(), "zyp:haha", &User{
|
||||
// Name: "redis",
|
||||
// Age: 2,
|
||||
//}, time.Hour); err != nil {
|
||||
// t.Fatal(err)
|
||||
//}
|
||||
}
|
||||
|
||||
func TestMemoryDefault(t *testing.T) {
|
||||
if err := Init(WithMemory()); err != nil {
|
||||
t.Fatal("init err:", err)
|
||||
}
|
||||
|
||||
if err := Default.Set(t.Context(), "123", "123"); err != nil {
|
||||
t.Fatal("set err:", err)
|
||||
}
|
||||
|
||||
val, err := Default.Get(t.Context(), "123")
|
||||
if err != nil {
|
||||
t.Fatal("get err:", err)
|
||||
}
|
||||
|
||||
t.Logf("%s", val)
|
||||
}
|
||||
|
||||
func TestMemoryNew(t *testing.T) {
|
||||
client, err := New(WithMemory())
|
||||
if err != nil {
|
||||
t.Fatal("init err:", err)
|
||||
}
|
||||
|
||||
if err := client.Set(t.Context(), "123", "123"); err != nil {
|
||||
t.Fatal("set err:", err)
|
||||
}
|
||||
|
||||
val, err := client.Get(t.Context(), "123")
|
||||
if err != nil {
|
||||
t.Fatal("get err:", err)
|
||||
}
|
||||
|
||||
t.Logf("%s", val)
|
||||
}
|
55
pkg/database/cache/option.go
vendored
Normal file
55
pkg/database/cache/option.go
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type option struct {
|
||||
ctx context.Context
|
||||
redis *string
|
||||
memory bool
|
||||
}
|
||||
|
||||
type Option func(*option)
|
||||
|
||||
func WithCtx(ctx context.Context) Option {
|
||||
return func(c *option) {
|
||||
if ctx != nil {
|
||||
c.ctx = ctx
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WithRedis(host string, port int, username, password string) Option {
|
||||
return func(c *option) {
|
||||
uri := fmt.Sprintf("redis://%s:%d", host, port)
|
||||
if username != "" || password != "" {
|
||||
uri = fmt.Sprintf("redis://%s:%s@%s:%d", username, password, host, port)
|
||||
}
|
||||
|
||||
c.redis = &uri
|
||||
}
|
||||
}
|
||||
|
||||
func WithRedisURI(uri string) Option {
|
||||
return func(c *option) {
|
||||
ins, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if ins.Scheme != "redis" {
|
||||
return
|
||||
}
|
||||
|
||||
c.redis = &uri
|
||||
}
|
||||
}
|
||||
|
||||
func WithMemory() Option {
|
||||
return func(c *option) {
|
||||
c.memory = true
|
||||
}
|
||||
}
|
153
pkg/database/cache/redis.go
vendored
Normal file
153
pkg/database/cache/redis.go
vendored
Normal file
@ -0,0 +1,153 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gitea.loveuer.com/yizhisec/packages/tool"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
var _ Cache = (*_redis)(nil)
|
||||
|
||||
type _redis struct {
|
||||
sync.Mutex
|
||||
ctx context.Context
|
||||
client *redis.Client
|
||||
}
|
||||
|
||||
func (r *_redis) Client() any {
|
||||
return r.client
|
||||
}
|
||||
|
||||
func newRedis(ctx context.Context, client *redis.Client) Cache {
|
||||
r := &_redis{ctx: ctx, client: client}
|
||||
|
||||
go func() {
|
||||
<-r.ctx.Done()
|
||||
if client != nil {
|
||||
r.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *_redis) GetDel(ctx context.Context, key string) ([]byte, error) {
|
||||
s, err := r.client.GetDel(ctx, key).Result()
|
||||
if err != nil {
|
||||
if errors.Is(err, redis.Nil) {
|
||||
return nil, ErrorKeyNotFound
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tool.StringToBytes(s), nil
|
||||
}
|
||||
|
||||
func (r *_redis) GetDelScan(ctx context.Context, key string) Scanner {
|
||||
bs, err := r.GetDel(ctx, key)
|
||||
return newScan(bs, err)
|
||||
}
|
||||
|
||||
func (r *_redis) Get(ctx context.Context, key string) ([]byte, error) {
|
||||
result, err := r.client.Get(ctx, key).Result()
|
||||
if err != nil {
|
||||
if errors.Is(err, redis.Nil) {
|
||||
return nil, ErrorKeyNotFound
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tool.StringToBytes(result), nil
|
||||
}
|
||||
|
||||
func (r *_redis) Gets(ctx context.Context, keys ...string) ([][]byte, error) {
|
||||
result, err := r.client.MGet(ctx, keys...).Result()
|
||||
if err != nil {
|
||||
if errors.Is(err, redis.Nil) {
|
||||
return nil, ErrorKeyNotFound
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tool.Map(
|
||||
result,
|
||||
func(item any, index int) []byte {
|
||||
return tool.StringToBytes(cast.ToString(item))
|
||||
},
|
||||
), nil
|
||||
}
|
||||
|
||||
func (r *_redis) GetScan(ctx context.Context, key string) Scanner {
|
||||
return newScan(r.Get(ctx, key))
|
||||
}
|
||||
|
||||
func (r *_redis) GetEx(ctx context.Context, key string, duration time.Duration) ([]byte, error) {
|
||||
result, err := r.client.GetEx(ctx, key, duration).Result()
|
||||
if err != nil {
|
||||
if errors.Is(err, redis.Nil) {
|
||||
return nil, ErrorKeyNotFound
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tool.StringToBytes(result), nil
|
||||
}
|
||||
|
||||
func (r *_redis) GetExScan(ctx context.Context, key string, duration time.Duration) Scanner {
|
||||
return newScan(r.GetEx(ctx, key, duration))
|
||||
}
|
||||
|
||||
func (r *_redis) Set(ctx context.Context, key string, value any) error {
|
||||
bs, err := handleValue(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = r.client.Set(ctx, key, bs, redis.KeepTTL).Result()
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *_redis) Sets(ctx context.Context, values map[string]any) error {
|
||||
vm := make(map[string]any)
|
||||
for k, v := range values {
|
||||
bs, err := handleValue(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vm[k] = bs
|
||||
}
|
||||
|
||||
return r.client.MSet(ctx, vm).Err()
|
||||
}
|
||||
|
||||
func (r *_redis) SetEx(ctx context.Context, key string, value any, duration time.Duration) error {
|
||||
bs, err := handleValue(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = r.client.SetEX(ctx, key, bs, duration).Result()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *_redis) Del(ctx context.Context, keys ...string) error {
|
||||
return r.client.Del(ctx, keys...).Err()
|
||||
}
|
||||
|
||||
func (r *_redis) Close() {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
_ = r.client.Close()
|
||||
r.client = nil
|
||||
}
|
49
pkg/database/db/db.go
Normal file
49
pkg/database/db/db.go
Normal file
@ -0,0 +1,49 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Debug bool
|
||||
DryRun bool
|
||||
}
|
||||
|
||||
type DB interface {
|
||||
Session(ctx context.Context, configs ...Config) *gorm.DB
|
||||
}
|
||||
|
||||
type db struct {
|
||||
tx *gorm.DB
|
||||
}
|
||||
|
||||
var (
|
||||
Default DB
|
||||
)
|
||||
|
||||
func (db *db) Session(ctx context.Context, configs ...Config) *gorm.DB {
|
||||
var (
|
||||
sc = &gorm.Session{Context: ctx}
|
||||
session *gorm.DB
|
||||
)
|
||||
|
||||
if len(configs) == 0 {
|
||||
session = db.tx.Session(sc)
|
||||
return session
|
||||
}
|
||||
|
||||
cfg := configs[0]
|
||||
|
||||
if cfg.DryRun {
|
||||
sc.DryRun = true
|
||||
}
|
||||
|
||||
session = db.tx.Session(sc)
|
||||
|
||||
if cfg.Debug {
|
||||
session = session.Debug()
|
||||
}
|
||||
|
||||
return session
|
||||
}
|
48
pkg/database/db/new.go
Normal file
48
pkg/database/db/new.go
Normal file
@ -0,0 +1,48 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"github.com/glebarez/sqlite"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var defaultSqlite = "data.db"
|
||||
|
||||
func New(opts ...OptionFn) (DB, error) {
|
||||
var (
|
||||
err error
|
||||
conf = &config{
|
||||
sqlite: &defaultSqlite,
|
||||
}
|
||||
tx *gorm.DB
|
||||
)
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(conf)
|
||||
}
|
||||
|
||||
if conf.mysql != nil {
|
||||
tx, err = gorm.Open(mysql.Open(*conf.mysql))
|
||||
goto CHECK
|
||||
}
|
||||
|
||||
if conf.pg != nil {
|
||||
tx, err = gorm.Open(postgres.Open(*conf.pg))
|
||||
goto CHECK
|
||||
}
|
||||
|
||||
tx, err = gorm.Open(sqlite.Open(*conf.sqlite))
|
||||
|
||||
CHECK:
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &db{tx: tx}, nil
|
||||
}
|
||||
|
||||
func Init(opts ...OptionFn) (err error) {
|
||||
Default, err = New(opts...)
|
||||
return err
|
||||
}
|
25
pkg/database/db/new_test.go
Normal file
25
pkg/database/db/new_test.go
Normal file
@ -0,0 +1,25 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
//mdb, err := New(WithMysql("127.0.0.1", 3306, "root", "MyPassw0rd", "mydb"))
|
||||
//if err != nil {
|
||||
// t.Fatal(err)
|
||||
//}
|
||||
//
|
||||
//type User struct {
|
||||
// Id uint64 `gorm:"primaryKey"`
|
||||
// Username string `gorm:"unique"`
|
||||
//}
|
||||
//
|
||||
//if err = mdb.Session(t.Context()).AutoMigrate(&User{}); err != nil {
|
||||
// t.Fatal(err)
|
||||
//}
|
||||
//
|
||||
//if err = mdb.Session(t.Context()).Create(&User{Username: "zyp"}).Error; err != nil {
|
||||
// t.Fatal(err)
|
||||
//}
|
||||
}
|
45
pkg/database/db/option.go
Normal file
45
pkg/database/db/option.go
Normal file
@ -0,0 +1,45 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
ctx context.Context
|
||||
mysql *string
|
||||
pg *string
|
||||
sqlite *string
|
||||
}
|
||||
|
||||
type OptionFn func(*config)
|
||||
|
||||
func WithCtx(ctx context.Context) OptionFn {
|
||||
return func(c *config) {
|
||||
if ctx != nil {
|
||||
c.ctx = ctx
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WithMysql(host string, port int, user string, password string, database string) OptionFn {
|
||||
return func(c *config) {
|
||||
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", user, password, host, port, database)
|
||||
c.mysql = &dsn
|
||||
}
|
||||
}
|
||||
|
||||
func WithPg(host string, port int, user string, password string, database string) OptionFn {
|
||||
return func(c *config) {
|
||||
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=disable TimeZone=Asia/Shanghai", host, user, password, database, port)
|
||||
c.pg = &dsn
|
||||
}
|
||||
}
|
||||
|
||||
func WithSqlite(path string) OptionFn {
|
||||
return func(c *config) {
|
||||
if path != "" {
|
||||
c.sqlite = &path
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,53 @@
|
||||
package logger
|
||||
|
||||
import "github.com/gofiber/fiber/v3"
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/spf13/cast"
|
||||
"loveuer/utodo/pkg/logger"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func New() fiber.Handler {
|
||||
pool := sync.Pool{
|
||||
New: func() any {
|
||||
return &strings.Builder{}
|
||||
},
|
||||
}
|
||||
|
||||
return func(c fiber.Ctx) error {
|
||||
return c.Next()
|
||||
start := time.Now()
|
||||
err := c.Next()
|
||||
|
||||
duration := time.Since(start)
|
||||
method := c.Method()
|
||||
path := c.Path()
|
||||
status := c.Response().StatusCode()
|
||||
traceId := c.Context().Value(logger.CtxKey)
|
||||
|
||||
buf := pool.Get().(*strings.Builder)
|
||||
defer pool.Put(buf)
|
||||
|
||||
buf.Reset()
|
||||
|
||||
buf.WriteString("API | ")
|
||||
buf.WriteString(start.Format("2006-01-02T15:04:05"))
|
||||
buf.WriteString(" | ")
|
||||
buf.WriteString(method)
|
||||
buf.WriteString(" | ")
|
||||
buf.WriteString(path)
|
||||
buf.WriteString(" | ")
|
||||
buf.WriteString(duration.String())
|
||||
buf.WriteString(" | ")
|
||||
buf.WriteString(strconv.Itoa(status))
|
||||
buf.WriteString(" | ")
|
||||
buf.WriteString(cast.ToString(traceId))
|
||||
|
||||
fmt.Println(buf.String())
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -1 +1,56 @@
|
||||
package resp
|
||||
|
||||
import "net/http"
|
||||
|
||||
type Error struct {
|
||||
Status int `json:"status"`
|
||||
Msg string `json:"msg"`
|
||||
Err error `json:"err"`
|
||||
Data any `json:"data"`
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
func (e *Error) _r() *res {
|
||||
data := &res{
|
||||
Status: e.Status,
|
||||
Msg: e.Msg,
|
||||
Data: e.Data,
|
||||
Err: e.Err,
|
||||
}
|
||||
|
||||
if data.Status < 0 || data.Status > 999 {
|
||||
data.Status = 500
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func NewError(err error, args ...any) *Error {
|
||||
e := &Error{
|
||||
Status: http.StatusInternalServerError,
|
||||
Err: err,
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
if status, ok := args[0].(int); ok {
|
||||
e.Status = status
|
||||
}
|
||||
}
|
||||
|
||||
e.Msg = Msg(e.Status)
|
||||
|
||||
if len(args) > 1 {
|
||||
if msg, ok := args[1].(string); ok {
|
||||
e.Msg = msg
|
||||
}
|
||||
}
|
||||
|
||||
if len(args) > 2 {
|
||||
e.Data = args[2]
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
@ -1 +1,34 @@
|
||||
package resp
|
||||
|
||||
const (
|
||||
Msg200 = "操作成功"
|
||||
Msg400 = "参数错误"
|
||||
Msg401 = "该账号登录已失效, 请重新登录"
|
||||
Msg401NoMulti = "用户已在其他地方登录"
|
||||
Msg403 = "权限不足"
|
||||
Msg404 = "资源不存在"
|
||||
Msg500 = "服务器开小差了"
|
||||
Msg501 = "服务不可用"
|
||||
Msg503 = "服务不可用或正在升级, 请联系管理员"
|
||||
)
|
||||
|
||||
func Msg(status int) string {
|
||||
switch status {
|
||||
case 400:
|
||||
return Msg400
|
||||
case 401:
|
||||
return Msg401
|
||||
case 403:
|
||||
return Msg403
|
||||
case 404:
|
||||
return Msg404
|
||||
case 500:
|
||||
return Msg500
|
||||
case 501:
|
||||
return Msg501
|
||||
case 503:
|
||||
return Msg503
|
||||
}
|
||||
|
||||
return "未知错误"
|
||||
}
|
||||
|
104
pkg/resp/resp.go
104
pkg/resp/resp.go
@ -1 +1,105 @@
|
||||
package resp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
|
||||
type res struct {
|
||||
Status int `json:"status"`
|
||||
Msg string `json:"msg"`
|
||||
Data any `json:"data"`
|
||||
Err any `json:"err"`
|
||||
}
|
||||
|
||||
func R200(c fiber.Ctx, data any, msgs ...string) error {
|
||||
r := &res{
|
||||
Status: 200,
|
||||
Msg: Msg200,
|
||||
Data: data,
|
||||
}
|
||||
|
||||
if len(msgs) > 0 && msgs[0] != "" {
|
||||
r.Msg = msgs[0]
|
||||
}
|
||||
|
||||
return c.JSON(r)
|
||||
}
|
||||
|
||||
func RC(c fiber.Ctx, status int, args ...any) error {
|
||||
return _r(c, &res{Status: status}, args...)
|
||||
}
|
||||
|
||||
func RE(c fiber.Ctx, err error) error {
|
||||
var re *Error
|
||||
|
||||
if errors.As(err, &re) {
|
||||
return _r(c, re._r())
|
||||
}
|
||||
|
||||
return R500(c, "", nil, err)
|
||||
}
|
||||
|
||||
func _r(c fiber.Ctx, r *res, args ...any) error {
|
||||
length := len(args)
|
||||
switch length {
|
||||
case 0:
|
||||
break
|
||||
case 1:
|
||||
if msg, ok := args[0].(string); ok {
|
||||
r.Msg = msg
|
||||
} else {
|
||||
r.Data = args[0]
|
||||
}
|
||||
case 2:
|
||||
r.Data = args[1]
|
||||
case 3:
|
||||
r.Err = args[2]
|
||||
}
|
||||
|
||||
if r.Msg == "" {
|
||||
r.Msg = Msg(r.Status)
|
||||
}
|
||||
|
||||
return c.Status(r.Status).JSON(r)
|
||||
}
|
||||
|
||||
func R400(c fiber.Ctx, args ...any) error {
|
||||
r := &res{
|
||||
Status: 400,
|
||||
}
|
||||
|
||||
return _r(c, r, args...)
|
||||
}
|
||||
|
||||
func R401(c fiber.Ctx, args ...any) error {
|
||||
r := &res{
|
||||
Status: 401,
|
||||
}
|
||||
|
||||
return _r(c, r, args...)
|
||||
}
|
||||
|
||||
func R403(c fiber.Ctx, args ...any) error {
|
||||
r := &res{
|
||||
Status: 403,
|
||||
}
|
||||
|
||||
return _r(c, r, args...)
|
||||
}
|
||||
|
||||
func R500(c fiber.Ctx, args ...any) error {
|
||||
r := &res{
|
||||
Status: 500,
|
||||
}
|
||||
|
||||
return _r(c, r, args...)
|
||||
}
|
||||
|
||||
func R501(c fiber.Ctx, args ...any) error {
|
||||
r := &res{
|
||||
Status: 501,
|
||||
}
|
||||
|
||||
return _r(c, r, args...)
|
||||
}
|
||||
|
44
pkg/tool/ctx.go
Normal file
44
pkg/tool/ctx.go
Normal file
@ -0,0 +1,44 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"gitea.loveuer.com/yizhisec/packages/opt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Timeout(seconds ...int) (ctx context.Context) {
|
||||
var (
|
||||
duration time.Duration
|
||||
)
|
||||
|
||||
if len(seconds) > 0 && seconds[0] > 0 {
|
||||
duration = time.Duration(seconds[0]) * time.Second
|
||||
} else {
|
||||
duration = time.Duration(30) * time.Second
|
||||
}
|
||||
|
||||
ctx, _ = context.WithTimeout(context.Background(), duration)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func CtxTimeout(ctx context.Context, seconds ...int) context.Context {
|
||||
var (
|
||||
duration time.Duration
|
||||
)
|
||||
|
||||
if len(seconds) > 0 && seconds[0] > 0 {
|
||||
duration = time.Duration(seconds[0]) * time.Second
|
||||
} else {
|
||||
duration = time.Duration(30) * time.Second
|
||||
}
|
||||
|
||||
nctx, _ := context.WithTimeout(ctx, duration)
|
||||
|
||||
return nctx
|
||||
}
|
||||
|
||||
func CtxTrace(ctx context.Context, key string) context.Context {
|
||||
return context.WithValue(ctx, opt.TraceKey, fmt.Sprintf("%36s", key))
|
||||
}
|
12
pkg/tool/gin.go
Normal file
12
pkg/tool/gin.go
Normal file
@ -0,0 +1,12 @@
|
||||
package tool
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
func Local(c *gin.Context, key string) any {
|
||||
data, ok := c.Get(key)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
50
pkg/tool/human.go
Normal file
50
pkg/tool/human.go
Normal file
@ -0,0 +1,50 @@
|
||||
package tool
|
||||
|
||||
import "fmt"
|
||||
|
||||
func HumanDuration(nano int64) string {
|
||||
duration := float64(nano)
|
||||
unit := "ns"
|
||||
if duration >= 1000 {
|
||||
duration /= 1000
|
||||
unit = "us"
|
||||
}
|
||||
|
||||
if duration >= 1000 {
|
||||
duration /= 1000
|
||||
unit = "ms"
|
||||
}
|
||||
|
||||
if duration >= 1000 {
|
||||
duration /= 1000
|
||||
unit = " s"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%6.2f%s", duration, unit)
|
||||
}
|
||||
|
||||
func HumanSize(size int64) string {
|
||||
const (
|
||||
_ = iota
|
||||
KB = 1 << (10 * iota) // 1 KB = 1024 bytes
|
||||
MB // 1 MB = 1024 KB
|
||||
GB // 1 GB = 1024 MB
|
||||
TB // 1 TB = 1024 GB
|
||||
PB // 1 PB = 1024 TB
|
||||
)
|
||||
|
||||
switch {
|
||||
case size >= PB:
|
||||
return fmt.Sprintf("%.2f PB", float64(size)/PB)
|
||||
case size >= TB:
|
||||
return fmt.Sprintf("%.2f TB", float64(size)/TB)
|
||||
case size >= GB:
|
||||
return fmt.Sprintf("%.2f GB", float64(size)/GB)
|
||||
case size >= MB:
|
||||
return fmt.Sprintf("%.2f MB", float64(size)/MB)
|
||||
case size >= KB:
|
||||
return fmt.Sprintf("%.2f KB", float64(size)/KB)
|
||||
default:
|
||||
return fmt.Sprintf("%d bytes", size)
|
||||
}
|
||||
}
|
59
pkg/tool/ip.go
Normal file
59
pkg/tool/ip.go
Normal file
@ -0,0 +1,59 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
var (
|
||||
privateIPv4Blocks []*net.IPNet
|
||||
privateIPv6Blocks []*net.IPNet
|
||||
)
|
||||
|
||||
func init() {
|
||||
// IPv4私有地址段
|
||||
for _, cidr := range []string{
|
||||
"10.0.0.0/8", // A类私有地址
|
||||
"172.16.0.0/12", // B类私有地址
|
||||
"192.168.0.0/16", // C类私有地址
|
||||
"169.254.0.0/16", // 链路本地地址
|
||||
"127.0.0.0/8", // 环回地址
|
||||
} {
|
||||
_, block, _ := net.ParseCIDR(cidr)
|
||||
privateIPv4Blocks = append(privateIPv4Blocks, block)
|
||||
}
|
||||
|
||||
// IPv6私有地址段
|
||||
for _, cidr := range []string{
|
||||
"fc00::/7", // 唯一本地地址
|
||||
"fe80::/10", // 链路本地地址
|
||||
"::1/128", // 环回地址
|
||||
} {
|
||||
_, block, _ := net.ParseCIDR(cidr)
|
||||
privateIPv6Blocks = append(privateIPv6Blocks, block)
|
||||
}
|
||||
}
|
||||
|
||||
func IsPrivateIP(ipStr string) bool {
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 处理IPv4和IPv4映射的IPv6地址
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
for _, block := range privateIPv4Blocks {
|
||||
if block.Contains(ip4) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 处理IPv6地址
|
||||
for _, block := range privateIPv6Blocks {
|
||||
if block.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
76
pkg/tool/loadash.go
Normal file
76
pkg/tool/loadash.go
Normal file
@ -0,0 +1,76 @@
|
||||
package tool
|
||||
|
||||
import "math"
|
||||
|
||||
func Map[T, R any](vals []T, fn func(item T, index int) R) []R {
|
||||
var result = make([]R, len(vals))
|
||||
for idx, v := range vals {
|
||||
result[idx] = fn(v, idx)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func Chunk[T any](vals []T, size int) [][]T {
|
||||
if size <= 0 {
|
||||
panic("Second parameter must be greater than 0")
|
||||
}
|
||||
|
||||
chunksNum := len(vals) / size
|
||||
if len(vals)%size != 0 {
|
||||
chunksNum += 1
|
||||
}
|
||||
|
||||
result := make([][]T, 0, chunksNum)
|
||||
|
||||
for i := 0; i < chunksNum; i++ {
|
||||
last := (i + 1) * size
|
||||
if last > len(vals) {
|
||||
last = len(vals)
|
||||
}
|
||||
result = append(result, vals[i*size:last:last])
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// 对 vals 取样 x 个
|
||||
func Sample[T any](vals []T, x int) []T {
|
||||
if x < 0 {
|
||||
panic("Second parameter can't be negative")
|
||||
}
|
||||
|
||||
n := len(vals)
|
||||
if n == 0 {
|
||||
return []T{}
|
||||
}
|
||||
|
||||
if x >= n {
|
||||
return vals
|
||||
}
|
||||
|
||||
// 处理x=1的特殊情况
|
||||
if x == 1 {
|
||||
return []T{vals[(n-1)/2]}
|
||||
}
|
||||
|
||||
// 计算采样步长并生成结果数组
|
||||
step := float64(n-1) / float64(x-1)
|
||||
result := make([]T, x)
|
||||
|
||||
for i := 0; i < x; i++ {
|
||||
// 计算采样位置并四舍五入
|
||||
pos := float64(i) * step
|
||||
index := int(math.Round(pos))
|
||||
result[i] = vals[index]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func If[T any](cond bool, trueVal, falseVal T) T {
|
||||
if cond {
|
||||
return trueVal
|
||||
}
|
||||
|
||||
return falseVal
|
||||
}
|
53
pkg/tool/must.go
Normal file
53
pkg/tool/must.go
Normal file
@ -0,0 +1,53 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"gitea.loveuer.com/yizhisec/packages/logger"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func Must(errs ...error) {
|
||||
for _, err := range errs {
|
||||
if err != nil {
|
||||
logger.Panic(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func MustWithData[T any](data T, err error) T {
|
||||
Must(err)
|
||||
return data
|
||||
}
|
||||
|
||||
func MustStop(ctx context.Context, stopFns ...func(ctx context.Context) error) {
|
||||
if len(stopFns) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
ok := make(chan struct{})
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(len(stopFns))
|
||||
|
||||
for _, fn := range stopFns {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
if err := fn(ctx); err != nil {
|
||||
logger.ErrorCtx(ctx, "stop function failed, err = %s", err.Error())
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
logger.FatalCtx(ctx, "stop function timeout, force down")
|
||||
case _, _ = <-ok:
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
close(ok)
|
||||
}
|
84
pkg/tool/password.go
Normal file
84
pkg/tool/password.go
Normal file
@ -0,0 +1,84 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gitea.loveuer.com/yizhisec/packages/logger"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
EncryptHeader string = "pbkdf2:sha256" // 用户密码加密
|
||||
)
|
||||
|
||||
func NewPassword(password string) string {
|
||||
return EncryptPassword(password, RandomString(8), int(RandomInt(50000)+100000))
|
||||
}
|
||||
|
||||
func ComparePassword(in, db string) bool {
|
||||
strs := strings.Split(db, "$")
|
||||
if len(strs) != 3 {
|
||||
logger.Error("password in db invalid: %s", db)
|
||||
return false
|
||||
}
|
||||
|
||||
encs := strings.Split(strs[0], ":")
|
||||
if len(encs) != 3 {
|
||||
logger.Error("password in db invalid: %s", db)
|
||||
return false
|
||||
}
|
||||
|
||||
encIteration, err := strconv.Atoi(encs[2])
|
||||
if err != nil {
|
||||
logger.Error("password in db invalid: %s, convert iter err: %s", db, err)
|
||||
return false
|
||||
}
|
||||
|
||||
return EncryptPassword(in, strs[1], encIteration) == db
|
||||
}
|
||||
|
||||
func EncryptPassword(password, salt string, iter int) string {
|
||||
hash := pbkdf2.Key([]byte(password), []byte(salt), iter, 32, sha256.New)
|
||||
encrypted := hex.EncodeToString(hash)
|
||||
return fmt.Sprintf("%s:%d$%s$%s", EncryptHeader, iter, salt, encrypted)
|
||||
}
|
||||
|
||||
func CheckPassword(password string) error {
|
||||
if len(password) < 8 || len(password) > 32 {
|
||||
return errors.New("密码长度不符合")
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
match bool
|
||||
patternList = []string{`[0-9]+`, `[a-z]+`, `[A-Z]+`, `[!@#%]+`} //, `[~!@#$%^&*?_-]+`}
|
||||
matchAccount = 0
|
||||
tips = []string{"缺少数字", "缺少小写字母", "缺少大写字母", "缺少'!@#%'"}
|
||||
locktips = make([]string, 0)
|
||||
)
|
||||
|
||||
for idx, pattern := range patternList {
|
||||
match, err = regexp.MatchString(pattern, password)
|
||||
if err != nil {
|
||||
logger.Warn("regex match string err, reg_str: %s, err: %v", pattern, err)
|
||||
return errors.New("密码强度不够")
|
||||
}
|
||||
|
||||
if match {
|
||||
matchAccount++
|
||||
} else {
|
||||
locktips = append(locktips, tips[idx])
|
||||
}
|
||||
}
|
||||
|
||||
if matchAccount < 3 {
|
||||
return fmt.Errorf("密码强度不够, 可能 %s", strings.Join(locktips, ", "))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
20
pkg/tool/password_test.go
Normal file
20
pkg/tool/password_test.go
Normal file
@ -0,0 +1,20 @@
|
||||
package tool
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestEncPassword(t *testing.T) {
|
||||
password := "123456"
|
||||
|
||||
result := EncryptPassword(password, RandomString(8), 50000)
|
||||
|
||||
t.Logf("sum => %s", result)
|
||||
}
|
||||
|
||||
func TestPassword(t *testing.T) {
|
||||
p := "wahaha@123"
|
||||
p = NewPassword(p)
|
||||
t.Logf("password => %s", p)
|
||||
|
||||
result := ComparePassword("wahaha@123", p)
|
||||
t.Logf("compare result => %v", result)
|
||||
}
|
75
pkg/tool/random.go
Normal file
75
pkg/tool/random.go
Normal file
@ -0,0 +1,75 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"math/big"
|
||||
mrand "math/rand"
|
||||
)
|
||||
|
||||
var (
|
||||
letters = []byte("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
letterNum = []byte("0123456789")
|
||||
letterLow = []byte("abcdefghijklmnopqrstuvwxyz")
|
||||
letterCap = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
letterSyb = []byte("!@#$%^&*()_+-=")
|
||||
adjectives = []string{
|
||||
"开心的", "灿烂的", "温暖的", "阳光的", "活泼的",
|
||||
"聪明的", "优雅的", "幸运的", "甜蜜的", "勇敢的",
|
||||
"宁静的", "热情的", "温柔的", "幽默的", "坚强的",
|
||||
"迷人的", "神奇的", "快乐的", "健康的", "自由的",
|
||||
"梦幻的", "勤劳的", "真诚的", "浪漫的", "自信的",
|
||||
}
|
||||
|
||||
plants = []string{
|
||||
"苹果", "香蕉", "橘子", "葡萄", "草莓",
|
||||
"西瓜", "樱桃", "菠萝", "柠檬", "蜜桃",
|
||||
"蓝莓", "芒果", "石榴", "甜瓜", "雪梨",
|
||||
"番茄", "南瓜", "土豆", "青椒", "洋葱",
|
||||
"黄瓜", "萝卜", "豌豆", "玉米", "蘑菇",
|
||||
"菠菜", "茄子", "芹菜", "莲藕", "西兰花",
|
||||
}
|
||||
)
|
||||
|
||||
func RandomInt(max int64) int64 {
|
||||
num, _ := rand.Int(rand.Reader, big.NewInt(max))
|
||||
return num.Int64()
|
||||
}
|
||||
|
||||
func RandomString(length int) string {
|
||||
result := make([]byte, length)
|
||||
for i := 0; i < length; i++ {
|
||||
num, _ := rand.Int(rand.Reader, big.NewInt(int64(len(letters))))
|
||||
result[i] = letters[num.Int64()]
|
||||
}
|
||||
return string(result)
|
||||
}
|
||||
|
||||
func RandomPassword(length int, withSymbol bool) string {
|
||||
result := make([]byte, length)
|
||||
kind := 3
|
||||
if withSymbol {
|
||||
kind++
|
||||
}
|
||||
|
||||
for i := 0; i < length; i++ {
|
||||
switch i % kind {
|
||||
case 0:
|
||||
num, _ := rand.Int(rand.Reader, big.NewInt(int64(len(letterNum))))
|
||||
result[i] = letterNum[num.Int64()]
|
||||
case 1:
|
||||
num, _ := rand.Int(rand.Reader, big.NewInt(int64(len(letterLow))))
|
||||
result[i] = letterLow[num.Int64()]
|
||||
case 2:
|
||||
num, _ := rand.Int(rand.Reader, big.NewInt(int64(len(letterCap))))
|
||||
result[i] = letterCap[num.Int64()]
|
||||
case 3:
|
||||
num, _ := rand.Int(rand.Reader, big.NewInt(int64(len(letterSyb))))
|
||||
result[i] = letterSyb[num.Int64()]
|
||||
}
|
||||
}
|
||||
return string(result)
|
||||
}
|
||||
|
||||
func RandomName() string {
|
||||
return adjectives[mrand.Intn(len(adjectives))] + plants[mrand.Intn(len(plants))]
|
||||
}
|
11
pkg/tool/string.go
Normal file
11
pkg/tool/string.go
Normal file
@ -0,0 +1,11 @@
|
||||
package tool
|
||||
|
||||
import "unsafe"
|
||||
|
||||
func BytesToString(b []byte) string {
|
||||
return unsafe.String(unsafe.SliceData(b), len(b))
|
||||
}
|
||||
|
||||
func StringToBytes(s string) []byte {
|
||||
return unsafe.Slice(unsafe.StringData(s), len(s))
|
||||
}
|
124
pkg/tool/table.go
Normal file
124
pkg/tool/table.go
Normal file
@ -0,0 +1,124 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gitea.loveuer.com/yizhisec/packages/logger"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func TablePrinter(data any, writers ...io.Writer) {
|
||||
var w io.Writer = os.Stdout
|
||||
if len(writers) > 0 && writers[0] != nil {
|
||||
w = writers[0]
|
||||
}
|
||||
|
||||
t := table.NewWriter()
|
||||
structPrinter(t, "", data)
|
||||
_, _ = fmt.Fprintln(w, t.Render())
|
||||
}
|
||||
|
||||
func structPrinter(w table.Writer, prefix string, item any) {
|
||||
Start:
|
||||
rv := reflect.ValueOf(item)
|
||||
if rv.IsZero() {
|
||||
return
|
||||
}
|
||||
|
||||
for rv.Type().Kind() == reflect.Pointer {
|
||||
rv = rv.Elem()
|
||||
}
|
||||
|
||||
switch rv.Type().Kind() {
|
||||
case reflect.Invalid,
|
||||
reflect.Uintptr,
|
||||
reflect.Chan,
|
||||
reflect.Func,
|
||||
reflect.UnsafePointer:
|
||||
case reflect.Bool,
|
||||
reflect.Int,
|
||||
reflect.Int8,
|
||||
reflect.Int16,
|
||||
reflect.Int32,
|
||||
reflect.Int64,
|
||||
reflect.Uint,
|
||||
reflect.Uint8,
|
||||
reflect.Uint16,
|
||||
reflect.Uint32,
|
||||
reflect.Uint64,
|
||||
reflect.Float32,
|
||||
reflect.Float64,
|
||||
reflect.Complex64,
|
||||
reflect.Complex128,
|
||||
reflect.Interface:
|
||||
w.AppendRow(table.Row{strings.TrimPrefix(prefix, "."), rv.Interface()})
|
||||
case reflect.String:
|
||||
val := rv.String()
|
||||
if len(val) <= 160 {
|
||||
w.AppendRow(table.Row{strings.TrimPrefix(prefix, "."), val})
|
||||
return
|
||||
}
|
||||
|
||||
w.AppendRow(table.Row{strings.TrimPrefix(prefix, "."), val[0:64] + "..." + val[len(val)-64:]})
|
||||
case reflect.Array, reflect.Slice:
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
p := strings.Join([]string{prefix, fmt.Sprintf("[%d]", i)}, ".")
|
||||
structPrinter(w, p, rv.Index(i).Interface())
|
||||
}
|
||||
case reflect.Map:
|
||||
for _, k := range rv.MapKeys() {
|
||||
structPrinter(w, fmt.Sprintf("%s.{%v}", prefix, k), rv.MapIndex(k).Interface())
|
||||
}
|
||||
case reflect.Pointer:
|
||||
goto Start
|
||||
case reflect.Struct:
|
||||
for i := 0; i < rv.NumField(); i++ {
|
||||
p := fmt.Sprintf("%s.%s", prefix, rv.Type().Field(i).Name)
|
||||
field := rv.Field(i)
|
||||
|
||||
//log.Debug("TablePrinter: prefix: %s, field: %v", p, rv.Field(i))
|
||||
|
||||
if !field.CanInterface() {
|
||||
return
|
||||
}
|
||||
|
||||
structPrinter(w, p, field.Interface())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TableMapPrinter(data []byte) {
|
||||
m := make(map[string]any)
|
||||
if err := json.Unmarshal(data, &m); err != nil {
|
||||
logger.Warn(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
t := table.NewWriter()
|
||||
addRow(t, "", m)
|
||||
fmt.Println(t.Render())
|
||||
}
|
||||
|
||||
func addRow(w table.Writer, prefix string, m any) {
|
||||
rv := reflect.ValueOf(m)
|
||||
switch rv.Type().Kind() {
|
||||
case reflect.Map:
|
||||
for _, k := range rv.MapKeys() {
|
||||
key := k.String()
|
||||
if prefix != "" {
|
||||
key = strings.Join([]string{prefix, k.String()}, ".")
|
||||
}
|
||||
addRow(w, key, rv.MapIndex(k).Interface())
|
||||
}
|
||||
case reflect.Slice, reflect.Array:
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
addRow(w, fmt.Sprintf("%s[%d]", prefix, i), rv.Index(i).Interface())
|
||||
}
|
||||
default:
|
||||
w.AppendRow(table.Row{prefix, m})
|
||||
}
|
||||
}
|
73
pkg/tool/tools.go
Normal file
73
pkg/tool/tools.go
Normal file
@ -0,0 +1,73 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
func Min[T ~int | ~uint | ~int8 | ~uint8 | ~int16 | ~uint16 | ~int32 | ~uint32 | ~int64 | ~uint64 | ~float32 | ~float64](a, b T) T {
|
||||
if a <= b {
|
||||
return a
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func Mins[T ~int | ~uint | ~int8 | ~uint8 | ~int16 | ~uint16 | ~int32 | ~uint32 | ~int64 | ~uint64 | ~float32 | ~float64](vals ...T) T {
|
||||
var val T
|
||||
|
||||
if len(vals) == 0 {
|
||||
return val
|
||||
}
|
||||
|
||||
val = vals[0]
|
||||
|
||||
for _, item := range vals[1:] {
|
||||
if item < val {
|
||||
val = item
|
||||
}
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
func Max[T ~int | ~uint | ~int8 | ~uint8 | ~int16 | ~uint16 | ~int32 | ~uint32 | ~int64 | ~uint64 | ~float32 | ~float64](a, b T) T {
|
||||
if a >= b {
|
||||
return a
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func Maxs[T ~int | ~uint | ~int8 | ~uint8 | ~int16 | ~uint16 | ~int32 | ~uint32 | ~int64 | ~uint64 | ~float32 | ~float64](vals ...T) T {
|
||||
var val T
|
||||
|
||||
if len(vals) == 0 {
|
||||
return val
|
||||
}
|
||||
|
||||
for _, item := range vals {
|
||||
if item > val {
|
||||
val = item
|
||||
}
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
func Sum[T ~int | ~uint | ~int8 | ~uint8 | ~int16 | ~uint16 | ~int32 | ~uint32 | ~int64 | ~uint64 | ~float32 | ~float64](vals ...T) T {
|
||||
var sum T = 0
|
||||
for i := range vals {
|
||||
sum += vals[i]
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
func Percent(val, minVal, maxVal, minPercent, maxPercent float64) string {
|
||||
return fmt.Sprintf(
|
||||
"%d%%",
|
||||
int(math.Round(
|
||||
((val-minVal)/(maxVal-minVal)*(maxPercent-minPercent)+minPercent)*100,
|
||||
)),
|
||||
)
|
||||
}
|
70
pkg/tool/tools_test.go
Normal file
70
pkg/tool/tools_test.go
Normal file
@ -0,0 +1,70 @@
|
||||
package tool
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestPercent(t *testing.T) {
|
||||
type args struct {
|
||||
val float64
|
||||
minVal float64
|
||||
maxVal float64
|
||||
minPercent float64
|
||||
maxPercent float64
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "case 1",
|
||||
args: args{
|
||||
val: 0.5,
|
||||
minVal: 0,
|
||||
maxVal: 1,
|
||||
minPercent: 0,
|
||||
maxPercent: 1,
|
||||
},
|
||||
want: "50%",
|
||||
},
|
||||
{
|
||||
name: "case 2",
|
||||
args: args{
|
||||
val: 0.3,
|
||||
minVal: 0.1,
|
||||
maxVal: 0.6,
|
||||
minPercent: 0,
|
||||
maxPercent: 1,
|
||||
},
|
||||
want: "40%",
|
||||
},
|
||||
{
|
||||
name: "case 3",
|
||||
args: args{
|
||||
val: 700,
|
||||
minVal: 700,
|
||||
maxVal: 766,
|
||||
minPercent: 0.1,
|
||||
maxPercent: 0.7,
|
||||
},
|
||||
want: "10%",
|
||||
},
|
||||
{
|
||||
name: "case 4",
|
||||
args: args{
|
||||
val: 766,
|
||||
minVal: 700,
|
||||
maxVal: 766,
|
||||
minPercent: 0.1,
|
||||
maxPercent: 0.7,
|
||||
},
|
||||
want: "70%",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := Percent(tt.args.val, tt.args.minVal, tt.args.maxVal, tt.args.minPercent, tt.args.maxPercent); got != tt.want {
|
||||
t.Errorf("Percent() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user