mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-02 15:30:25 +08:00
feat: 支持redis缓存权限等信息
This commit is contained in:
@@ -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
|
||||
@@ -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
34
server/pkg/cache/str_cache.go
vendored
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -18,6 +18,8 @@ func RunWebServer() {
|
||||
printBanner()
|
||||
// 初始化并赋值数据库全局变量
|
||||
initDb()
|
||||
// 有配置redis信息,则初始化redis。多台机器部署需要使用redis存储验证码、权限、公私钥等
|
||||
initRedis()
|
||||
// 运行web服务
|
||||
runWebServer()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user