|
@@ -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
|