Browse Source

casbin整合,登录成功获取用户菜单

yxh 4 năm trước cách đây
mục cha
commit
94e22e2268

+ 1 - 0
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"`
 }

+ 2 - 1
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
 )

+ 9 - 7
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=

+ 239 - 0
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
+}

+ 20 - 20
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"
 )

+ 2 - 2
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
 }

+ 48 - 0
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"`
+}

+ 72 - 0
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
+}

+ 13 - 11
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) {
+	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)
-		if err != nil {
-			g.Log().Error(ctx, err)
-			err = gerror.New("获取角色数据失败")
-		}
+		liberr.ErrIsNil(ctx, err, "获取角色数据失败")
 		value = v
-		return
-	}, 0, consts.SysAuthTag)
-	if iList != nil {
-		err = gconv.Struct(iList, &list)
-	}
+	})
 	return
 }

+ 173 - 0
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
+}

+ 12 - 0
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"
+)

+ 9 - 3
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"
 
 

+ 14 - 0
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

+ 5 - 0
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