递归检查权限树变慢是因为未剪枝导致重复遍历及栈溢出风险;应采用组合模式,通过component接口的iscomposite()和match()方法实现提前中断,叶子节点直接比对,容器节点匹配成功即返回,避免冗余遍历。

为什么递归检查权限树时会变慢
权限树深度超过5层、节点数破千后,HasPermission函数响应明显拖慢,不是因为逻辑错,而是每次递归都完整遍历子树——哪怕上层已确认无权访问。Go 默认栈大小仅2KB,深递归还可能触发runtime: goroutine stack exceeds 1000000000-byte limit panic。
如何用组合模式实现带剪枝的递归检查
关键不是“怎么递归”,而是“在哪儿停”。组合模式下,Component接口必须暴露IsComposite()和Match(code string) bool两个方法,让叶子节点能快速否定、容器节点能提前中断子树遍历。
-
Match方法返回true表示当前节点匹配目标code(如"system:menu:user"),返回false不等于“不匹配”,而是“不确定”,需继续查子树 - 容器节点的
Match实现里,先调自身逻辑(比如前缀匹配),再遍历Children;一旦某个子节点返回true,立刻return true,不继续后续子节点 - 叶子节点的
Match直接比对Code == code,不递归,也不调用IsComposite() - 避免在递归路径中重复解析code层级,比如
strings.Split(code, ":")应在入口处做一次,传入切片而非原始字符串
指针嵌套与nil Children切片的坑
声明Children []*PermissionNode时,如果某节点没子项,必须初始化为[]*PermissionNode{},不能是nil。否则for _, c := range node.Children会直接跳过循环,导致剪枝失效——你以为它没子节点,其实只是没初始化。
常见错误写法:Children: nil;正确写法:Children: make([]*PermissionNode, 0) 或 Children: []*PermissionNode{}。GORM 查询结果默认不会帮你初始化空切片,得在Scan后手动补。
并发场景下如何安全剪枝
权限检查常被高频调用(比如HTTP中间件),但树结构本身是只读的,无需锁;真正要防的是树构建阶段的并发写。剪枝函数本身无状态,可放心并发调用。
唯一要注意:别把context.Context或用户session信息塞进树节点结构体里——这会让节点失去复用性,也破坏组合模式的纯粹性。所有动态上下文应作为参数传入Match方法,例如:node.Match(code, userID)。
剪枝生效的前提,是你真正在意那几毫秒——如果树只有3层、几十个节点,优化反而增加心智负担。复杂点永远在“什么时候不该优化”。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











