feat: 优化web
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"giter.top/smart/internal/iam/entity"
|
||||
"giter.top/smart/internal/iam/repository"
|
||||
"giter.top/smart/pkg/utils/id"
|
||||
)
|
||||
|
||||
// RoleService 角色
|
||||
type RoleService interface {
|
||||
Create(ctx context.Context, tenantID string, req *CreateRoleRequest, grantorUserID *string) (*entity.Role, error)
|
||||
Update(ctx context.Context, tenantID string, rid string, req *UpdateRoleRequest, grantorUserID *string) (*entity.Role, error)
|
||||
Delete(ctx context.Context, tenantID string, ids []string) error
|
||||
Get(ctx context.Context, tenantID string, rid string) (*entity.Role, error)
|
||||
List(ctx context.Context, tenantID string, name, code string, page, pageSize int) (*RoleListResponse, error)
|
||||
AssignMenus(ctx context.Context, tenantID string, roleID string, menuIDs []string, grantorUserID *string) error
|
||||
}
|
||||
|
||||
type CreateRoleRequest struct {
|
||||
RoleCode string `json:"role_code" binding:"required,max=64"`
|
||||
RoleName string `json:"role_name" binding:"required,max=128"`
|
||||
DataScope int16 `json:"data_scope" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
MenuIDs []string `json:"menu_ids"`
|
||||
}
|
||||
|
||||
type UpdateRoleRequest struct {
|
||||
RoleName *string `json:"role_name"`
|
||||
DataScope *int16 `json:"data_scope"`
|
||||
Description *string `json:"description"`
|
||||
MenuIDs []string `json:"menu_ids"`
|
||||
}
|
||||
|
||||
type RoleListResponse struct {
|
||||
Items []entity.Role `json:"items"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
}
|
||||
|
||||
type roleService struct {
|
||||
roles repository.RoleRepository
|
||||
users repository.UserRepository
|
||||
menus repository.MenuRepository
|
||||
}
|
||||
|
||||
func NewRoleService(roles repository.RoleRepository, users repository.UserRepository, menus repository.MenuRepository) RoleService {
|
||||
return &roleService{roles: roles, users: users, menus: menus}
|
||||
}
|
||||
|
||||
func (s *roleService) grantorMenuSet(ctx context.Context, grantorUserID string) (map[string]struct{}, error) {
|
||||
rids, err := s.users.ListRoleIDs(ctx, grantorUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ids, err := s.roles.ListMenuIDsByRoles(ctx, rids)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m := make(map[string]struct{}, len(ids))
|
||||
for _, mid := range ids {
|
||||
m[mid] = struct{}{}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (s *roleService) assertMenuSubset(ctx context.Context, grantorUserID *string, menuIDs []string) error {
|
||||
if grantorUserID == nil || *grantorUserID == "" {
|
||||
return nil
|
||||
}
|
||||
allowed, err := s.grantorMenuSet(ctx, *grantorUserID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, mid := range menuIDs {
|
||||
if _, ok := allowed[mid]; !ok {
|
||||
return fmt.Errorf("防越权: 不能分配自身未拥有的菜单权限 (menu_id=%s)", mid)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *roleService) Create(ctx context.Context, tenantID string, req *CreateRoleRequest, grantorUserID *string) (*entity.Role, error) {
|
||||
ok, err := s.roles.ExistsCode(ctx, tenantID, req.RoleCode, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ok {
|
||||
return nil, fmt.Errorf("角色编码已存在")
|
||||
}
|
||||
if err := s.assertMenuSubset(ctx, grantorUserID, req.MenuIDs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := &entity.Role{
|
||||
ID: id.New(),
|
||||
TenantID: tenantID,
|
||||
RoleCode: req.RoleCode,
|
||||
RoleName: req.RoleName,
|
||||
DataScope: req.DataScope,
|
||||
Description: req.Description,
|
||||
Status: 1,
|
||||
}
|
||||
if err := s.roles.Create(ctx, r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(req.MenuIDs) > 0 {
|
||||
if err := s.roles.ReplaceRoleMenus(ctx, r.ID, req.MenuIDs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (s *roleService) Update(ctx context.Context, tenantID string, rid string, req *UpdateRoleRequest, grantorUserID *string) (*entity.Role, error) {
|
||||
r, err := s.roles.GetByID(ctx, rid)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrNotFound) {
|
||||
return nil, fmt.Errorf("角色不存在")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if r.TenantID != tenantID {
|
||||
return nil, fmt.Errorf("角色不属于当前租户")
|
||||
}
|
||||
if r.IsBuiltin {
|
||||
// 内置角色仅允许改部分字段(MVP:允许改名称与数据范围与菜单需业务再定)
|
||||
}
|
||||
if req.RoleName != nil {
|
||||
r.RoleName = *req.RoleName
|
||||
}
|
||||
if req.DataScope != nil {
|
||||
r.DataScope = *req.DataScope
|
||||
}
|
||||
if req.Description != nil {
|
||||
r.Description = *req.Description
|
||||
}
|
||||
if err := s.roles.Update(ctx, r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if req.MenuIDs != nil {
|
||||
if err := s.assertMenuSubset(ctx, grantorUserID, req.MenuIDs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.roles.ReplaceRoleMenus(ctx, r.ID, req.MenuIDs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (s *roleService) Delete(ctx context.Context, tenantID string, ids []string) error {
|
||||
for _, rid := range ids {
|
||||
r, err := s.roles.GetByID(ctx, rid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.TenantID != tenantID {
|
||||
return fmt.Errorf("角色 %s 不属于当前租户", rid)
|
||||
}
|
||||
if r.IsBuiltin {
|
||||
return fmt.Errorf("内置角色不可删除")
|
||||
}
|
||||
n, err := s.roles.CountUsers(ctx, rid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n > 0 {
|
||||
return fmt.Errorf("角色仍被用户使用")
|
||||
}
|
||||
if err := s.roles.Delete(ctx, rid); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *roleService) Get(ctx context.Context, tenantID string, rid string) (*entity.Role, error) {
|
||||
r, err := s.roles.GetByID(ctx, rid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if r.TenantID != tenantID {
|
||||
return nil, fmt.Errorf("角色不属于当前租户")
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (s *roleService) List(ctx context.Context, tenantID string, name, code string, page, pageSize int) (*RoleListResponse, error) {
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize <= 0 {
|
||||
pageSize = 10
|
||||
}
|
||||
rows, total, err := s.roles.List(ctx, tenantID, name, code, page, pageSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tp := int(total) / pageSize
|
||||
if int(total)%pageSize != 0 {
|
||||
tp++
|
||||
}
|
||||
return &RoleListResponse{Items: rows, Total: total, Page: page, PageSize: pageSize, TotalPages: tp}, nil
|
||||
}
|
||||
|
||||
func (s *roleService) AssignMenus(ctx context.Context, tenantID string, roleID string, menuIDs []string, grantorUserID *string) error {
|
||||
r, err := s.roles.GetByID(ctx, roleID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.TenantID != tenantID {
|
||||
return fmt.Errorf("角色不属于当前租户")
|
||||
}
|
||||
if err := s.assertMenuSubset(ctx, grantorUserID, menuIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.roles.ReplaceRoleMenus(ctx, roleID, menuIDs)
|
||||
}
|
||||
Reference in New Issue
Block a user