362 lines
10 KiB
Go
362 lines
10 KiB
Go
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)
|
||
}
|