wip: 登录和认证

This commit is contained in:
loveuer
2025-07-13 22:57:57 +08:00
parent 48af538f98
commit b48fa05d9f
33 changed files with 1961 additions and 33 deletions

28
go.mod
View File

@ -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
View File

@ -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=

View File

@ -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))

View File

@ -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
}

View File

@ -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
}
}

View File

@ -7,6 +7,7 @@ type config struct {
Svc struct {
Address string
DBFile string
Cache string
}
}

98
pkg/database/cache/cache.go vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
}

View 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
View 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
}
}
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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 "未知错误"
}

View File

@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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)
}
})
}
}