xuhy
2026-04-23 d4ee06a8a87ebcba64c22a45f04edca4085f28c0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
package com.xiaopeng.operation.service.impl;
 
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiaopeng.api.feignClient.account.SysGroupMenuClient;
import com.xiaopeng.api.feignClient.account.SysRoleClient;
import com.xiaopeng.api.feignClient.account.SysUserRoleClient;
import com.xiaopeng.api.feignClient.account.XpOperationsRoleClient;
import com.xiaopeng.api.model.account.SysRole;
import com.xiaopeng.api.model.account.SysUserRole;
import com.xiaopeng.api.model.account.XpOperationsRole;
import com.xiaopeng.api.model.operation.SysMenu;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.security.service.TokenService;
import com.xiaopeng.operation.mapper.SysMenuMapper;
import com.xiaopeng.operation.service.ISysMenuService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
 
import java.util.*;
import java.util.stream.Collectors;
 
/**
 * <p>
 * 菜单权限表 服务实现类
 * </p>
 *
 * @author mitao
 * @since 2026-02-03
 */
@Slf4j
@Service
public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> implements ISysMenuService {
 
    @Autowired
    private TokenService tokenService;
 
    @Autowired
    private SysUserRoleClient sysUserRoleClient;
 
    @Autowired
    private SysRoleClient sysRoleClient;
 
    @Autowired
    private SysGroupMenuClient sysGroupMenuClient;
 
    @Autowired
    private XpOperationsRoleClient xpOperationsRoleClient;
 
    /**
     * 根据菜单ID列表查询菜单列表
     *
     * @param menuIds 菜单ID列表
     * @return 菜单列表
     */
    @Override
    public List<SysMenu> selectMenusByMenuIds(List<Long> menuIds) {
        if (menuIds == null || menuIds.isEmpty()) {
            return new ArrayList<>();
        }
 
        // 使用 MyBatis-Plus 的 listByIds 方法批量查询
        return this.listByIds(menuIds);
    }
 
    @Override
    public List<SysMenu> getMenuTree(Integer type) {
        List<SysMenu> all = lambdaQuery().eq(SysMenu::getGroupType, type).list();
        if (all == null || all.isEmpty()) {
            return Collections.emptyList();
        }
 
        // 1) 按 parentId 分组(M/C/F 都先放进去,后面再按类型筛)
        Map<Long, List<SysMenu>> childrenMap = all.stream()
                .filter(Objects::nonNull)
                .collect(Collectors.groupingBy(m -> m.getParentId() == null ? 0L : m.getParentId()));
 
        // 2) 找到根目录:通常 parentId = 0 或 null 的目录(M)
        List<SysMenu> roots = childrenMap.getOrDefault(0L, Collections.emptyList()).stream()
                .filter(m -> "M".equals(m.getMenuType()))
                .sorted(Comparator.comparing(SysMenu::getOrderNum, Comparator.nullsLast(Integer::compareTo)))
                .collect(Collectors.toList());
 
        // 3) 递归挂载:M 下挂 C;C 下挂 F(也允许 C 下还有 C,或 M 下还有 M,按你的数据结构来)
        for (SysMenu root : roots) {
            fillChildren(root, childrenMap);
        }
 
        return roots;
    }
 
    private void fillChildren(SysMenu node, Map<Long, List<SysMenu>> childrenMap) {
        Long id = node.getId();
        if (id == null) {
            node.setChildren(Collections.emptyList());
            return;
        }
 
        List<SysMenu> rawChildren = childrenMap.getOrDefault(id, Collections.emptyList());
        if (rawChildren.isEmpty()) {
            node.setChildren(Collections.emptyList());
            return;
        }
 
        // 根据你的层级规则过滤子节点:
        // - M 只能挂 M/C(看你业务,很多系统 M 下是 C)
        // - C 只能挂 F(按钮)或(有些系统 C 下还能挂 C)
        // - F 一般不挂任何子节点
        String type = node.getMenuType();
 
        List<SysMenu> children = rawChildren.stream()
                .filter(Objects::nonNull)
                .filter(child -> {
                    if ("M".equals(type)) {
                        return "M".equals(child.getMenuType()) || "C".equals(child.getMenuType());
                    }
                    if ("C".equals(type)) {
                        return "F".equals(child.getMenuType()); // 若允许 C 下还有 C,可改为:return "C".equals(...) || "F".equals(...)
                    }
                    return false; // F 不再往下挂
                })
                .sorted(Comparator.comparing(SysMenu::getOrderNum, Comparator.nullsLast(Integer::compareTo)))
                .collect(Collectors.toList());
 
        node.setChildren(children);
 
        // 继续递归
        for (SysMenu child : children) {
            fillChildren(child, childrenMap);
        }
    }
 
    @Override
    public List<SysMenu> getPermission() {
        try {
            // 1. 获取当前登录用户ID
            Long userId = tokenService.getLoginUser().getUserid();
            if (userId == null) {
                log.warn("获取当前登录用户ID失败");
                return Collections.emptyList();
            }
 
            // 2. 查询用户角色关联
            R<SysUserRole> userRoleResult = sysUserRoleClient.selectSysUserRoleByUserId(userId);
            if (userRoleResult == null || userRoleResult.getCode() != 200 || userRoleResult.getData() == null) {
                log.warn("查询用户角色关联失败, userId={}", userId);
                return Collections.emptyList();
            }
 
            Long roleId = userRoleResult.getData().getRoleId();
            if (roleId == null) {
                log.warn("用户未关联角色, userId={}", userId);
                return Collections.emptyList();
            }
 
            // 3. 查询角色信息
            R<SysRole> roleResult = sysRoleClient.getRoleById(roleId);
            if (roleResult == null || roleResult.getCode() != 200 || roleResult.getData() == null) {
                log.warn("查询角色信息失败, roleId={}", roleId);
                return Collections.emptyList();
            }
 
            Long groupId = roleResult.getData().getGroupId();
            if (groupId == null) {
                log.warn("角色未关联权限组, roleId={}", roleId);
                return Collections.emptyList();
            }
 
            // 4. 查询权限组关联的菜单ID列表
            R<List<Long>> menuIdsResult = sysGroupMenuClient.selectMenuIdsByGroupId(groupId);
            if (menuIdsResult == null || menuIdsResult.getCode() != 200 || menuIdsResult.getData() == null) {
                log.warn("查询权限组菜单ID列表失败, groupId={}", groupId);
                return Collections.emptyList();
            }
 
            List<Long> menuIds = menuIdsResult.getData();
            if (menuIds.isEmpty()) {
                log.info("权限组未关联菜单, groupId={}", groupId);
                return Collections.emptyList();
            }
 
            // 5. 查询菜单详情
            List<SysMenu> menus = this.listByIds(menuIds);
            log.info("查询用户权限菜单成功, userId={}, menuCount={}", userId, menus.size());
            return menus;
 
        } catch (Exception e) {
            log.error("查询用户权限菜单异常", e);
            return Collections.emptyList();
        }
    }
 
    @Override
    public List<SysMenu> getAppPermission() {
        // 1. 获取当前登录用户ID
        Long operationUserId = tokenService.getLoginOperationUser().getId();
        if (operationUserId == null) {
            log.warn("获取当前登录用户ID失败");
            return Collections.emptyList();
        }
 
        // 2. 查询运维APP菜单(group_type=2)
        List<SysMenu> appMenus = this.lambdaQuery()
                .eq(SysMenu::getGroupType, 2)
                .list();
        if (appMenus == null || appMenus.isEmpty()) {
            return Collections.emptyList();
        }
 
        // 3. 查询运维人员关联的角色(一个用户可多个角色)
        Set<Long> roleIds = new HashSet<>();
        try {
            R<List<XpOperationsRole>> rolesResult = xpOperationsRoleClient.listByOperationUserId(operationUserId);
            if (rolesResult != null && rolesResult.getData() != null) {
                for (XpOperationsRole r : rolesResult.getData()) {
                    if (r != null && r.getRoleId() != null) {
                        roleIds.add(r.getRoleId());
                    }
                }
            }
        } catch (Exception e) {
            log.error("查询运维人员角色关联异常, operationUserId={}", operationUserId, e);
        }
 
        // 4. 通过 role -> groupId -> group_menu 获取允许的菜单ID集合
        Set<Long> allowedMenuIds = new HashSet<>();
        if (!roleIds.isEmpty()) {
            Set<Long> groupIds = new HashSet<>();
            for (Long roleId : roleIds) {
                try {
                    R<SysRole> roleResult = sysRoleClient.getRoleById(roleId);
                    if (roleResult != null && roleResult.getData() != null && roleResult.getData().getGroupId() != null) {
                        groupIds.add(roleResult.getData().getGroupId());
                    }
                } catch (Exception e) {
                    log.error("查询角色信息异常, roleId={}", roleId, e);
                }
            }
 
            for (Long groupId : groupIds) {
                try {
                    R<List<Long>> menuIdsResult = sysGroupMenuClient.selectMenuIdsByGroupId(groupId);
                    if (menuIdsResult != null && menuIdsResult.getData() != null) {
                        allowedMenuIds.addAll(menuIdsResult.getData());
                    }
                } catch (Exception e) {
                    log.error("查询权限组菜单ID异常, groupId={}", groupId, e);
                }
            }
        }
 
        // 5. 仅保留运维APP的菜单ID,避免无关ID影响 visible
        Set<Long> appMenuIds = appMenus.stream()
                .filter(Objects::nonNull)
                .map(SysMenu::getId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        allowedMenuIds.retainAll(appMenuIds);
 
        // 6. 组装树形结构,并用 visible 体现权限可见性(0可见 1不可见)
        return buildAppMenuTreeWithVisible(appMenus, allowedMenuIds);
    }
 
    private List<SysMenu> buildAppMenuTreeWithVisible(List<SysMenu> menus, Set<Long> allowedMenuIds) {
        Map<Long, SysMenu> idMap = new HashMap<>();
        for (SysMenu m : menus) {
            if (m != null && m.getId() != null) {
                idMap.put(m.getId(), m);
            }
        }
 
        Map<Long, List<SysMenu>> childrenMap = menus.stream()
                .filter(Objects::nonNull)
                .filter(m -> m.getId() != null)
                .collect(Collectors.groupingBy(m -> {
                    Long pid = m.getParentId();
                    return pid == null ? 0L : pid;
                }));
 
        // 根节点:parentId 为 0/null,或父节点不在当前 group_type=2 集合内
        List<SysMenu> roots = menus.stream()
                .filter(Objects::nonNull)
                .filter(m -> m.getId() != null)
                .filter(m -> {
                    Long pid = m.getParentId();
                    if (pid == null || pid == 0L) {
                        return true;
                    }
                    return !idMap.containsKey(pid);
                })
                .sorted(Comparator.comparing(SysMenu::getOrderNum, Comparator.nullsLast(Integer::compareTo)))
                .collect(Collectors.toList());
 
        for (SysMenu root : roots) {
            fillChildrenAndVisible(root, childrenMap, allowedMenuIds, new HashSet<>());
        }
        return roots;
    }
 
    private boolean fillChildrenAndVisible(SysMenu node, Map<Long, List<SysMenu>> childrenMap, Set<Long> allowedMenuIds, Set<Long> path) {
        Long id = node.getId();
        if (id != null && !path.add(id)) {
            boolean visible = allowedMenuIds.contains(id);
            node.setChildren(Collections.emptyList());
            node.setVisible(visible ? "0" : "1");
            return visible;
        }
        List<SysMenu> children = childrenMap.getOrDefault(id == null ? 0L : id, Collections.emptyList()).stream()
                .filter(Objects::nonNull)
                .sorted(Comparator.comparing(SysMenu::getOrderNum, Comparator.nullsLast(Integer::compareTo)))
                .collect(Collectors.toList());
 
        boolean anyChildVisible = false;
        for (SysMenu child : children) {
            anyChildVisible |= fillChildrenAndVisible(child, childrenMap, allowedMenuIds, path);
        }
        node.setChildren(children);
 
        boolean selfVisible = id != null && allowedMenuIds.contains(id);
        boolean visible = selfVisible || anyChildVisible;
        node.setVisible(visible ? "0" : "1");
        if (id != null) {
            path.remove(id);
        }
        return visible;
    }
}