Kaynağa Gözat

fix(admin): 优化菜单列表获取逻辑并添加排序功能

- 提前判断超级管理员身份,简化权限判断逻辑
- 在获取菜单列表时建议进行初步排序
- 添加稳定的菜单排序功能,确保每次返回的菜单顺序一致
- 优化了菜单数据结构的处理流程,提高了代码可读性和维护性
fusu 1 ay önce
ebeveyn
işleme
8ce8f5e054
1 değiştirilmiş dosya ile 34 ekleme ve 27 silme
  1. 34 27
      internal/service/admin.go

+ 34 - 27
internal/service/admin.go

@@ -10,6 +10,7 @@ import (
 	"go.uber.org/zap"
 	"golang.org/x/crypto/bcrypt"
 	"gorm.io/gorm"
+	"sort"
 
 	"strings"
 	"time"
@@ -329,9 +330,11 @@ func (s *adminService) MenuDelete(ctx context.Context, id uint) error {
 	return s.adminRepository.MenuDelete(ctx, id)
 }
 
+
 func (s *adminService) GetMenus(ctx context.Context, uid uint) (*v1.GetMenuResponseData, error) {
 	// 步骤 1: 从数据库中一次性获取所有菜单项。
-	menuList, err := s.adminRepository.GetMenuList(ctx)
+	// 建议在这里就进行初步排序,确保基础顺序
+	menuList, err := s.adminRepository.GetMenuList(ctx) // 最好让此方法返回按 Weight, ID 排序后的结果
 	if err != nil {
 		s.logger.WithContext(ctx).Error("GetMenuList error", zap.Error(err))
 		return nil, err
@@ -344,7 +347,6 @@ func (s *adminService) GetMenus(ctx context.Context, uid uint) (*v1.GetMenuRespo
 	}
 
 	// 步骤 3: 创建一个以菜单ID为键、菜单对象指针为值的映射(Map),用于快速查找。
-	// 这样可以避免在后续操作中反复遍历列表,提高效率。
 	allMenusMap := make(map[uint]*model.Menu)
 	for i := range menuList {
 		allMenusMap[menuList[i].ID] = &menuList[i]
@@ -352,35 +354,25 @@ func (s *adminService) GetMenus(ctx context.Context, uid uint) (*v1.GetMenuRespo
 
 	// 步骤 4: 创建一个集合(Set),存放用户被明确授权可以访问的菜单路径。
 	permittedMenuPaths := make(map[string]struct{})
+	isSuperAdmin := convertor.ToString(uid) == model.AdminUserID // 提前判断是否为超级管理员
+
 	for _, permission := range permissions {
-		// 特殊处理:超级管理员(Admin)拥有所有菜单的权限。
-		if convertor.ToString(uid) == model.AdminUserID {
-			// Casbin返回的权限格式通常是 [role, resource, action],我们取 resource (permission[1])。
-			// 并移除菜单资源特有的前缀,得到干净的菜单路径。
+		// 超级管理员拥有所有菜单权限,或普通用户拥有特定菜单权限
+		if isSuperAdmin || (len(permission) == 3 && strings.HasPrefix(permission[1], model.MenuResourcePrefix)) {
+			// 移除菜单资源特有的前缀,得到干净的菜单路径。
 			permittedMenuPaths[strings.TrimPrefix(permission[1], model.MenuResourcePrefix)] = struct{}{}
-		} else {
-			// 普通用户的权限策略,只处理菜单相关的权限。
-			if len(permission) == 3 && strings.HasPrefix(permission[1], model.MenuResourcePrefix) {
-				permittedMenuPaths[strings.TrimPrefix(permission[1], model.MenuResourcePrefix)] = struct{}{}
-			}
 		}
 	}
 
 	// 步骤 5: 构建最终的菜单列表,核心逻辑是确保所有有权限访问的子菜单,其父菜单也一定会被包含进来。
-	// 使用 Map (finalMenusMap) 来存放最终结果,可以巧妙地实现自动去重。
 	finalMenusMap := make(map[uint]v1.MenuDataItem)
 	for _, menu := range menuList {
-		// 检查当前遍历到的菜单,其路径是否在用户的权限集合内。
 		if _, ok := permittedMenuPaths[menu.Path]; ok {
-			// 如果用户有权访问此菜单,则启动一个循环,向上追溯并添加其所有的父级菜单。
-			curr := &menu // 从当前这个有权限的菜单开始追溯。
+			curr := &menu
 			for curr != nil {
-				// 如果当前菜单(或其父菜单)已经存在于最终结果中,说明这条路径已经处理过,跳出循环以避免重复工作。
 				if _, exists := finalMenusMap[curr.ID]; exists {
 					break
 				}
-
-				// 将当前菜单转换为API需要的数据结构,并添加到最终结果Map中。
 				finalMenusMap[curr.ID] = v1.MenuDataItem{
 					ID:         curr.ID,
 					Name:       curr.Name,
@@ -397,25 +389,40 @@ func (s *adminService) GetMenus(ctx context.Context, uid uint) (*v1.GetMenuRespo
 					UpdatedAt:  curr.UpdatedAt.Format("2006-01-02 15:04:05"),
 					URL:        curr.URL,
 				}
-
-				// 移动到父菜单,准备下一次循环。
 				if curr.ParentID > 0 {
-					// 利用步骤3创建的快速索引Map,高效地找到父菜单对象。
 					curr = allMenusMap[curr.ParentID]
 				} else {
-					// 如果没有ParentID了,说明已经追溯到根节点,结束循环。
 					curr = nil
 				}
 			}
 		}
 	}
 
-	// 步骤 6: 将Map形式的最终结果转换为列表(Slice),以符合API响应的格式。
-	data := &v1.GetMenuResponseData{
-		List: make([]v1.MenuDataItem, 0, len(finalMenusMap)),
-	}
+	// 步骤 6: 将Map形式的最终结果转换为列表(Slice)。
+	list := make([]v1.MenuDataItem, 0, len(finalMenusMap))
 	for _, menuItem := range finalMenusMap {
-		data.List = append(data.List, menuItem)
+		list = append(list, menuItem)
+	}
+
+	// ================= FIX STARTS HERE =================
+
+	// 步骤 7: 对菜单列表进行排序,确保每次返回的顺序一致。
+	// 排序规则:
+	// 1. 主要按权重 (Weight) 升序排列。
+	// 2. 如果权重相同,则按 ID 升序排列,作为稳定的次要排序,确保顺序唯一。
+	sort.Slice(list, func(i, j int) bool {
+		// 如果权重不同,按权重比较
+		if list[i].Weight != list[j].Weight {
+			return list[i].Weight < list[j].Weight
+		}
+		// 如果权重相同,按ID比较,保证稳定排序
+		return list[i].ID < list[j].ID
+	})
+
+	// ================= FIX ENDS HERE ===================
+
+	data := &v1.GetMenuResponseData{
+		List: list,
 	}
 
 	return data, nil