赞
踩
秋招进入一家游戏公司做golang后台工程师,最近来实习了。导师给了一个课题,要求先熟悉一下gin-vue-admin框架,大概看了一下框架的流程,感觉应该是和renrenfast差不多的逻辑。网上的一些文档都是说怎么去使用的,我这里记录一下gin-vue-admin的启动流程吧(主要讲一下后台逻辑,我也是前端小菜鸡,什么都不懂只会使用的)
项目使用支持使用docker-compose部署,使用go module管理包,包含两个项目server(后台)、web(前端)。当然了,我们也可以不使用容器化的技术,手动部署,前提是把MySQL、redis、node.js、nginx环境搭建好。
├─server (后端文件夹) │ ├─api (API) │ ├─config (配置包) │ ├─core (內核) │ ├─docs (swagger文档目录) │ ├─global (全局对象) │ ├─initialiaze (初始化) │ ├─middleware (中间件) │ ├─model (结构体层) │ ├─resource (资源) │ ├─router (路由) │ ├─service (服务) │ └─utils (公共功能) └─web (前端文件) ├─public (发布模板) └─src (源码包) ├─api (向后台发送ajax的封装层) ├─assets (静态文件) ├─components(组件) ├─router (前端路由) ├─store (vuex 状态管理仓) ├─style (通用样式文件) ├─utils (前端工具库) └─view (前端页面)
github地址:https://github.com/flipped-aurora/gin-vue-admin
github上面有项目的启动及其使用过程,这里不再赘述
其实我看到这个项目的使用,感觉和renrenfast是一样的逻辑,只不过renrenfast使用的是springboot
,而这里使用的gin
,仅此而已
这里我先讲一下rerenfast的基本逻辑:
renrenfast使用的是springboot+mybatis+vue+shiro
所集成的一个框架,使用前后端分离技术。springboot
其实就是和ssm一样,使用MVC架构把项目逻辑一层一层划分,使用mybatis
做数据持久化处理。在用户登录的时候,后台给生成一个token返回。往后请求后台的时候,shiro
会把相关的请求进行拦截,验证token,确定角色和权限,再考虑是否进行下去还是拒绝。前端动态展示的页面路由由数据库获取,再在前端动态展示,同时会请求所有的角色信息(权限信息)存储在local storage里面,通过角色信息(权限信息)来判断用户的角色,动态的展示相应展示的页面…
然后就是今天的主角了:gin-vue-admin
server目录下面有一个main.go
文件
注释里面写的很清楚了:
viper
是用来加载配置到全局配置的,我们可以通过global来使用全局的信息,例如mysql、redis等等的西悉尼zap
是用来进行日志记录的,和springboot的log差不多吧,反正就是写日志的gorm
的话感觉和mybatis不太一样,反而和hibernate
差不多,支持自动建表,通过对象来查询core.RunWindowsServer()
package main import ( "gin-vue-admin/core" "gin-vue-admin/global" "gin-vue-admin/initialize" ) // @title Swagger Example API // @version 0.0.1 // @description This is a sample Server pets // @securityDefinitions.apikey ApiKeyAuth // @in header // @name x-token // @BasePath / func main() { global.GVA_VP = core.Viper() // 初始化Viper global.GVA_LOG = core.Zap() // 初始化zap日志库 global.GVA_DB = initialize.Gorm() // gorm连接数据库 if global.GVA_DB != nil { initialize.MysqlTables(global.GVA_DB) // 初始化表 // 程序结束前关闭数据库链接 db, _ := global.GVA_DB.DB() defer db.Close() } core.RunWindowsServer() }
这里主要就是初始化了redis,路由,配置server信息,然后就是启动服务,下面在看一下Router := initialize.Routers()
是怎么初始化路由的
//core.RunWindowsServer() func RunWindowsServer() { if global.GVA_CONFIG.System.UseMultipoint { // 初始化redis服务 initialize.Redis() } Router := initialize.Routers() Router.Static("/form-generator", "./resource/page") address := fmt.Sprintf(":%d", global.GVA_CONFIG.System.Addr) s := initServer(address, Router) // 保证文本顺序输出 // In order to ensure that the text order output can be deleted time.Sleep(10 * time.Microsecond) global.GVA_LOG.Info("server run success on ", zap.String("address", address)) fmt.Printf(` 欢迎使用 Gin-Vue-Admin 当前版本:V2.4.0 加群方式:微信号:shouzi_1994 QQ群:622360840 默认自动化文档地址:http://127.0.0.1%s/swagger/index.html 默认前端文件运行地址:http://127.0.0.1:8080 如果项目让您获得了收益,希望您能请团队喝杯可乐:https://www.gin-vue-admin.com/docs/coffee `, address) global.GVA_LOG.Error(s.ListenAndServe().Error()) }
这里就是和我们gin框架一样了,使用gin.Default()
来使用默认的路由,然后就是添加静态资源(用于swagger文档),这里可以看到有global.GVA_LOG.Info("register swagger handler")
这里就是使用了zap来进行日志的输出,使用PrivateGroup := Router.Group("")
来进行路由分组,使用PrivateGroup.Use(middleware.JWTAuth()).Use(middleware.CasbinHandler())
来做类似shiro的鉴权操作,然后就是路由的注册了
// Router := initialize.Routers() func Routers() *gin.Engine { var Router = gin.Default() Router.StaticFS(global.GVA_CONFIG.Local.Path, http.Dir(global.GVA_CONFIG.Local.Path)) // 为用户头像和文件提供静态地址 // Router.Use(middleware.LoadTls()) // 打开就能玩https了 global.GVA_LOG.Info("use middleware logger") // 跨域 //Router.Use(middleware.Cors()) // 如需跨域可以打开 global.GVA_LOG.Info("use middleware cors") Router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) global.GVA_LOG.Info("register swagger handler") // 方便统一添加路由组前缀 多服务器上线使用 PublicGroup := Router.Group("") { router.InitBaseRouter(PublicGroup) // 注册基础功能路由 不做鉴权 router.InitInitRouter(PublicGroup) // 自动初始化相关 } PrivateGroup := Router.Group("") PrivateGroup.Use(middleware.JWTAuth()).Use(middleware.CasbinHandler()) { router.InitApiRouter(PrivateGroup) // 注册功能api路由 router.InitJwtRouter(PrivateGroup) // jwt相关路由 router.InitUserRouter(PrivateGroup) // 注册用户路由 router.InitMenuRouter(PrivateGroup) // 注册menu路由 router.InitEmailRouter(PrivateGroup) // 邮件相关路由 router.InitSystemRouter(PrivateGroup) // system相关路由 router.InitCasbinRouter(PrivateGroup) // 权限相关路由 router.InitCustomerRouter(PrivateGroup) // 客户路由 router.InitAutoCodeRouter(PrivateGroup) // 创建自动化代码 router.InitAuthorityRouter(PrivateGroup) // 注册角色路由 router.InitSimpleUploaderRouter(PrivateGroup) // 断点续传(插件版) router.InitSysDictionaryRouter(PrivateGroup) // 字典管理 router.InitSysOperationRecordRouter(PrivateGroup) // 操作记录 router.InitSysDictionaryDetailRouter(PrivateGroup) // 字典详情管理 router.InitFileUploadAndDownloadRouter(PrivateGroup) // 文件上传下载功能路由 router.InitExcelRouter(PrivateGroup) // 表格导入导出 // Code generated by gin-vue-admin Begin; DO NOT EDIT. // Code generated by gin-vue-admin End; DO NOT EDIT. } global.GVA_LOG.Info("router register success") return Router }
我们再来看一下鉴权的逻辑,是使用PrivateGroup.Use(middleware.JWTAuth()).Use(middleware.CasbinHandler())
来做到的
我们可以看到,也是和renrenfast逻辑一样的,先拦截,再判断
// middleware.JWTAuth() func JWTAuth() gin.HandlerFunc { return func(c *gin.Context) { // 我们这里jwt鉴权取头部信息 x-token 登录时回返回token信息 这里前端需要把token存储到cookie或者本地localStorage中 不过需要跟后端协商过期时间 可以约定刷新令牌或者重新登录 token := c.Request.Header.Get("x-token") if token == "" { response.FailWithDetailed(gin.H{"reload": true}, "未登录或非法访问", c) c.Abort() return } if service.IsBlacklist(token) { response.FailWithDetailed(gin.H{"reload": true}, "您的帐户异地登陆或令牌失效", c) c.Abort() return } j := NewJWT() // parseToken 解析token包含的信息 claims, err := j.ParseToken(token) if err != nil { if err == TokenExpired { response.FailWithDetailed(gin.H{"reload": true}, "授权已过期", c) c.Abort() return } response.FailWithDetailed(gin.H{"reload": true}, err.Error(), c) c.Abort() return } if err, _ = service.FindUserByUuid(claims.UUID.String()); err != nil { _ = service.JsonInBlacklist(model.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.CreateToken(*claims) newClaims, _ := j.ParseToken(newToken) c.Header("new-token", newToken) c.Header("new-expires-at", strconv.FormatInt(newClaims.ExpiresAt, 10)) if global.GVA_CONFIG.System.UseMultipoint { err, RedisJwtToken := service.GetRedisJWT(newClaims.Username) if err != nil { global.GVA_LOG.Error("get redis jwt failed", zap.Any("err", err)) } else { // 当之前的取成功时才进行拉黑操作 _ = service.JsonInBlacklist(model.JwtBlacklist{Jwt: RedisJwtToken}) } // 无论如何都要记录当前的活跃状态 _ = service.SetRedisJWT(newToken, newClaims.Username) } } c.Set("claims", claims) c.Next() } } // middleware.CasbinHandler() // 拦截器 func CasbinHandler() gin.HandlerFunc { return func(c *gin.Context) { claims, _ := c.Get("claims") waitUse := claims.(*request.CustomClaims) // 获取请求的URI obj := c.Request.URL.RequestURI() // 获取请求方法 act := c.Request.Method // 获取用户的角色 sub := waitUse.AuthorityId e := service.Casbin() // 判断策略中是否存在 success, _ := e.Enforce(sub, obj, act) if global.GVA_CONFIG.System.Env == "develop" || success { c.Next() } else { response.FailWithDetailed(gin.H{}, "权限不足", c) c.Abort() return } } }
最后,前端页面的展示应该是通过/server/router/sys_api.go
里面进行注册并且获取展示
func InitApiRouter(Router *gin.RouterGroup) {
ApiRouter := Router.Group("api").Use(middleware.OperationRecord())
{
ApiRouter.POST("createApi", v1.CreateApi) // 创建Api
ApiRouter.POST("deleteApi", v1.DeleteApi) // 删除Api
ApiRouter.POST("getApiList", v1.GetApiList) // 获取Api列表
ApiRouter.POST("getApiById", v1.GetApiById) // 获取单条Api消息
ApiRouter.POST("updateApi", v1.UpdateApi) // 更新api
ApiRouter.POST("getAllApis", v1.GetAllApis) // 获取所有api
ApiRouter.DELETE("deleteApisByIds", v1.DeleteApisByIds) // 删除选中api
}
}
基本的业务逻辑应该就是这样的,然后还有一些分页的框架,一些细节我没认真去看。如果有错误的话,欢迎大家指正,交流学习。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。