From 4a26bb3ba51c6dc5e1e2f6059a60540e363768f3 Mon Sep 17 00:00:00 2001 From: "meilin.huang" <954537473@qq.com> Date: Mon, 26 Dec 2022 20:53:07 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81redis=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E6=9D=83=E9=99=90=E7=AD=89=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/config.yml | 12 ++- server/go.mod | 16 ++-- server/pkg/cache/str_cache.go | 34 ++++++++ server/pkg/captcha/captcha.go | 43 +++++++++- server/pkg/config/config.go | 2 +- server/pkg/ctx/permission_handler.go | 117 ++++++++++++++++++--------- server/pkg/global/global.go | 6 +- server/pkg/rediscli/rediscli.go | 18 ++--- server/pkg/starter/redis.go | 9 ++- server/pkg/starter/run.go | 2 + server/pkg/utils/crypto_utils.go | 27 ++++--- 11 files changed, 200 insertions(+), 86 deletions(-) create mode 100644 server/pkg/cache/str_cache.go diff --git a/server/config.yml b/server/config.yml index 47758973..a0aaee28 100644 --- a/server/config.yml +++ b/server/config.yml @@ -24,9 +24,15 @@ mysql: db-name: mayfly-go config: charset=utf8&loc=Local&parseTime=true max-idle-conns: 5 +# 若同时部署多台机器,则需要配置redis信息用于缓存权限码、验证码、公私钥等 +# redis: +# host: localhost +# port: 6379 +# passsord: +# db: 0 log: # 日志等级, trace, debug, info, warn, error, fatal level: info - file: - path: ./ - name: mayfly-go.log \ No newline at end of file + # file: + # path: ./ + # name: mayfly-go.log \ No newline at end of file diff --git a/server/go.mod b/server/go.mod index 164d91b4..1381e01f 100644 --- a/server/go.mod +++ b/server/go.mod @@ -3,10 +3,10 @@ module mayfly-go go 1.19 require ( - github.com/gin-gonic/gin v1.8.1 + github.com/gin-gonic/gin v1.8.2 github.com/go-redis/redis/v8 v8.11.5 github.com/go-sql-driver/mysql v1.7.0 - github.com/golang-jwt/jwt/v4 v4.4.2 + github.com/golang-jwt/jwt/v4 v4.4.3 github.com/gorilla/websocket v1.5.0 github.com/lib/pq v1.10.6 github.com/mojocn/base64Captcha v1.3.5 // 验证码 @@ -28,8 +28,8 @@ require ( github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect - github.com/go-playground/validator/v10 v10.10.1 // indirect - github.com/goccy/go-json v0.9.7 // indirect + github.com/go-playground/validator/v10 v10.11.1 // indirect + github.com/goccy/go-json v0.9.11 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/golang/snappy v0.0.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect @@ -38,11 +38,11 @@ require ( github.com/klauspost/compress v1.13.6 // indirect github.com/kr/fs v0.1.0 // indirect github.com/leodido/go-urn v1.2.1 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect - github.com/pelletier/go-toml/v2 v2.0.1 // indirect + github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/ugorji/go/codec v1.2.7 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect @@ -50,11 +50,11 @@ require ( github.com/xdg-go/stringprep v1.0.3 // indirect github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect golang.org/x/image v0.0.0-20220302094943-723b81ca9867 // indirect - golang.org/x/net v0.3.0 // indirect + golang.org/x/net v0.4.0 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sys v0.3.0 // indirect golang.org/x/text v0.5.0 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect - google.golang.org/protobuf v1.28.0 // indirect + google.golang.org/protobuf v1.28.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/server/pkg/cache/str_cache.go b/server/pkg/cache/str_cache.go new file mode 100644 index 00000000..9e70ee14 --- /dev/null +++ b/server/pkg/cache/str_cache.go @@ -0,0 +1,34 @@ +package cache + +import "mayfly-go/pkg/rediscli" + +var strCache map[string]string + +// 如果系统有设置redis信息,则从redis获取,否则本机内存获取 +func GetStr(key string) string { + if rediscli.GetCli() == nil { + checkStrCache() + return strCache[key] + } + res, err := rediscli.Get(key) + if err != nil { + return "" + } + return res +} + +// 如果系统有设置redis信息,则使用redis存,否则存于本机内存 +func SetStr(key, value string) { + if rediscli.GetCli() == nil { + checkStrCache() + strCache[key] = value + return + } + rediscli.Set(key, value, 0) +} + +func checkStrCache() { + if strCache == nil { + strCache = make(map[string]string) + } +} diff --git a/server/pkg/captcha/captcha.go b/server/pkg/captcha/captcha.go index c4c1746b..6ef48250 100644 --- a/server/pkg/captcha/captcha.go +++ b/server/pkg/captcha/captcha.go @@ -2,15 +2,25 @@ package captcha import ( "mayfly-go/pkg/biz" + "mayfly-go/pkg/rediscli" + "time" "github.com/mojocn/base64Captcha" ) -var store = base64Captcha.DefaultMemStore +var store base64Captcha.Store var driver base64Captcha.Driver = base64Captcha.DefaultDriverDigit // 生成验证码 func Generate() (string, string) { + if store == nil { + if rediscli.GetCli() != nil { + store = new(RedisStore) + } else { + store = base64Captcha.DefaultMemStore + } + } + c := base64Captcha.NewCaptcha(driver, store) // 获取 id, b64s, err := c.Generate() @@ -26,3 +36,34 @@ func Verify(id string, val string) bool { // 同时清理掉这个图片 return store.Verify(id, val, true) } + +type RedisStore struct { +} + +const CAPTCHA = "mayfly:captcha:" + +// 实现设置captcha的方法 +func (r RedisStore) Set(id string, value string) error { + //time.Minute*2:有效时间2分钟 + rediscli.Set(CAPTCHA+id, value, time.Minute*2) + return nil +} + +// 实现获取captcha的方法 +func (r RedisStore) Get(id string, clear bool) string { + key := CAPTCHA + id + val, err := rediscli.Get(key) + if err != nil { + return "" + } + if clear { + //clear为true,验证通过,删除这个验证码 + rediscli.Del(key) + } + return val +} + +// 实现验证captcha的方法 +func (r RedisStore) Verify(id, answer string, clear bool) bool { + return r.Get(id, clear) == answer +} diff --git a/server/pkg/config/config.go b/server/pkg/config/config.go index 9937cbc0..12948c24 100644 --- a/server/pkg/config/config.go +++ b/server/pkg/config/config.go @@ -40,8 +40,8 @@ type Config struct { Server *Server `yaml:"server"` Jwt *Jwt `yaml:"jwt"` Aes *Aes `yaml:"aes"` - Redis *Redis `yaml:"redis"` Mysql *Mysql `yaml:"mysql"` + Redis *Redis `yaml:"redis"` Log *Log `yaml:"log"` } diff --git a/server/pkg/ctx/permission_handler.go b/server/pkg/ctx/permission_handler.go index d08cc6cc..015c4097 100644 --- a/server/pkg/ctx/permission_handler.go +++ b/server/pkg/ctx/permission_handler.go @@ -1,10 +1,13 @@ package ctx import ( + "encoding/json" "fmt" "mayfly-go/pkg/biz" "mayfly-go/pkg/cache" "mayfly-go/pkg/config" + "mayfly-go/pkg/rediscli" + "mayfly-go/pkg/utils" "time" ) @@ -22,6 +25,62 @@ func (p *Permission) WithNeedToken(needToken bool) *Permission { return p } +var ( + permissionCodeRegistry PermissionCodeRegistry +) + +func PermissionHandler(rc *ReqCtx) error { + if permissionCodeRegistry == nil { + if rediscli.GetCli() == nil { + permissionCodeRegistry = new(DefaultPermissionCodeRegistry) + } else { + permissionCodeRegistry = new(RedisPermissionCodeRegistry) + } + } + + permission := rc.RequiredPermission + // 如果需要的权限信息不为空,并且不需要token,则不返回错误,继续后续逻辑 + if permission != nil && !permission.NeedToken { + return nil + } + tokenStr := rc.GinCtx.Request.Header.Get("Authorization") + // header不存在则从查询参数token中获取 + if tokenStr == "" { + tokenStr = rc.GinCtx.Query("token") + } + if tokenStr == "" { + return biz.PermissionErr + } + loginAccount, err := ParseToken(tokenStr) + if err != nil || loginAccount == nil { + return biz.PermissionErr + } + // 权限不为nil,并且permission code不为空,则校验是否有权限code + if permission != nil && permission.Code != "" { + if !permissionCodeRegistry.HasCode(loginAccount.Id, permission.Code) { + return biz.PermissionErr + } + } + + rc.LoginAccount = loginAccount + return nil +} + +// 保存用户权限code +func SavePermissionCodes(userId uint64, codes []string) { + permissionCodeRegistry.SaveCodes(userId, codes) +} + +// 删除用户权限code +func DeletePermissionCodes(userId uint64) { + permissionCodeRegistry.Remove(userId) +} + +// 设置权限code注册器 +func SetPermissionCodeRegistery(pcr PermissionCodeRegistry) { + permissionCodeRegistry = pcr +} + type PermissionCodeRegistry interface { // 保存用户权限code SaveCodes(userId uint64, codes []string) @@ -63,51 +122,29 @@ func (r *DefaultPermissionCodeRegistry) Remove(userId uint64) { r.cache.Delete(fmt.Sprintf("%v", userId)) } -// 保存用户权限code -func SavePermissionCodes(userId uint64, codes []string) { - permissionCodeRegistry.SaveCodes(userId, codes) +type RedisPermissionCodeRegistry struct { } -// 删除用户权限code -func DeletePermissionCodes(userId uint64) { - permissionCodeRegistry.Remove(userId) +func (r *RedisPermissionCodeRegistry) SaveCodes(userId uint64, codes []string) { + rediscli.Set(fmt.Sprintf("mayfly:%v:codes", userId), utils.ToString(codes), time.Minute*time.Duration(config.Conf.Jwt.ExpireTime)) } -// 设置权限code注册器 -func SetPermissionCodeRegistery(pcr PermissionCodeRegistry) { - permissionCodeRegistry = pcr -} +func (r *RedisPermissionCodeRegistry) HasCode(userId uint64, code string) bool { + str, err := rediscli.Get(fmt.Sprintf("mayfly:%v:codes", userId)) + if err != nil || str == "" { + return false + } -var ( - permissionCodeRegistry PermissionCodeRegistry = &DefaultPermissionCodeRegistry{} - // permissionError = biz.NewBizErrCode(biz.TokenErrorCode, biz.TokenErrorMsg) -) - -func PermissionHandler(rc *ReqCtx) error { - permission := rc.RequiredPermission - // 如果需要的权限信息不为空,并且不需要token,则不返回错误,继续后续逻辑 - if permission != nil && !permission.NeedToken { - return nil - } - tokenStr := rc.GinCtx.Request.Header.Get("Authorization") - // header不存在则从查询参数token中获取 - if tokenStr == "" { - tokenStr = rc.GinCtx.Query("token") - } - if tokenStr == "" { - return biz.PermissionErr - } - loginAccount, err := ParseToken(tokenStr) - if err != nil || loginAccount == nil { - return biz.PermissionErr - } - // 权限不为nil,并且permission code不为空,则校验是否有权限code - if permission != nil && permission.Code != "" { - if !permissionCodeRegistry.HasCode(loginAccount.Id, permission.Code) { - return biz.PermissionErr + var codes []string + _ = json.Unmarshal([]byte(str), &codes) + for _, v := range codes { + if v == code { + return true } } - - rc.LoginAccount = loginAccount - return nil + return false +} + +func (r *RedisPermissionCodeRegistry) Remove(userId uint64) { + rediscli.Del(fmt.Sprintf("mayfly:%v:codes", userId)) } diff --git a/server/pkg/global/global.go b/server/pkg/global/global.go index e0424469..3eb6cf3e 100644 --- a/server/pkg/global/global.go +++ b/server/pkg/global/global.go @@ -1,13 +1,11 @@ package global import ( - "github.com/go-redis/redis/v8" "github.com/sirupsen/logrus" "gorm.io/gorm" ) var ( - Log *logrus.Logger // 日志 - Db *gorm.DB // gorm - RedisCli *redis.Client // redis + Log *logrus.Logger // 日志 + Db *gorm.DB // gorm ) diff --git a/server/pkg/rediscli/rediscli.go b/server/pkg/rediscli/rediscli.go index e94bb821..0fa48027 100644 --- a/server/pkg/rediscli/rediscli.go +++ b/server/pkg/rediscli/rediscli.go @@ -2,7 +2,6 @@ package rediscli import ( "context" - "fmt" "time" "github.com/go-redis/redis/v8" @@ -19,17 +18,8 @@ func GetCli() *redis.Client { } // get key value -func Get(key string) string { - val, err := cli.Get(context.TODO(), key).Result() - switch { - case err == redis.Nil: - fmt.Println("key does not exist") - case err != nil: - fmt.Println("Get failed", err) - case val == "": - fmt.Println("value is empty") - } - return val +func Get(key string) (string, error) { + return cli.Get(context.TODO(), key).Result() } // set key value @@ -37,6 +27,10 @@ func Set(key string, val string, expiration time.Duration) { cli.Set(context.TODO(), key, val, expiration) } +func Del(key string) { + cli.Del(context.TODO(), key) +} + func HSet(key string, field string, val interface{}) { cli.HSet(context.TODO(), key, field, val) } diff --git a/server/pkg/starter/redis.go b/server/pkg/starter/redis.go index 16f2c490..60721481 100644 --- a/server/pkg/starter/redis.go +++ b/server/pkg/starter/redis.go @@ -5,19 +5,20 @@ import ( "fmt" "mayfly-go/pkg/config" "mayfly-go/pkg/global" + "mayfly-go/pkg/rediscli" "github.com/go-redis/redis/v8" ) -func InitRedis() { - global.RedisCli = ConnRedis() +func initRedis() { + rediscli.SetCli(connRedis()) } -func ConnRedis() *redis.Client { +func connRedis() *redis.Client { // 设置redis客户端 redisConf := config.Conf.Redis if redisConf == nil { - global.Log.Panic("未找到redis配置信息") + // global.Log.Panic("未找到redis配置信息") return nil } global.Log.Infof("连接redis [%s:%d]", redisConf.Host, redisConf.Port) diff --git a/server/pkg/starter/run.go b/server/pkg/starter/run.go index d161de34..7717da23 100644 --- a/server/pkg/starter/run.go +++ b/server/pkg/starter/run.go @@ -18,6 +18,8 @@ func RunWebServer() { printBanner() // 初始化并赋值数据库全局变量 initDb() + // 有配置redis信息,则初始化redis。多台机器部署需要使用redis存储验证码、权限、公私钥等 + initRedis() // 运行web服务 runWebServer() } diff --git a/server/pkg/utils/crypto_utils.go b/server/pkg/utils/crypto_utils.go index f1ab8476..958cd6e0 100644 --- a/server/pkg/utils/crypto_utils.go +++ b/server/pkg/utils/crypto_utils.go @@ -12,6 +12,7 @@ import ( "encoding/hex" "encoding/pem" "errors" + "mayfly-go/pkg/cache" "golang.org/x/crypto/bcrypt" ) @@ -34,9 +35,6 @@ func CheckPwdHash(password, hash string) bool { return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) == nil } -// 系统统一RSA秘钥对 -var RsaPair []string - // 生成RSA私钥和公钥字符串 // bits 证书大小 // @return privateKeyStr publicKeyStr error @@ -130,33 +128,36 @@ func DefaultRsaDecrypt(data string, useBase64 bool) (string, error) { return string(val), nil } +const publicKeyK = "mayfly:public-key" +const privateKeyK = "mayfly:private-key" + // 获取系统的RSA公钥 func GetRsaPublicKey() (string, error) { - if len(RsaPair) == 2 { - return RsaPair[1], nil + publicKey := cache.GetStr(publicKeyK) + if publicKey != "" { + return publicKey, nil } - privateKey, publicKey, err := GenerateRSAKey(1024) if err != nil { return "", err } - RsaPair = append(RsaPair, privateKey) - RsaPair = append(RsaPair, publicKey) + cache.SetStr(publicKeyK, publicKey) + cache.SetStr(privateKeyK, privateKey) return publicKey, nil } // 获取系统私钥 func GetRsaPrivateKey() (string, error) { - if len(RsaPair) == 2 { - return RsaPair[0], nil + privateKey := cache.GetStr(privateKeyK) + if privateKey != "" { + return privateKey, nil } - privateKey, publicKey, err := GenerateRSAKey(1024) if err != nil { return "", err } - RsaPair = append(RsaPair, privateKey) - RsaPair = append(RsaPair, publicKey) + cache.SetStr(publicKeyK, publicKey) + cache.SetStr(privateKeyK, privateKey) return privateKey, nil }