diff --git a/internal/biz/system/api.go b/internal/biz/system/api.go index 9f9db61..de1ade0 100644 --- a/internal/biz/system/api.go +++ b/internal/biz/system/api.go @@ -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用例 diff --git a/internal/biz/system/authority.go b/internal/biz/system/authority.go new file mode 100644 index 0000000..a3d22e9 --- /dev/null +++ b/internal/biz/system/authority.go @@ -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) +} diff --git a/internal/biz/system/menu.go b/internal/biz/system/menu.go index f7c33f3..5fa9487 100644 --- a/internal/biz/system/menu.go +++ b/internal/biz/system/menu.go @@ -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 +} diff --git a/internal/data/system/authority.go b/internal/data/system/authority.go new file mode 100644 index 0000000..efc46f2 --- /dev/null +++ b/internal/data/system/authority.go @@ -0,0 +1,269 @@ +package system + +import ( + "context" + "errors" + + "kra/internal/biz/system" + "kra/internal/data/model" + "kra/internal/data/query" + + "gorm.io/gorm" +) + +type authorityRepo struct { + db *gorm.DB +} + +// NewAuthorityRepo 创建角色仓储 +func NewAuthorityRepo(db *gorm.DB) system.AuthorityRepo { + return &authorityRepo{db: db} +} + +func (r *authorityRepo) Create(ctx context.Context, auth *system.Authority) error { + m := &model.SysAuthority{ + AuthorityID: int64(auth.AuthorityId), + AuthorityName: &auth.AuthorityName, + DefaultRouter: &auth.DefaultRouter, + } + if auth.ParentId != nil { + parentId := int64(*auth.ParentId) + m.ParentID = &parentId + } + return r.db.WithContext(ctx).Create(m).Error +} + +func (r *authorityRepo) Update(ctx context.Context, auth *system.Authority) error { + updates := map[string]any{ + "authority_name": auth.AuthorityName, + "default_router": auth.DefaultRouter, + } + if auth.ParentId != nil { + updates["parent_id"] = *auth.ParentId + } + return r.db.WithContext(ctx).Model(&model.SysAuthority{}). + Where("authority_id = ?", auth.AuthorityId). + Updates(updates).Error +} + +func (r *authorityRepo) Delete(ctx context.Context, authorityId uint) error { + return r.db.WithContext(ctx).Unscoped(). + Where("authority_id = ?", authorityId). + Delete(&model.SysAuthority{}).Error +} + +func (r *authorityRepo) FindByID(ctx context.Context, authorityId uint) (*system.Authority, error) { + m, err := query.SysAuthority.WithContext(ctx). + Where(query.SysAuthority.AuthorityID.Eq(int64(authorityId))).First() + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, nil + } + return nil, err + } + return toBizAuthority(m), nil +} + +func (r *authorityRepo) FindByIDWithDataAuthority(ctx context.Context, authorityId uint) (*system.AuthorityFull, error) { + m, err := query.SysAuthority.WithContext(ctx). + Where(query.SysAuthority.AuthorityID.Eq(int64(authorityId))).First() + if err != nil { + return nil, err + } + + // 获取数据权限 + var dataAuthIds []model.SysDataAuthorityID + r.db.WithContext(ctx).Where("sys_authority_authority_id = ?", authorityId).Find(&dataAuthIds) + + var dataAuthorities []*system.Authority + for _, da := range dataAuthIds { + auth, _ := r.FindByID(ctx, uint(da.DataAuthorityIDAuthorityID)) + if auth != nil { + dataAuthorities = append(dataAuthorities, auth) + } + } + + return &system.AuthorityFull{ + Authority: *toBizAuthority(m), + DataAuthorityId: dataAuthorities, + }, nil +} + +func (r *authorityRepo) FindByParentID(ctx context.Context, parentId uint) ([]*system.AuthorityFull, error) { + list, err := query.SysAuthority.WithContext(ctx). + Where(query.SysAuthority.ParentID.Eq(int64(parentId))).Find() + if err != nil { + return nil, err + } + + result := make([]*system.AuthorityFull, len(list)) + for i, m := range list { + // 获取数据权限 + var dataAuthIds []model.SysDataAuthorityID + r.db.WithContext(ctx).Where("sys_authority_authority_id = ?", m.AuthorityID).Find(&dataAuthIds) + + var dataAuthorities []*system.Authority + for _, da := range dataAuthIds { + auth, _ := r.FindByID(ctx, uint(da.DataAuthorityIDAuthorityID)) + if auth != nil { + dataAuthorities = append(dataAuthorities, auth) + } + } + + result[i] = &system.AuthorityFull{ + Authority: *toBizAuthority(m), + DataAuthorityId: dataAuthorities, + } + } + return result, nil +} + +func (r *authorityRepo) FindChildren(ctx context.Context, authorityId uint) ([]*system.AuthorityFull, error) { + return r.FindByParentID(ctx, authorityId) +} + +func (r *authorityRepo) HasUsers(ctx context.Context, authorityId uint) (bool, error) { + var count int64 + err := r.db.WithContext(ctx).Model(&model.SysUserAuthority{}). + Where("sys_authority_authority_id = ?", authorityId).Count(&count).Error + return count > 0, err +} + +func (r *authorityRepo) HasChildren(ctx context.Context, authorityId uint) (bool, error) { + count, err := query.SysAuthority.WithContext(ctx). + Where(query.SysAuthority.ParentID.Eq(int64(authorityId))).Count() + return count > 0, err +} + +func (r *authorityRepo) GetParentAuthorityID(ctx context.Context, authorityId uint) (uint, error) { + m, err := query.SysAuthority.WithContext(ctx). + Where(query.SysAuthority.AuthorityID.Eq(int64(authorityId))).First() + if err != nil { + return 0, err + } + if m.ParentID == nil { + return 0, nil + } + return uint(*m.ParentID), nil +} + +func (r *authorityRepo) SetDataAuthority(ctx context.Context, authorityId uint, dataAuthorityIds []uint) error { + return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + // 删除旧的数据权限 + if err := tx.Where("sys_authority_authority_id = ?", authorityId). + Delete(&model.SysDataAuthorityID{}).Error; err != nil { + return err + } + + // 创建新的数据权限 + for _, daId := range dataAuthorityIds { + da := &model.SysDataAuthorityID{ + SysAuthorityAuthorityID: int64(authorityId), + DataAuthorityIDAuthorityID: int64(daId), + } + if err := tx.Create(da).Error; err != nil { + return err + } + } + return nil + }) +} + +func (r *authorityRepo) SetMenuAuthority(ctx context.Context, authorityId uint, menuIds []uint) error { + return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + // 删除旧的菜单权限 + if err := tx.Where("sys_authority_authority_id = ?", authorityId). + Delete(&model.SysAuthorityMenu{}).Error; err != nil { + return err + } + + // 创建新的菜单权限 + for _, menuId := range menuIds { + am := &model.SysAuthorityMenu{ + SysAuthorityAuthorityID: int64(authorityId), + SysBaseMenuID: int64(menuId), + } + if err := tx.Create(am).Error; err != nil { + return err + } + } + return nil + }) +} + +func (r *authorityRepo) GetMenuIds(ctx context.Context, authorityId uint) ([]uint, error) { + var menus []model.SysAuthorityMenu + if err := r.db.WithContext(ctx).Where("sys_authority_authority_id = ?", authorityId). + Find(&menus).Error; err != nil { + return nil, err + } + + ids := make([]uint, len(menus)) + for i, m := range menus { + ids[i] = uint(m.SysBaseMenuID) + } + return ids, nil +} + +func (r *authorityRepo) CopyAuthorityBtns(ctx context.Context, oldAuthorityId, newAuthorityId uint) error { + var btns []model.SysAuthorityBtn + if err := r.db.WithContext(ctx).Where("authority_id = ?", oldAuthorityId). + Find(&btns).Error; err != nil { + return err + } + + if len(btns) == 0 { + return nil + } + + newAuthorityIdInt64 := int64(newAuthorityId) + for i := range btns { + btns[i].AuthorityID = &newAuthorityIdInt64 + } + + return r.db.WithContext(ctx).Create(&btns).Error +} + +func (r *authorityRepo) DeleteAuthorityRelations(ctx context.Context, authorityId uint) error { + return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + // 删除菜单权限 + if err := tx.Where("sys_authority_authority_id = ?", authorityId). + Delete(&model.SysAuthorityMenu{}).Error; err != nil { + return err + } + + // 删除数据权限 + if err := tx.Where("sys_authority_authority_id = ?", authorityId). + Delete(&model.SysDataAuthorityID{}).Error; err != nil { + return err + } + + // 删除用户角色关联 + if err := tx.Where("sys_authority_authority_id = ?", authorityId). + Delete(&model.SysUserAuthority{}).Error; err != nil { + return err + } + + // 删除按钮权限 + if err := tx.Where("authority_id = ?", authorityId). + Delete(&model.SysAuthorityBtn{}).Error; err != nil { + return err + } + + return nil + }) +} + +// 转换函数 +func toBizAuthority(m *model.SysAuthority) *system.Authority { + auth := &system.Authority{ + AuthorityId: uint(m.AuthorityID), + AuthorityName: safeString(m.AuthorityName), + DefaultRouter: safeString(m.DefaultRouter), + } + if m.ParentID != nil { + parentId := uint(*m.ParentID) + auth.ParentId = &parentId + } + return auth +} diff --git a/internal/data/system/menu.go b/internal/data/system/menu.go index 4c4096a..0d1aaa6 100644 --- a/internal/data/system/menu.go +++ b/internal/data/system/menu.go @@ -249,6 +249,40 @@ func (r *menuRepo) FindBaseMenuByID(ctx context.Context, id uint) (*system.BaseM return toBizBaseMenu(&menu, params, btns), nil } +// FindBaseMenuByName 根据名称获取基础菜单 +func (r *menuRepo) FindBaseMenuByName(ctx context.Context, name string) (*system.BaseMenu, error) { + var menu model.SysBaseMenu + if err := r.db.WithContext(ctx).Where("name = ?", name).First(&menu).Error; err != nil { + return nil, err + } + return toBizBaseMenu(&menu, nil, nil), nil +} + +// CheckMenuInAuthority 检查菜单是否在角色权限中 +func (r *menuRepo) CheckMenuInAuthority(ctx context.Context, authorityId uint, menuName string) (bool, error) { + // 获取角色的菜单ID列表 + var menuIds []string + if err := r.db.WithContext(ctx).Model(&model.SysAuthorityMenu{}). + Where("sys_authority_authority_id = ?", authorityId). + Pluck("sys_base_menu_id", &menuIds).Error; err != nil { + return false, err + } + + if len(menuIds) == 0 { + return false, nil + } + + // 检查菜单名称是否在这些菜单中 + var count int64 + if err := r.db.WithContext(ctx).Model(&model.SysBaseMenu{}). + Where("name = ? AND id IN (?)", menuName, menuIds). + Count(&count).Error; err != nil { + return false, err + } + + return count > 0, nil +} + // FindAllBaseMenus 获取所有基础菜单 func (r *menuRepo) FindAllBaseMenus(ctx context.Context) ([]*system.BaseMenu, error) { var menus []model.SysBaseMenu diff --git a/internal/service/system/authority.go b/internal/service/system/authority.go new file mode 100644 index 0000000..1cdc82d --- /dev/null +++ b/internal/service/system/authority.go @@ -0,0 +1,271 @@ +package system + +import ( + "context" + + "kra/internal/biz/system" + "kra/internal/server/middleware" + + "github.com/go-kratos/kratos/v2/errors" + "github.com/go-kratos/kratos/v2/transport/http" +) + +// AuthorityService 角色服务 +type AuthorityService struct { + uc *system.AuthorityUsecase + casbin system.CasbinRepo +} + +// NewAuthorityService 创建角色服务 +func NewAuthorityService(uc *system.AuthorityUsecase, casbin system.CasbinRepo) *AuthorityService { + return &AuthorityService{ + uc: uc, + casbin: casbin, + } +} + +// AuthorityInfo 角色信息 +type AuthorityFullInfo struct { + AuthorityId uint `json:"authorityId"` + AuthorityName string `json:"authorityName"` + ParentId *uint `json:"parentId,omitempty"` + DefaultRouter string `json:"defaultRouter"` + DataAuthorityId []*AuthorityInfo `json:"dataAuthorityId,omitempty"` + Children []*AuthorityFullInfo `json:"children,omitempty"` +} + +// CreateAuthorityRequest 创建角色请求 +type CreateAuthorityRequest struct { + AuthorityId uint `json:"authorityId"` + AuthorityName string `json:"authorityName"` + ParentId uint `json:"parentId"` + DefaultRouter string `json:"defaultRouter"` +} + +// CreateAuthority 创建角色 +func (s *AuthorityService) CreateAuthority(ctx context.Context, req *CreateAuthorityRequest) (*AuthorityFullInfo, error) { + auth := &system.Authority{ + AuthorityId: req.AuthorityId, + AuthorityName: req.AuthorityName, + DefaultRouter: req.DefaultRouter, + } + if req.ParentId != 0 { + auth.ParentId = &req.ParentId + } + + result, err := s.uc.CreateAuthority(ctx, auth) + if err != nil { + return nil, err + } + + // 刷新Casbin + _ = s.casbin.FreshCasbin() + + return toAuthorityFullInfo(result), nil +} + +// CopyAuthorityRequest 复制角色请求 +type CopyAuthorityRequest struct { + Authority CreateAuthorityRequest `json:"authority"` + OldAuthorityId uint `json:"oldAuthorityId"` +} + +// CopyAuthority 复制角色 +func (s *AuthorityService) CopyAuthority(ctx context.Context, adminAuthorityID uint, req *CopyAuthorityRequest) (*AuthorityFullInfo, error) { + newAuth := &system.Authority{ + AuthorityId: req.Authority.AuthorityId, + AuthorityName: req.Authority.AuthorityName, + DefaultRouter: req.Authority.DefaultRouter, + } + if req.Authority.ParentId != 0 { + newAuth.ParentId = &req.Authority.ParentId + } + + result, err := s.uc.CopyAuthority(ctx, adminAuthorityID, req.OldAuthorityId, newAuth) + if err != nil { + return nil, err + } + + return toAuthorityFullInfo(result), nil +} + +// UpdateAuthorityRequest 更新角色请求 +type UpdateAuthorityRequest struct { + AuthorityId uint `json:"authorityId"` + AuthorityName string `json:"authorityName"` + ParentId uint `json:"parentId"` + DefaultRouter string `json:"defaultRouter"` +} + +// UpdateAuthority 更新角色 +func (s *AuthorityService) UpdateAuthority(ctx context.Context, req *UpdateAuthorityRequest) (*AuthorityFullInfo, error) { + auth := &system.Authority{ + AuthorityId: req.AuthorityId, + AuthorityName: req.AuthorityName, + DefaultRouter: req.DefaultRouter, + } + if req.ParentId != 0 { + auth.ParentId = &req.ParentId + } + + result, err := s.uc.UpdateAuthority(ctx, auth) + if err != nil { + return nil, err + } + + return toAuthorityFullInfo(result), nil +} + +// DeleteAuthority 删除角色 +func (s *AuthorityService) DeleteAuthority(ctx context.Context, authorityId uint) error { + if err := s.uc.DeleteAuthority(ctx, authorityId); err != nil { + return err + } + + // 刷新Casbin + _ = s.casbin.FreshCasbin() + + return nil +} + +// GetAuthorityList 获取角色列表 +func (s *AuthorityService) GetAuthorityList(ctx context.Context, authorityId uint) ([]*AuthorityFullInfo, error) { + list, err := s.uc.GetAuthorityInfoList(ctx, authorityId) + if err != nil { + return nil, err + } + + return toAuthorityFullInfoList(list), nil +} + +// SetDataAuthorityRequest 设置角色资源权限请求 +type SetDataAuthorityRequest struct { + AuthorityId uint `json:"authorityId"` + DataAuthorityIds []uint `json:"dataAuthorityId"` +} + +// SetDataAuthority 设置角色资源权限 +func (s *AuthorityService) SetDataAuthority(ctx context.Context, adminAuthorityID uint, req *SetDataAuthorityRequest) error { + return s.uc.SetDataAuthority(ctx, adminAuthorityID, req.AuthorityId, req.DataAuthorityIds) +} + +// 转换函数 +func toAuthorityFullInfo(auth *system.Authority) *AuthorityFullInfo { + info := &AuthorityFullInfo{ + AuthorityId: auth.AuthorityId, + AuthorityName: auth.AuthorityName, + ParentId: auth.ParentId, + DefaultRouter: auth.DefaultRouter, + } + return info +} + +func toAuthorityFullInfoList(list []*system.AuthorityFull) []*AuthorityFullInfo { + result := make([]*AuthorityFullInfo, len(list)) + for i, auth := range list { + result[i] = &AuthorityFullInfo{ + AuthorityId: auth.AuthorityId, + AuthorityName: auth.AuthorityName, + ParentId: auth.ParentId, + DefaultRouter: auth.DefaultRouter, + Children: toAuthorityFullInfoList(auth.Children), + } + // DataAuthorityId + if len(auth.DataAuthorityId) > 0 { + result[i].DataAuthorityId = make([]*AuthorityInfo, len(auth.DataAuthorityId)) + for j, da := range auth.DataAuthorityId { + result[i].DataAuthorityId[j] = &AuthorityInfo{ + AuthorityId: da.AuthorityId, + AuthorityName: da.AuthorityName, + ParentId: da.ParentId, + DefaultRouter: da.DefaultRouter, + } + } + } + } + return result +} + +// RegisterRoutes 注册路由 +func (s *AuthorityService) RegisterRoutes(srv *http.Server) { + r := srv.Route("/") + + r.POST("/authority/createAuthority", s.handleCreateAuthority) + r.POST("/authority/copyAuthority", s.handleCopyAuthority) + r.PUT("/authority/updateAuthority", s.handleUpdateAuthority) + r.POST("/authority/deleteAuthority", s.handleDeleteAuthority) + r.POST("/authority/getAuthorityList", s.handleGetAuthorityList) + r.POST("/authority/setDataAuthority", s.handleSetDataAuthority) +} + +// HTTP Handlers +func (s *AuthorityService) handleCreateAuthority(ctx http.Context) error { + var req CreateAuthorityRequest + if err := ctx.Bind(&req); err != nil { + return err + } + resp, err := s.CreateAuthority(ctx, &req) + if err != nil { + return errors.BadRequest("CREATE_FAILED", err.Error()) + } + return ctx.Result(200, map[string]any{"code": 0, "msg": "创建成功", "data": map[string]any{"authority": resp}}) +} + +func (s *AuthorityService) handleCopyAuthority(ctx http.Context) error { + var req CopyAuthorityRequest + if err := ctx.Bind(&req); err != nil { + return err + } + adminAuthorityID := middleware.GetAuthorityID(ctx) + resp, err := s.CopyAuthority(ctx, adminAuthorityID, &req) + if err != nil { + return errors.BadRequest("COPY_FAILED", err.Error()) + } + return ctx.Result(200, map[string]any{"code": 0, "msg": "复制成功", "data": map[string]any{"authority": resp}}) +} + +func (s *AuthorityService) handleUpdateAuthority(ctx http.Context) error { + var req UpdateAuthorityRequest + if err := ctx.Bind(&req); err != nil { + return err + } + resp, err := s.UpdateAuthority(ctx, &req) + if err != nil { + return errors.BadRequest("UPDATE_FAILED", err.Error()) + } + return ctx.Result(200, map[string]any{"code": 0, "msg": "更新成功", "data": map[string]any{"authority": resp}}) +} + +func (s *AuthorityService) handleDeleteAuthority(ctx http.Context) error { + var req struct { + AuthorityId uint `json:"authorityId"` + } + if err := ctx.Bind(&req); err != nil { + return err + } + if err := s.DeleteAuthority(ctx, req.AuthorityId); err != nil { + return errors.BadRequest("DELETE_FAILED", err.Error()) + } + return ctx.Result(200, map[string]any{"code": 0, "msg": "删除成功"}) +} + +func (s *AuthorityService) handleGetAuthorityList(ctx http.Context) error { + authorityId := middleware.GetAuthorityID(ctx) + resp, err := s.GetAuthorityList(ctx, authorityId) + if err != nil { + return errors.InternalServer("LIST_ERROR", err.Error()) + } + return ctx.Result(200, map[string]any{"code": 0, "msg": "获取成功", "data": map[string]any{"list": resp}}) +} + +func (s *AuthorityService) handleSetDataAuthority(ctx http.Context) error { + var req SetDataAuthorityRequest + if err := ctx.Bind(&req); err != nil { + return err + } + adminAuthorityID := middleware.GetAuthorityID(ctx) + if err := s.SetDataAuthority(ctx, adminAuthorityID, &req); err != nil { + return errors.BadRequest("SET_FAILED", err.Error()) + } + return ctx.Result(200, map[string]any{"code": 0, "msg": "设置成功"}) +}