任务三

This commit is contained in:
Yvan
2026-01-07 14:02:53 +08:00
parent 7df0450b1e
commit 03252d34e1
6 changed files with 955 additions and 0 deletions
+3
View File
@@ -50,6 +50,9 @@ type CasbinRepo interface {
UpdateCasbinApi(oldPath, newPath, oldMethod, newMethod string) error
GetPolicyPathByAuthorityId(authorityId uint) []CasbinRule
FreshCasbin() error
AddPolicies(rules [][]string) error
UpdateCasbin(adminAuthorityID, authorityId uint, casbinInfos []CasbinRule) error
RemoveFilteredPolicy(authorityId string) error
}
// ApiUsecase API用例
+361
View File
@@ -0,0 +1,361 @@
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)
}
+17
View File
@@ -67,11 +67,13 @@ type MenuRepo interface {
UpdateBaseMenu(ctx context.Context, menu *BaseMenu) error
DeleteBaseMenu(ctx context.Context, id uint) error
FindBaseMenuByID(ctx context.Context, id uint) (*BaseMenu, error)
FindBaseMenuByName(ctx context.Context, name string) (*BaseMenu, error)
FindAllBaseMenus(ctx context.Context) ([]*BaseMenu, error)
FindBaseMenusByIds(ctx context.Context, ids []string) ([]*BaseMenu, error)
FindAuthorityMenuIds(ctx context.Context, authorityId uint) ([]string, error)
FindAuthorityBtns(ctx context.Context, authorityId uint) ([]*AuthorityBtn, error)
SetMenuAuthority(ctx context.Context, authorityId uint, menuIds []uint) error
CheckMenuInAuthority(ctx context.Context, authorityId uint, menuName string) (bool, error)
}
// MenuUsecase 菜单用例
@@ -291,3 +293,18 @@ func (uc *MenuUsecase) GetMenuAuthority(ctx context.Context, authorityId uint) (
}
return menus, nil
}
// UserAuthorityDefaultRouter 用户角色默认路由检查
// 如果用户的默认路由不在其权限菜单中,则设置为404
func (uc *MenuUsecase) UserAuthorityDefaultRouter(ctx context.Context, authorityId uint, defaultRouter string) string {
if defaultRouter == "" {
return "404"
}
// 检查默认路由是否在用户的权限菜单中
exists, err := uc.repo.CheckMenuInAuthority(ctx, authorityId, defaultRouter)
if err != nil || !exists {
return "404"
}
return defaultRouter
}