很多用过若依RuoYi做后台系统的朋友大概都经历过这么一幕开发环境里为了验证某个功能、给同事做演示顺手加了一堆菜单栏页面从“测试管理”“模拟数据”到“临时工具”什么都有。等到功能提测、准备上线前想把这些自己加的菜单清理掉结果发现后台“菜单管理”页面里点删除要么提示“存在子菜单不允许删除”要么删完刷新侧边栏还在甚至同一个用户换个浏览器登录又能看到。今天这篇就把“删除若依里自己加的菜单栏”这件事彻底捋一遍以若依为例不同版本思路通用保证你照着操作就能删干净。1. 为什么“加菜单容易、删干净”却这么难先说个现象。我刚接触若依那段时间也是在系统管理里点几下就加好一个菜单感觉很简单。结果到了要清理的时候才发现自己当时的“简单”其实是在三个地方各点了一两下菜单表里插了一条记录、角色配置里勾了个权限、前端静态路由里也许还写了一行。等到删除时就不只是删一条记录那么简单了。若依的菜单体系是分层的不是一条平铺的数据。一个真正能点击并跳转的“菜单栏”在数据库里通常涉及目录M、菜单C、按钮F三种类型。你在侧边栏看到的一级分组可能是目录点进去的页面是菜单页面里面某个操作按钮的权限则是按钮类型的数据。如果你只是想删掉一个“自测功能”的页面入口结果它的父级目录还有其它菜单挂着系统就会拦着你不让删。如果你把目录删了下属的菜单还在数据库里变成“孤儿数据”前端渲染路由时可能直接白屏或报错。删不干净的后果不只是不好看。最麻烦的是权限残留。若依的接口权限大多靠PreAuthorize注解配合权限标识字符串控制比如system:user:list。这个权限标识挂在菜单表或按钮表上再通过角色关联到账号。如果你只是把页面入口从界面上隐藏了却没有处理按钮权限数据和角色关联数据那么知道接口地址的人依然可以调用这个接口这在验收或上线前是个实打实的安全隐患。为什么说“所有版本适用”因为若依不管怎么变——单体版本、前后端分离版本、微服务版本——核心菜单表结构基本没动过字段都是menu_id、parent_id、menu_name、menu_type、path、perms那一套前端动态路由的生成逻辑也大体一致。所以下面讲的原理和操作换一个版本照样能用差别只在于按钮位置和页面样式。2. 动手之前先看透若依菜单数据到底存在哪有的朋友可能直接打开数据库看到一堆表就开始犯晕。其实跟菜单相关的核心表就两张sys_menu和sys_role_menu再加上一张sys_role作为角色维度。搞清楚它们的结构删除逻辑就清晰了。2.1 sys_menu表的字段含义sys_menu是菜单的主表一行记录代表一个目录、菜单或按钮。下面这些字段是你删除和排查时必须理解的字段含义删除时为什么重要menu_id菜单ID主键删除时要拿它做条件parent_id父菜单ID顶级为0有父子关系删父前要先处理子menu_name菜单名称按名字定位你加的菜单menu_type类型M目录/C菜单/F按钮不同类型影响路由生成规则path路由地址删除后前端路由会消失component组件路径指向vue页面文件删菜单不影响源文件perms权限标识按钮类型特有后端鉴权靠它visible是否显示0显示1隐藏影响侧边栏status状态0正常1停用is_frame是否外链外链菜单的path是完整URL2.2 menu_type决定菜单在页面上的表现你加菜单时在“菜单类型”下拉框里选的选项直接写进menu_type字段M目录侧边栏的分组节点点击会展开子菜单没有自己的页面组件。C菜单真正可以跳转的页面入口有component指向对应的Vue组件。F按钮不显示在侧边栏只作为权限标识挂在某个菜单下用来控制页面里的操作按钮是否可见、后端接口能否调用。删除的时候很多人只关注到侧边栏看见的“目录”和“菜单”忽略了下属的按钮数据。如果漏删按钮角色配置里还会残留这个权限后端接口依然会被判定为“已授权”。2.3 parent_id与树形结构菜单表通过parent_id形成一棵树。比如你在“系统管理”目录下加了一个“测试页面”这条记录的parent_id就是“系统管理”的menu_id。删除时必须先从最底层开始删否则会遇到“存在子菜单不允许删除”的提示。举一组简单的示例数据便于你理解结构menu_idmenu_nameparent_idmenu_type1系统管理0M100用户管理1C101用户查询100F102用户新增100F200测试菜单1C如果你要删“测试菜单”它没有子节点直接删200这一行即可。如果你要删“系统管理”而100、101、102都挂在它下面系统会拦着提示存在子菜单。2.4 sys_role_menu被忽略的中间表sys_role_menu存的是“哪个角色拥有哪个菜单权限”字段很简单role_id、menu_id。这决定了菜单管理页面里“分配权限”树形控件勾选的数据来源。有几种删除场景必须处理这张表一是菜单已经被分配给角色删除时若依会提示“菜单已分配不允许删除”二是你希望彻底清除某个按钮权限即使菜单记录删了角色菜单关联和相关缓存也要一并清掉否则会残留。2.5 前端动态路由与缓存的关系若依前后端分离版本中侧边栏不是写死在代码里的而是登录后后端根据当前用户角色动态返回菜单路由集合接口通常是/getRouters。也就是说你菜单表里删掉记录后理论上用户重新登录或刷新页面侧边栏就会少掉那一项。但实际开发中大家会遇到“删了还在”的情况原因多半出在缓存上。若依会把登录用户信息和权限标识放到Redis里如果Redis里的旧数据没清掉前端拿到的还是旧路由。这个点后面“翻车现场”部分我会详细说。3. 三种删除路线页面删除、数据库直删、组合拳理解了底层结构删除操作本身就不复杂了。我按“省事程度”给你三条路线你可以根据场景自己选。3.1 路线一后台页面删除适合单个菜单这是最规范、最不容易出错的方案适合删单个或几个菜单。用管理员账号登录系统进入“系统管理 - 菜单管理”。左侧菜单树中找到你要删的菜单。如果目标菜单下面还有子菜单先展开把子菜单逐个删掉。选中目标菜单点击“删除”按钮弹出确认框后确定。到“系统管理 - 角色管理”里检查哪些角色还关联了这个菜单。如果删除时提示“菜单已分配”先编辑对应角色在菜单权限树中取消这个菜单的勾选保存后再删除。页面删除的底层逻辑是若依的MenuServiceImpl.deleteMenu方法。它会先检查是否有子菜单有就直接抛出异常接着删除sys_role_menu里的关联数据最后删除sys_menu里的记录。所以正常情况下操作完两张表都被清干净了。提示如果删除时提示“存在子菜单不允许删除”说明下面还有子节点去子级列表里先处理。如果提示“菜单已分配”说明有角色绑定了这个菜单权限需要去角色管理里取消勾选。3.2 路线二数据库直接删除适合批量清理如果你一次性加了几十个测试菜单一个个在页面上点太累了可以直接操作数据库。走这条路前务必备份。我自己习惯先把两张表备份出来CREATE TABLE sys_menu_bak_20260601 AS SELECT * FROM sys_menu; CREATE TABLE sys_role_menu_bak_20260601 AS SELECT * FROM sys_role_menu;备份完成后先定位自己加的菜单。怎么定位最好的办法是看menu_name、remark或者create_by字段比如SELECT menu_id, menu_name, parent_id, menu_type, path, component, perms, create_by, create_time FROM sys_menu WHERE menu_name LIKE %测试% OR remark LIKE %测试% OR create_by admin ORDER BY parent_id, order_num;小技巧查询结果按parent_id排序能直观看出父子关系先处理叶子节点。如果要递归删除一个目录下的所有子菜单MySQL 8 支持递归CTE思路是先查出所有子孙节点再统一删除。不过用递归CTE直接DELETE同一张表在MySQL里会报错推荐的做法是先查出来确认无误后再执行删除-- 先查出某个父ID下的所有子节点ID包含自身 WITH RECURSIVE menu_tree AS ( SELECT menu_id, menu_name, parent_id FROM sys_menu WHERE menu_id 目标ID UNION ALL SELECT m.menu_id, m.menu_name, m.parent_id FROM sys_menu m INNER JOIN menu_tree t ON m.parent_id t.menu_id ) SELECT menu_id FROM menu_tree;拿到ID列表后再执行两条DELETE一条清理角色关联一条清理菜单本身DELETE FROM sys_role_menu WHERE menu_id IN (...); DELETE FROM sys_menu WHERE menu_id IN (...);注意顺序不能反先删关联表再删主表。另外如果你不确定哪些菜单是自己加的宁可先查出来人工核对也别在OpenAI式的“三思而后行”上偷懒——数据库删除没有后悔药。3.3 路线三页面数据库组合拳最推荐实际项目里我推荐组合拳小体量用页面删除立体化清理用数据库最后统一做缓存刷新。具体做法是先在页面上能删的删一遍删不掉的大概率是“被角色占用”或“存在子节点”。打开数据库处理页面删不掉的情况解除角色关联、删除子节点。清理Redis缓存。最简单的方式是在测试环境执行redis-cli FLUSHDB生产环境慎用最好用DEL删指定key或者在系统“缓存监控”里清空缓存。让相关用户重新登录一次确保前端拿到最新的路由和权限数据。这套流程下来菜单栏、角色分配、权限缓存就都同步了。4. 高频翻车现场这些坑我基本都踩过理论说完了说点实战里最容易翻车的地方。下面几个场景都是我真实遇到过的情况每一个背后都有对应的排查思路。4.1 坑一页面删除报“存在子菜单”怎么都删不掉这是我被问过最多的问题。很多同事以为目录下面没有可见的菜单了为什么还提示存在子菜单原因是按钮F类型也属于子菜单。你在菜单管理左侧树里展开目录看到的可能只有“删除、新增、编辑、导出”这类按钮节点它们也是子节点。所以删除目录前先展开目录把所有按钮节点一并删掉再删目录就能成功。如果你在页面树形里看不到这些按钮比如显示被折叠了直接跑一条SQL确认更靠谱SELECT menu_id, menu_name, parent_id, menu_type FROM sys_menu WHERE parent_id 这里填你的目录ID;4.2 坑二提示“菜单已分配”角色配置里却找不到这个情况也很邪门。删除菜单时提示分配给了角色但你去角色管理里打开菜单权限树翻遍了也没找到那个菜单。原因在于角色管理里的菜单权限树渲染的是可勾选的菜单节点有些状态异常或visible1隐藏的菜单节点可能不在当前渲染条件下显示但在sys_role_menu里又确实有记录。排查手段就是查库SELECT r.role_id, r.role_name, rm.menu_id, m.menu_name FROM sys_role_menu rm LEFT JOIN sys_role r ON rm.role_id r.role_id LEFT JOIN sys_menu m ON rm.menu_id m.menu_id WHERE rm.menu_id 目标菜单ID;查到是哪些角色占用了之后手动清理关联数据或者用如下SQL解除关联DELETE FROM sys_role_menu WHERE menu_id 目标菜单ID;这种情况处理完以后记得同步清理Redis缓存中对应角色的权限数据否则角色权限树里可能还会显示旧状态。4.3 坑三删除后刷新侧边栏还在删完菜单表、角色关联也清了用户不退出登录直接刷新页面菜单居然还在。这个我一开始也懵了很久。原因是若依的登录用户信息存储在Redis中包含权限标识集合和路由数据。前端路由一般会在页面刷新时重新拉取但如果后端返回的逻辑里优先读了Redis缓存中旧的权限集合就会出现“已删除但仍可访问”的错觉。处理方法最省事让用户退出登录再重新登录。开发环境清空Redis缓存FLUSHDB是测试环境最常用的手段。生产环境有单点登录或统一session管理的话按用户维度删除旧的登录缓存。提示不要把这个和你前端静态路由混淆。若依的动态路由是后端下发的所以只要数据库和缓存都清干净前端最终会恢复正常。4.4 坑四删了按钮权限接口还是能访问按钮菜单对应的perms字段直接对后端接口的权限判断负责。比如PreAuthorize(ss.hasPermi(system:test:remove))如果你删掉了这个按钮菜单但角色关联数据或Redis缓存里还留着system:test:remove这段字符串用户依然能调用这个接口。所以删除按钮时除了删菜单本身还要检查sys_role_menu中该菜单ID是否还有关联记录。Redis里该用户缓存的权限集合是否还包含perms。角色配置页面的“权限字符”是否还有残留。标准的善后操作是删除按钮菜单后将涉及的角色保存并重新分配或者直接让用户重新登录保证权限集合重新从数据库加载。4.5 坑五误删菜单后想恢复发现要手动补一堆字段还有一类情况是菜单删得太顺手误删了若依自带的系统菜单比如“用户管理”下面的某个按钮等项目启动后发现页面渲染乱了或者某个角色权限树缺了节点。恢复方法其实不复杂前提是你有备份表。如果你按前面的习惯做了备份直接执行INSERT INTO sys_menu SELECT * FROM sys_menu_bak_20260601 WHERE menu_id 被误删的ID; INSERT INTO sys_role_menu SELECT * FROM sys_role_menu_bak_20260601 WHERE menu_id 被误删的ID;如果没有备份那就查到若依官方项目中同菜单模块的SQL重新INSERT。这里也暴露了一个习惯问题凡是自己往菜单表里插数据务必保留一份SQL脚本或INSERT记录否则恢复靠记忆力是真的痛苦。5. 删完之后怎么避免下次再手忙脚乱清理一次菜单不难难的是每次都能快速定位、各版本通用、绝不误删。我总结了一套自己的做法分享给你参考。5.1 用“命名前缀备注”给自己加的菜单做标记我在若依里做二次开发时凡是自己增加的菜单菜单名称一律加个固定前缀比如“DEV-测试页面”“TMP-临时报表”并在备注或remark字段里写明用途、添加时间、对应需求单号。这样以后想清理时一条SQL就能把自己加的全部捞出来SELECT menu_id, menu_name, remark FROM sys_menu WHERE menu_name LIKE DEV-% OR remark LIKE %临时%;别小看这个习惯。和我配合过的前端同事有几次就靠这个备注快速区分了哪些是系统自带菜单、哪些是测试菜单省了很多沟通成本。5.2 菜单规划三层结构想清楚再加如果只是一个测试页面就别在目录层新开一个“测试管理”分组了挂到已有的某个菜单下面即可。这样清理时只需要处理一个叶子节点不会出现“为了删一个页面要把整棵树拆掉”的情况。合理的结构应该是第一层目录按业务模块划分如“系统管理”“业务中心”“报表中心”。第二层菜单对应具体的功能页面如“用户管理”“角色管理”。第三层按钮对应页面内的操作权限如“用户查询”“用户新增”“用户删除”。自测菜单如果一定要建单独建一个“DEV-测试菜单”目录下面挂测试页面测试完整个目录一键清理不会波及业务菜单。5.3 清理前先定位再动库我每次做清理无论多少菜单都先跑一遍定位SQL确认菜单ID和父子关系然后按“先子后父、先关联后主表”的顺序操作。为了防止手滑我还会把要删的ID先复制到文本里人工核对一遍核对无误再执行删除。高权限账号在正式环境里执行DELETE前最好再单独备份一次哪怕只是其中一张表。这不算麻烦但能救命。5.4 缓存刷新别漏掉最后一步清理菜单后最后一道工序一定是缓存和登录态。开发环境中我直接清空Redis生产环境则在低峰期按账号维度清理或者通知相关用户重新登录。没有这一步你前面做的事情在用户端的感知就是“没变化”很容易被误认为操作无效。最后如果你基于某个版本的若依做项目菜单删除的逻辑是通用的看清sys_menu的层级处理sys_role_menu的关联再清掉Redis缓存基本就收工了。个人经验是最优雅的删除方式不是“出问题再补”而是在加菜单时就规划好前缀、层级和备注让删除变成一条简单SQL的事。这套方法我在单体版、前后端分离版上都用过稳定、直接、不需要反复试。下次再遇到“删不掉”“删了还在”照着上面步骤一步步查应该能解决你八成以上的烦恼。