diff --git a/etc/config.json b/etc/config.json index 6933ead..bb2e5a3 100644 --- a/etc/config.json +++ b/etc/config.json @@ -24,4 +24,4 @@ "username": "admin", "password": "password" } -} +} \ No newline at end of file diff --git a/go.mod b/go.mod index fd26f9c..b5491f2 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module ultone -go 1.21 - -toolchain go1.23.4 +go 1.20 require ( gitea.com/taozitaozi/gredis v0.0.0-20241226104049-af698e5ad477 @@ -18,7 +16,7 @@ require ( github.com/jackc/pgtype v1.12.0 github.com/jedib0t/go-pretty/v6 v6.5.9 github.com/loveuer/esgo2dump v0.3.3 - github.com/loveuer/nf v0.2.12 + github.com/loveuer/nf v0.3.1 github.com/loveuer/ngorm/v2 v2.1.1 github.com/olivere/elastic/v7 v7.0.32 github.com/rabbitmq/amqp091-go v1.10.0 diff --git a/go.sum b/go.sum index 312be98..e9c3ea4 100644 --- a/go.sum +++ b/go.sum @@ -69,11 +69,8 @@ github.com/facebook/fbthrift v0.31.1-0.20211129061412-801ed7f9f295/go.mod h1:2tn github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= -github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= 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.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo= github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= @@ -112,7 +109,6 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= -github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -184,12 +180,10 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -197,8 +191,8 @@ github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/loveuer/esgo2dump v0.3.3 h1:/AidoaFV7bDRyT1ycyBKs4XGmyVs2ShaUKrpEBiUWkM= github.com/loveuer/esgo2dump v0.3.3/go.mod h1:thZvfsO0kd7Ck3TA0jc9rRc4CuIa4Iuiq6tF3tCqXEY= -github.com/loveuer/nf v0.2.12 h1:1Og+ORHsOWKFmy9kKJhjvXDkdbaurH82HjIxuGA3nNM= -github.com/loveuer/nf v0.2.12/go.mod h1:M6reF17/kJBis30H4DxR5hrtgo/oJL4AV4cBe4HzJLw= +github.com/loveuer/nf v0.3.1 h1:FTmyAC9LQF06BVGeGwrwaYfbC6MIQMqr+GoZUQQPvXU= +github.com/loveuer/nf v0.3.1/go.mod h1:aApO+2cSP0ULczkfS4OVw8zfWM3rY8gQrzc5PnVV7lY= github.com/loveuer/ngorm/v2 v2.1.1 h1:v+ut5BjeSBFU87o800pI8Q3fXEOUAkvk5+btMw2oOEc= github.com/loveuer/ngorm/v2 v2.1.1/go.mod h1:BVhFGQsRMdcf08MtmwwRihwCR/x7wDd0Fzy8Xj+edM0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= @@ -216,13 +210,10 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/olivere/elastic/v7 v7.0.32 h1:R7CXvbu8Eq+WlsLgxmKVKPox0oOwAE/2T9Si5BnvK6E= github.com/olivere/elastic/v7 v7.0.32/go.mod h1:c7PVmLe3Fxq77PIfY/bZmxY/TAamBhCzZ8xDOE09a9k= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= -github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0= github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -240,7 +231,6 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= @@ -282,7 +272,6 @@ github.com/tdewolff/parse/v2 v2.7.11 h1:v+W45LnzmjndVlfqPCT5gGjAAZKd1GJGOPJveTIk github.com/tdewolff/parse/v2 v2.7.11/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA= github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo= -github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= github.com/vesoft-inc/nebula-go/v3 v3.5.0 h1:2ZSkoBxtIfs15AXJXqrAPDPd0Z9HrzKR7YKXPqlJcR0= github.com/vesoft-inc/nebula-go/v3 v3.5.0/go.mod h1:+sXv05jYQBARdTbTcIEsWVXCnF/6ttOlDK35xQ6m54s= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= @@ -299,7 +288,6 @@ go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= @@ -456,10 +444,8 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= 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.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/httptest/user.http b/httptest/user.http index 2d0a21e..18a0996 100644 --- a/httptest/user.http +++ b/httptest/user.http @@ -7,6 +7,12 @@ Content-Type: application/json "password": "123456" } +### logout +POST http://127.0.0.1:8080/api/user/auth/logout +Content-Type: application/json + +{} + ### verify login state GET http://127.0.0.1:8080/api/user/auth/login diff --git a/internal/cmd/execute.go b/internal/cmd/execute.go index a7fc99b..16163da 100644 --- a/internal/cmd/execute.go +++ b/internal/cmd/execute.go @@ -2,6 +2,7 @@ package cmd import ( "context" + "ultone/internal/api" "ultone/internal/controller" "ultone/internal/database/cache" @@ -12,19 +13,17 @@ import ( "ultone/internal/tool" ) -var ( - filename string -) +var filename string func execute(ctx context.Context) error { tool.Must(opt.Init(filename)) tool.Must(db.Init(ctx, opt.Cfg.DB.Uri)) - tool.Must(cache.Init()) + tool.Must(cache.Init(ctx, opt.Cfg.Cache.Uri)) // todo: if elastic search required - //tool.Must(es.Init(ctx, opt.Cfg.ES.Uri)) + // tool.Must(es.Init(ctx, opt.Cfg.ES.Uri)) // 或者使用 https://github.com/olivere/elastic - //tool.Must(elastic.Init(ctx, opt.Cfg.ES.Uri)) + // tool.Must(elastic.Init(ctx, opt.Cfg.ES.Uri)) // todo: if nebula required // tool.Must(nebula.Init(ctx, opt.Cfg.Nebula)) @@ -34,7 +33,7 @@ func execute(ctx context.Context) error { tool.Must(api.Start(ctx)) // todo: if need some cli operation, should start local unix rpc svc - //tool.Must(unix.Start(ctx)) + // tool.Must(unix.Start(ctx)) <-ctx.Done() diff --git a/internal/database/cache/cache.go b/internal/database/cache/cache.go new file mode 100644 index 0000000..22b7740 --- /dev/null +++ b/internal/database/cache/cache.go @@ -0,0 +1,63 @@ +package cache + +import ( + "context" + "encoding/json" + "errors" + "time" +) + +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 + Close() error +} + +var Client Cache + +type Scanner interface { + Scan(model any) error +} + +type encoded_value interface { + MarshalBinary() ([]byte, error) +} + +type decoded_value interface { + UnmarshalBinary(bs []byte) error +} + +const ( + Prefix = "upp:" +) + +var ErrorKeyNotFound = errors.New("key not found") + +func handleValue(value any) ([]byte, error) { + var ( + bs []byte + err error + ) + + switch value.(type) { + case []byte: + return value.([]byte), nil + } + + if imp, ok := value.(encoded_value); ok { + bs, err = imp.MarshalBinary() + } else { + bs, err = json.Marshal(value) + } + + return bs, err +} diff --git a/internal/database/cache/cache_lru.go b/internal/database/cache/cache.lru.go similarity index 69% rename from internal/database/cache/cache_lru.go rename to internal/database/cache/cache.lru.go index 9d73943..f3f76f5 100644 --- a/internal/database/cache/cache_lru.go +++ b/internal/database/cache/cache.lru.go @@ -2,13 +2,13 @@ package cache import ( "context" + "time" + "github.com/hashicorp/golang-lru/v2/expirable" _ "github.com/hashicorp/golang-lru/v2/expirable" - "time" - "ultone/internal/interfaces" ) -var _ interfaces.Cacher = (*_lru)(nil) +var _ Cache = (*_lru)(nil) type _lru struct { client *expirable.LRU[string, *_lru_value] @@ -38,6 +38,24 @@ func (l *_lru) Get(ctx context.Context, key string) ([]byte, error) { return v.bs, nil } +func (l *_lru) Gets(ctx context.Context, keys ...string) ([][]byte, error) { + bss := make([][]byte, 0, len(keys)) + for _, key := range keys { + bs, err := l.Get(ctx, key) + if err != nil { + return nil, err + } + + bss = append(bss, bs) + } + + return bss, nil +} + +func (l *_lru) GetScan(ctx context.Context, key string) Scanner { + return newScanner(l.Get(ctx, key)) +} + func (l *_lru) GetEx(ctx context.Context, key string, duration time.Duration) ([]byte, error) { v, ok := l.client.Get(key) if !ok { @@ -64,6 +82,10 @@ func (l *_lru) GetEx(ctx context.Context, key string, duration time.Duration) ([ return v.bs, nil } +func (l *_lru) GetExScan(ctx context.Context, key string, duration time.Duration) Scanner { + return newScanner(l.GetEx(ctx, key, duration)) +} + func (l *_lru) Set(ctx context.Context, key string, value any) error { bs, err := handleValue(value) if err != nil { @@ -94,6 +116,16 @@ func (l *_lru) SetEx(ctx context.Context, key string, value any, duration time.D return nil } +func (l *_lru) Sets(ctx context.Context, m map[string]any) error { + for k, v := range m { + if err := l.Set(ctx, k, v); err != nil { + return err + } + } + + return nil +} + func (l *_lru) Del(ctx context.Context, keys ...string) error { for _, key := range keys { l.client.Remove(key) @@ -102,7 +134,12 @@ func (l *_lru) Del(ctx context.Context, keys ...string) error { return nil } -func newLRUCache() (interfaces.Cacher, error) { +func (l *_lru) Close() error { + l.client = nil + return nil +} + +func newLRUCache() (Cache, error) { client := expirable.NewLRU[string, *_lru_value](1024*1024, nil, 0) return &_lru{client: client}, nil diff --git a/internal/database/cache/cache_memory.go b/internal/database/cache/cache.memory.go similarity index 63% rename from internal/database/cache/cache_memory.go rename to internal/database/cache/cache.memory.go index a6896fd..c3266f8 100644 --- a/internal/database/cache/cache_memory.go +++ b/internal/database/cache/cache.memory.go @@ -5,17 +5,24 @@ import ( "errors" "fmt" "time" - "ultone/internal/interfaces" "gitea.com/taozitaozi/gredis" ) -var _ interfaces.Cacher = (*_mem)(nil) +var _ Cache = (*_mem)(nil) type _mem struct { client *gredis.Gredis } +func (m *_mem) GetScan(ctx context.Context, key string) Scanner { + return newScanner(m.Get(ctx, key)) +} + +func (m *_mem) GetExScan(ctx context.Context, key string, duration time.Duration) Scanner { + return newScanner(m.GetEx(ctx, key, duration)) +} + func (m *_mem) Get(ctx context.Context, key string) ([]byte, error) { v, err := m.client.Get(key) if err != nil { @@ -34,6 +41,20 @@ func (m *_mem) Get(ctx context.Context, key string) ([]byte, error) { return bs, nil } +func (m *_mem) Gets(ctx context.Context, keys ...string) ([][]byte, error) { + bss := make([][]byte, 0, len(keys)) + for _, key := range keys { + bs, err := m.Get(ctx, key) + if err != nil { + return nil, err + } + + bss = append(bss, bs) + } + + return bss, nil +} + func (m *_mem) GetEx(ctx context.Context, key string, duration time.Duration) ([]byte, error) { v, err := m.client.GetEx(key, duration) if err != nil { @@ -60,6 +81,16 @@ func (m *_mem) Set(ctx context.Context, key string, value any) error { return m.client.Set(key, bs) } +func (m *_mem) Sets(ctx context.Context, vm map[string]any) error { + for k, v := range vm { + if err := m.Set(ctx, k, v); err != nil { + return err + } + } + + return nil +} + func (m *_mem) SetEx(ctx context.Context, key string, value any, duration time.Duration) error { bs, err := handleValue(value) if err != nil { @@ -72,3 +103,9 @@ func (m *_mem) Del(ctx context.Context, keys ...string) error { m.client.Delete(keys...) return nil } + +func (m *_mem) Close() error { + m.client = nil + + return nil +} diff --git a/internal/database/cache/cache_redis.go b/internal/database/cache/cache.redis.go similarity index 56% rename from internal/database/cache/cache_redis.go rename to internal/database/cache/cache.redis.go index 6edfa3d..ccca10f 100644 --- a/internal/database/cache/cache_redis.go +++ b/internal/database/cache/cache.redis.go @@ -3,8 +3,11 @@ package cache import ( "context" "errors" - "github.com/go-redis/redis/v8" "time" + + "github.com/go-redis/redis/v8" + "github.com/samber/lo" + "github.com/spf13/cast" ) type _redis struct { @@ -24,6 +27,28 @@ func (r *_redis) Get(ctx context.Context, key string) ([]byte, error) { return []byte(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 lo.Map( + result, + func(item any, index int) []byte { + return []byte(cast.ToString(item)) + }, + ), nil +} + +func (r *_redis) GetScan(ctx context.Context, key string) Scanner { + return newScanner(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 { @@ -37,6 +62,10 @@ func (r *_redis) GetEx(ctx context.Context, key string, duration time.Duration) return []byte(result), nil } +func (r *_redis) GetExScan(ctx context.Context, key string, duration time.Duration) Scanner { + return newScanner(r.GetEx(ctx, key, duration)) +} + func (r *_redis) Set(ctx context.Context, key string, value any) error { bs, err := handleValue(value) if err != nil { @@ -47,6 +76,20 @@ func (r *_redis) Set(ctx context.Context, key string, value any) error { 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 { @@ -61,3 +104,7 @@ func (r *_redis) SetEx(ctx context.Context, key string, value any, duration time func (r *_redis) Del(ctx context.Context, keys ...string) error { return r.client.Del(ctx, keys...).Err() } + +func (r *_redis) Close() error { + return r.client.Close() +} diff --git a/internal/database/cache/client.go b/internal/database/cache/client.go deleted file mode 100644 index 16c941d..0000000 --- a/internal/database/cache/client.go +++ /dev/null @@ -1,38 +0,0 @@ -package cache - -import ( - "encoding/json" - "ultone/internal/interfaces" -) - -var ( - Client interfaces.Cacher -) - -type encoded_value interface { - MarshalBinary() ([]byte, error) -} - -type decoded_value interface { - UnmarshalBinary(bs []byte) error -} - -func handleValue(value any) ([]byte, error) { - var ( - bs []byte - err error - ) - - switch value.(type) { - case []byte: - return value.([]byte), nil - } - - if imp, ok := value.(encoded_value); ok { - bs, err = imp.MarshalBinary() - } else { - bs, err = json.Marshal(value) - } - - return bs, err -} diff --git a/internal/database/cache/error.go b/internal/database/cache/error.go deleted file mode 100644 index f0798b5..0000000 --- a/internal/database/cache/error.go +++ /dev/null @@ -1,7 +0,0 @@ -package cache - -import "errors" - -var ( - ErrorKeyNotFound = errors.New("key not found") -) diff --git a/internal/database/cache/init.go b/internal/database/cache/new.go similarity index 54% rename from internal/database/cache/init.go rename to internal/database/cache/new.go index ba3d0a5..8dad90d 100644 --- a/internal/database/cache/init.go +++ b/internal/database/cache/new.go @@ -1,30 +1,31 @@ package cache import ( + "context" "fmt" - "gitea.com/taozitaozi/gredis" - "github.com/go-redis/redis/v8" "net/url" "strings" - "ultone/internal/opt" + "ultone/internal/tool" + + "gitea.com/taozitaozi/gredis" + "github.com/go-redis/redis/v8" ) -func Init() error { - +func New(ctx context.Context, uri string) (Cache, error) { var ( - err error + err error + newClient Cache + strs = strings.Split(uri, "::") ) - strs := strings.Split(opt.Cfg.Cache.Uri, "::") - switch strs[0] { case "memory": gc := gredis.NewGredis(1024 * 1024) - Client = &_mem{client: gc} + newClient = &_mem{client: gc} case "lru": - if Client, err = newLRUCache(); err != nil { - return err + if newClient, err = newLRUCache(); err != nil { + return nil, err } case "redis": var ( @@ -33,7 +34,7 @@ func Init() error { ) if len(strs) != 2 { - return fmt.Errorf("cache.Init: invalid cache uri: %s", opt.Cfg.Cache.Uri) + return nil, fmt.Errorf("cache.Init: invalid cache uri: %s", uri) } uri := strs[1] @@ -43,7 +44,7 @@ func Init() error { } if ins, err = url.Parse(uri); err != nil { - return fmt.Errorf("cache.Init: url parse cache uri: %s, err: %s", opt.Cfg.Cache.Uri, err.Error()) + return nil, fmt.Errorf("cache.Init: url parse cache uri: %s, err: %s", uri, err.Error()) } addr := ins.Host @@ -58,13 +59,18 @@ func Init() error { }) if err = rc.Ping(tool.Timeout(5)).Err(); err != nil { - return fmt.Errorf("cache.Init: redis ping err: %s", err.Error()) + return nil, fmt.Errorf("cache.Init: redis ping err: %s", err.Error()) } - Client = &_redis{client: rc} + newClient = &_redis{client: rc} default: - return fmt.Errorf("cache type %s not support", strs[0]) + return nil, fmt.Errorf("cache type %s not support", strs[0]) } - return nil + return newClient, nil +} + +func Init(ctx context.Context, uri string) (err error) { + Client, err = New(ctx, uri) + return } diff --git a/internal/database/cache/scan.go b/internal/database/cache/scan.go new file mode 100644 index 0000000..c65d267 --- /dev/null +++ b/internal/database/cache/scan.go @@ -0,0 +1,20 @@ +package cache + +import "encoding/json" + +type scanner struct { + err error + bs []byte +} + +func (s *scanner) Scan(model any) error { + if s.err != nil { + return s.err + } + + return json.Unmarshal(s.bs, model) +} + +func newScanner(bs []byte, err error) *scanner { + return &scanner{bs: bs, err: err} +} diff --git a/internal/handler/user.go b/internal/handler/user.go index c52d106..ea1a6ef 100644 --- a/internal/handler/user.go +++ b/internal/handler/user.go @@ -9,6 +9,7 @@ import ( "ultone/internal/controller" "ultone/internal/database/cache" "ultone/internal/database/db" + "ultone/internal/log" "ultone/internal/middleware/oplog" "ultone/internal/model" "ultone/internal/opt" @@ -75,26 +76,27 @@ func AuthLogin(c *nf.Ctx) error { if !opt.MultiLogin { var ( - last = fmt.Sprintf("%s:user:last_token:%d", opt.CachePrefix, target.Id) - bs []byte + lastKey = fmt.Sprintf("%s:user:last_token:%d", opt.CachePrefix, target.Id) + lastToken string ) // 获取之前的 token - if bs, err = cache.Client.Get(tool.Timeout(3), last); err == nil { - key := fmt.Sprintf("%s:user:token:%s", opt.CachePrefix, string(bs)) - _ = cache.Client.Del(tool.Timeout(3), key) + if err = cache.Client.GetScan(tool.Timeout(3), lastKey).Scan(&lastToken); err != nil { + if !errors.Is(err, cache.ErrorKeyNotFound) { + log.Warn(c.Context(), "handler.AuthLogin: get last token err = %v", err) + goto HandleMultiEnd + } } // 删掉之前的 token - if len(bs) > 0 { - _ = controller.UserController.RmToken(c.Context(), string(bs)) - } + controller.UserController.RmToken(c.Context(), lastToken) // 将当前的 token 存入 last_token - if err = cache.Client.Set(tool.Timeout(3), last, token); err != nil { + if err = cache.Client.Set(tool.Timeout(3), lastKey, token); err != nil { return resp.Resp500(c, err.Error()) } } +HandleMultiEnd: c.Set("Set-Cookie", fmt.Sprintf("%s=%s; Path=/", opt.CookieName, token)) c.Locals("user", target) @@ -121,12 +123,28 @@ func AuthVerify(c *nf.Ctx) error { } func AuthLogout(c *nf.Ctx) error { + defer func() { + c.Set("Set-Cookie", fmt.Sprintf("%s=; Path=/; Max-Age=0", opt.CookieName)) + }() + op, ok := c.Locals("user").(*model.User) if !ok { return resp.Resp401(c, nil) } - _ = controller.UserController.RmUserCache(c.Context(), op.Id) + token, ok := c.Locals("token").(string) + if !ok { + return resp.Resp401(c, nil) + } + + if !opt.MultiLogin { + _ = controller.UserController.RmUserCache(c.Context(), op.Id) + lastKey := fmt.Sprintf("%s:user:last_token:%d", opt.CachePrefix, op.Id) + cache.Client.Del(c.Context(), lastKey) + } + + // 删掉之前的 token + controller.UserController.RmToken(c.Context(), token) c.Locals(opt.OpLogLocalKey, &oplog.OpLog{ Type: model.OpLogTypeLogout, diff --git a/internal/interfaces/database.go b/internal/interfaces/database.go deleted file mode 100644 index 8c75e07..0000000 --- a/internal/interfaces/database.go +++ /dev/null @@ -1,16 +0,0 @@ -package interfaces - -import ( - "context" - "time" -) - -type Cacher interface { - Get(ctx context.Context, key string) ([]byte, error) - GetEx(ctx context.Context, key string, duration time.Duration) ([]byte, error) - // Set value 会被序列化, 优先使用 MarshalBinary 方法, 没有则执行 json.Marshal - Set(ctx context.Context, key string, value 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 -} diff --git a/internal/tool/tools.go b/internal/tool/tools.go index 521cba8..d6bb10a 100644 --- a/internal/tool/tools.go +++ b/internal/tool/tools.go @@ -1,8 +1,6 @@ package tool -import "cmp" - -func Min[T cmp.Ordered](a, b T) T { +func Min[T ~int | ~uint | ~int8 | ~uint8 | ~int16 | ~uint16 | ~int32 | ~uint32 | ~int64 | ~uint64 | ~float32 | ~float64](a, b T) T { if a <= b { return a } @@ -10,7 +8,7 @@ func Min[T cmp.Ordered](a, b T) T { return b } -func Max[T cmp.Ordered](a, b T) T { +func Max[T ~int | ~uint | ~int8 | ~uint8 | ~int16 | ~uint16 | ~int32 | ~uint32 | ~int64 | ~uint64 | ~float32 | ~float64](a, b T) T { if a >= b { return a }