diff --git a/api/v1/system/user.go b/api/v1/system/user.go index a7c6b96..5524850 100644 --- a/api/v1/system/user.go +++ b/api/v1/system/user.go @@ -16,4 +16,5 @@ type UserLoginRes struct { g.Meta `mime:"text/html" example:""` UserInfo *model.LoginUserRes `json:"userInfo"` Token string `json:"token"` + MenuList []*model.UserMenus `json:"menuList"` } diff --git a/go.mod b/go.mod index 654386b..7e122f7 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,10 @@ module github.com/tiger1103/gfast/v3 go 1.15 require ( + github.com/casbin/casbin/v2 v2.42.0 // indirect github.com/gogf/gf/v2 v2.0.3 github.com/mojocn/base64Captcha v1.3.5 github.com/mssola/user_agent v0.5.3 - github.com/tiger1103/gfast-cache v0.0.3 // indirect + github.com/tiger1103/gfast-cache v0.0.6 github.com/tiger1103/gfast-token v0.0.4 ) diff --git a/go.sum b/go.sum index 215e5c1..26f6fd9 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,9 @@ github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw= github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/casbin/casbin/v2 v2.42.0 h1:EA0aE5PZnFSYY6WulzTScOo4YO6xrGAAZkXRLs8p2ME= +github.com/casbin/casbin/v2 v2.42.0/go.mod h1:sEL80qBYTbd+BPeL4iyvwYzFT3qwLaESq5aFKVLbLfA= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E= @@ -21,16 +25,14 @@ github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfC github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 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 h1:FkmLFhgOCZnyr24H/Yj9V1psS7fJ79DtPuSz+l/kwsc= github.com/gogf/gf/v2 v2.0.0-rc3/go.mod h1:apktt6TleWtCIwpz63vBqUnw8MX8gWKoZyxgDpXFtgM= -github.com/gogf/gf/v2 v2.0.2 h1:XB8QavORTCYDV0Ko7m/LmXm3leWjcqyL6Ql2pcRNZmw= -github.com/gogf/gf/v2 v2.0.2/go.mod h1:apktt6TleWtCIwpz63vBqUnw8MX8gWKoZyxgDpXFtgM= github.com/gogf/gf/v2 v2.0.3 h1:T/PSaaE+N/czwC3/fYGv6bvbJZsK6Em2DsNNLbyqQ4w= github.com/gogf/gf/v2 v2.0.3/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= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -82,10 +84,8 @@ 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/gfast-cache v0.0.2 h1:MLQrFooBRV5IvSoHyChB3Wcarhfsxo4oGa95ZB9vLmM= -github.com/tiger1103/gfast-cache v0.0.2/go.mod h1:s6cRWyr87wz6IJNGKRV6Ahq9hcuVz8h2PAtGrO66JO8= -github.com/tiger1103/gfast-cache v0.0.3 h1:8iK1dNib35pDdGherjSoPKWiKV0fyQHi03DDfcltsnA= -github.com/tiger1103/gfast-cache v0.0.3/go.mod h1:s6cRWyr87wz6IJNGKRV6Ahq9hcuVz8h2PAtGrO66JO8= +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/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -104,6 +104,7 @@ golang.org/x/image v0.0.0-20190501045829-6d32002ffd75/go.mod h1:kZ7UVZpmo3dzQBMx golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= @@ -140,6 +141,7 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2 h1:GLw7MR8AfAG2GmGcmVgObFOHXYypgGjnGno25RDwn3Y= golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= diff --git a/internal/app/common/service/casbin.go b/internal/app/common/service/casbin.go new file mode 100644 index 0000000..7200a84 --- /dev/null +++ b/internal/app/common/service/casbin.go @@ -0,0 +1,239 @@ +package service + +import ( + "context" + "github.com/casbin/casbin/v2" + "github.com/casbin/casbin/v2/model" + "github.com/casbin/casbin/v2/persist" + "github.com/gogf/gf/v2/frame/g" + "github.com/tiger1103/gfast/v3/internal/app/common/model/entity" + "github.com/tiger1103/gfast/v3/internal/app/common/service/internal/dao" + "sync" +) + +type cabinImpl struct{} + +type adapterCasbin struct { + Enforcer *casbin.SyncedEnforcer + EnforcerErr error + ctx context.Context +} + +var ( + cb = cabinImpl{} + once sync.Once + ac *adapterCasbin +) + +// CasbinEnforcer 获取adapter单例对象 +func CasbinEnforcer(ctx context.Context) (enforcer *casbin.SyncedEnforcer, err error) { + once.Do(func() { + ac = cb.newAdapter(ctx) + }) + enforcer = ac.Enforcer + err = ac.EnforcerErr + return +} + +//初始化adapter操作 +func (s *cabinImpl) newAdapter(ctx context.Context) (a *adapterCasbin) { + a = new(adapterCasbin) + a.initPolicy(ctx) + a.ctx = ctx + return +} + +func (a *adapterCasbin) initPolicy(ctx context.Context) { + // Because the DB is empty at first, + // so we need to load the policy from the file adapter (.CSV) first. + e, err := casbin.NewSyncedEnforcer(g.Cfg().MustGet(ctx, "casbin.modelFile").String(), + g.Cfg().MustGet(ctx, "casbin.policyFile").String()) + + if err != nil { + a.EnforcerErr = err + return + } + + // This is a trick to save the current policy to the DB. + // We can't call e.SavePolicy() because the adapter in the enforcer is still the file adapter. + // The current policy means the policy in the Casbin enforcer (aka in memory). + //err = a.SavePolicy(e.GetModel()) + //if err != nil { + // return err + //} + //set adapter + e.SetAdapter(a) + // Clear the current policy. + e.ClearPolicy() + a.Enforcer = e + // Load the policy from DB. + err = a.LoadPolicy(e.GetModel()) + if err != nil { + a.EnforcerErr = err + return + } +} + +// SavePolicy saves policy to database. +func (a *adapterCasbin) SavePolicy(model model.Model) (err error) { + err = a.dropTable() + if err != nil { + return + } + err = a.createTable() + if err != nil { + return + } + for ptype, ast := range model["p"] { + for _, rule := range ast.Policy { + line := savePolicyLine(ptype, rule) + _, err := dao.CasbinRule.Ctx(a.ctx).Data(line).Insert() + if err != nil { + return err + } + } + } + + for ptype, ast := range model["g"] { + for _, rule := range ast.Policy { + line := savePolicyLine(ptype, rule) + _, err := dao.CasbinRule.Ctx(a.ctx).Data(line).Insert() + if err != nil { + return err + } + } + } + return +} + +func (a *adapterCasbin) dropTable() (err error) { + return +} + +func (a *adapterCasbin) createTable() (err error) { + return +} + +// LoadPolicy loads policy from database. +func (a *adapterCasbin) LoadPolicy(model model.Model) error { + var lines []*entity.CasbinRule + if err := dao.CasbinRule.Ctx(a.ctx).Scan(&lines); err != nil { + return err + } + for _, line := range lines { + loadPolicyLine(line, model) + } + return nil +} + +// AddPolicy adds a policy rule to the storage. +func (a *adapterCasbin) AddPolicy(sec string, ptype string, rule []string) error { + line := savePolicyLine(ptype, rule) + _, err := dao.CasbinRule.Ctx(a.ctx).Data(line).Insert() + return err +} + +// RemovePolicy removes a policy rule from the storage. +func (a *adapterCasbin) RemovePolicy(sec string, ptype string, rule []string) error { + line := savePolicyLine(ptype, rule) + err := rawDelete(a, line) + return err +} + +// RemoveFilteredPolicy removes policy rules that match the filter from the storage. +func (a *adapterCasbin) RemoveFilteredPolicy(sec string, ptype string, + fieldIndex int, fieldValues ...string) error { + line := &entity.CasbinRule{} + line.Ptype = ptype + if fieldIndex <= 0 && 0 < fieldIndex+len(fieldValues) { + line.V0 = fieldValues[0-fieldIndex] + } + if fieldIndex <= 1 && 1 < fieldIndex+len(fieldValues) { + line.V1 = fieldValues[1-fieldIndex] + } + if fieldIndex <= 2 && 2 < fieldIndex+len(fieldValues) { + line.V2 = fieldValues[2-fieldIndex] + } + if fieldIndex <= 3 && 3 < fieldIndex+len(fieldValues) { + line.V3 = fieldValues[3-fieldIndex] + } + if fieldIndex <= 4 && 4 < fieldIndex+len(fieldValues) { + line.V4 = fieldValues[4-fieldIndex] + } + if fieldIndex <= 5 && 5 < fieldIndex+len(fieldValues) { + line.V5 = fieldValues[5-fieldIndex] + } + err := rawDelete(a, line) + return err +} + +func loadPolicyLine(line *entity.CasbinRule, model model.Model) { + lineText := line.Ptype + if line.V0 != "" { + lineText += ", " + line.V0 + } + if line.V1 != "" { + lineText += ", " + line.V1 + } + if line.V2 != "" { + lineText += ", " + line.V2 + } + if line.V3 != "" { + lineText += ", " + line.V3 + } + if line.V4 != "" { + lineText += ", " + line.V4 + } + if line.V5 != "" { + lineText += ", " + line.V5 + } + persist.LoadPolicyLine(lineText, model) +} + +func savePolicyLine(ptype string, rule []string) *entity.CasbinRule { + line := &entity.CasbinRule{} + line.Ptype = ptype + if len(rule) > 0 { + line.V0 = rule[0] + } + if len(rule) > 1 { + line.V1 = rule[1] + } + if len(rule) > 2 { + line.V2 = rule[2] + } + if len(rule) > 3 { + line.V3 = rule[3] + } + if len(rule) > 4 { + line.V4 = rule[4] + } + if len(rule) > 5 { + line.V5 = rule[5] + } + return line +} + +func rawDelete(a *adapterCasbin, line *entity.CasbinRule) error { + db := dao.CasbinRule.Ctx(a.ctx).Where("ptype = ?", line.Ptype) + if line.V0 != "" { + db = db.Where("v0 = ?", line.V0) + } + if line.V1 != "" { + db = db.Where("v1 = ?", line.V1) + } + if line.V2 != "" { + db = db.Where("v2 = ?", line.V2) + } + if line.V3 != "" { + db = db.Where("v3 = ?", line.V3) + } + if line.V4 != "" { + db = db.Where("v4 = ?", line.V4) + } + if line.V5 != "" { + db = db.Where("v5 = ?", line.V5) + } + _, err := db.Delete() + return err +} diff --git a/internal/app/system/consts/cache.go b/internal/app/system/consts/cache.go index 6527b8e..d95845d 100644 --- a/internal/app/system/consts/cache.go +++ b/internal/app/system/consts/cache.go @@ -8,25 +8,25 @@ package consts const ( - // SysAuthMenu 缓存菜单KEY - SysAuthMenu = "sysAuthMenu" - // SysDict 字典缓存菜单KEY - SysDict = "sysDict" - // SysRole 角色缓存key - SysRole = "sysRole" - // SysWebSet 站点配置缓存key - SysWebSet = "sysWebSet" - // SysCmsMenu cms缓存key - SysCmsMenu = "sysCmsMenu" + // CacheSysAuthMenu 缓存菜单KEY + CacheSysAuthMenu = "sysAuthMenu" + // CacheSysDict 字典缓存菜单KEY + CacheSysDict = "sysDict" + // CacheSysRole 角色缓存key + CacheSysRole = "sysRole" + // CacheSysWebSet 站点配置缓存key + CacheSysWebSet = "sysWebSet" + // CacheSysCmsMenu cms缓存key + CacheSysCmsMenu = "sysCmsMenu" - // SysAuthTag 权限缓存TAG标签 - SysAuthTag = "sysAuthTag" - // SysDictTag 字典缓存标签 - SysDictTag = "sysDictTag" - // SysConfigTag 系统参数配置 - SysConfigTag = "sysConfigTag" - // SysModelTag 模型缓存标签 - SysModelTag = "sysModelTag" - // SysCmsTag cms缓存标签 - SysCmsTag = "sysCmsTag" + // CacheSysAuthTag 权限缓存TAG标签 + CacheSysAuthTag = "sysAuthTag" + // CacheSysDictTag 字典缓存标签 + CacheSysDictTag = "sysDictTag" + // CacheSysConfigTag 系统参数配置 + CacheSysConfigTag = "sysConfigTag" + // CacheSysModelTag 模型缓存标签 + CacheSysModelTag = "sysModelTag" + // CacheSysCmsTag cms缓存标签 + CacheSysCmsTag = "sysCmsTag" ) diff --git a/internal/app/system/controller/user.go b/internal/app/system/controller/user.go index 45edcef..46d555d 100644 --- a/internal/app/system/controller/user.go +++ b/internal/app/system/controller/user.go @@ -72,14 +72,14 @@ func (c *UserController) Login(ctx context.Context, req *system.UserLoginReq) (r return } //获取用户菜单数据 - roleList, err := service.Role().GetRoleList(ctx) + menuList, err := service.User().GetAdminRules(ctx, user.Id) if err != nil { return } - g.Log().Debug(ctx, roleList) res = &system.UserLoginRes{ UserInfo: user, Token: token, + MenuList: menuList, } return } diff --git a/internal/app/system/model/sys_auth_rule.go b/internal/app/system/model/sys_auth_rule.go new file mode 100644 index 0000000..1a8af39 --- /dev/null +++ b/internal/app/system/model/sys_auth_rule.go @@ -0,0 +1,48 @@ +/* +* @desc:菜单model +* @company:云南奇讯科技有限公司 +* @Author: yixiaohu +* @Date: 2022/3/11 14:53 + */ + +package model + +type SysAuthRuleInfoRes struct { + Id uint `orm:"id,primary" json:"id"` // + Pid uint `orm:"pid" json:"pid"` // 父ID + Name string `orm:"name,unique" json:"name"` // 规则名称 + Title string `orm:"title" json:"title"` // 规则名称 + Icon string `orm:"icon" json:"icon"` // 图标 + Condition string `orm:"condition" json:"condition"` // 条件 + Remark string `orm:"remark" json:"remark"` // 备注 + MenuType uint `orm:"menu_type" json:"menuType"` // 类型 0目录 1菜单 2按钮 + Weigh int `orm:"weigh" json:"weigh"` // 权重 + Status uint `orm:"status" json:"status"` // 状态 + AlwaysShow uint `orm:"always_show" json:"alwaysShow"` // 显示状态 + Path string `orm:"path" json:"path"` // 路由地址 + JumpPath string `orm:"jump_path" json:"jumpPath"` // 跳转路由 + Component string `orm:"component" json:"component"` // 组件路径 + IsFrame uint `orm:"is_frame" json:"isFrame"` // 是否外链 1是 0否 + ModuleType string `orm:"module_type" json:"moduleType"` // 所属模块 + ModelId uint `orm:"model_id" json:"modelId"` // 模型ID +} + +type UserMenu struct { + *SysAuthRuleInfoRes + Index string `json:"index"` + Name string `json:"name"` + MenuName string `json:"menuName"` + Component string `json:"component"` + Path string `json:"path"` + Meta struct { + Icon string `json:"icon"` + Title string `json:"title"` + } `json:"meta"` + Hidden bool `json:"hidden"` + AlwaysShow bool `json:"alwaysShow"` +} + +type UserMenus struct { + *UserMenu + Children []*UserMenus `json:"children"` +} diff --git a/internal/app/system/service/sys_auth_rule.go b/internal/app/system/service/sys_auth_rule.go new file mode 100644 index 0000000..138a729 --- /dev/null +++ b/internal/app/system/service/sys_auth_rule.go @@ -0,0 +1,72 @@ +/* +* @desc:菜单处理 +* @company:云南奇讯科技有限公司 +* @Author: yixiaohu +* @Date: 2022/3/11 15:07 + */ + +package service + +import ( + "context" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" + "github.com/tiger1103/gfast/v3/internal/app/common/service" + "github.com/tiger1103/gfast/v3/internal/app/system/consts" + "github.com/tiger1103/gfast/v3/internal/app/system/model" + "github.com/tiger1103/gfast/v3/internal/app/system/service/internal/dao" + "github.com/tiger1103/gfast/v3/library/liberr" +) + +type IRule interface { + GetIsMenuStatusList(ctx context.Context) ([]*model.SysAuthRuleInfoRes, error) + GetMenuList(ctx context.Context) (list []*model.SysAuthRuleInfoRes, err error) +} + +type ruleImpl struct { +} + +var rule = ruleImpl{} + +func Rule() IRule { + return IRule(&rule) +} + +// GetIsMenuStatusList 获取isMenu=0|1且status=1的菜单列表 +func (s *ruleImpl) GetIsMenuStatusList(ctx context.Context) ([]*model.SysAuthRuleInfoRes, error) { + list, err := s.GetMenuList(ctx) + if err != nil { + return nil, err + } + var gList = make([]*model.SysAuthRuleInfoRes, 0, len(list)) + for _, v := range list { + if (v.MenuType == 0 || v.MenuType == 1) && v.Status == 1 { + gList = append(gList, v) + } + } + return gList, nil +} + +// GetMenuList 获取所有菜单 +func (s *ruleImpl) GetMenuList(ctx context.Context) (list []*model.SysAuthRuleInfoRes, err error) { + cache := service.Cache(ctx) + //从缓存获取 + iList := cache.GetOrSetFuncLock(ctx, consts.CacheSysAuthMenu, s.getMenuListFromDb, 0, consts.CacheSysAuthTag) + if iList != nil { + err = gconv.Struct(iList, &list) + } + return +} + +// 从数据库获取所有菜单 +func (s *ruleImpl) getMenuListFromDb(ctx context.Context) (value interface{}, err error) { + err = g.Try(func() { + var v []*model.SysAuthRuleInfoRes + //从数据库获取 + err = dao.SysAuthRule.Ctx(ctx). + Fields(model.SysAuthRuleInfoRes{}).Order("weigh desc,id asc").Scan(&v) + liberr.ErrIsNil(ctx, err, "获取菜单数据失败") + value = v + }) + return +} diff --git a/internal/app/system/service/sys_role.go b/internal/app/system/service/sys_role.go index 8c5fd27..f003c3d 100644 --- a/internal/app/system/service/sys_role.go +++ b/internal/app/system/service/sys_role.go @@ -9,13 +9,13 @@ package service import ( "context" - "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/util/gconv" commonService "github.com/tiger1103/gfast/v3/internal/app/common/service" "github.com/tiger1103/gfast/v3/internal/app/system/consts" "github.com/tiger1103/gfast/v3/internal/app/system/model/entity" "github.com/tiger1103/gfast/v3/internal/app/system/service/internal/dao" + "github.com/tiger1103/gfast/v3/library/liberr" ) type IRole interface { @@ -35,21 +35,23 @@ func Role() IRole { func (s *roleImpl) GetRoleList(ctx context.Context) (list []*entity.SysRole, err error) { cache := commonService.Cache(ctx) //从缓存获取 - iList := cache.GetOrSetFuncLock(ctx, consts.SysRole, func(ctx context.Context) (value interface{}, err error) { - var v []*entity.SysRole - //从数据库获取 - err = dao.SysRole.Ctx(ctx). - Order(dao.SysRole.Columns().ListOrder + " asc," + dao.SysRole.Columns().Id + " asc"). - Scan(&v) - if err != nil { - g.Log().Error(ctx, err) - err = gerror.New("获取角色数据失败") - } - value = v - return - }, 0, consts.SysAuthTag) + iList := cache.GetOrSetFuncLock(ctx, consts.CacheSysRole, s.getRoleListFromDb, 0, consts.CacheSysAuthTag) if iList != nil { err = gconv.Struct(iList, &list) } return } + +// 从数据库获取所有角色 +func (s *roleImpl) getRoleListFromDb(ctx context.Context) (value interface{}, err error) { + err = g.Try(func() { + var v []*entity.SysRole + //从数据库获取 + err = dao.SysRole.Ctx(ctx). + Order(dao.SysRole.Columns().ListOrder + " asc," + dao.SysRole.Columns().Id + " asc"). + Scan(&v) + liberr.ErrIsNil(ctx, err, "获取角色数据失败") + value = v + }) + return +} diff --git a/internal/app/system/service/sys_user.go b/internal/app/system/service/sys_user.go index e209bcc..cbc798a 100644 --- a/internal/app/system/service/sys_user.go +++ b/internal/app/system/service/sys_user.go @@ -9,13 +9,18 @@ package service import ( "context" + "fmt" "github.com/gogf/gf/v2/container/gset" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/text/gstr" + "github.com/gogf/gf/v2/util/gconv" "github.com/mssola/user_agent" "github.com/tiger1103/gfast/v3/api/v1/system" + "github.com/tiger1103/gfast/v3/internal/app/common/service" "github.com/tiger1103/gfast/v3/internal/app/system/model" + "github.com/tiger1103/gfast/v3/internal/app/system/model/entity" "github.com/tiger1103/gfast/v3/internal/app/system/service/internal/dao" "github.com/tiger1103/gfast/v3/internal/app/system/service/internal/do" "github.com/tiger1103/gfast/v3/library/libUtils" @@ -27,6 +32,7 @@ type IUser interface { LoginLog(ctx context.Context, params *model.LoginLogParams) UpdateLoginInfo(ctx context.Context, id uint64, ip string) (err error) NotCheckAuthAdminIds(ctx context.Context) *gset.Set + GetAdminRules(ctx context.Context, userId uint64) (menuList []*model.UserMenus, err error) } type userImpl struct{} @@ -106,3 +112,170 @@ func (s *userImpl) UpdateLoginInfo(ctx context.Context, id uint64, ip string) (e }) return } + +// GetAdminRules 获取用户菜单数据 +func (s *userImpl) GetAdminRules(ctx context.Context, userId uint64) (menuList []*model.UserMenus, err error) { + err = g.Try(func() { + //是否超管 + isSuperAdmin := false + //获取无需验证权限的用户id + s.NotCheckAuthAdminIds(ctx).Iterator(func(v interface{}) bool { + if gconv.Uint64(v) == userId { + isSuperAdmin = true + return false + } + return true + }) + //获取用户菜单数据 + allRoles, err := Role().GetRoleList(ctx) + liberr.ErrIsNil(ctx, err) + roles, err := s.GetAdminRole(ctx, userId, allRoles) + liberr.ErrIsNil(ctx, err) + name := make([]string, len(roles)) + roleIds := make([]uint, len(roles)) + for k, v := range roles { + name[k] = v.Name + roleIds[k] = v.Id + } + //获取菜单信息 + if isSuperAdmin { + //超管获取所有菜单 + menuList, err = s.GetAllMenus(ctx) + } else { + menuList, err = s.GetAdminMenusByRoleIds(ctx, roleIds) + } + }) + return +} + +// GetAdminRole 获取用户角色 +func (s *userImpl) GetAdminRole(ctx context.Context, userId uint64, allRoleList []*entity.SysRole) (roles []*entity.SysRole, err error) { + var roleIds []uint + roleIds, err = s.GetAdminRoleIds(ctx, userId) + if err != nil { + return + } + roles = make([]*entity.SysRole, 0, len(allRoleList)) + for _, v := range allRoleList { + for _, id := range roleIds { + if id == v.Id { + roles = append(roles, v) + } + } + if len(roles) == len(roleIds) { + break + } + } + return +} + +// GetAdminRoleIds 获取用户角色ids +func (s *userImpl) GetAdminRoleIds(ctx context.Context, userId uint64) (roleIds []uint, err error) { + enforcer, e := service.CasbinEnforcer(ctx) + if e != nil { + err = e + return + } + //查询关联角色规则 + groupPolicy := enforcer.GetFilteredGroupingPolicy(0, gconv.String(userId)) + if len(groupPolicy) > 0 { + roleIds = make([]uint, len(groupPolicy)) + //得到角色id的切片 + for k, v := range groupPolicy { + roleIds[k] = gconv.Uint(v[1]) + } + } + return +} + +func (s *userImpl) GetAllMenus(ctx context.Context) (menus []*model.UserMenus, err error) { + //获取所有开启的菜单 + var allMenus []*model.SysAuthRuleInfoRes + allMenus, err = Rule().GetIsMenuStatusList(ctx) + if err != nil { + return + } + menus = make([]*model.UserMenus, len(allMenus)) + for k, v := range allMenus { + var menu *model.UserMenu + menu = s.setMenuData(menu, v) + menus[k] = &model.UserMenus{UserMenu: menu} + } + menus = s.GetMenusTree(menus, 0) + return +} + +func (s *userImpl) GetAdminMenusByRoleIds(ctx context.Context, roleIds []uint) (menus []*model.UserMenus, err error) { + //获取角色对应的菜单id + err = g.Try(func() { + enforcer, e := service.CasbinEnforcer(ctx) + liberr.ErrIsNil(ctx, e) + menuIds := map[int64]int64{} + for _, roleId := range roleIds { + //查询当前权限 + gp := enforcer.GetFilteredPolicy(0, fmt.Sprintf("%d", roleId)) + for _, p := range gp { + mid := gconv.Int64(p[1]) + menuIds[mid] = mid + } + } + //获取所有开启的菜单 + allMenus, err := Rule().GetIsMenuStatusList(ctx) + liberr.ErrIsNil(ctx, err) + menus = make([]*model.UserMenus, 0, len(allMenus)) + for _, v := range allMenus { + if _, ok := menuIds[gconv.Int64(v.Id)]; gstr.Equal(v.Condition, "nocheck") || ok { + var roleMenu *model.UserMenu + roleMenu = s.setMenuData(roleMenu, v) + menus = append(menus, &model.UserMenus{UserMenu: roleMenu}) + } + } + menus = s.GetMenusTree(menus, 0) + }) + return +} + +func (s *userImpl) GetMenusTree(menus []*model.UserMenus, pid uint) []*model.UserMenus { + returnList := make([]*model.UserMenus, 0, len(menus)) + for _, menu := range menus { + if menu.Pid == pid { + menu.Children = s.GetMenusTree(menus, menu.Id) + returnList = append(returnList, menu) + } + } + return returnList +} + +func (s *userImpl) setMenuData(menu *model.UserMenu, entity *model.SysAuthRuleInfoRes) *model.UserMenu { + menu = &model.UserMenu{ + SysAuthRuleInfoRes: entity, + Index: entity.Name, + Name: gstr.UcFirst(entity.Path), + MenuName: entity.Title, + Meta: struct { + Icon string `json:"icon"` + Title string `json:"title"` + }(struct { + Icon string + Title string + }{Icon: entity.Icon, Title: entity.Title}), + } + if entity.MenuType != 0 { + menu.Component = entity.Component + menu.Path = entity.Path + } else { + menu.Component = "Layout" + menu.Path = "/" + entity.Path + } + if entity.AlwaysShow == 1 { + menu.Hidden = false + } else { + menu.Hidden = true + } + if entity.AlwaysShow == 1 && entity.MenuType == 0 { + menu.AlwaysShow = true + } else { + menu.AlwaysShow = false + } + return menu +} diff --git a/internal/const/version.go b/internal/const/version.go new file mode 100644 index 0000000..d4449b2 --- /dev/null +++ b/internal/const/version.go @@ -0,0 +1,12 @@ +/* +* @desc:版本号 +* @company:云南奇讯科技有限公司 +* @Author: yixiaohu +* @Date: 2022/3/11 11:24 + */ + +package consts + +const ( + Version = "3.0.0" +) diff --git a/manifest/config/config.yaml b/manifest/config/config.yaml index 873ceed..5fabaae 100644 --- a/manifest/config/config.yaml +++ b/manifest/config/config.yaml @@ -57,21 +57,27 @@ redis: maxActive: 100 system: - notCheckAuthAdminIds: [1,2,31] #无需验证后台权限的用户id + notCheckAuthAdminIds: [1,2] #无需验证后台权限的用户id dataDir: "./data" cache: model: "redis" #缓存模式 memory OR redis prefix: "gFastV3Cache_" #缓存前缀 +#casbin配置 +casbin: + modelFile: "./resource/casbin/rbac_model.conf" + policyFile: "./resource/casbin/rbac_policy.csv" + + # CLI. gfcli: gen: dao: - link: "mysql:root:123456@tcp(127.0.0.1:3306)/gfast-v3" - tables: "sys_auth_rule,sys_role" + tables: "casbin_rule" removePrefix: "gf_" descriptionTag: true noModelComment: true - path: "./internal/app/system" + path: "./internal/app/common" diff --git a/resource/casbin/rbac_model.conf b/resource/casbin/rbac_model.conf new file mode 100644 index 0000000..71159e3 --- /dev/null +++ b/resource/casbin/rbac_model.conf @@ -0,0 +1,14 @@ +[request_definition] +r = sub, obj, act + +[policy_definition] +p = sub, obj, act + +[role_definition] +g = _, _ + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act \ No newline at end of file diff --git a/resource/casbin/rbac_policy.csv b/resource/casbin/rbac_policy.csv new file mode 100644 index 0000000..f93d6df --- /dev/null +++ b/resource/casbin/rbac_policy.csv @@ -0,0 +1,5 @@ +p, alice, data1, read +p, bob, data2, write +p, data2_admin, data2, read +p, data2_admin, data2, write +g, alice, data2_admin \ No newline at end of file