mengstack-api/internal/modules/rbac/application/service.go
MengStack Dev f8c374a624 feat(rbac): M3 RBAC 权限模型完整实现
- 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
2026-10-03 00:54:26 +08:00

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
}