From dd12dbfe07e8e9b5e1459978d4b3b272a41cf579 Mon Sep 17 00:00:00 2001 From: richjohnrambo Date: Wed, 23 Sep 2026 00:30:40 +0800 Subject: [PATCH] fix: cache admin permissions in redis --- server/internal/library/casbin/enforcer.go | 126 ++++++++++++++++++--- server/internal/logic/admin/menu.go | 15 ++- server/internal/logic/admin/role.go | 14 ++- 3 files changed, 129 insertions(+), 26 deletions(-) diff --git a/server/internal/library/casbin/enforcer.go b/server/internal/library/casbin/enforcer.go index 3676d11..b0a1ad7 100644 --- a/server/internal/library/casbin/enforcer.go +++ b/server/internal/library/casbin/enforcer.go @@ -10,13 +10,18 @@ import ( "fmt" "hotgo/internal/consts" "hotgo/internal/dao" + "hotgo/internal/library/cache" "net/http" + "regexp" "strings" + "time" "github.com/casbin/casbin/v2" "github.com/casbin/casbin/v2/model" + casbinutil "github.com/casbin/casbin/v2/util" _ "github.com/gogf/gf/contrib/drivers/mysql/v2" "github.com/gogf/gf/v2/container/gvar" + "github.com/gogf/gf/v2/encoding/gjson" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gfile" "github.com/gogf/gf/v2/os/gres" @@ -33,6 +38,14 @@ const ( var Enforcer *casbin.Enforcer +const permissionCacheKey = "admin:role:permissions" + +type PermissionPolicy struct { + Key string `json:"key"` + Perm string `json:"perm"` + ActRegexp string `json:"actRegexp"` +} + // InitEnforcer 初始化 func InitEnforcer(ctx context.Context) { var ( @@ -69,6 +82,9 @@ func InitEnforcer(ctx context.Context) { g.Log().Panicf(ctx, "casbin NewEnforcer err:%v", err) } loadPermissions(ctx) + if err = RefreshPermissionCache(ctx); err != nil { + g.Log().Panicf(ctx, "casbin RefreshPermissionCache err:%v", err) + } } // GetDbLink 获取数据库链接 @@ -91,15 +107,13 @@ func getDbLink(ctx context.Context) *gvar.Var { } -func loadPermissions(ctx context.Context) { - type Policy struct { +func queryPermissionPolicies(ctx context.Context) (policies []*PermissionPolicy, err error) { + type RolePermission struct { Key string `json:"key"` Permissions string `json:"permissions"` } var ( - rules [][]string - polices []*Policy - err error + records []*RolePermission ) //别名拼接 r.key m.permissions q := func(alias string, column string) string { @@ -113,23 +127,43 @@ func loadPermissions(ctx context.Context) { Where(q("m", dao.AdminMenu.Columns().Status), consts.StatusEnabled). WhereNot(q("m", dao.AdminMenu.Columns().Permissions), ""). WhereNot(q("r", dao.AdminRole.Columns().Key), consts.SuperRoleKey). - Scan(&polices) + Scan(&records) + if err != nil { + return nil, err + } + + for _, record := range records { + if strings.Contains(record.Permissions, ",") { + lst := strings.Split(record.Permissions, ",") + for _, permission := range lst { + permission = strings.TrimSpace(permission) + if permission == "" { + continue + } + policies = append(policies, &PermissionPolicy{Key: record.Key, Perm: permission, ActRegexp: ActionAll}) + } + } else { + permission := strings.TrimSpace(record.Permissions) + if permission == "" { + continue + } + policies = append(policies, &PermissionPolicy{Key: record.Key, Perm: permission, ActRegexp: ActionAll}) + } + } + return +} + +func loadPermissions(ctx context.Context) { + policies, err := queryPermissionPolicies(ctx) if err != nil { g.Log().Fatalf(ctx, "loadPermissions Scan err:%v", err) return } - for _, policy := range polices { - if strings.Contains(policy.Permissions, ",") { - lst := strings.Split(policy.Permissions, ",") - for _, permissions := range lst { - rules = append(rules, []string{policy.Key, permissions, ActionAll}) - } - } else { - rules = append(rules, []string{policy.Key, policy.Permissions, ActionAll}) - } + rules := make([][]string, 0, len(policies)) + for _, policy := range policies { + rules = append(rules, []string{policy.Key, policy.Perm, policy.ActRegexp}) } - if _, err = Enforcer.AddPolicies(rules); err != nil { g.Log().Fatalf(ctx, "loadPermissions AddPolicies err:%v", err) return @@ -160,5 +194,65 @@ func Refresh(ctx context.Context) (err error) { return err } loadPermissions(ctx) + return RefreshPermissionCache(ctx) +} + +func RefreshPermissionCache(ctx context.Context) (err error) { + policies, err := queryPermissionPolicies(ctx) + if err != nil { + return err + } + rolePolicies := make(map[string][]*PermissionPolicy) + for _, policy := range policies { + rolePolicies[policy.Key] = append(rolePolicies[policy.Key], policy) + } + data, err := gjson.Encode(rolePolicies) + if err != nil { + return err + } + return cache.Instance().Set(ctx, permissionCacheKey, string(data), 30*24*time.Hour) +} + +func cachedPermissionPolicies(ctx context.Context) (rolePolicies map[string][]*PermissionPolicy, err error) { + value, err := cache.Instance().Get(ctx, permissionCacheKey) + if err != nil { + return nil, err + } + if value.IsNil() { + if err = RefreshPermissionCache(ctx); err != nil { + return nil, err + } + value, err = cache.Instance().Get(ctx, permissionCacheKey) + if err != nil { + return nil, err + } + } + rolePolicies = make(map[string][]*PermissionPolicy) + if value.IsNil() { + return rolePolicies, nil + } + if err = gjson.DecodeTo([]byte(value.String()), &rolePolicies); err != nil { + return nil, err + } return } + +func Verify(ctx context.Context, roleKey, path, method string) (bool, error) { + rolePolicies, err := cachedPermissionPolicies(ctx) + if err != nil { + return false, err + } + for _, policy := range rolePolicies[roleKey] { + if !casbinutil.KeyMatch(path, policy.Perm) { + continue + } + ok, err := regexp.MatchString(policy.ActRegexp, method) + if err != nil { + return false, err + } + if ok { + return true, nil + } + } + return false, nil +} diff --git a/server/internal/logic/admin/menu.go b/server/internal/logic/admin/menu.go index feb3aed..61a02d3 100644 --- a/server/internal/logic/admin/menu.go +++ b/server/internal/logic/admin/menu.go @@ -53,8 +53,10 @@ func (s *sAdminMenu) Delete(ctx context.Context, in *adminin.MenuDeleteInp) (err if !exist.IsEmpty() { return gerror.New("请先删除该菜单下的所有菜单!") } - _, err = dao.AdminMenu.Ctx(ctx).Where(dao.AdminMenu.Columns().Id, in.Id).Delete() - return + if _, err = dao.AdminMenu.Ctx(ctx).Where(dao.AdminMenu.Columns().Id, in.Id).Delete(); err != nil { + return + } + return casbin.Refresh(ctx) } // VerifyUnique 验证菜单唯一属性 @@ -104,7 +106,7 @@ func (s *sAdminMenu) Edit(ctx context.Context, in *adminin.MenuEditInp) (err err return } - return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + if err = g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { in.Pid, in.Level, in.Tree, err = hgorm.AutoUpdateTree(ctx, &dao.AdminMenu, in.Id, in.Pid) if err != nil { return err @@ -121,8 +123,11 @@ func (s *sAdminMenu) Edit(ctx context.Context, in *adminin.MenuEditInp) (err err return err } } - return casbin.Refresh(ctx) - }) + return nil + }); err != nil { + return + } + return casbin.Refresh(ctx) } // List 获取菜单列表 diff --git a/server/internal/logic/admin/role.go b/server/internal/logic/admin/role.go index d671d29..9920072 100644 --- a/server/internal/logic/admin/role.go +++ b/server/internal/logic/admin/role.go @@ -82,9 +82,9 @@ func (s *sAdminRole) Verify(ctx context.Context, path, method string) bool { return true } - ok, err := casbin.Enforcer.Enforce(user.RoleKey, path, method) + ok, err := casbin.Verify(ctx, user.RoleKey, path, method) if err != nil { - g.Log().Infof(ctx, "admin Verify Enforce err:%+v", err) + g.Log().Infof(ctx, "admin Verify cache err:%+v", err) return false } return ok @@ -221,7 +221,10 @@ func (s *sAdminRole) Edit(ctx context.Context, in *adminin.RoleEditInp) (err err // 如果当前角色有子级,更新子级tree关系树 return updateRoleChildrenTree(ctx, in.Id, in.Level, in.Tree) }) - return + if err != nil { + return + } + return casbin.Refresh(ctx) } // 新增 @@ -229,7 +232,7 @@ func (s *sAdminRole) Edit(ctx context.Context, in *adminin.RoleEditInp) (err err err = gerror.Wrap(err, consts.ErrorORM) return } - return + return casbin.Refresh(ctx) } func updateRoleChildrenTree(ctx context.Context, _id int64, _level int, _tree string) (err error) { @@ -288,8 +291,9 @@ func (s *sAdminRole) Delete(ctx context.Context, in *adminin.RoleDeleteInp) (err if _, err = dao.AdminRole.Ctx(ctx).Where("id", in.Id).Delete(); err != nil { err = gerror.Wrap(err, consts.ErrorORM) + return } - return + return casbin.Refresh(ctx) } func (s *sAdminRole) DataScopeSelect() (res form.Selects) {