kra/internal/biz/system/authority.go

362 lines
10 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package system
import (
"context"
"errors"
"strconv"
)
var (
ErrAuthorityExists = errors.New("存在相同角色id")
ErrAuthorityNotFound = errors.New("该角色不存在")
ErrAuthorityHasUsers = errors.New("此角色有用户正在使用禁止删除")
ErrAuthorityHasChildren = errors.New("此角色存在子角色不允许删除")
ErrAuthorityIDInvalid = errors.New("您提交的角色ID不合法")
)
// AuthorityFull 完整角色实体(包含关联)
type AuthorityFull struct {
Authority
DataAuthorityId []*Authority
Children []*AuthorityFull
SysBaseMenus []*BaseMenu
}
// CasbinInfo Casbin规则信息
type CasbinInfo struct {
Path string
Method string
}
// DefaultCasbin 默认Casbin规则
func DefaultCasbin() []CasbinInfo {
return []CasbinInfo{
{Path: "/menu/getMenu", Method: "POST"},
{Path: "/jwt/jsonInBlacklist", Method: "POST"},
{Path: "/base/login", Method: "POST"},
{Path: "/user/changePassword", Method: "POST"},
{Path: "/user/setUserAuthority", Method: "POST"},
{Path: "/user/getUserInfo", Method: "GET"},
{Path: "/user/setSelfInfo", Method: "PUT"},
{Path: "/fileUploadAndDownload/upload", Method: "POST"},
{Path: "/sysDictionary/findSysDictionary", Method: "GET"},
}
}
// DefaultMenu 默认菜单
func DefaultMenu() []*BaseMenu {
return []*BaseMenu{{
ID: 1,
ParentId: 0,
Path: "dashboard",
Name: "dashboard",
Component: "view/dashboard/index.vue",
Sort: 1,
Title: "仪表盘",
Icon: "setting",
}}
}
// AuthorityRepo 角色仓储接口
type AuthorityRepo interface {
Create(ctx context.Context, auth *Authority) error
Update(ctx context.Context, auth *Authority) error
Delete(ctx context.Context, authorityId uint) error
FindByID(ctx context.Context, authorityId uint) (*Authority, error)
FindByIDWithDataAuthority(ctx context.Context, authorityId uint) (*AuthorityFull, error)
FindByParentID(ctx context.Context, parentId uint) ([]*AuthorityFull, error)
FindChildren(ctx context.Context, authorityId uint) ([]*AuthorityFull, error)
HasUsers(ctx context.Context, authorityId uint) (bool, error)
HasChildren(ctx context.Context, authorityId uint) (bool, error)
GetParentAuthorityID(ctx context.Context, authorityId uint) (uint, error)
SetDataAuthority(ctx context.Context, authorityId uint, dataAuthorityIds []uint) error
SetMenuAuthority(ctx context.Context, authorityId uint, menuIds []uint) error
GetMenuIds(ctx context.Context, authorityId uint) ([]uint, error)
CopyAuthorityBtns(ctx context.Context, oldAuthorityId, newAuthorityId uint) error
DeleteAuthorityRelations(ctx context.Context, authorityId uint) error
}
// AuthorityUsecase 角色用例
type AuthorityUsecase struct {
repo AuthorityRepo
casbinRepo CasbinRepo
useStrictAuth bool
}
// NewAuthorityUsecase 创建角色用例
func NewAuthorityUsecase(repo AuthorityRepo, casbinRepo CasbinRepo, useStrictAuth bool) *AuthorityUsecase {
return &AuthorityUsecase{
repo: repo,
casbinRepo: casbinRepo,
useStrictAuth: useStrictAuth,
}
}
// CreateAuthority 创建角色
func (uc *AuthorityUsecase) CreateAuthority(ctx context.Context, auth *Authority) (*Authority, error) {
// 检查是否存在相同角色ID
existing, _ := uc.repo.FindByID(ctx, auth.AuthorityId)
if existing != nil {
return nil, ErrAuthorityExists
}
// 创建角色
if err := uc.repo.Create(ctx, auth); err != nil {
return nil, err
}
// 设置默认菜单
defaultMenus := DefaultMenu()
menuIds := make([]uint, len(defaultMenus))
for i, m := range defaultMenus {
menuIds[i] = m.ID
}
if err := uc.repo.SetMenuAuthority(ctx, auth.AuthorityId, menuIds); err != nil {
return nil, err
}
// 添加默认Casbin规则
casbinInfos := DefaultCasbin()
rules := make([][]string, len(casbinInfos))
for i, v := range casbinInfos {
rules[i] = []string{uintToStr(auth.AuthorityId), v.Path, v.Method}
}
if err := uc.casbinRepo.AddPolicies(rules); err != nil {
return nil, err
}
return auth, nil
}
// CopyAuthority 复制角色
func (uc *AuthorityUsecase) CopyAuthority(ctx context.Context, adminAuthorityID, oldAuthorityId uint, newAuth *Authority) (*Authority, error) {
// 检查新角色ID是否已存在
existing, _ := uc.repo.FindByID(ctx, newAuth.AuthorityId)
if existing != nil {
return nil, ErrAuthorityExists
}
// 获取旧角色的菜单
menuIds, err := uc.repo.GetMenuIds(ctx, oldAuthorityId)
if err != nil {
return nil, err
}
// 创建新角色
if err := uc.repo.Create(ctx, newAuth); err != nil {
return nil, err
}
// 复制菜单权限
if err := uc.repo.SetMenuAuthority(ctx, newAuth.AuthorityId, menuIds); err != nil {
return nil, err
}
// 复制按钮权限
if err := uc.repo.CopyAuthorityBtns(ctx, oldAuthorityId, newAuth.AuthorityId); err != nil {
return nil, err
}
// 复制Casbin规则
paths := uc.casbinRepo.GetPolicyPathByAuthorityId(oldAuthorityId)
if err := uc.casbinRepo.UpdateCasbin(adminAuthorityID, newAuth.AuthorityId, paths); err != nil {
_ = uc.DeleteAuthority(ctx, newAuth.AuthorityId)
return nil, err
}
return newAuth, nil
}
// UpdateAuthority 更新角色
func (uc *AuthorityUsecase) UpdateAuthority(ctx context.Context, auth *Authority) (*Authority, error) {
existing, err := uc.repo.FindByID(ctx, auth.AuthorityId)
if err != nil {
return nil, errors.New("查询角色数据失败")
}
if existing == nil {
return nil, ErrAuthorityNotFound
}
if err := uc.repo.Update(ctx, auth); err != nil {
return nil, err
}
return auth, nil
}
// DeleteAuthority 删除角色
func (uc *AuthorityUsecase) DeleteAuthority(ctx context.Context, authorityId uint) error {
// 检查角色是否存在
existing, err := uc.repo.FindByID(ctx, authorityId)
if err != nil || existing == nil {
return ErrAuthorityNotFound
}
// 检查是否有用户使用此角色
hasUsers, err := uc.repo.HasUsers(ctx, authorityId)
if err != nil {
return err
}
if hasUsers {
return ErrAuthorityHasUsers
}
// 检查是否有子角色
hasChildren, err := uc.repo.HasChildren(ctx, authorityId)
if err != nil {
return err
}
if hasChildren {
return ErrAuthorityHasChildren
}
// 删除关联关系
if err := uc.repo.DeleteAuthorityRelations(ctx, authorityId); err != nil {
return err
}
// 删除角色
if err := uc.repo.Delete(ctx, authorityId); err != nil {
return err
}
// 删除Casbin规则
uc.casbinRepo.RemoveFilteredPolicy(uintToStr(authorityId))
return nil
}
// GetAuthorityInfoList 获取角色列表(树形)
func (uc *AuthorityUsecase) GetAuthorityInfoList(ctx context.Context, authorityID uint) ([]*AuthorityFull, error) {
auth, err := uc.repo.FindByID(ctx, authorityID)
if err != nil {
return nil, err
}
var authorities []*AuthorityFull
if uc.useStrictAuth {
if auth.ParentId == nil || *auth.ParentId == 0 {
// 顶级角色可以修改自己的权限和以下权限
authFull, err := uc.repo.FindByIDWithDataAuthority(ctx, authorityID)
if err != nil {
return nil, err
}
authorities = []*AuthorityFull{authFull}
} else {
// 非顶级角色只能修改以下权限
authorities, err = uc.repo.FindByParentID(ctx, authorityID)
if err != nil {
return nil, err
}
}
} else {
authorities, err = uc.repo.FindByParentID(ctx, 0)
if err != nil {
return nil, err
}
}
// 递归获取子角色
for i := range authorities {
if err := uc.findChildrenAuthority(ctx, authorities[i]); err != nil {
return nil, err
}
}
return authorities, nil
}
func (uc *AuthorityUsecase) findChildrenAuthority(ctx context.Context, authority *AuthorityFull) error {
children, err := uc.repo.FindChildren(ctx, authority.AuthorityId)
if err != nil {
return err
}
authority.Children = children
for i := range authority.Children {
if err := uc.findChildrenAuthority(ctx, authority.Children[i]); err != nil {
return err
}
}
return nil
}
// GetStructAuthorityList 获取角色ID列表包含子角色
func (uc *AuthorityUsecase) GetStructAuthorityList(ctx context.Context, authorityID uint) ([]uint, error) {
auth, err := uc.repo.FindByID(ctx, authorityID)
if err != nil {
return nil, err
}
var list []uint
children, err := uc.repo.FindChildren(ctx, authorityID)
if err != nil {
return nil, err
}
for _, child := range children {
list = append(list, child.AuthorityId)
childList, err := uc.GetStructAuthorityList(ctx, child.AuthorityId)
if err == nil {
list = append(list, childList...)
}
}
if auth.ParentId == nil || *auth.ParentId == 0 {
list = append(list, authorityID)
}
return list, nil
}
// CheckAuthorityIDAuth 检查角色ID权限
func (uc *AuthorityUsecase) CheckAuthorityIDAuth(ctx context.Context, authorityID, targetID uint) error {
if !uc.useStrictAuth {
return nil
}
authIDs, err := uc.GetStructAuthorityList(ctx, authorityID)
if err != nil {
return err
}
for _, id := range authIDs {
if id == targetID {
return nil
}
}
return ErrAuthorityIDInvalid
}
// GetAuthorityInfo 获取角色信息
func (uc *AuthorityUsecase) GetAuthorityInfo(ctx context.Context, authorityId uint) (*AuthorityFull, error) {
return uc.repo.FindByIDWithDataAuthority(ctx, authorityId)
}
// SetDataAuthority 设置角色资源权限
func (uc *AuthorityUsecase) SetDataAuthority(ctx context.Context, adminAuthorityID uint, authorityId uint, dataAuthorityIds []uint) error {
// 检查权限
checkIDs := append([]uint{authorityId}, dataAuthorityIds...)
for _, id := range checkIDs {
if err := uc.CheckAuthorityIDAuth(ctx, adminAuthorityID, id); err != nil {
return err
}
}
return uc.repo.SetDataAuthority(ctx, authorityId, dataAuthorityIds)
}
// SetMenuAuthority 设置角色菜单权限
func (uc *AuthorityUsecase) SetMenuAuthority(ctx context.Context, authorityId uint, menuIds []uint) error {
return uc.repo.SetMenuAuthority(ctx, authorityId, menuIds)
}
// GetParentAuthorityID 获取父角色ID
func (uc *AuthorityUsecase) GetParentAuthorityID(ctx context.Context, authorityId uint) (uint, error) {
return uc.repo.GetParentAuthorityID(ctx, authorityId)
}
// 辅助函数
func uintToStr(n uint) string {
return strconv.FormatUint(uint64(n), 10)
}