feat: 支持redis缓存权限等信息

This commit is contained in:
meilin.huang
2022-12-26 20:53:07 +08:00
parent 4fec38724d
commit 4a26bb3ba5
11 changed files with 200 additions and 86 deletions

View File

@@ -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
# file:
# path: ./
# name: mayfly-go.log

View File

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

34
server/pkg/cache/str_cache.go vendored Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -18,6 +18,8 @@ func RunWebServer() {
printBanner()
// 初始化并赋值数据库全局变量
initDb()
// 有配置redis信息则初始化redis。多台机器部署需要使用redis存储验证码、权限、公私钥等
initRedis()
// 运行web服务
runWebServer()
}

View File

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