Files
junhong_cmp_fiber/internal/service/permission/service.go
huang 5556b1028c feat(role): 新增平台角色管理功能增强
- 权限表增加 available_for_role_types 字段,支持标记权限可用角色类型
- 权限列表和权限树接口支持按 available_for_role_type 过滤
- 新增角色状态切换接口 PUT /api/admin/roles/:id/status
- 角色分配权限时验证权限的可用角色类型
- 完善数据库迁移脚本和单元测试
- 补充数据库迁移相关开发规范文档
2026-01-14 12:15:57 +08:00

281 lines
8.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package permission 提供权限管理的业务逻辑服务
// 包含权限创建、查询、更新、删除、权限检查等功能
package permission
import (
"context"
"fmt"
"regexp"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/store"
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/middleware"
"gorm.io/gorm"
)
// permCodeRegex 权限编码格式验证正则module:action
var permCodeRegex = regexp.MustCompile(`^[a-z][a-z0-9_]*:[a-z][a-z0-9_]*$`)
// Service 权限业务服务
type Service struct {
permissionStore *postgres.PermissionStore
}
// New 创建权限服务
func New(permissionStore *postgres.PermissionStore) *Service {
return &Service{
permissionStore: permissionStore,
}
}
// Create 创建权限
func (s *Service) Create(ctx context.Context, req *model.CreatePermissionRequest) (*model.Permission, error) {
// 获取当前用户 ID
currentUserID := middleware.GetUserIDFromContext(ctx)
if currentUserID == 0 {
return nil, errors.New(errors.CodeUnauthorized, "未授权访问")
}
// 验证权限编码格式
if !permCodeRegex.MatchString(req.PermCode) {
return nil, errors.New(errors.CodeInvalidPermCode, "权限编码格式不正确(应为 module:action 格式)")
}
// 检查权限编码唯一性
existing, err := s.permissionStore.GetByCode(ctx, req.PermCode)
if err == nil && existing != nil {
return nil, errors.New(errors.CodePermCodeExists, "权限编码已存在")
}
// 验证 parent_id 存在(如果提供)
if req.ParentID != nil {
parent, err := s.permissionStore.GetByID(ctx, *req.ParentID)
if err != nil || parent == nil {
return nil, errors.New(errors.CodeNotFound, "上级权限不存在")
}
}
// 创建权限
permission := &model.Permission{
PermName: req.PermName,
PermCode: req.PermCode,
PermType: req.PermType,
Platform: req.Platform,
URL: req.URL,
ParentID: req.ParentID,
Sort: req.Sort,
Status: constants.StatusEnabled,
}
// 如果未指定 platform默认为 all
if permission.Platform == "" {
permission.Platform = constants.PlatformAll
}
if err := s.permissionStore.Create(ctx, permission); err != nil {
return nil, fmt.Errorf("创建权限失败: %w", err)
}
return permission, nil
}
// Get 获取权限
func (s *Service) Get(ctx context.Context, id uint) (*model.Permission, error) {
permission, err := s.permissionStore.GetByID(ctx, id)
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, errors.New(errors.CodePermissionNotFound, "权限不存在")
}
return nil, fmt.Errorf("获取权限失败: %w", err)
}
return permission, nil
}
// Update 更新权限
func (s *Service) Update(ctx context.Context, id uint, req *model.UpdatePermissionRequest) (*model.Permission, error) {
// 获取当前用户 ID
currentUserID := middleware.GetUserIDFromContext(ctx)
if currentUserID == 0 {
return nil, errors.New(errors.CodeUnauthorized, "未授权访问")
}
// 获取现有权限
permission, err := s.permissionStore.GetByID(ctx, id)
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, errors.New(errors.CodePermissionNotFound, "权限不存在")
}
return nil, fmt.Errorf("获取权限失败: %w", err)
}
// 更新字段
if req.PermName != nil {
permission.PermName = *req.PermName
}
if req.PermCode != nil {
// 验证权限编码格式
if !permCodeRegex.MatchString(*req.PermCode) {
return nil, errors.New(errors.CodeInvalidPermCode, "权限编码格式不正确(应为 module:action 格式)")
}
// 检查新权限编码唯一性
existing, err := s.permissionStore.GetByCode(ctx, *req.PermCode)
if err == nil && existing != nil && existing.ID != id {
return nil, errors.New(errors.CodePermCodeExists, "权限编码已存在")
}
permission.PermCode = *req.PermCode
}
if req.Platform != nil {
permission.Platform = *req.Platform
}
if req.URL != nil {
permission.URL = *req.URL
}
if req.ParentID != nil {
// 验证 parent_id 存在
parent, err := s.permissionStore.GetByID(ctx, *req.ParentID)
if err != nil || parent == nil {
return nil, errors.New(errors.CodeNotFound, "上级权限不存在")
}
permission.ParentID = req.ParentID
}
if req.Sort != nil {
permission.Sort = *req.Sort
}
if req.Status != nil {
permission.Status = *req.Status
}
permission.Updater = currentUserID
if err := s.permissionStore.Update(ctx, permission); err != nil {
return nil, fmt.Errorf("更新权限失败: %w", err)
}
return permission, nil
}
// Delete 软删除权限
func (s *Service) Delete(ctx context.Context, id uint) error {
// 检查权限存在
_, err := s.permissionStore.GetByID(ctx, id)
if err != nil {
if err == gorm.ErrRecordNotFound {
return errors.New(errors.CodePermissionNotFound, "权限不存在")
}
return fmt.Errorf("获取权限失败: %w", err)
}
if err := s.permissionStore.Delete(ctx, id); err != nil {
return fmt.Errorf("删除权限失败: %w", err)
}
return nil
}
// List 查询权限列表
func (s *Service) List(ctx context.Context, req *model.PermissionListRequest) ([]*model.Permission, int64, error) {
opts := &store.QueryOptions{
Page: req.Page,
PageSize: req.PageSize,
OrderBy: "sort ASC, id ASC",
}
if opts.Page == 0 {
opts.Page = 1
}
if opts.PageSize == 0 {
opts.PageSize = constants.DefaultPageSize
}
filters := make(map[string]interface{})
if req.PermName != "" {
filters["perm_name"] = req.PermName
}
if req.PermCode != "" {
filters["perm_code"] = req.PermCode
}
if req.PermType != nil {
filters["perm_type"] = *req.PermType
}
if req.Platform != "" {
filters["platform"] = req.Platform
}
if req.AvailableForRoleType != nil {
filters["available_for_role_type"] = *req.AvailableForRoleType
}
if req.ParentID != nil {
filters["parent_id"] = *req.ParentID
}
if req.Status != nil {
filters["status"] = *req.Status
}
return s.permissionStore.List(ctx, opts, filters)
}
// GetTree 获取权限树
func (s *Service) GetTree(ctx context.Context, availableForRoleType *int) ([]*model.PermissionTreeNode, error) {
permissions, err := s.permissionStore.GetAll(ctx, availableForRoleType)
if err != nil {
return nil, fmt.Errorf("获取权限列表失败: %w", err)
}
return buildPermissionTree(permissions), nil
}
// buildPermissionTree 构建权限树
func buildPermissionTree(permissions []*model.Permission) []*model.PermissionTreeNode {
nodeMap := make(map[uint]*model.PermissionTreeNode)
for _, p := range permissions {
nodeMap[p.ID] = &model.PermissionTreeNode{
ID: p.ID,
PermName: p.PermName,
PermCode: p.PermCode,
PermType: p.PermType,
Platform: p.Platform,
AvailableForRoleTypes: p.AvailableForRoleTypes,
URL: p.URL,
Sort: p.Sort,
Children: make([]*model.PermissionTreeNode, 0),
}
}
var roots []*model.PermissionTreeNode
for _, p := range permissions {
node := nodeMap[p.ID]
if p.ParentID == nil || *p.ParentID == 0 {
roots = append(roots, node)
} else if parent, ok := nodeMap[*p.ParentID]; ok {
parent.Children = append(parent.Children, node)
} else {
roots = append(roots, node)
}
}
return roots
}
// CheckPermission 检查用户是否拥有指定权限(实现 PermissionChecker 接口)
// userID: 用户ID
// permCode: 权限编码
// platform: 端口类型 (all/web/h5)
func (s *Service) CheckPermission(ctx context.Context, userID uint, permCode string, platform string) (bool, error) {
// 查询用户的所有权限(通过角色获取)
// 1. 先获取用户的角色列表
// 2. 再获取角色的权限列表
// 3. 检查是否包含指定权限编码,并且 platform 匹配
// 注意:这个方法需要访问 AccountRoleStore 和 RolePermissionStore
// 但为了避免循环依赖,我们可以:
// 方案1: 在 Service 中注入这些 Store推荐
// 方案2: 在 PermissionStore 中添加一个查询方法
// 方案3: 使用缓存层Redis来存储用户权限映射
// 这里先返回一个占位实现
// TODO: 实现完整的权限检查逻辑
// 需要在构造函数中注入 AccountRoleStore 和 RolePermissionStore
return false, errors.New(errors.CodeInternalError, "权限检查功能尚未完全实现")
}