- 4 表设计: permissions, roles, role_permissions(m2m), user_roles - 多租户隔离: 角色和用户角色分配按 tenant_id 隔离 - 权限并集: 多角色用户取权限并集,无继承 - 系统角色保护: is_system 角色不可修改/删除 - 种子数据: 7 个默认权限 + 3 个系统角色(super_admin/tenant_admin/member) - 权限中间件: RequirePermission 支持按路由粒度控制 - API 路由: /api/v1/rbac/ 下完整 CRUD 端点 - fx 注册: RBAC Module 集成到 app.go
267 lines
6.8 KiB
Go
267 lines
6.8 KiB
Go
package application
|
|
|
|
import (
|
|
"context"
|
|
|
|
"mengstack/internal/kernel/errors"
|
|
"mengstack/internal/kernel/tenant"
|
|
"mengstack/internal/modules/rbac/domain"
|
|
)
|
|
|
|
type Service struct {
|
|
permRepo domain.PermissionRepository
|
|
roleRepo domain.RoleRepository
|
|
userRoleRepo domain.UserRoleRepository
|
|
}
|
|
|
|
func NewService(
|
|
permRepo domain.PermissionRepository,
|
|
roleRepo domain.RoleRepository,
|
|
userRoleRepo domain.UserRoleRepository,
|
|
) *Service {
|
|
return &Service{
|
|
permRepo: permRepo,
|
|
roleRepo: roleRepo,
|
|
userRoleRepo: userRoleRepo,
|
|
}
|
|
}
|
|
|
|
func (s *Service) ListPermissions(ctx context.Context) ([]domain.PermissionDTO, error) {
|
|
perms, err := s.permRepo.FindAll(ctx)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "QUERY_PERMS_FAILED", "failed to query permissions", 500)
|
|
}
|
|
dtos := make([]domain.PermissionDTO, len(perms))
|
|
for i, p := range perms {
|
|
dtos[i] = domain.ToPermissionDTO(&p)
|
|
}
|
|
return dtos, nil
|
|
}
|
|
|
|
func (s *Service) ListPermissionsByModule(ctx context.Context, module string) ([]domain.PermissionDTO, error) {
|
|
perms, err := s.permRepo.FindByModule(ctx, module)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "QUERY_PERMS_FAILED", "failed to query permissions", 500)
|
|
}
|
|
dtos := make([]domain.PermissionDTO, len(perms))
|
|
for i, p := range perms {
|
|
dtos[i] = domain.ToPermissionDTO(&p)
|
|
}
|
|
return dtos, nil
|
|
}
|
|
|
|
func (s *Service) CreateRole(ctx context.Context, req domain.CreateRoleRequest) (domain.RoleDTO, error) {
|
|
tenantID, ok := tenant.FromContext(ctx)
|
|
if !ok {
|
|
return domain.RoleDTO{}, errors.ErrTenantRequired
|
|
}
|
|
|
|
role := &domain.Role{
|
|
TenantID: tenantID,
|
|
Name: req.Name,
|
|
Description: req.Description,
|
|
}
|
|
|
|
if err := s.roleRepo.Create(ctx, role); err != nil {
|
|
return domain.RoleDTO{}, errors.Wrap(err, "CREATE_ROLE_FAILED", "failed to create role", 500)
|
|
}
|
|
|
|
return domain.ToRoleDTO(role), nil
|
|
}
|
|
|
|
func (s *Service) ListRoles(ctx context.Context) ([]domain.RoleDTO, error) {
|
|
tenantID, ok := tenant.FromContext(ctx)
|
|
if !ok {
|
|
return nil, errors.ErrTenantRequired
|
|
}
|
|
|
|
roles, err := s.roleRepo.FindByTenant(ctx, tenantID)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "QUERY_ROLES_FAILED", "failed to query roles", 500)
|
|
}
|
|
|
|
dtos := make([]domain.RoleDTO, len(roles))
|
|
for i, r := range roles {
|
|
dtos[i] = domain.ToRoleDTO(&r)
|
|
}
|
|
return dtos, nil
|
|
}
|
|
|
|
func (s *Service) GetRole(ctx context.Context, roleID uint) (domain.RoleDTO, error) {
|
|
tenantID, ok := tenant.FromContext(ctx)
|
|
if !ok {
|
|
return domain.RoleDTO{}, errors.ErrTenantRequired
|
|
}
|
|
|
|
role, err := s.roleRepo.FindByIDWithPermissions(ctx, tenantID, roleID)
|
|
if err != nil {
|
|
return domain.RoleDTO{}, errors.ErrNotFound
|
|
}
|
|
|
|
return domain.ToRoleDTO(role), nil
|
|
}
|
|
|
|
func (s *Service) UpdateRole(ctx context.Context, roleID uint, req domain.UpdateRoleRequest) (domain.RoleDTO, error) {
|
|
tenantID, ok := tenant.FromContext(ctx)
|
|
if !ok {
|
|
return domain.RoleDTO{}, errors.ErrTenantRequired
|
|
}
|
|
|
|
role, err := s.roleRepo.FindByID(ctx, tenantID, roleID)
|
|
if err != nil {
|
|
return domain.RoleDTO{}, errors.ErrNotFound
|
|
}
|
|
|
|
if role.IsSystem {
|
|
return domain.RoleDTO{}, errors.New("SYSTEM_ROLE", "cannot modify system role", 403)
|
|
}
|
|
|
|
if req.Name != "" {
|
|
role.Name = req.Name
|
|
}
|
|
if req.Description != "" {
|
|
role.Description = req.Description
|
|
}
|
|
|
|
if err := s.roleRepo.Update(ctx, role); err != nil {
|
|
return domain.RoleDTO{}, errors.Wrap(err, "UPDATE_ROLE_FAILED", "failed to update role", 500)
|
|
}
|
|
|
|
return domain.ToRoleDTO(role), nil
|
|
}
|
|
|
|
func (s *Service) DeleteRole(ctx context.Context, roleID uint) error {
|
|
tenantID, ok := tenant.FromContext(ctx)
|
|
if !ok {
|
|
return errors.ErrTenantRequired
|
|
}
|
|
|
|
role, err := s.roleRepo.FindByID(ctx, tenantID, roleID)
|
|
if err != nil {
|
|
return errors.ErrNotFound
|
|
}
|
|
|
|
if role.IsSystem {
|
|
return errors.New("SYSTEM_ROLE", "cannot delete system role", 403)
|
|
}
|
|
|
|
if err := s.roleRepo.Delete(ctx, tenantID, roleID); err != nil {
|
|
return errors.Wrap(err, "DELETE_ROLE_FAILED", "failed to delete role", 500)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) SetRolePermissions(ctx context.Context, roleID uint, req domain.SetRolePermissionsRequest) error {
|
|
tenantID, ok := tenant.FromContext(ctx)
|
|
if !ok {
|
|
return errors.ErrTenantRequired
|
|
}
|
|
|
|
role, err := s.roleRepo.FindByID(ctx, tenantID, roleID)
|
|
if err != nil {
|
|
return errors.ErrNotFound
|
|
}
|
|
|
|
if role.IsSystem {
|
|
return errors.New("SYSTEM_ROLE", "cannot modify system role permissions", 403)
|
|
}
|
|
|
|
for _, pid := range req.PermissionIDs {
|
|
if _, err := s.permRepo.FindByID(ctx, pid); err != nil {
|
|
return errors.New("PERM_NOT_FOUND", "permission not found", 404)
|
|
}
|
|
}
|
|
|
|
if err := s.roleRepo.SetPermissions(ctx, roleID, req.PermissionIDs); err != nil {
|
|
return errors.Wrap(err, "SET_PERMS_FAILED", "failed to set permissions", 500)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) AssignRoleToUser(ctx context.Context, userID uint, req domain.AssignRoleRequest) error {
|
|
tenantID, ok := tenant.FromContext(ctx)
|
|
if !ok {
|
|
return errors.ErrTenantRequired
|
|
}
|
|
|
|
if _, err := s.roleRepo.FindByID(ctx, tenantID, req.RoleID); err != nil {
|
|
return errors.ErrNotFound
|
|
}
|
|
|
|
ur := &domain.UserRole{
|
|
UserID: userID,
|
|
TenantID: tenantID,
|
|
RoleID: req.RoleID,
|
|
}
|
|
|
|
if err := s.userRoleRepo.Assign(ctx, ur); err != nil {
|
|
return errors.Wrap(err, "ASSIGN_ROLE_FAILED", "failed to assign role", 500)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) RemoveRoleFromUser(ctx context.Context, userID uint, roleID uint) error {
|
|
tenantID, ok := tenant.FromContext(ctx)
|
|
if !ok {
|
|
return errors.ErrTenantRequired
|
|
}
|
|
|
|
if err := s.userRoleRepo.Remove(ctx, userID, tenantID, roleID); err != nil {
|
|
return errors.Wrap(err, "REMOVE_ROLE_FAILED", "failed to remove role", 500)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) GetUserPermissions(ctx context.Context, userID uint) ([]domain.PermissionDTO, error) {
|
|
tenantID, ok := tenant.FromContext(ctx)
|
|
if !ok {
|
|
return nil, errors.ErrTenantRequired
|
|
}
|
|
|
|
roles, err := s.userRoleRepo.FindRolesByUser(ctx, userID, tenantID)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "QUERY_USER_ROLES_FAILED", "failed to query user roles", 500)
|
|
}
|
|
|
|
permMap := make(map[uint]domain.PermissionDTO)
|
|
for _, role := range roles {
|
|
for _, perm := range role.Permissions {
|
|
if _, exists := permMap[perm.ID]; !exists {
|
|
permMap[perm.ID] = domain.ToPermissionDTO(&perm)
|
|
}
|
|
}
|
|
}
|
|
|
|
dtos := make([]domain.PermissionDTO, 0, len(permMap))
|
|
for _, dto := range permMap {
|
|
dtos = append(dtos, dto)
|
|
}
|
|
return dtos, nil
|
|
}
|
|
|
|
func (s *Service) UserHasPermission(ctx context.Context, userID uint, permCode string) (bool, error) {
|
|
tenantID, ok := tenant.FromContext(ctx)
|
|
if !ok {
|
|
return false, errors.ErrTenantRequired
|
|
}
|
|
|
|
roles, err := s.userRoleRepo.FindRolesByUser(ctx, userID, tenantID)
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "QUERY_USER_ROLES_FAILED", "failed to query user roles", 500)
|
|
}
|
|
|
|
for _, role := range roles {
|
|
for _, perm := range role.Permissions {
|
|
if perm.Code == permCode {
|
|
return true, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|