From 318db649b214951fd594b7b15f95f887b216051a Mon Sep 17 00:00:00 2001 From: yxh Date: Wed, 16 Mar 2022 17:11:43 +0800 Subject: [PATCH] =?UTF-8?q?=E8=8E=B7=E5=8F=96=E7=99=BB=E5=BD=95=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E4=BF=A1=E6=81=AF=EF=BC=8C=E5=B7=A5=E5=85=B7=E5=8C=85?= =?UTF-8?q?=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/v1/system/user.go | 12 +++ go.mod | 6 +- go.sum | 8 +- internal/app/common/service/token.go | 1 + internal/app/system/consts/context.go | 13 +++ internal/app/system/controller/base.go | 24 ++++- internal/app/system/controller/menu.go | 8 ++ internal/app/system/controller/user.go | 16 +++ internal/app/system/model/context.go | 16 +++ internal/app/system/model/sys_auth_rule.go | 20 ++-- internal/app/system/service/context.go | 68 +++++++++++++ internal/app/system/service/sys_user.go | 28 ++---- internal/cmd/cmd.go | 1 - internal/router/router.go | 16 +-- library/libResponse/response.go | 107 +++++++++++++++++++++ manifest/config/config.yaml | 2 + 16 files changed, 298 insertions(+), 48 deletions(-) create mode 100644 internal/app/system/consts/context.go create mode 100644 internal/app/system/controller/menu.go create mode 100644 internal/app/system/model/context.go create mode 100644 internal/app/system/service/context.go create mode 100644 library/libResponse/response.go diff --git a/api/v1/system/user.go b/api/v1/system/user.go index 7d48c6a..a545e73 100644 --- a/api/v1/system/user.go +++ b/api/v1/system/user.go @@ -12,6 +12,7 @@ type UserLoginReq struct { VerifyCode string `p:"verifyCode" v:"required#验证码不能为空"` VerifyKey string `p:"verifyKey"` } + type UserLoginRes struct { g.Meta `mime:"application/json" example:""` UserInfo *model.LoginUserRes `json:"userInfo"` @@ -19,3 +20,14 @@ type UserLoginRes struct { MenuList []*model.UserMenus `json:"menuList"` Permissions []string `json:"permissions"` } + +type UserMenusReq struct { + g.Meta `path:"/getUserMenus" tags:"getUserMenus" method:"get" summary:"获取用户菜单"` + Authorization string `p:"Authorization" in:"header" dc:"Bearer {{token}}"` +} + +type UserMenusRes struct { + g.Meta `mime:"application/json" example:""` + MenuList []*model.UserMenus `json:"menuList"` + Permissions []string `json:"permissions"` +} diff --git a/go.mod b/go.mod index b533b11..23c0671 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,9 @@ go 1.15 require ( github.com/casbin/casbin/v2 v2.42.0 - github.com/gogf/gf/v2 v2.0.3 + github.com/gogf/gf/v2 v2.0.4 github.com/mojocn/base64Captcha v1.3.5 github.com/mssola/user_agent v0.5.3 github.com/tiger1103/gfast-cache v0.0.6 - github.com/tiger1103/gfast-token v0.0.4 + github.com/tiger1103/gfast-token v0.0.7 ) - -replace github.com/gogf/gf/v2 v2.0.3 => github.com/tiger1103/gf/v2 v2.0.4 diff --git a/go.sum b/go.sum index 92b0401..7860ee0 100644 --- a/go.sum +++ b/go.sum @@ -26,6 +26,8 @@ github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gogf/gf/v2 v2.0.0-rc.0.20220117131058-9345eb5e946f/go.mod h1:apktt6TleWtCIwpz63vBqUnw8MX8gWKoZyxgDpXFtgM= github.com/gogf/gf/v2 v2.0.0-rc3/go.mod h1:apktt6TleWtCIwpz63vBqUnw8MX8gWKoZyxgDpXFtgM= +github.com/gogf/gf/v2 v2.0.4 h1:m5F/f2lX+etXhI6rAQCR6szQoHE1ulAYlIvH0UXa2wM= +github.com/gogf/gf/v2 v2.0.4/go.mod h1:apktt6TleWtCIwpz63vBqUnw8MX8gWKoZyxgDpXFtgM= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= @@ -83,12 +85,10 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/tiger1103/gf/v2 v2.0.4 h1:RQmqPGIhRi6Y3ZLvMz/0OVZHiKXFuf+Ly3d1bEPDaTg= -github.com/tiger1103/gf/v2 v2.0.4/go.mod h1:apktt6TleWtCIwpz63vBqUnw8MX8gWKoZyxgDpXFtgM= github.com/tiger1103/gfast-cache v0.0.6 h1:H40Txv6co5bjaRlFQ/ixcMfpgsihhPpNftvCHm7hLpI= github.com/tiger1103/gfast-cache v0.0.6/go.mod h1:s6cRWyr87wz6IJNGKRV6Ahq9hcuVz8h2PAtGrO66JO8= -github.com/tiger1103/gfast-token v0.0.4 h1:h59pbFd/VyORNunsDdzIdqJkZIHYHsYCzdmPbcqGkZs= -github.com/tiger1103/gfast-token v0.0.4/go.mod h1:RnVRqmWD3h4qfTU1vJNXNhQjh2L5ht1vxLnjwShwGuY= +github.com/tiger1103/gfast-token v0.0.7 h1:LJxa4wvtorig/aiXcrqbO9BpJwuGVNAgp1fQVcR0Des= +github.com/tiger1103/gfast-token v0.0.7/go.mod h1:RnVRqmWD3h4qfTU1vJNXNhQjh2L5ht1vxLnjwShwGuY= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opentelemetry.io/otel v1.0.0 h1:qTTn6x71GVBvoafHK/yaRUmFzI4LcONZD0/kXxl5PHI= diff --git a/internal/app/common/service/token.go b/internal/app/common/service/token.go index 2f1b07b..f0161ed 100644 --- a/internal/app/common/service/token.go +++ b/internal/app/common/service/token.go @@ -19,6 +19,7 @@ type IGfToken interface { GenerateToken(ctx context.Context, key string, data interface{}) (keys string, err error) Middleware(group *ghttp.RouterGroup) error ParseToken(r *ghttp.Request) (*gftoken.CustomClaims, error) + IsLogin(r *ghttp.Request) (b bool, failed *gftoken.AuthFailed) } type gfTokenImpl struct { diff --git a/internal/app/system/consts/context.go b/internal/app/system/consts/context.go new file mode 100644 index 0000000..827c13e --- /dev/null +++ b/internal/app/system/consts/context.go @@ -0,0 +1,13 @@ +/* +* @desc:context 相关常量 +* @company:云南奇讯科技有限公司 +* @Author: yixiaohu +* @Date: 2022/3/16 14:52 + */ + +package consts + +const ( + // CtxKey 上下文变量存储键名,前后端系统共享 + CtxKey = "GFastContext" +) diff --git a/internal/app/system/controller/base.go b/internal/app/system/controller/base.go index bbc80f0..9d4ecc0 100644 --- a/internal/app/system/controller/base.go +++ b/internal/app/system/controller/base.go @@ -8,9 +8,13 @@ package controller import ( - "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/util/gconv" + "github.com/tiger1103/gfast-token/gftoken" commonController "github.com/tiger1103/gfast/v3/internal/app/common/controller" + "github.com/tiger1103/gfast/v3/internal/app/system/model" + "github.com/tiger1103/gfast/v3/internal/app/system/service" + "github.com/tiger1103/gfast/v3/library/libResponse" ) type baseController struct { @@ -20,5 +24,21 @@ type baseController struct { // Init 自动执行的初始化方法 func (c *baseController) Init(r *ghttp.Request) { c.BaseController.Init(r) - g.Log().Debug(r.GetCtx(), "system.baseController.Init") + ctx := r.GetCtx() + // 验证用户是否登录 + b, failed := service.GfToken(ctx).IsLogin(r) + if !b { + libResponse.JsonExit(r, failed.Code, failed.Message) + } + // 初始化登录用户信息 + data, err := service.GfToken(ctx).ParseToken(r) + if err != nil { + libResponse.JsonExit(r, gftoken.FailedAuthCode, err.Error()) + } + context := new(model.Context) + err = gconv.Struct(data.Data, &context.User) + if err != nil { + libResponse.JsonExit(r, gftoken.FailedAuthCode, err.Error()) + } + service.Context().Init(r, context) } diff --git a/internal/app/system/controller/menu.go b/internal/app/system/controller/menu.go new file mode 100644 index 0000000..11ae099 --- /dev/null +++ b/internal/app/system/controller/menu.go @@ -0,0 +1,8 @@ +/* +* @desc:菜单 +* @company:云南奇讯科技有限公司 +* @Author: yixiaohu +* @Date: 2022/3/16 10:36 + */ + +package controller diff --git a/internal/app/system/controller/user.go b/internal/app/system/controller/user.go index 62ffbfa..38eefec 100644 --- a/internal/app/system/controller/user.go +++ b/internal/app/system/controller/user.go @@ -69,6 +69,7 @@ func (c *UserController) Login(ctx context.Context, req *system.UserLoginReq) (r if g.Cfg().MustGet(ctx, "gfToken.multiLogin").Bool() { key = gconv.String(user.Id) + "-" + gmd5.MustEncryptString(user.UserName) + gmd5.MustEncryptString(user.UserPassword+ip+userAgent) } + user.UserPassword = "" token, err = service.GfToken(ctx).GenerateToken(ctx, key, user) if err != nil { return @@ -86,3 +87,18 @@ func (c *UserController) Login(ctx context.Context, req *system.UserLoginReq) (r } return } + +// GetUserMenus 获取用户菜单及按钮权限 +func (c *UserController) GetUserMenus(ctx context.Context, req *system.UserMenusReq) (res *system.UserMenusRes, err error) { + var ( + permissions []string + menuList []*model.UserMenus + ) + userId := service.Context().GetUserId(ctx) + menuList, permissions, err = service.User().GetAdminRules(ctx, userId) + res = &system.UserMenusRes{ + MenuList: menuList, + Permissions: permissions, + } + return +} diff --git a/internal/app/system/model/context.go b/internal/app/system/model/context.go new file mode 100644 index 0000000..6007206 --- /dev/null +++ b/internal/app/system/model/context.go @@ -0,0 +1,16 @@ +/* +* @desc:context-model +* @company:云南奇讯科技有限公司 +* @Author: yixiaohu +* @Date: 2022/3/16 14:45 + */ + +package model + +type Context struct { + User *ContextUser // User in context. +} + +type ContextUser struct { + *LoginUserRes +} diff --git a/internal/app/system/model/sys_auth_rule.go b/internal/app/system/model/sys_auth_rule.go index d1f231a..29ff137 100644 --- a/internal/app/system/model/sys_auth_rule.go +++ b/internal/app/system/model/sys_auth_rule.go @@ -35,18 +35,20 @@ type UserMenu struct { MenuName string `json:"menuName"` Component string `json:"component"` Path string `json:"path"` - Meta struct { - Icon string `json:"icon"` - Title string `json:"title"` - IsLink string `json:"isLink"` - IsHide bool `json:"isHide"` - IsKeepAlive bool `json:"isKeepAlive"` - IsAffix bool `json:"isAffix"` - IsIframe bool `json:"isIframe"` - } `json:"meta"` + *MenuMeta `json:"meta"` } type UserMenus struct { *UserMenu `json:""` Children []*UserMenus `json:"children"` } + +type MenuMeta struct { + Icon string `json:"icon"` + Title string `json:"title"` + IsLink string `json:"isLink"` + IsHide bool `json:"isHide"` + IsKeepAlive bool `json:"isKeepAlive"` + IsAffix bool `json:"isAffix"` + IsIframe bool `json:"isIframe"` +} diff --git a/internal/app/system/service/context.go b/internal/app/system/service/context.go new file mode 100644 index 0000000..1f91a85 --- /dev/null +++ b/internal/app/system/service/context.go @@ -0,0 +1,68 @@ +/* +* @desc:context-service +* @company:云南奇讯科技有限公司 +* @Author: yixiaohu +* @Date: 2022/3/16 14:46 + */ + +package service + +import ( + "context" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/tiger1103/gfast/v3/internal/app/system/consts" + "github.com/tiger1103/gfast/v3/internal/app/system/model" +) + +type IContext interface { + Init(r *ghttp.Request, customCtx *model.Context) + Get(ctx context.Context) *model.Context + SetUser(ctx context.Context, ctxUser *model.ContextUser) + GetLoginUser(ctx context.Context) *model.ContextUser + GetUserId(ctx context.Context) uint64 +} + +// Context 上下文管理服务 +var contextImpl = contextServiceImpl{} + +type contextServiceImpl struct{} + +func Context() IContext { + return IContext(&contextImpl) +} + +// Init 初始化上下文对象指针到上下文对象中,以便后续的请求流程中可以修改。 +func (s *contextServiceImpl) Init(r *ghttp.Request, customCtx *model.Context) { + r.SetCtxVar(consts.CtxKey, customCtx) +} + +// Get 获得上下文变量,如果没有设置,那么返回nil +func (s *contextServiceImpl) Get(ctx context.Context) *model.Context { + value := ctx.Value(consts.CtxKey) + if value == nil { + return nil + } + if localCtx, ok := value.(*model.Context); ok { + return localCtx + } + return nil +} + +// SetUser 将上下文信息设置到上下文请求中,注意是完整覆盖 +func (s *contextServiceImpl) SetUser(ctx context.Context, ctxUser *model.ContextUser) { + s.Get(ctx).User = ctxUser +} + +// GetLoginUser 获取当前登陆用户信息 +func (s *contextServiceImpl) GetLoginUser(ctx context.Context) *model.ContextUser { + context := s.Get(ctx) + if context == nil { + return nil + } + return context.User +} + +// GetUserId 获取当前登录用户id +func (s *contextServiceImpl) GetUserId(ctx context.Context) uint64 { + return s.GetLoginUser(ctx).Id +} diff --git a/internal/app/system/service/sys_user.go b/internal/app/system/service/sys_user.go index a7950c3..2c7684f 100644 --- a/internal/app/system/service/sys_user.go +++ b/internal/app/system/service/sys_user.go @@ -258,23 +258,7 @@ func (s *userImpl) setMenuData(menu *model.UserMenu, entity *model.SysAuthRuleIn Index: entity.Name, Name: gstr.UcFirst(entity.Path), MenuName: entity.Title, - Meta: struct { - Icon string `json:"icon"` - Title string `json:"title"` - IsLink string `json:"isLink"` - IsHide bool `json:"isHide"` - IsKeepAlive bool `json:"isKeepAlive"` - IsAffix bool `json:"isAffix"` - IsIframe bool `json:"isIframe"` - }(struct { - Icon string - Title string - IsLink string - IsHide bool - IsKeepAlive bool - IsAffix bool - IsIframe bool - }{ + MenuMeta: &model.MenuMeta{ Icon: entity.Icon, Title: entity.Title, IsLink: "", @@ -282,7 +266,7 @@ func (s *userImpl) setMenuData(menu *model.UserMenu, entity *model.SysAuthRuleIn IsKeepAlive: false, IsAffix: false, IsIframe: false, - }), + }, } if entity.MenuType != 0 { menu.Component = entity.Component @@ -292,14 +276,14 @@ func (s *userImpl) setMenuData(menu *model.UserMenu, entity *model.SysAuthRuleIn menu.Path = "/" + entity.Path } if entity.AlwaysShow == 1 { - menu.Meta.IsHide = false + menu.MenuMeta.IsHide = false } else { - menu.Meta.IsHide = true + menu.MenuMeta.IsHide = true } if entity.AlwaysShow == 1 && entity.MenuType == 0 { - menu.Meta.IsHide = true + menu.MenuMeta.IsHide = true } else { - menu.Meta.IsHide = false + menu.MenuMeta.IsHide = false } return menu } diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go index 07595cf..b9af5e7 100644 --- a/internal/cmd/cmd.go +++ b/internal/cmd/cmd.go @@ -18,7 +18,6 @@ var ( Func: func(ctx context.Context, parser *gcmd.Parser) (err error) { s := g.Server() s.Group("/", func(group *ghttp.RouterGroup) { - group.Middleware(ghttp.MiddlewareHandlerResponse) router.BindController(group) }) enhanceOpenAPIDoc(s) diff --git a/internal/router/router.go b/internal/router/router.go index 7d155a2..5560478 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -15,10 +15,14 @@ import ( ) func BindController(group *ghttp.RouterGroup) { - // 绑定后台路由 - systemRouter.BindController(group) - // 绑定测试路由 - demoRouter.BindController(group) - // 绑定公共路由 - commonRouter.BindController(group) + group.Group("/api/v1", func(group *ghttp.RouterGroup) { + group.Middleware(ghttp.MiddlewareHandlerResponse) + // 绑定后台路由 + systemRouter.BindController(group) + // 绑定测试路由 + demoRouter.BindController(group) + // 绑定公共路由 + commonRouter.BindController(group) + }) + } diff --git a/library/libResponse/response.go b/library/libResponse/response.go new file mode 100644 index 0000000..b6d21c3 --- /dev/null +++ b/library/libResponse/response.go @@ -0,0 +1,107 @@ +package libResponse + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gview" + "github.com/gogf/gf/v2/text/gstr" + "github.com/gogf/gf/v2/util/gconv" +) + +const ( + SuccessCode int = 0 + ErrorCode int = -1 +) + +type Response struct { + // 代码 + Code int `json:"code" example:"200"` + // 数据集 + Data interface{} `json:"data"` + // 消息 + Msg string `json:"msg"` +} + +var response = new(Response) + +func JsonExit(r *ghttp.Request, code int, msg string, data ...interface{}) { + response.JsonExit(r, code, msg, data...) +} + +func RJson(r *ghttp.Request, code int, msg string, data ...interface{}) { + response.RJson(r, code, msg, data...) +} + +func SusJson(isExit bool, r *ghttp.Request, msg string, data ...interface{}) { + response.SusJson(isExit, r, msg, data...) +} + +func FailJson(isExit bool, r *ghttp.Request, msg string, data ...interface{}) { + response.FailJson(isExit, r, msg, data...) +} + +func WriteTpl(r *ghttp.Request, tpl string, view *gview.View, params ...gview.Params) error { + return response.WriteTpl(r, tpl, view, params...) +} + +// 返回JSON数据并退出当前HTTP执行函数。 +func (res *Response) JsonExit(r *ghttp.Request, code int, msg string, data ...interface{}) { + res.RJson(r, code, msg, data...) + r.Exit() +} + +// 标准返回结果数据结构封装。 +// 返回固定数据结构的JSON: +// code: 状态码(200:成功,302跳转,和http请求状态码一至); +// msg: 请求结果信息; +// data: 请求结果,根据不同接口返回结果的数据结构不同; +func (res *Response) RJson(r *ghttp.Request, code int, msg string, data ...interface{}) { + responseData := interface{}(nil) + if len(data) > 0 { + responseData = data[0] + } + response = &Response{ + Code: code, + Msg: msg, + Data: responseData, + } + r.Response.WriteJson(response) +} + +//成功返回JSON +func (res *Response) SusJson(isExit bool, r *ghttp.Request, msg string, data ...interface{}) { + if isExit { + res.JsonExit(r, SuccessCode, msg, data...) + } + res.RJson(r, SuccessCode, msg, data...) +} + +//失败返回JSON +func (res *Response) FailJson(isExit bool, r *ghttp.Request, msg string, data ...interface{}) { + if isExit { + res.JsonExit(r, ErrorCode, msg, data...) + } + res.RJson(r, ErrorCode, msg, data...) +} + +// WriteTpl 模板输出 +func (res *Response) WriteTpl(r *ghttp.Request, tpl string, view *gview.View, params ...gview.Params) error { + //绑定模板中需要用到的方法 + view.BindFuncMap(g.Map{ + // 根据长度i来切割字符串 + "subStr": func(str interface{}, i int) (s string) { + s1 := gconv.String(str) + if gstr.LenRune(s1) > i { + s = gstr.SubStrRune(s1, 0, i) + "..." + return s + } + return s1 + }, + }) + r.Response.Write(view.Parse(r.GetCtx(), tpl, params...)) + return nil +} + +func (res *Response) Redirect(r *ghttp.Request, location string, code ...int) { + r.Response.RedirectTo(location, code...) +} diff --git a/manifest/config/config.yaml b/manifest/config/config.yaml index 5fabaae..30a25c0 100644 --- a/manifest/config/config.yaml +++ b/manifest/config/config.yaml @@ -46,6 +46,8 @@ gfToken: multiLogin: true encryptKey: "49c54195e750b04e74a8429b17896586" cacheModel: "redis" + excludePaths: + - "/api/v1/system/login" # Redis 配置示例 redis: