前言

树形菜单作为企业级应用中的重要组件,广泛应用于组织架构、菜单管理、分类管理等场景。通过合理的树形结构设计和递归算法实现,能够构建出高效、灵活、可扩展的树形菜单系统。本文从树形结构设计到递归算法实现,从基础实现到企业级应用,系统梳理树形菜单的完整解决方案。

一、树形菜单架构设计

1.1 树形菜单整体架构

1.2 树形菜单策略架构

二、树形结构设计实现

2.1 树形数据结构设计

2.1.1 树形节点实体

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
/**
* 树形节点实体
*/
@Data
@Entity
@Table(name = "tree_node")
public class TreeNode {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "name", nullable = false, length = 100)
private String name;

@Column(name = "code", nullable = false, length = 50)
private String code;

@Column(name = "parent_id")
private Long parentId;

@Column(name = "level", nullable = false)
private Integer level;

@Column(name = "sort_order", nullable = false)
private Integer sortOrder;

@Column(name = "path", length = 500)
private String path;

@Column(name = "left_value")
private Integer leftValue;

@Column(name = "right_value")
private Integer rightValue;

@Column(name = "is_leaf", nullable = false)
private Boolean isLeaf;

@Column(name = "is_enabled", nullable = false)
private Boolean isEnabled;

@Column(name = "icon", length = 100)
private String icon;

@Column(name = "url", length = 200)
private String url;

@Column(name = "description", length = 500)
private String description;

@Column(name = "create_time", nullable = false)
private Date createTime;

@Column(name = "update_time", nullable = false)
private Date updateTime;

@Column(name = "create_by", length = 50)
private String createBy;

@Column(name = "update_by", length = 50)
private String updateBy;

// 子节点列表(非数据库字段)
@Transient
private List<TreeNode> children;

// 父节点(非数据库字段)
@Transient
private TreeNode parent;

// 是否展开(非数据库字段)
@Transient
private Boolean expanded;

// 是否选中(非数据库字段)
@Transient
private Boolean selected;

// 是否禁用(非数据库字段)
@Transient
private Boolean disabled;

/**
* 构造函数
*/
public TreeNode() {
this.children = new ArrayList<>();
this.isLeaf = true;
this.isEnabled = true;
this.expanded = false;
this.selected = false;
this.disabled = false;
this.createTime = new Date();
this.updateTime = new Date();
}

/**
* 添加子节点
*/
public void addChild(TreeNode child) {
if (child != null) {
child.setParent(this);
child.setLevel(this.level + 1);
this.children.add(child);
this.isLeaf = false;
}
}

/**
* 移除子节点
*/
public void removeChild(TreeNode child) {
if (child != null && this.children.contains(child)) {
this.children.remove(child);
child.setParent(null);
this.isLeaf = this.children.isEmpty();
}
}

/**
* 获取所有子节点
*/
public List<TreeNode> getAllChildren() {
List<TreeNode> allChildren = new ArrayList<>();
for (TreeNode child : this.children) {
allChildren.add(child);
allChildren.addAll(child.getAllChildren());
}
return allChildren;
}

/**
* 获取所有父节点
*/
public List<TreeNode> getAllParents() {
List<TreeNode> parents = new ArrayList<>();
TreeNode parent = this.parent;
while (parent != null) {
parents.add(parent);
parent = parent.getParent();
}
return parents;
}

/**
* 检查是否为根节点
*/
public boolean isRoot() {
return this.parentId == null || this.parentId == 0;
}

/**
* 检查是否为叶子节点
*/
public boolean isLeafNode() {
return this.isLeaf || this.children.isEmpty();
}

/**
* 获取节点深度
*/
public int getDepth() {
int depth = 0;
TreeNode parent = this.parent;
while (parent != null) {
depth++;
parent = parent.getParent();
}
return depth;
}

/**
* 获取节点高度
*/
public int getHeight() {
if (this.isLeafNode()) {
return 0;
}

int maxHeight = 0;
for (TreeNode child : this.children) {
int childHeight = child.getHeight();
if (childHeight > maxHeight) {
maxHeight = childHeight;
}
}

return maxHeight + 1;
}
}

2.1.2 树形结构服务

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
328
329
330
331
332
333
334
335
336
337
/**
* 树形结构服务
*/
@Service
public class TreeStructureService {

@Autowired
private TreeNodeMapper treeNodeMapper;

@Autowired
private RedisTemplate<String, Object> redisTemplate;

private final String TREE_CACHE_PREFIX = "tree_cache:";
private final long TREE_CACHE_EXPIRE = 3600; // 1小时

/**
* 构建树形结构
*/
public TreeNode buildTree(List<TreeNode> nodes) {
try {
if (nodes == null || nodes.isEmpty()) {
return null;
}

// 创建根节点
TreeNode root = new TreeNode();
root.setId(0L);
root.setName("根节点");
root.setLevel(0);
root.setIsLeaf(false);

// 构建节点映射
Map<Long, TreeNode> nodeMap = new HashMap<>();
for (TreeNode node : nodes) {
nodeMap.put(node.getId(), node);
}

// 构建树形结构
for (TreeNode node : nodes) {
if (node.isRoot()) {
root.addChild(node);
} else {
TreeNode parent = nodeMap.get(node.getParentId());
if (parent != null) {
parent.addChild(node);
}
}
}

// 排序子节点
sortChildren(root);

return root;

} catch (Exception e) {
log.error("构建树形结构失败", e);
throw new TreeException("构建树形结构失败", e);
}
}

/**
* 排序子节点
*/
private void sortChildren(TreeNode node) {
if (node.getChildren() != null && !node.getChildren().isEmpty()) {
// 按排序字段排序
node.getChildren().sort((n1, n2) -> {
if (n1.getSortOrder() == null && n2.getSortOrder() == null) {
return 0;
}
if (n1.getSortOrder() == null) {
return 1;
}
if (n2.getSortOrder() == null) {
return -1;
}
return n1.getSortOrder().compareTo(n2.getSortOrder());
});

// 递归排序子节点
for (TreeNode child : node.getChildren()) {
sortChildren(child);
}
}
}

/**
* 获取树形结构
*/
public TreeNode getTreeStructure(String treeType) {
try {
// 从缓存获取
String cacheKey = TREE_CACHE_PREFIX + treeType;
TreeNode cachedTree = (TreeNode) redisTemplate.opsForValue().get(cacheKey);

if (cachedTree != null) {
return cachedTree;
}

// 从数据库获取
List<TreeNode> nodes = treeNodeMapper.selectByTreeType(treeType);
TreeNode tree = buildTree(nodes);

// 缓存结果
if (tree != null) {
redisTemplate.opsForValue().set(cacheKey, tree, Duration.ofSeconds(TREE_CACHE_EXPIRE));
}

return tree;

} catch (Exception e) {
log.error("获取树形结构失败", e);
throw new TreeException("获取树形结构失败", e);
}
}

/**
* 获取树形结构(带权限)
*/
public TreeNode getTreeStructureWithPermission(String treeType, String userId) {
try {
// 获取用户权限
Set<String> permissions = getUserPermissions(userId);

// 获取树形结构
TreeNode tree = getTreeStructure(treeType);

// 过滤权限
TreeNode filteredTree = filterTreeByPermission(tree, permissions);

return filteredTree;

} catch (Exception e) {
log.error("获取带权限的树形结构失败", e);
throw new TreeException("获取带权限的树形结构失败", e);
}
}

/**
* 过滤树形结构(按权限)
*/
private TreeNode filterTreeByPermission(TreeNode node, Set<String> permissions) {
if (node == null) {
return null;
}

TreeNode filteredNode = new TreeNode();
filteredNode.setId(node.getId());
filteredNode.setName(node.getName());
filteredNode.setCode(node.getCode());
filteredNode.setParentId(node.getParentId());
filteredNode.setLevel(node.getLevel());
filteredNode.setSortOrder(node.getSortOrder());
filteredNode.setPath(node.getPath());
filteredNode.setIsLeaf(node.getIsLeaf());
filteredNode.setIsEnabled(node.getIsEnabled());
filteredNode.setIcon(node.getIcon());
filteredNode.setUrl(node.getUrl());
filteredNode.setDescription(node.getDescription());

// 过滤子节点
if (node.getChildren() != null && !node.getChildren().isEmpty()) {
for (TreeNode child : node.getChildren()) {
if (hasPermission(child, permissions)) {
TreeNode filteredChild = filterTreeByPermission(child, permissions);
if (filteredChild != null) {
filteredNode.addChild(filteredChild);
}
}
}
}

return filteredNode;
}

/**
* 检查权限
*/
private boolean hasPermission(TreeNode node, Set<String> permissions) {
// 简单的权限检查,实际应用中需要更复杂的权限逻辑
return permissions.contains(node.getCode()) || permissions.contains("ALL");
}

/**
* 获取用户权限
*/
private Set<String> getUserPermissions(String userId) {
// 实际应用中需要从权限系统获取
Set<String> permissions = new HashSet<>();
permissions.add("ALL");
return permissions;
}

/**
* 添加节点
*/
public TreeNode addNode(TreeNode node) {
try {
// 设置默认值
if (node.getLevel() == null) {
node.setLevel(0);
}
if (node.getSortOrder() == null) {
node.setSortOrder(0);
}
if (node.getIsLeaf() == null) {
node.setIsLeaf(true);
}
if (node.getIsEnabled() == null) {
node.setIsEnabled(true);
}

// 设置路径
if (node.getParentId() != null && node.getParentId() != 0) {
TreeNode parent = treeNodeMapper.selectById(node.getParentId());
if (parent != null) {
node.setPath(parent.getPath() + "/" + node.getCode());
node.setLevel(parent.getLevel() + 1);
}
} else {
node.setPath("/" + node.getCode());
node.setLevel(0);
}

// 插入数据库
treeNodeMapper.insert(node);

// 清除缓存
clearTreeCache(node.getCode());

log.info("添加节点成功: {}", node.getName());
return node;

} catch (Exception e) {
log.error("添加节点失败", e);
throw new TreeException("添加节点失败", e);
}
}

/**
* 更新节点
*/
public TreeNode updateNode(TreeNode node) {
try {
// 更新数据库
treeNodeMapper.updateById(node);

// 清除缓存
clearTreeCache(node.getCode());

log.info("更新节点成功: {}", node.getName());
return node;

} catch (Exception e) {
log.error("更新节点失败", e);
throw new TreeException("更新节点失败", e);
}
}

/**
* 删除节点
*/
public boolean deleteNode(Long nodeId) {
try {
// 检查是否有子节点
List<TreeNode> children = treeNodeMapper.selectByParentId(nodeId);
if (!children.isEmpty()) {
throw new TreeException("节点有子节点,不能删除");
}

// 删除数据库记录
treeNodeMapper.deleteById(nodeId);

// 清除缓存
clearTreeCache(nodeId.toString());

log.info("删除节点成功: {}", nodeId);
return true;

} catch (Exception e) {
log.error("删除节点失败", e);
throw new TreeException("删除节点失败", e);
}
}

/**
* 移动节点
*/
public boolean moveNode(Long nodeId, Long newParentId) {
try {
TreeNode node = treeNodeMapper.selectById(nodeId);
if (node == null) {
throw new TreeException("节点不存在");
}

// 更新父节点
node.setParentId(newParentId);

// 更新路径
if (newParentId != null && newParentId != 0) {
TreeNode parent = treeNodeMapper.selectById(newParentId);
if (parent != null) {
node.setPath(parent.getPath() + "/" + node.getCode());
node.setLevel(parent.getLevel() + 1);
}
} else {
node.setPath("/" + node.getCode());
node.setLevel(0);
}

// 更新数据库
treeNodeMapper.updateById(node);

// 清除缓存
clearTreeCache(node.getCode());

log.info("移动节点成功: {} -> {}", nodeId, newParentId);
return true;

} catch (Exception e) {
log.error("移动节点失败", e);
throw new TreeException("移动节点失败", e);
}
}

/**
* 清除树形缓存
*/
private void clearTreeCache(String treeType) {
try {
String cacheKey = TREE_CACHE_PREFIX + treeType;
redisTemplate.delete(cacheKey);
} catch (Exception e) {
log.error("清除树形缓存失败", e);
}
}
}

2.2 递归算法实现

2.2.1 递归算法服务

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
/**
* 递归算法服务
*/
@Service
public class RecursiveAlgorithmService {

@Autowired
private TreeNodeMapper treeNodeMapper;

/**
* 递归构建树形结构
*/
public TreeNode buildTreeRecursively(List<TreeNode> nodes) {
try {
if (nodes == null || nodes.isEmpty()) {
return null;
}

// 创建根节点
TreeNode root = new TreeNode();
root.setId(0L);
root.setName("根节点");
root.setLevel(0);
root.setIsLeaf(false);

// 构建节点映射
Map<Long, TreeNode> nodeMap = new HashMap<>();
for (TreeNode node : nodes) {
nodeMap.put(node.getId(), node);
}

// 递归构建树形结构
buildTreeRecursively(root, nodeMap, nodes);

return root;

} catch (Exception e) {
log.error("递归构建树形结构失败", e);
throw new TreeException("递归构建树形结构失败", e);
}
}

/**
* 递归构建树形结构(内部方法)
*/
private void buildTreeRecursively(TreeNode parent, Map<Long, TreeNode> nodeMap, List<TreeNode> nodes) {
for (TreeNode node : nodes) {
if (node.getParentId() != null && node.getParentId().equals(parent.getId())) {
parent.addChild(node);
buildTreeRecursively(node, nodeMap, nodes);
}
}
}

/**
* 递归遍历树形结构(深度优先)
*/
public List<TreeNode> traverseTreeDepthFirst(TreeNode root) {
List<TreeNode> result = new ArrayList<>();
traverseTreeDepthFirst(root, result);
return result;
}

/**
* 递归遍历树形结构(深度优先,内部方法)
*/
private void traverseTreeDepthFirst(TreeNode node, List<TreeNode> result) {
if (node == null) {
return;
}

result.add(node);

if (node.getChildren() != null && !node.getChildren().isEmpty()) {
for (TreeNode child : node.getChildren()) {
traverseTreeDepthFirst(child, result);
}
}
}

/**
* 递归遍历树形结构(广度优先)
*/
public List<TreeNode> traverseTreeBreadthFirst(TreeNode root) {
List<TreeNode> result = new ArrayList<>();
Queue<TreeNode> queue = new LinkedList<>();

if (root != null) {
queue.offer(root);
}

while (!queue.isEmpty()) {
TreeNode node = queue.poll();
result.add(node);

if (node.getChildren() != null && !node.getChildren().isEmpty()) {
for (TreeNode child : node.getChildren()) {
queue.offer(child);
}
}
}

return result;
}

/**
* 递归查找节点
*/
public TreeNode findNodeRecursively(TreeNode root, Long nodeId) {
if (root == null || nodeId == null) {
return null;
}

if (root.getId().equals(nodeId)) {
return root;
}

if (root.getChildren() != null && !root.getChildren().isEmpty()) {
for (TreeNode child : root.getChildren()) {
TreeNode found = findNodeRecursively(child, nodeId);
if (found != null) {
return found;
}
}
}

return null;
}

/**
* 递归查找节点(按代码)
*/
public TreeNode findNodeByCodeRecursively(TreeNode root, String code) {
if (root == null || code == null || code.trim().isEmpty()) {
return null;
}

if (code.equals(root.getCode())) {
return root;
}

if (root.getChildren() != null && !root.getChildren().isEmpty()) {
for (TreeNode child : root.getChildren()) {
TreeNode found = findNodeByCodeRecursively(child, code);
if (found != null) {
return found;
}
}
}

return null;
}

/**
* 递归查找节点(按名称)
*/
public List<TreeNode> findNodesByNameRecursively(TreeNode root, String name) {
List<TreeNode> result = new ArrayList<>();
findNodesByNameRecursively(root, name, result);
return result;
}

/**
* 递归查找节点(按名称,内部方法)
*/
private void findNodesByNameRecursively(TreeNode node, String name, List<TreeNode> result) {
if (node == null || name == null || name.trim().isEmpty()) {
return;
}

if (name.equals(node.getName())) {
result.add(node);
}

if (node.getChildren() != null && !node.getChildren().isEmpty()) {
for (TreeNode child : node.getChildren()) {
findNodesByNameRecursively(child, name, result);
}
}
}

/**
* 递归计算树形结构统计信息
*/
public TreeStatistics calculateTreeStatistics(TreeNode root) {
TreeStatistics statistics = new TreeStatistics();
calculateTreeStatistics(root, statistics);
return statistics;
}

/**
* 递归计算树形结构统计信息(内部方法)
*/
private void calculateTreeStatistics(TreeNode node, TreeStatistics statistics) {
if (node == null) {
return;
}

statistics.setTotalNodes(statistics.getTotalNodes() + 1);

if (node.isLeafNode()) {
statistics.setLeafNodes(statistics.getLeafNodes() + 1);
}

if (node.getLevel() > statistics.getMaxDepth()) {
statistics.setMaxDepth(node.getLevel());
}

if (node.getChildren() != null && !node.getChildren().isEmpty()) {
statistics.setInternalNodes(statistics.getInternalNodes() + 1);

for (TreeNode child : node.getChildren()) {
calculateTreeStatistics(child, statistics);
}
}
}

/**
* 递归复制树形结构
*/
public TreeNode copyTreeRecursively(TreeNode root) {
if (root == null) {
return null;
}

TreeNode copy = new TreeNode();
copy.setId(root.getId());
copy.setName(root.getName());
copy.setCode(root.getCode());
copy.setParentId(root.getParentId());
copy.setLevel(root.getLevel());
copy.setSortOrder(root.getSortOrder());
copy.setPath(root.getPath());
copy.setIsLeaf(root.getIsLeaf());
copy.setIsEnabled(root.getIsEnabled());
copy.setIcon(root.getIcon());
copy.setUrl(root.getUrl());
copy.setDescription(root.getDescription());

if (root.getChildren() != null && !root.getChildren().isEmpty()) {
for (TreeNode child : root.getChildren()) {
TreeNode childCopy = copyTreeRecursively(child);
copy.addChild(childCopy);
}
}

return copy;
}

/**
* 递归验证树形结构
*/
public TreeValidationResult validateTreeRecursively(TreeNode root) {
TreeValidationResult result = new TreeValidationResult();
validateTreeRecursively(root, result);
return result;
}

/**
* 递归验证树形结构(内部方法)
*/
private void validateTreeRecursively(TreeNode node, TreeValidationResult result) {
if (node == null) {
return;
}

// 验证节点数据
if (node.getName() == null || node.getName().trim().isEmpty()) {
result.addError("节点名称不能为空: " + node.getId());
}

if (node.getCode() == null || node.getCode().trim().isEmpty()) {
result.addError("节点代码不能为空: " + node.getId());
}

if (node.getLevel() == null || node.getLevel() < 0) {
result.addError("节点层级不能为空或负数: " + node.getId());
}

// 验证子节点
if (node.getChildren() != null && !node.getChildren().isEmpty()) {
for (TreeNode child : node.getChildren()) {
if (child.getParentId() == null || !child.getParentId().equals(node.getId())) {
result.addError("子节点父ID不匹配: " + child.getId());
}

if (child.getLevel() == null || !child.getLevel().equals(node.getLevel() + 1)) {
result.addError("子节点层级不正确: " + child.getId());
}

validateTreeRecursively(child, result);
}
}
}
}

三、企业级树形菜单方案

3.1 树形菜单管理服务

3.1.1 树形菜单管理服务

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
328
/**
* 树形菜单管理服务
*/
@Service
public class TreeMenuManagementService {

@Autowired
private TreeStructureService treeStructureService;

@Autowired
private RecursiveAlgorithmService recursiveAlgorithmService;

@Autowired
private TreeNodeMapper treeNodeMapper;

@Autowired
private RedisTemplate<String, Object> redisTemplate;

private final String MENU_CACHE_PREFIX = "menu_cache:";
private final long MENU_CACHE_EXPIRE = 1800; // 30分钟

/**
* 获取树形菜单
*/
public TreeMenuResult getTreeMenu(String menuType, String userId) {
try {
TreeMenuResult result = new TreeMenuResult();
result.setMenuType(menuType);
result.setUserId(userId);
result.setStartTime(new Date());

// 从缓存获取
String cacheKey = MENU_CACHE_PREFIX + menuType + ":" + userId;
TreeMenuResult cachedResult = (TreeMenuResult) redisTemplate.opsForValue().get(cacheKey);

if (cachedResult != null) {
return cachedResult;
}

// 获取树形结构
TreeNode tree = treeStructureService.getTreeStructureWithPermission(menuType, userId);

// 构建菜单数据
List<MenuNode> menuNodes = buildMenuNodes(tree);

result.setMenuNodes(menuNodes);
result.setTotalNodes(menuNodes.size());
result.setStatus(TreeMenuStatus.SUCCESS);
result.setEndTime(new Date());

// 缓存结果
redisTemplate.opsForValue().set(cacheKey, result, Duration.ofSeconds(MENU_CACHE_EXPIRE));

log.info("获取树形菜单成功: 类型={}, 用户={}, 节点数={}", menuType, userId, menuNodes.size());
return result;

} catch (Exception e) {
log.error("获取树形菜单失败", e);
throw new TreeException("获取树形菜单失败", e);
}
}

/**
* 构建菜单节点
*/
private List<MenuNode> buildMenuNodes(TreeNode tree) {
List<MenuNode> menuNodes = new ArrayList<>();

if (tree != null && tree.getChildren() != null) {
for (TreeNode child : tree.getChildren()) {
MenuNode menuNode = buildMenuNode(child);
menuNodes.add(menuNode);
}
}

return menuNodes;
}

/**
* 构建菜单节点
*/
private MenuNode buildMenuNode(TreeNode node) {
MenuNode menuNode = new MenuNode();
menuNode.setId(node.getId());
menuNode.setName(node.getName());
menuNode.setCode(node.getCode());
menuNode.setIcon(node.getIcon());
menuNode.setUrl(node.getUrl());
menuNode.setLevel(node.getLevel());
menuNode.setSortOrder(node.getSortOrder());
menuNode.setExpanded(node.getExpanded());
menuNode.setSelected(node.getSelected());
menuNode.setDisabled(node.getDisabled());
menuNode.setIsLeaf(node.getIsLeaf());
menuNode.setIsEnabled(node.getIsEnabled());

// 构建子节点
if (node.getChildren() != null && !node.getChildren().isEmpty()) {
List<MenuNode> children = new ArrayList<>();
for (TreeNode child : node.getChildren()) {
MenuNode childMenuNode = buildMenuNode(child);
children.add(childMenuNode);
}
menuNode.setChildren(children);
}

return menuNode;
}

/**
* 获取菜单节点详情
*/
public MenuNodeDetail getMenuNodeDetail(Long nodeId) {
try {
TreeNode node = treeNodeMapper.selectById(nodeId);
if (node == null) {
throw new TreeException("菜单节点不存在");
}

MenuNodeDetail detail = new MenuNodeDetail();
detail.setId(node.getId());
detail.setName(node.getName());
detail.setCode(node.getCode());
detail.setParentId(node.getParentId());
detail.setLevel(node.getLevel());
detail.setSortOrder(node.getSortOrder());
detail.setPath(node.getPath());
detail.setIsLeaf(node.getIsLeaf());
detail.setIsEnabled(node.getIsEnabled());
detail.setIcon(node.getIcon());
detail.setUrl(node.getUrl());
detail.setDescription(node.getDescription());
detail.setCreateTime(node.getCreateTime());
detail.setUpdateTime(node.getUpdateTime());
detail.setCreateBy(node.getCreateBy());
detail.setUpdateBy(node.getUpdateBy());

// 获取子节点数量
List<TreeNode> children = treeNodeMapper.selectByParentId(nodeId);
detail.setChildrenCount(children.size());

// 获取父节点信息
if (node.getParentId() != null && node.getParentId() != 0) {
TreeNode parent = treeNodeMapper.selectById(node.getParentId());
if (parent != null) {
detail.setParentName(parent.getName());
}
}

return detail;

} catch (Exception e) {
log.error("获取菜单节点详情失败", e);
throw new TreeException("获取菜单节点详情失败", e);
}
}

/**
* 创建菜单节点
*/
public MenuNode createMenuNode(CreateMenuNodeRequest request) {
try {
TreeNode node = new TreeNode();
node.setName(request.getName());
node.setCode(request.getCode());
node.setParentId(request.getParentId());
node.setSortOrder(request.getSortOrder());
node.setIcon(request.getIcon());
node.setUrl(request.getUrl());
node.setDescription(request.getDescription());
node.setIsEnabled(request.getIsEnabled());
node.setCreateBy(request.getCreateBy());
node.setUpdateBy(request.getUpdateBy());

TreeNode createdNode = treeStructureService.addNode(node);

MenuNode menuNode = buildMenuNode(createdNode);

log.info("创建菜单节点成功: {}", createdNode.getName());
return menuNode;

} catch (Exception e) {
log.error("创建菜单节点失败", e);
throw new TreeException("创建菜单节点失败", e);
}
}

/**
* 更新菜单节点
*/
public MenuNode updateMenuNode(UpdateMenuNodeRequest request) {
try {
TreeNode node = treeNodeMapper.selectById(request.getId());
if (node == null) {
throw new TreeException("菜单节点不存在");
}

node.setName(request.getName());
node.setCode(request.getCode());
node.setSortOrder(request.getSortOrder());
node.setIcon(request.getIcon());
node.setUrl(request.getUrl());
node.setDescription(request.getDescription());
node.setIsEnabled(request.getIsEnabled());
node.setUpdateBy(request.getUpdateBy());
node.setUpdateTime(new Date());

TreeNode updatedNode = treeStructureService.updateNode(node);

MenuNode menuNode = buildMenuNode(updatedNode);

log.info("更新菜单节点成功: {}", updatedNode.getName());
return menuNode;

} catch (Exception e) {
log.error("更新菜单节点失败", e);
throw new TreeException("更新菜单节点失败", e);
}
}

/**
* 删除菜单节点
*/
public boolean deleteMenuNode(Long nodeId) {
try {
boolean success = treeStructureService.deleteNode(nodeId);

if (success) {
log.info("删除菜单节点成功: {}", nodeId);
}

return success;

} catch (Exception e) {
log.error("删除菜单节点失败", e);
throw new TreeException("删除菜单节点失败", e);
}
}

/**
* 移动菜单节点
*/
public boolean moveMenuNode(Long nodeId, Long newParentId) {
try {
boolean success = treeStructureService.moveNode(nodeId, newParentId);

if (success) {
log.info("移动菜单节点成功: {} -> {}", nodeId, newParentId);
}

return success;

} catch (Exception e) {
log.error("移动菜单节点失败", e);
throw new TreeException("移动菜单节点失败", e);
}
}

/**
* 搜索菜单节点
*/
public List<MenuNode> searchMenuNodes(String menuType, String keyword, String userId) {
try {
// 获取树形结构
TreeNode tree = treeStructureService.getTreeStructureWithPermission(menuType, userId);

// 搜索节点
List<TreeNode> searchResults = recursiveAlgorithmService.findNodesByNameRecursively(tree, keyword);

// 构建菜单节点
List<MenuNode> menuNodes = new ArrayList<>();
for (TreeNode node : searchResults) {
MenuNode menuNode = buildMenuNode(node);
menuNodes.add(menuNode);
}

log.info("搜索菜单节点成功: 关键词={}, 结果数={}", keyword, menuNodes.size());
return menuNodes;

} catch (Exception e) {
log.error("搜索菜单节点失败", e);
throw new TreeException("搜索菜单节点失败", e);
}
}

/**
* 获取菜单统计信息
*/
public MenuStatistics getMenuStatistics(String menuType) {
try {
TreeNode tree = treeStructureService.getTreeStructure(menuType);
TreeStatistics treeStats = recursiveAlgorithmService.calculateTreeStatistics(tree);

MenuStatistics menuStats = new MenuStatistics();
menuStats.setMenuType(menuType);
menuStats.setTotalNodes(treeStats.getTotalNodes());
menuStats.setLeafNodes(treeStats.getLeafNodes());
menuStats.setInternalNodes(treeStats.getInternalNodes());
menuStats.setMaxDepth(treeStats.getMaxDepth());
menuStats.setCreateTime(new Date());

return menuStats;

} catch (Exception e) {
log.error("获取菜单统计信息失败", e);
throw new TreeException("获取菜单统计信息失败", e);
}
}

/**
* 清除菜单缓存
*/
public void clearMenuCache(String menuType) {
try {
String cacheKey = MENU_CACHE_PREFIX + menuType + ":*";
Set<String> keys = redisTemplate.keys(cacheKey);

if (keys != null && !keys.isEmpty()) {
redisTemplate.delete(keys);
}

log.info("清除菜单缓存成功: {}", menuType);

} catch (Exception e) {
log.error("清除菜单缓存失败", e);
}
}
}

3.2 树形菜单优化服务

3.2.1 树形菜单优化服务

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
328
329
330
331
332
333
334
335
336
337
338
/**
* 树形菜单优化服务
*/
@Service
public class TreeMenuOptimizationService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Autowired
private CaffeineCache localCache;

private final String MENU_OPTIMIZATION_CACHE_PREFIX = "menu_optimization:";

/**
* 优化树形菜单性能
*/
public MenuOptimizationResult optimizeMenuPerformance(String menuType, String userId) {
try {
MenuOptimizationResult result = new MenuOptimizationResult();
result.setMenuType(menuType);
result.setUserId(userId);
result.setStartTime(new Date());

// 1. 分析菜单结构
MenuStructureAnalysis analysis = analyzeMenuStructure(menuType);
result.setStructureAnalysis(analysis);

// 2. 优化缓存策略
CacheOptimizationResult cacheOptimization = optimizeCacheStrategy(menuType, userId);
result.setCacheOptimization(cacheOptimization);

// 3. 优化加载策略
LoadOptimizationResult loadOptimization = optimizeLoadStrategy(menuType, analysis);
result.setLoadOptimization(loadOptimization);

// 4. 优化渲染策略
RenderOptimizationResult renderOptimization = optimizeRenderStrategy(menuType, analysis);
result.setRenderOptimization(renderOptimization);

result.setStatus(OptimizationStatus.COMPLETED);
result.setEndTime(new Date());

log.info("菜单性能优化完成: 类型={}, 用户={}", menuType, userId);
return result;

} catch (Exception e) {
log.error("菜单性能优化失败", e);
throw new TreeException("菜单性能优化失败", e);
}
}

/**
* 分析菜单结构
*/
private MenuStructureAnalysis analyzeMenuStructure(String menuType) {
try {
MenuStructureAnalysis analysis = new MenuStructureAnalysis();
analysis.setMenuType(menuType);

// 获取菜单数据
List<TreeNode> nodes = treeNodeMapper.selectByTreeType(menuType);

// 分析结构特征
analysis.setTotalNodes(nodes.size());
analysis.setMaxDepth(calculateMaxDepth(nodes));
analysis.setAvgChildrenPerNode(calculateAvgChildrenPerNode(nodes));
analysis.setLeafNodeRatio(calculateLeafNodeRatio(nodes));

// 分析性能特征
analysis.setLargeNodeCount(countLargeNodes(nodes));
analysis.setDeepNodeCount(countDeepNodes(nodes));
analysis.setComplexNodeCount(countComplexNodes(nodes));

return analysis;

} catch (Exception e) {
log.error("分析菜单结构失败", e);
throw new TreeException("分析菜单结构失败", e);
}
}

/**
* 计算最大深度
*/
private int calculateMaxDepth(List<TreeNode> nodes) {
int maxDepth = 0;
for (TreeNode node : nodes) {
if (node.getLevel() > maxDepth) {
maxDepth = node.getLevel();
}
}
return maxDepth;
}

/**
* 计算平均子节点数
*/
private double calculateAvgChildrenPerNode(List<TreeNode> nodes) {
int totalChildren = 0;
int parentNodes = 0;

for (TreeNode node : nodes) {
if (!node.getIsLeaf()) {
parentNodes++;
totalChildren += treeNodeMapper.countByParentId(node.getId());
}
}

return parentNodes > 0 ? (double) totalChildren / parentNodes : 0;
}

/**
* 计算叶子节点比例
*/
private double calculateLeafNodeRatio(List<TreeNode> nodes) {
int leafNodes = 0;
for (TreeNode node : nodes) {
if (node.getIsLeaf()) {
leafNodes++;
}
}
return nodes.size() > 0 ? (double) leafNodes / nodes.size() : 0;
}

/**
* 统计大节点数量
*/
private int countLargeNodes(List<TreeNode> nodes) {
int count = 0;
for (TreeNode node : nodes) {
int childrenCount = treeNodeMapper.countByParentId(node.getId());
if (childrenCount > 10) { // 子节点超过10个的节点
count++;
}
}
return count;
}

/**
* 统计深节点数量
*/
private int countDeepNodes(List<TreeNode> nodes) {
int count = 0;
for (TreeNode node : nodes) {
if (node.getLevel() > 5) { // 深度超过5层的节点
count++;
}
}
return count;
}

/**
* 统计复杂节点数量
*/
private int countComplexNodes(List<TreeNode> nodes) {
int count = 0;
for (TreeNode node : nodes) {
int childrenCount = treeNodeMapper.countByParentId(node.getId());
if (childrenCount > 5 && node.getLevel() > 3) { // 子节点多且深度深的节点
count++;
}
}
return count;
}

/**
* 优化缓存策略
*/
private CacheOptimizationResult optimizeCacheStrategy(String menuType, String userId) {
try {
CacheOptimizationResult result = new CacheOptimizationResult();
result.setMenuType(menuType);
result.setUserId(userId);

// 分析缓存命中率
double hitRate = analyzeCacheHitRate(menuType, userId);
result.setCurrentHitRate(hitRate);

// 优化缓存配置
if (hitRate < 0.8) {
result.setRecommendedCacheExpire(3600); // 1小时
result.setRecommendedCacheSize(1000);
result.setRecommendedCacheStrategy("LRU");
} else {
result.setRecommendedCacheExpire(1800); // 30分钟
result.setRecommendedCacheSize(500);
result.setRecommendedCacheStrategy("LFU");
}

return result;

} catch (Exception e) {
log.error("优化缓存策略失败", e);
throw new TreeException("优化缓存策略失败", e);
}
}

/**
* 分析缓存命中率
*/
private double analyzeCacheHitRate(String menuType, String userId) {
try {
String cacheKey = MENU_CACHE_PREFIX + menuType + ":" + userId;
Object cachedData = redisTemplate.opsForValue().get(cacheKey);

// 简单的命中率计算,实际应用中需要更复杂的统计
return cachedData != null ? 1.0 : 0.0;

} catch (Exception e) {
log.error("分析缓存命中率失败", e);
return 0.0;
}
}

/**
* 优化加载策略
*/
private LoadOptimizationResult optimizeLoadStrategy(String menuType, MenuStructureAnalysis analysis) {
try {
LoadOptimizationResult result = new LoadOptimizationResult();
result.setMenuType(menuType);

// 根据结构分析结果优化加载策略
if (analysis.getTotalNodes() > 1000) {
result.setRecommendedStrategy("LAZY_LOAD");
result.setRecommendedBatchSize(100);
result.setRecommendedLoadDepth(3);
} else if (analysis.getMaxDepth() > 5) {
result.setRecommendedStrategy("PROGRESSIVE_LOAD");
result.setRecommendedBatchSize(50);
result.setRecommendedLoadDepth(2);
} else {
result.setRecommendedStrategy("FULL_LOAD");
result.setRecommendedBatchSize(200);
result.setRecommendedLoadDepth(analysis.getMaxDepth());
}

return result;

} catch (Exception e) {
log.error("优化加载策略失败", e);
throw new TreeException("优化加载策略失败", e);
}
}

/**
* 优化渲染策略
*/
private RenderOptimizationResult optimizeRenderStrategy(String menuType, MenuStructureAnalysis analysis) {
try {
RenderOptimizationResult result = new RenderOptimizationResult();
result.setMenuType(menuType);

// 根据结构分析结果优化渲染策略
if (analysis.getTotalNodes() > 500) {
result.setRecommendedStrategy("VIRTUAL_SCROLL");
result.setRecommendedVisibleCount(20);
result.setRecommendedRenderMode("CLIENT_SIDE");
} else if (analysis.getMaxDepth() > 3) {
result.setRecommendedStrategy("TREE_VIEW");
result.setRecommendedVisibleCount(50);
result.setRecommendedRenderMode("HYBRID");
} else {
result.setRecommendedStrategy("LIST_VIEW");
result.setRecommendedVisibleCount(100);
result.setRecommendedRenderMode("SERVER_SIDE");
}

return result;

} catch (Exception e) {
log.error("优化渲染策略失败", e);
throw new TreeException("优化渲染策略失败", e);
}
}

/**
* 预热菜单缓存
*/
@PostConstruct
public void warmupMenuCache() {
try {
// 预热常用菜单
List<String> commonMenuTypes = Arrays.asList("system", "business", "user");

for (String menuType : commonMenuTypes) {
try {
// 预热根节点
String cacheKey = MENU_OPTIMIZATION_CACHE_PREFIX + menuType;
Object warmupData = new Object();
redisTemplate.opsForValue().set(cacheKey, warmupData, Duration.ofHours(1));
} catch (Exception e) {
log.error("预热菜单缓存失败: {}", menuType, e);
}
}

} catch (Exception e) {
log.error("预热菜单缓存失败", e);
}
}

/**
* 清理过期缓存
*/
@Scheduled(fixedRate = 300000) // 5分钟
public void cleanupExpiredCache() {
try {
// 清理本地缓存
localCache.cleanUp();

// 清理Redis过期缓存
cleanupRedisExpiredCache();

} catch (Exception e) {
log.error("清理过期缓存失败", e);
}
}

/**
* 清理Redis过期缓存
*/
private void cleanupRedisExpiredCache() {
try {
Set<String> cacheKeys = redisTemplate.keys(MENU_OPTIMIZATION_CACHE_PREFIX + "*");

for (String key : cacheKeys) {
Long ttl = redisTemplate.getExpire(key);
if (ttl != null && ttl <= 0) {
redisTemplate.delete(key);
}
}

} catch (Exception e) {
log.error("清理Redis过期缓存失败", e);
}
}
}

四、性能优化与监控

4.1 性能优化

4.1.1 树形菜单性能优化

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
/**
* 树形菜单性能优化服务
*/
@Service
public class TreeMenuPerformanceOptimizationService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Autowired
private CaffeineCache localCache;

private final String MENU_PERFORMANCE_CACHE_PREFIX = "menu_performance:";

/**
* 缓存菜单性能数据
*/
public void cacheMenuPerformanceData(String menuType, String userId, Object data) {
String cacheKey = MENU_PERFORMANCE_CACHE_PREFIX + menuType + ":" + userId;

try {
// 写入本地缓存
localCache.put(cacheKey, data);

// 写入Redis缓存
String redisCacheKey = "redis_cache:" + cacheKey;
redisTemplate.opsForValue().set(redisCacheKey, data, Duration.ofHours(1));

} catch (Exception e) {
log.error("缓存菜单性能数据失败", e);
}
}

/**
* 获取缓存的菜单性能数据
*/
public Object getCachedMenuPerformanceData(String menuType, String userId) {
String cacheKey = MENU_PERFORMANCE_CACHE_PREFIX + menuType + ":" + userId;

try {
// 从本地缓存获取
Object cachedData = localCache.getIfPresent(cacheKey);
if (cachedData != null) {
return cachedData;
}

// 从Redis获取
String redisCacheKey = "redis_cache:" + cacheKey;
Object redisData = redisTemplate.opsForValue().get(redisCacheKey);
if (redisData != null) {
localCache.put(cacheKey, redisData);
return redisData;
}

} catch (Exception e) {
log.error("获取缓存的菜单性能数据失败", e);
}

return null;
}

/**
* 批量处理菜单数据
*/
public List<MenuNode> batchProcessMenuData(List<TreeNode> nodes) {
List<MenuNode> menuNodes = new ArrayList<>();

try {
// 按层级分组
Map<Integer, List<TreeNode>> levelGroups = nodes.stream()
.collect(Collectors.groupingBy(TreeNode::getLevel));

// 并行处理各层级
levelGroups.entrySet().parallelStream().forEach(entry -> {
Integer level = entry.getKey();
List<TreeNode> levelNodes = entry.getValue();

try {
List<MenuNode> levelMenuNodes = processLevelNodes(level, levelNodes);

synchronized (menuNodes) {
menuNodes.addAll(levelMenuNodes);
}

} catch (Exception e) {
log.error("处理层级节点失败: {}", level, e);
}
});

} catch (Exception e) {
log.error("批量处理菜单数据失败", e);
}

return menuNodes;
}

/**
* 处理层级节点
*/
private List<MenuNode> processLevelNodes(Integer level, List<TreeNode> nodes) {
List<MenuNode> menuNodes = new ArrayList<>();

for (TreeNode node : nodes) {
try {
MenuNode menuNode = buildMenuNode(node);
menuNodes.add(menuNode);
} catch (Exception e) {
log.error("处理节点失败: {}", node.getId(), e);
}
}

return menuNodes;
}

/**
* 构建菜单节点
*/
private MenuNode buildMenuNode(TreeNode node) {
MenuNode menuNode = new MenuNode();
menuNode.setId(node.getId());
menuNode.setName(node.getName());
menuNode.setCode(node.getCode());
menuNode.setIcon(node.getIcon());
menuNode.setUrl(node.getUrl());
menuNode.setLevel(node.getLevel());
menuNode.setSortOrder(node.getSortOrder());
menuNode.setExpanded(node.getExpanded());
menuNode.setSelected(node.getSelected());
menuNode.setDisabled(node.getDisabled());
menuNode.setIsLeaf(node.getIsLeaf());
menuNode.setIsEnabled(node.getIsEnabled());

return menuNode;
}

/**
* 预热菜单性能缓存
*/
@PostConstruct
public void warmupMenuPerformanceCache() {
try {
// 预热常用菜单性能数据
List<String> commonMenuTypes = Arrays.asList("system", "business", "user");

for (String menuType : commonMenuTypes) {
try {
String cacheKey = MENU_PERFORMANCE_CACHE_PREFIX + menuType + ":default";
Object performanceData = new Object();
cacheMenuPerformanceData(menuType, "default", performanceData);
} catch (Exception e) {
log.error("预热菜单性能缓存失败: {}", menuType, e);
}
}

} catch (Exception e) {
log.error("预热菜单性能缓存失败", e);
}
}

/**
* 清理过期缓存
*/
@Scheduled(fixedRate = 300000) // 5分钟
public void cleanupExpiredCache() {
try {
// 清理本地缓存
localCache.cleanUp();

// 清理Redis过期缓存
cleanupRedisExpiredCache();

} catch (Exception e) {
log.error("清理过期缓存失败", e);
}
}

/**
* 清理Redis过期缓存
*/
private void cleanupRedisExpiredCache() {
try {
Set<String> cacheKeys = redisTemplate.keys("redis_cache:" + MENU_PERFORMANCE_CACHE_PREFIX + "*");

for (String key : cacheKeys) {
Long ttl = redisTemplate.getExpire(key);
if (ttl != null && ttl <= 0) {
redisTemplate.delete(key);
}
}

} catch (Exception e) {
log.error("清理Redis过期缓存失败", e);
}
}
}

4.2 监控告警

4.2.1 监控指标

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
/**
* 树形菜单监控指标
*/
@Component
public class TreeMenuMetrics {

private final MeterRegistry meterRegistry;

public TreeMenuMetrics(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}

/**
* 记录菜单加载次数
*/
public void recordMenuLoadCount(String menuType, String userId) {
Counter.builder("tree.menu.load.count")
.description("树形菜单加载次数")
.tag("menu_type", menuType)
.tag("user_id", userId)
.register(meterRegistry)
.increment();
}

/**
* 记录菜单加载时间
*/
public void recordMenuLoadTime(String menuType, long duration) {
Timer.builder("tree.menu.load.time")
.description("树形菜单加载时间")
.tag("menu_type", menuType)
.register(meterRegistry)
.record(duration, TimeUnit.MILLISECONDS);
}

/**
* 记录菜单节点数量
*/
public void recordMenuNodeCount(String menuType, int nodeCount) {
Gauge.builder("tree.menu.node.count")
.description("树形菜单节点数量")
.tag("menu_type", menuType)
.register(meterRegistry, nodeCount);
}

/**
* 记录菜单深度
*/
public void recordMenuDepth(String menuType, int depth) {
Gauge.builder("tree.menu.depth")
.description("树形菜单深度")
.tag("menu_type", menuType)
.register(meterRegistry, depth);
}

/**
* 记录菜单缓存命中率
*/
public void recordMenuCacheHitRate(String menuType, double hitRate) {
Gauge.builder("tree.menu.cache.hit.rate")
.description("树形菜单缓存命中率")
.tag("menu_type", menuType)
.register(meterRegistry, hitRate);
}

/**
* 记录菜单渲染时间
*/
public void recordMenuRenderTime(String menuType, long duration) {
Timer.builder("tree.menu.render.time")
.description("树形菜单渲染时间")
.tag("menu_type", menuType)
.register(meterRegistry)
.record(duration, TimeUnit.MILLISECONDS);
}

/**
* 记录菜单异常次数
*/
public void recordMenuExceptionCount(String menuType, String exceptionType) {
Counter.builder("tree.menu.exception.count")
.description("树形菜单异常次数")
.tag("menu_type", menuType)
.tag("exception_type", exceptionType)
.register(meterRegistry)
.increment();
}
}

4.2.2 告警规则

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
# prometheus-rules.yml
groups:
- name: tree_menu_alerts
rules:
- alert: HighTreeMenuLoadTime
expr: tree_menu_load_time{quantile="0.95"} > 5000
for: 2m
labels:
severity: warning
annotations:
summary: "树形菜单加载时间过长"
description: "树形菜单加载时间P95超过5秒,当前值: {{ $value }}ms"

- alert: HighTreeMenuNodeCount
expr: tree_menu_node_count > 1000
for: 2m
labels:
severity: warning
annotations:
summary: "树形菜单节点数量过多"
description: "树形菜单节点数量超过1000个,当前值: {{ $value }}"

- alert: HighTreeMenuDepth
expr: tree_menu_depth > 10
for: 2m
labels:
severity: warning
annotations:
summary: "树形菜单深度过深"
description: "树形菜单深度超过10层,当前值: {{ $value }}"

- alert: LowTreeMenuCacheHitRate
expr: tree_menu_cache_hit_rate < 0.8
for: 5m
labels:
severity: warning
annotations:
summary: "树形菜单缓存命中率过低"
description: "树形菜单缓存命中率低于80%,当前值: {{ $value }}"

- alert: HighTreeMenuRenderTime
expr: tree_menu_render_time{quantile="0.95"} > 2000
for: 2m
labels:
severity: warning
annotations:
summary: "树形菜单渲染时间过长"
description: "树形菜单渲染时间P95超过2秒,当前值: {{ $value }}ms"

- alert: HighTreeMenuExceptionCount
expr: rate(tree_menu_exception_count[5m]) > 5
for: 2m
labels:
severity: critical
annotations:
summary: "树形菜单异常次数过多"
description: "树形菜单异常频率超过5次/分钟,当前值: {{ $value }}"

五、总结

树形菜单作为企业级应用中的重要组件,通过合理的树形结构设计和递归算法实现,能够构建出高效、灵活、可扩展的树形菜单系统。本文从树形结构设计到递归算法实现,从基础实现到企业级应用,系统梳理了树形菜单的完整解决方案。

5.1 关键要点

  1. 树形结构设计:合理的数据结构设计,支持多种树形模型
  2. 递归算法实现:高效的递归算法,支持深度优先和广度优先遍历
  3. 菜单管理服务:完整的菜单管理功能,支持增删改查和权限控制
  4. 性能优化:通过缓存、懒加载、虚拟滚动等手段优化性能
  5. 监控告警:建立完善的监控体系,及时发现和处理问题

5.2 最佳实践

  1. 数据结构选择:根据业务需求选择合适的树形数据模型
  2. 算法优化:使用递归算法处理树形结构,注意性能优化
  3. 缓存策略:合理使用缓存,提高菜单加载性能
  4. 权限控制:完善的权限控制机制,确保数据安全
  5. 监控告警:建立完善的监控体系,确保系统稳定运行

通过以上措施,可以构建一个高效、稳定、可扩展的树形菜单系统,为企业的各种业务场景提供菜单管理支持。