Browse Source

Merge pull request #654 from flipped-aurora/gva-vue3-2.4.5

Gva vue3 2.4.5
奇淼(piexlmax 3 năm trước cách đây
mục cha
commit
f70b4b1209

+ 2 - 3
server/api/v1/system/sys_user.go

@@ -5,7 +5,6 @@ import (
 	"time"
 
 	"github.com/flipped-aurora/gin-vue-admin/server/global"
-	"github.com/flipped-aurora/gin-vue-admin/server/middleware"
 	"github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
 	"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
 	"github.com/flipped-aurora/gin-vue-admin/server/model/system"
@@ -47,7 +46,7 @@ func (b *BaseApi) Login(c *gin.Context) {
 
 // 登录以后签发jwt
 func (b *BaseApi) tokenNext(c *gin.Context, user system.SysUser) {
-	j := &middleware.JWT{SigningKey: []byte(global.GVA_CONFIG.JWT.SigningKey)} // 唯一签名
+	j := &utils.JWT{SigningKey: []byte(global.GVA_CONFIG.JWT.SigningKey)} // 唯一签名
 	claims := systemReq.CustomClaims{
 		UUID:        user.UUID,
 		ID:          user.ID,
@@ -210,7 +209,7 @@ func (b *BaseApi) SetUserAuthority(c *gin.Context) {
 		response.FailWithMessage(err.Error(), c)
 	} else {
 		claims := utils.GetUserInfo(c)
-		j := &middleware.JWT{SigningKey: []byte(global.GVA_CONFIG.JWT.SigningKey)} // 唯一签名
+		j := &utils.JWT{SigningKey: []byte(global.GVA_CONFIG.JWT.SigningKey)} // 唯一签名
 		claims.AuthorityId = sua.AuthorityId
 		if token, err := j.CreateToken(*claims); err != nil {
 			global.GVA_LOG.Error("修改失败!", zap.Any("err", err))

+ 1 - 0
server/config.yaml

@@ -130,6 +130,7 @@ Timer:
     # interval: 时间间隔, 具体配置详看 time.ParseDuration() 中字符串表示 且不能为负数
     # 2160h = 24 * 30 * 3 -> 三个月
     { tableName: "sys_operation_records" , compareField: "created_at", interval: "2160h" },
+    { tableName: "jwt_blacklists" , compareField: "created_at", interval: "168h" }
     #{ tableName: "log2" , compareField: "created_at", interval: "2160h" }
   ]
 

+ 11 - 0
server/core/viper.go

@@ -5,6 +5,11 @@ import (
 	"fmt"
 	"os"
 	"path/filepath"
+	"time"
+
+	"github.com/flipped-aurora/gin-vue-admin/server/service/system"
+
+	"github.com/songzhibin97/gkit/cache/local_cache"
 
 	"github.com/flipped-aurora/gin-vue-admin/server/global"
 	_ "github.com/flipped-aurora/gin-vue-admin/server/packfile"
@@ -54,5 +59,11 @@ func Viper(path ...string) *viper.Viper {
 		fmt.Println(err)
 	}
 	global.GVA_CONFIG.AutoCode.Root, _ = filepath.Abs("..")
+	global.BlackCache = local_cache.NewCache(
+		local_cache.SetDefaultExpire(time.Duration(global.GVA_CONFIG.JWT.ExpiresTime)))
+	// 从db加载jwt数据
+	if global.GVA_DB != nil {
+		system.LoadAll()
+	}
 	return v
 }

+ 3 - 0
server/global/global.go

@@ -2,6 +2,7 @@ package global
 
 import (
 	"github.com/flipped-aurora/gin-vue-admin/server/utils/timer"
+	"github.com/songzhibin97/gkit/cache/local_cache"
 
 	"golang.org/x/sync/singleflight"
 
@@ -23,4 +24,6 @@ var (
 	GVA_LOG                 *zap.Logger
 	GVA_Timer               timer.Timer = timer.NewTimerTask()
 	GVA_Concurrency_Control             = &singleflight.Group{}
+
+	BlackCache local_cache.Cache
 )

+ 4 - 1
server/go.mod

@@ -9,6 +9,7 @@ require (
 	github.com/casbin/gorm-adapter/v3 v3.0.2
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 	github.com/flipped-aurora/gva-plugins v0.0.0-20210828060501-fc8b729b9a4a
+	github.com/flipped-aurora/ws v1.0.2
 	github.com/fsnotify/fsnotify v1.4.9
 	github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6
 	github.com/gin-gonic/gin v1.6.3
@@ -22,14 +23,16 @@ require (
 	github.com/robfig/cron/v3 v3.0.1
 	github.com/satori/go.uuid v1.2.0
 	github.com/shirou/gopsutil v3.21.1+incompatible
+	github.com/songzhibin97/gkit v1.1.1
 	github.com/spf13/viper v1.7.0
 	github.com/swaggo/gin-swagger v1.3.0
 	github.com/swaggo/swag v1.7.0
 	github.com/tencentyun/cos-go-sdk-v5 v0.7.19
 	github.com/unrolled/secure v1.0.7
 	github.com/xuri/excelize/v2 v2.4.1
-	go.uber.org/zap v1.10.0
+	go.uber.org/zap v1.16.0
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 	gorm.io/driver/mysql v1.0.1
 	gorm.io/gorm v1.20.7
+	nhooyr.io/websocket v1.8.6
 )

+ 2 - 2
server/initialize/timer.go

@@ -10,7 +10,7 @@ import (
 
 func Timer() {
 	if global.GVA_CONFIG.Timer.Start {
-		for _, detail := range global.GVA_CONFIG.Timer.Detail {
+		for i := range global.GVA_CONFIG.Timer.Detail {
 			go func(detail config.Detail) {
 				global.GVA_Timer.AddTaskByFunc("ClearDB", global.GVA_CONFIG.Timer.Spec, func() {
 					err := utils.ClearTable(global.GVA_DB, detail.TableName, detail.CompareField, detail.Interval)
@@ -18,7 +18,7 @@ func Timer() {
 						fmt.Println("timer error:", err)
 					}
 				})
-			}(detail)
+			}(global.GVA_CONFIG.Timer.Detail[i])
 		}
 	}
 }

+ 9 - 91
server/middleware/jwt.go

@@ -1,17 +1,15 @@
 package middleware
 
 import (
-	"errors"
+	"github.com/flipped-aurora/gin-vue-admin/server/utils"
 	"strconv"
 	"time"
 
 	"github.com/flipped-aurora/gin-vue-admin/server/global"
 	"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
 	"github.com/flipped-aurora/gin-vue-admin/server/model/system"
-	"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
 	"github.com/flipped-aurora/gin-vue-admin/server/service"
 
-	"github.com/dgrijalva/jwt-go"
 	"github.com/gin-gonic/gin"
 	"go.uber.org/zap"
 )
@@ -32,11 +30,11 @@ func JWTAuth() gin.HandlerFunc {
 			c.Abort()
 			return
 		}
-		j := NewJWT()
+		j := utils.NewJWT()
 		// parseToken 解析token包含的信息
 		claims, err := j.ParseToken(token)
 		if err != nil {
-			if err == TokenExpired {
+			if err == utils.TokenExpired {
 				response.FailWithDetailed(gin.H{"reload": true}, "授权已过期", c)
 				c.Abort()
 				return
@@ -45,11 +43,12 @@ func JWTAuth() gin.HandlerFunc {
 			c.Abort()
 			return
 		}
-		if err, _ = userService.FindUserByUuid(claims.UUID.String()); err != nil {
-			_ = jwtService.JsonInBlacklist(system.JwtBlacklist{Jwt: token})
-			response.FailWithDetailed(gin.H{"reload": true}, err.Error(), c)
-			c.Abort()
-		}
+		// 用户被删除的逻辑 需要优化 此处比较消耗性能 如果需要 请自行打开
+		//if err, _ = userService.FindUserByUuid(claims.UUID.String()); err != nil {
+		//	_ = jwtService.JsonInBlacklist(system.JwtBlacklist{Jwt: token})
+		//	response.FailWithDetailed(gin.H{"reload": true}, err.Error(), c)
+		//	c.Abort()
+		//}
 		if claims.ExpiresAt-time.Now().Unix() < claims.BufferTime {
 			claims.ExpiresAt = time.Now().Unix() + global.GVA_CONFIG.JWT.ExpiresTime
 			newToken, _ := j.CreateTokenByOldToken(token, *claims)
@@ -72,84 +71,3 @@ func JWTAuth() gin.HandlerFunc {
 	}
 }
 
-type JWT struct {
-	SigningKey []byte
-}
-
-var (
-	TokenExpired     = errors.New("Token is expired")
-	TokenNotValidYet = errors.New("Token not active yet")
-	TokenMalformed   = errors.New("That's not even a token")
-	TokenInvalid     = errors.New("Couldn't handle this token:")
-)
-
-func NewJWT() *JWT {
-	return &JWT{
-		[]byte(global.GVA_CONFIG.JWT.SigningKey),
-	}
-}
-
-// 创建一个token
-func (j *JWT) CreateToken(claims request.CustomClaims) (string, error) {
-	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
-	return token.SignedString(j.SigningKey)
-}
-
-// CreateTokenByOldToken 旧token 换新token 使用归并回源避免并发问题
-func (j *JWT) CreateTokenByOldToken(oldToken string, claims request.CustomClaims) (string, error) {
-	v, err, _ := global.GVA_Concurrency_Control.Do("JWT:"+oldToken, func() (interface{}, error) {
-		return j.CreateToken(claims)
-	})
-	return v.(string), err
-}
-
-// 解析 token
-func (j *JWT) ParseToken(tokenString string) (*request.CustomClaims, error) {
-	token, err := jwt.ParseWithClaims(tokenString, &request.CustomClaims{}, func(token *jwt.Token) (i interface{}, e error) {
-		return j.SigningKey, nil
-	})
-	if err != nil {
-		if ve, ok := err.(*jwt.ValidationError); ok {
-			if ve.Errors&jwt.ValidationErrorMalformed != 0 {
-				return nil, TokenMalformed
-			} else if ve.Errors&jwt.ValidationErrorExpired != 0 {
-				// Token is expired
-				return nil, TokenExpired
-			} else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 {
-				return nil, TokenNotValidYet
-			} else {
-				return nil, TokenInvalid
-			}
-		}
-	}
-	if token != nil {
-		if claims, ok := token.Claims.(*request.CustomClaims); ok && token.Valid {
-			return claims, nil
-		}
-		return nil, TokenInvalid
-
-	} else {
-		return nil, TokenInvalid
-
-	}
-
-}
-
-// 更新token
-//func (j *JWT) RefreshToken(tokenString string) (string, error) {
-//	jwt.TimeFunc = func() time.Time {
-//		return time.Unix(0, 0)
-//	}
-//	token, err := jwt.ParseWithClaims(tokenString, &request.CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
-//		return j.SigningKey, nil
-//	})
-//	if err != nil {
-//		return "", err
-//	}
-//	if claims, ok := token.Claims.(*request.CustomClaims); ok && token.Valid {
-//		jwt.TimeFunc = time.Now
-//		claims.StandardClaims.ExpiresAt = time.Now().Unix() + 60*60*24*7
-//		return j.CreateToken(*claims)
-//	}
-//	return "", TokenInvalid
-//}

+ 52 - 0
server/plugin/ws/utils/utils.go

@@ -0,0 +1,52 @@
+package utils
+
+import (
+	"github.com/flipped-aurora/gin-vue-admin/server/global"
+	systemReq "github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
+	"github.com/gin-gonic/gin"
+	uuid "github.com/satori/go.uuid"
+)
+
+// 从Gin的Context中获取从jwt解析出来的用户ID
+func GetUserID(c *gin.Context) uint {
+	if claims, exists := c.Get("claims"); !exists {
+		global.GVA_LOG.Error("从Gin的Context中获取从jwt解析出来的用户ID失败, 请检查路由是否使用jwt中间件!")
+		return 0
+	} else {
+		waitUse := claims.(*systemReq.CustomClaims)
+		return waitUse.ID
+	}
+}
+
+// 从Gin的Context中获取从jwt解析出来的用户UUID
+func GetUserUuid(c *gin.Context) uuid.UUID {
+	if claims, exists := c.Get("claims"); !exists {
+		global.GVA_LOG.Error("从Gin的Context中获取从jwt解析出来的用户UUID失败, 请检查路由是否使用jwt中间件!")
+		return uuid.UUID{}
+	} else {
+		waitUse := claims.(*systemReq.CustomClaims)
+		return waitUse.UUID
+	}
+}
+
+// 从Gin的Context中获取从jwt解析出来的用户角色id
+func GetUserAuthorityId(c *gin.Context) string {
+	if claims, exists := c.Get("claims"); !exists {
+		global.GVA_LOG.Error("从Gin的Context中获取从jwt解析出来的用户UUID失败, 请检查路由是否使用jwt中间件!")
+		return ""
+	} else {
+		waitUse := claims.(*systemReq.CustomClaims)
+		return waitUse.AuthorityId
+	}
+}
+
+// 从Gin的Context中获取从jwt解析出来的用户角色id
+func GetUserInfo(c *gin.Context) *systemReq.CustomClaims {
+	if claims, exists := c.Get("claims"); !exists {
+		global.GVA_LOG.Error("从Gin的Context中获取从jwt解析出来的用户UUID失败, 请检查路由是否使用jwt中间件!")
+		return nil
+	} else {
+		waitUse := claims.(*systemReq.CustomClaims)
+		return waitUse
+	}
+}

+ 83 - 0
server/plugin/ws/ws.go

@@ -0,0 +1,83 @@
+package ws
+
+import (
+	"github.com/flipped-aurora/ws/core/biz"
+	"github.com/flipped-aurora/ws/core/data"
+	"github.com/gin-gonic/gin"
+	"go.uber.org/zap"
+	"nhooyr.io/websocket"
+)
+
+type wsPlugin struct {
+	logger               *zap.Logger                       // 日志输出对象
+	manageBuf            int64                             // buffer
+	registeredMsgHandler map[int32]func(biz.IMessage) bool // 消息处理
+	checkMap             map[string]biz.CheckFunc          // 用户校验
+
+	admin     biz.IManage
+	adminCase *biz.AdminCase
+}
+
+func DefaultRegisteredMsgHandler(admin biz.IManage, logger *zap.Logger) map[int32]func(biz.IMessage) bool {
+	return map[int32]func(msg biz.IMessage) bool{
+		1: func(msg biz.IMessage) bool {
+			// w.admin 里面找到注册客户端的方法
+			client, ok := admin.FindClient(msg.GetTo())
+			if !ok {
+				logger.Info("没有找到该用户")
+				return false
+			}
+			return client.SendMes(msg)
+		},
+	}
+}
+
+func DefaultCheckMap() map[string]biz.CheckFunc {
+	return map[string]biz.CheckFunc{
+		"gva_ws": func(c interface{}) (string, bool) {
+			// 先断言是gin.content
+			cc, ok := c.(*gin.Context)
+			if !ok {
+				return "", false
+			}
+			token := cc.Query("jwt")
+			// 可以携带jwt
+			if len(token) == 0 {
+				return "", false
+			}
+			// 解析 jwt...
+
+			return token, true
+		},
+	}
+}
+
+func (w *wsPlugin) Register(g *gin.RouterGroup) {
+	// gva_ws 为身份校验函数
+	g.GET("/ws", w.adminCase.HandlerWS("gva_ws", &websocket.AcceptOptions{
+		InsecureSkipVerify: true,
+	}))
+	g.POST("/sendMsg", w.adminCase.SendMsg("gva_ws"))
+
+}
+
+func (w *wsPlugin) RouterPath() string {
+	return "gva_ws"
+}
+
+func GenerateWs(logger *zap.Logger, manageBuf int64, checkMap map[string]biz.CheckFunc) *wsPlugin {
+	m := data.NewManage(manageBuf)
+	t := data.NewTopic()
+	h := data.NewHandle()
+	admin := data.NewAdmin(m, t, h, logger)
+	for s, checkFunc := range checkMap {
+		admin.AddCheckFunc(s, checkFunc)
+	}
+	registeredMsgHandler := DefaultRegisteredMsgHandler(admin, logger)
+
+	for key, handler := range registeredMsgHandler {
+		admin.RegisteredMsgHandler(key, handler)
+	}
+	return &wsPlugin{logger: logger, manageBuf: manageBuf,
+		registeredMsgHandler: registeredMsgHandler, checkMap: checkMap, admin: admin, adminCase: biz.NewAdmin(admin)}
+}

+ 22 - 6
server/service/system/jwt_black_list.go

@@ -2,13 +2,10 @@ package system
 
 import (
 	"context"
-	"errors"
 	"time"
 
 	"github.com/flipped-aurora/gin-vue-admin/server/global"
 	"github.com/flipped-aurora/gin-vue-admin/server/model/system"
-
-	"gorm.io/gorm"
 )
 
 type JwtService struct {
@@ -22,6 +19,11 @@ type JwtService struct {
 
 func (jwtService *JwtService) JsonInBlacklist(jwtList system.JwtBlacklist) (err error) {
 	err = global.GVA_DB.Create(&jwtList).Error
+	if err != nil {
+		return
+	}
+	global.BlackCache.SetDefault(jwtList.Jwt, struct {
+	}{})
 	return
 }
 
@@ -32,9 +34,11 @@ func (jwtService *JwtService) JsonInBlacklist(jwtList system.JwtBlacklist) (err
 //@return: bool
 
 func (jwtService *JwtService) IsBlacklist(jwt string) bool {
-	err := global.GVA_DB.Where("jwt = ?", jwt).First(&system.JwtBlacklist{}).Error
-	isNotFound := errors.Is(err, gorm.ErrRecordNotFound)
-	return !isNotFound
+	_, ok := global.BlackCache.Get(jwt)
+	return ok
+	//err := global.GVA_DB.Where("jwt = ?", jwt).First(&system.JwtBlacklist{}).Error
+	//isNotFound := errors.Is(err, gorm.ErrRecordNotFound)
+	//return !isNotFound
 }
 
 //@author: [piexlmax](https://github.com/piexlmax)
@@ -60,3 +64,15 @@ func (jwtService *JwtService) SetRedisJWT(jwt string, userName string) (err erro
 	err = global.GVA_REDIS.Set(context.Background(), userName, jwt, timer).Err()
 	return err
 }
+
+func LoadAll() {
+	var data []string
+	err := global.GVA_DB.Find(&system.JwtBlacklist{}).Select("jwt").Find(&data).Error
+	if err != nil {
+		// 从db加载jwt数据
+		for i := range data {
+			global.BlackCache.SetDefault(data[i], struct {
+			}{})
+		}
+	}
+}

+ 71 - 0
server/utils/jwt.go

@@ -0,0 +1,71 @@
+package utils
+
+import (
+	"errors"
+	"github.com/dgrijalva/jwt-go"
+	"github.com/flipped-aurora/gin-vue-admin/server/global"
+	"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
+)
+
+type JWT struct {
+	SigningKey []byte
+}
+
+var (
+	TokenExpired     = errors.New("Token is expired")
+	TokenNotValidYet = errors.New("Token not active yet")
+	TokenMalformed   = errors.New("That's not even a token")
+	TokenInvalid     = errors.New("Couldn't handle this token:")
+)
+
+func NewJWT() *JWT {
+	return &JWT{
+		[]byte(global.GVA_CONFIG.JWT.SigningKey),
+	}
+}
+
+// 创建一个token
+func (j *JWT) CreateToken(claims request.CustomClaims) (string, error) {
+	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+	return token.SignedString(j.SigningKey)
+}
+
+// CreateTokenByOldToken 旧token 换新token 使用归并回源避免并发问题
+func (j *JWT) CreateTokenByOldToken(oldToken string, claims request.CustomClaims) (string, error) {
+	v, err, _ := global.GVA_Concurrency_Control.Do("JWT:"+oldToken, func() (interface{}, error) {
+		return j.CreateToken(claims)
+	})
+	return v.(string), err
+}
+
+// 解析 token
+func (j *JWT) ParseToken(tokenString string) (*request.CustomClaims, error) {
+	token, err := jwt.ParseWithClaims(tokenString, &request.CustomClaims{}, func(token *jwt.Token) (i interface{}, e error) {
+		return j.SigningKey, nil
+	})
+	if err != nil {
+		if ve, ok := err.(*jwt.ValidationError); ok {
+			if ve.Errors&jwt.ValidationErrorMalformed != 0 {
+				return nil, TokenMalformed
+			} else if ve.Errors&jwt.ValidationErrorExpired != 0 {
+				// Token is expired
+				return nil, TokenExpired
+			} else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 {
+				return nil, TokenNotValidYet
+			} else {
+				return nil, TokenInvalid
+			}
+		}
+	}
+	if token != nil {
+		if claims, ok := token.Claims.(*request.CustomClaims); ok && token.Valid {
+			return claims, nil
+		}
+		return nil, TokenInvalid
+
+	} else {
+		return nil, TokenInvalid
+
+	}
+
+}

+ 1 - 1
web/package.json

@@ -1,6 +1,6 @@
 {
   "name": "gin-vue-admin",
-  "version": "0.1.0",
+  "version": "2.3.5",
   "private": true,
   "scripts": {
     "serve": "node openDocument.js && vue-cli-service serve",

+ 2 - 4
web/src/core/gin-vue-admin.js

@@ -3,12 +3,10 @@
 *
 * */
 // 加载网站配置文件夹
-import config from './config'
+import { register } from './global'
 
 export const run = function(app) {
-  app.config.globalProperties.$GIN_VUE_ADMIN = config
-  // app.use(uploader)
-
+  register(app)
   console.log(`
      欢迎使用 Gin-Vue-Admin
      当前版本:V2.4.5 alpha

+ 11 - 0
web/src/core/global.js

@@ -0,0 +1,11 @@
+import config from './config'
+import { emitter } from '@/utils/bus.js'
+
+const closeThisPage = () => {
+  emitter.emit('closeThisPage')
+}
+
+export const register = (app) => {
+  app.config.globalProperties.$GIN_VUE_ADMIN = config
+  app.config.globalProperties.$CloseThisPage = closeThisPage
+}

+ 11 - 7
web/src/view/layout/aside/historyComponent/history.vue

@@ -74,6 +74,9 @@ export default {
       }
     },
     $route(to, now) {
+      if (to.name === 'Login') {
+        return
+      }
       this.historys = this.historys.filter(item => !item.meta.closeTab)
       this.setTab(to)
       sessionStorage.setItem('historys', JSON.stringify(this.historys))
@@ -84,6 +87,14 @@ export default {
     }
   },
   created() {
+    // 全局监听 关闭当前页面函数
+    emitter.on('closeThisPage', () => {
+      this.removeTab(this.name(this.$route))
+    })
+    // 全局监听 关闭所有页面函数
+    emitter.on('closeAllPage', () => {
+      this.closeAll()
+    })
     emitter.on('mobile', isMobile => {
       this.isMobile = isMobile
     })
@@ -109,12 +120,6 @@ export default {
     }
     this.setTab(this.$route)
   },
-  mounted() {
-    // 全局监听 关闭当前页面函数
-    emitter.on('closeThisPage', () => {
-      this.removeTab(this.name(this.$route))
-    })
-  },
   beforeDestroy() {
     emitter.off('collapse')
     emitter.off('mobile')
@@ -252,7 +257,6 @@ export default {
       })
     },
     removeTab(tab) {
-      console.log(tab)
       const index = this.historys.findIndex(
         item => getFmtString(item) === tab
       )

+ 1 - 0
web/src/view/layout/index.vue

@@ -212,6 +212,7 @@ export default {
         authorityId: id
       })
       if (res.code === 0) {
+        emitter.emit('closeAllPage')
         window.location.reload()
       }
     },