RuoYi框架新增模块接口404排查与解决 说句实话后台管理系统开发里最让人血压飙升的场景之一就是你明明把Controller写好了、Service也写了启动项目时日志也没报错结果浏览器一访问新接口迎面就是一个冷冰冰的404。尤其当项目用的是RuoYi这种基于Spring Boot的快速开发平台时这种新增模块后接口404的问题几乎每个新人都要踩一遍。最近我就在带一个RuoYi项目团队里隔三岔五就有人跑过来问我代码写的没问题啊怎么一访问就是404 今天干脆把这类问题的排查思路和解决方案完整梳理一遍基于Spring Boot的RuoYi框架里新增模块接口404的坑基本都能在这篇文章里找到答案。这篇文章适合所有在用RuoYi无论Vue分离版、单体版还是微服务版做开发的朋友尤其是刚开始接触RuoYi工程结构的同学。我会按先定位、再排查、后修复的逻辑把整个问题拆开讲每个坑都会附上实操验证手段尽量让你下一次遇到类似问题能直接照着做而不是靠上网搜半天。1. 先搞清楚404到底是哪一层返回的1.1 前端路由404和后端接口404是两回事RuoYi-Vue分离版的项目前端是一个Vue单页应用后端是Spring Boot工程。这两种结构混在一起时最容易让人晕的一个点就是你看到的404可能压根不是后端返回的。Vue Router自己有一套前端路由机制如果新页面没在菜单管理里配置或者当前登录用户没有分配对应菜单权限前端路由匹配不到就会直接渲染一个404 Not Found页面。这类404发生在浏览器前端根本没有发出任何HTTP请求到后端。怎么快速分辨打开浏览器开发者工具F12切到Network面板然后刷新页面看请求列表如果压根没有请求发出去只是页面显示了404那基本可以断定是前端路由的问题。如果确实看到了一次HTTP请求状态码是404那才是后端接口404。还可以看响应体Spring Boot默认的404是Whitelabel Error Page或者一段JSON格式的错误体里面通常有timestamp、status、error、path这些字段而前端路由404返回的是Vue渲染出来的HTML页面。把这个搞清楚了排查方向才不会跑偏。我在实际带人时发现大概有三分之一的新增模块404问题其实是前端路由没配或者菜单权限没分配但人全在后端代码里翻来翻去白白浪费时间。1.2 后端接口404的四类根源真正走到后端这层Spring Boot接口返回404总结下来基本逃不出四种情况Controller根本没有被Spring容器管理也就是组件扫描没覆盖到。请求路径与Controller、RequestMapping里配置的映射路径不一致。请求在过滤器或拦截器层面被提前处理比如权限拦截后转发到了不存在的路径。应用实际加载的代码不是最新版本也就是编译产物或依赖版本陈旧这种情况最坑因为代码看起来明明是对的。后面所有的排查手段本质上都是在围绕这四类可能做排除。2. 组件扫描与包路径最容易看起来没问题的深坑2.1 SpringBootApplication的默认扫描范围RuoYi的主启动类通常在com.ruoyi包下面长这样SpringBootApplication public class RuoYiApplication { public static void main(String[] args) { SpringApplication.run(RuoYiApplication.class, args); } }这个注解最大的一个隐含行为是默认只会扫描它所在包以及子包下的所有类把它们注册成Spring容器里的Bean。如果你新增模块的Controller放在了com.ruoyi.business这种子包下那没问题但如果手一抖包名写成了com.example.business启动类默认扫不到你的Controller永远不会被加载接口自然404。RuoYi是标准的多模块Maven工程常见的模块包括ruoyi-admin启动模块、ruoyi-framework框架配置、ruoyi-system系统管理、ruoyi-common公共工具。如果你照着新建了一个ruoyi-business模块但主启动模块一般是ruoyi-admin的pom.xml里没有把这个新模块加为依赖那即便代码文件在IDE里看得见最终运行时classpath里也没有它。这两种情况的表现很像但排查重点不同。如果Controller类和启动类在同一个Maven模块下那么优先检查包路径如果Controller写在另一个Maven模块里那么优先检查依赖引用和组件扫描范围。踩过几次坑之后我个人的习惯是新增的模块包名一律放在com.ruoyi下面依赖也直接在ruoyi-admin的pom.xml里声明这样最省心。如果不方便调整包名也可以在启动类上显式追加扫描范围SpringBootApplication ComponentScan(basePackages {com.ruoyi, com.yourbusiness}) public class RuoYiApplication { ... }不过要提醒一句这属于兜底方案能用但会坏了RuoYi的包结构规范建议还是按官方推荐的分包方式来组织代码。2.2 Maven多模块里代码存在但未被打包的坑多模块Maven工程里还有一个极其隐蔽的问题就是模块依赖对了但新代码没有真正install到本地仓库。RuoYi工程里如果你改了ruoyi-system模块下的类然后在本地直接通过IDEA运行ruoyi-admin大多数情况下IDEA会编译整个工程没问题。但如果你用命令行打包或者是另一台机器通过CI/CD来构建那就有可能出现问题ruoyi-admin依赖的ruoyi-system是本地仓库里的旧jar包你本地IDE里看到的最新代码根本没参与构建。解决方法是构建前强制把依赖模块装进本地Maven仓库mvn clean install -DskipTests如果依赖的是快照版本比如1.0.0-SNAPSHOTMaven默认对本地仓库的快照依赖有缓存逻辑不会每次重新拉取所以最好加上-U参数强制更新mvn clean install -U -DskipTests这正是热搜词里出现过mvn : 无法将mvn项...这类问题的场景来源。很多新人第一次在这些命令里栽跟头除了环境变量没配好之外就是不理解install和package之间的区别。简单理解package只是把当前模块打成jar包install是除了打包之外再把它安装到本地的Maven仓库里供其他模块引用。多模块项目的根目录下执行install才能确保所有子模块的改动都被其他依赖方使用。2.3 注解写错Controller变成了无头苍蝇还有一类情况是代码确实被扫描进来了路径也对得上但依然404——注解写错了。最常见的是把RestController写成了Controller。这两个注解的差别在于Controller是Spring MVC里传统的控制器方法默认返回的是视图名称比如JSP、Thymeleaf模板名而不是数据。如果方法上没有加ResponseBody那当你请求/business/order/list时Spring会尝试去找一个叫list的视图找不到就抛异常表现上可能是一堆HTML错误页甚至404。RuoYi后端接口的标准写法是直接用RestController方法直接返回AjaxResult、TableDataInfo这些统一结果对象RestController RequestMapping(/business/order) public class OrderController { GetMapping(/list) public TableDataInfo list() { // 业务逻辑... return getDataTable(list); } }这里顺便说一个RuoYi自带的细节BaseController里封装了分页、获取当前用户等通用方法正常业务Controller继承它就好。如果你发现新Controller里没有继承功能一般也不受影响但统一性会差一些。还有一个容易疏忽的小点RequestMapping路径不要忘了类级别的路径。举个例子类上写了RequestMapping(/business/order)方法上写了GetMapping(/list)实际访问路径就是/business/order/list。如果类上漏写了实际路径就变成了/list外部访问/business/order/list自然404。3. 编译与运行产物陈旧代码写了却不生效3.1 IDEA热部署模式下新文件没编译用IDEA开发Spring Boot项目如果项目里引入了spring-boot-devtools或者开启了IDEA自带的HotSwap/热部署功能确实能省不少重启时间。但它有个让人抓狂的副作用有时候新添加的Java文件不会自动编译进target/classes目录。我处理过的最典型的一个案例是这样的同事从已有的Controller文件里复制了一个新文件改了类名、改了请求路径保存后直接点了重启结果接口404。我让他去target/classes目录下找新Controller的class文件找了半天发现根本没有。执行一次mvn clean compile之后问题立即消失。在IDEA里确认编译产物最直接的方式就是在target/classes目录下逐级查看有没有你新增类的class文件。如果没有别犹豫先强制重新编译mvn clean compile或者直接在IDEA右侧的Maven面板里先点根项目的clean再点compile。这里有个小经验如果只改了单个模块就在对应模块上单独执行clean和compile速度更快。3.2 打出来的jar包里没有新接口这个问题在高频发版阶段尤其常见。代码在本地跑得好好的部署到测试服务器就404那大概率是打包环节出问题了。处理思路是先确认jar包里到底有没有你的类。jar tf ruoyi-admin.jar | grep OrderController如果没有输出说明编译/打包环节就没把这个类放进去回头检查模块依赖和构建命令。如果有输出再用curl绕开Nginx等代理直接打后端端口看接口是否通。如果直连后端接口正常那就是部署环境里代理层的路径转发问题。我见过一个实际案例新模块的接口路径是/business/order/listNginx里配了location /business/的转发规则但由于配置文件加载的是旧版本导致请求被转到了一个不存在的上游路径直接404。这种问题不看配置很难凭代码排查出来。3.3 路径匹配策略差异引发的未映射再提一个比较隐蔽、但遇到就整懵你的情况Spring Boot 2.6版本开始Spring MVC的路径匹配策略从AntPathMatcher换成了PathPatternParser。两者对路径的通配符支持有差异。如果你在RuoYi里手动升级过Spring Boot的版本或者用了一些较新的Spring Boot底座有些老风格接口路径在新策略下可能匹配失败。比如/business/{id}.do这种带后缀的写法或者正则式占位符新策略下的表现就不一样。排查起来其实也不难如果同一个Controller里其他接口能访问单独某个接口路径404优先检查是不是路径参数写法有问题。改成标准的REST风格/business/{id}绝大部分情况都能解决。4. 权限拦截与Security配置被藏起来的4044.1 未认证请求被拦截的伪404RuoYi默认集成了Spring Security整个框架的接口默认都是受保护的。它有一个SecurityConfig类里面会配置哪些路径允许匿名访问哪些需要登录。如果你新加的接口是给外部系统调用的比如回调、开放API又没有加到permitAll列表里那未登录状态下访问时请求会被Security拦截。RuoYi默认的认证入口返回的是401但在某些版本或自定义配置下前端统一拦截处理时可能把401包装成了找不到页面的提示。从用户视角看就是接口404。如果你确认Controller、编译都没问题但带Token和不带Token访问表现不一样那就去SecurityConfig里看看路径放行规则.antMatchers(/login, /register, /captchaImage, /error).permitAll() .anyRequest().authenticated()需要放行的路径加上.antMatchers(/business/open/**).permitAll()4.2 菜单权限与前端路由的联动性RuoYi的前端路由是动态生成的数据来源是数据库里的sys_menu菜单表。也就是说你新增了一个页面和接口如果没有在系统管理-菜单管理里配置对应的菜单也没有给当前用户分配角色前端根本不会生成这个页面的路由。此时用户在前端手动输入页面地址看到的就是404。这个点完美呼应了第1节讲的前端路由404。很多新人看到页面上一个404就跑到后端翻代码翻了半天发现后端接口其实是好的。所以在RuoYi里新增模块后的标准流程是后端代码写好前端页面写好然后去菜单管理配置菜单最后给角色分配权限。少一步前端路由404都会出现。4.3 PreAuthorize权限不足被包装成404还有一种真正在后端发生、但表现得像404的情况比较少见。RuoYi的Controller方法上经常会有权限校验的注解比如PreAuthorize(ss.hasPermi(business:order:list))如果登录用户的角色没有business:order:list这个权限标识后端会抛出权限异常。RuoYi的全局异常处理器对这个异常一般返回403但如果你在自定义配置里覆盖了异常处理策略或者Security的accessDeniedHandler做了特殊包装在某些版本组合下这个403可能被包装修改为404。这种假404的排查方法是去看后端控制台日志。如果日志里出现了AccessDeniedException、AuthorizationDeniedException或者RuoYi自己的ServiceException那基本就是这个原因。处理方式要么给角色分配对应菜单权限要么临时把注解注释掉做验证。这里也顺带说一个更贴近RuoYi的实际场景如果你用的是RuoYi-Cloud微服务版本那么网关路由也是一个重点排查对象。新注册的服务如果没有在Nacos里正确注册或者Gateway的routes配置里没有新模块的转发路径前端请求过来时网关直接返回404。这个问题在微服务架构里尤其常见排查时看一眼Nacos的服务列表再对照一下路由配置基本能定位。5. 实操排查三板斧让接口注册情况可视化5.1 启动日志里搜MappedSpring Boot启动时Spring MVC会把所有注册好的接口路径打印在日志里。RuoYi项目启动后在控制台搜Mapped关键字能看到类似这样的日志Mapped {[/business/order/list],methods[GET]} onto public com.ruoyi.common.core.domain.AjaxResult com.ruoyi.business.controller.OrderController.list()能搜到这条日志说明接口已成功注册到Spring MVC那404基本就和组件扫描、路径映射无关问题更可能出在代理层或者权限拦截。如果搜不到说明Controller压根没被加载回头查包扫描和模块依赖。5.2 curl直连后端绕过所有中间层如果应用已经启动直接在命令行里测试是最快的验证手段# 直连后端绕过Nginx等代理 curl -i http://127.0.0.1:8080/business/order/list # 看响应体是不是Spring Boot默认错误页 curl -s http://127.0.0.1:8080/business/order/list | head -n 30这个步骤的价值在于可以把后端代码问题和部署环境问题快速分开。如果直连8080是正常的但通过域名访问404那问题就在Nginx、网关或者前端路由如果直连8080也是404再往下推代码问题。5.3 actuator映射端点检查如果项目里引入了spring-boot-starter-actuator还可以直接用它的映射端点来查看所有接口curl http://localhost:8080/actuator/mappings | grep business/order没有启用actuator的话也可以临时在启动类里加一个输出RequestMapping列表的代码但相对麻烦一般启动日志就够用了。6. 常见问题速查表与避坑心得6.1 一份可以直接保存的排查清单现象优先怀疑方向快速验证手段前端页面404Network里无HTTP请求前端路由未配置或菜单权限未分配菜单管理里检查路由配置和角色分配请求发出后端返回404组件扫描、路径映射、依赖未加载启动日志搜Mapped检查包路径本地正常服务器404编译产物陈旧或代理配置错误jar tf查看class是否存在curl直连后端未登录访问404登录后正常Security拦截接口未放行检查SecurityConfig里的permitAll配置日志出现AccessDenied权限注解导致被包装检查PreAuthorize权限标识分配对应菜单权限微服务版新增服务404网关路由或注册中心未生效查看服务注册列表确认Nacos实例路径含通配符或正则时404Spring Boot 2.6路径匹配策略变化改成标准REST风格路径6.2 我对这类问题的一点实操心得新增模块接口404这个问题其实是个典型的排查思路问题而不是技术深浅问题。因为绝大多数情况下代码就是那么几行问题不在代码本身而在工程结构、编译状态、权限配置这些环境因素上。我个人的排查顺序已经固化了按这个顺序走基本能在十分钟内定位95%的问题先看浏览器Network面板确认404是哪一层返回的再看后端启动日志里有没有Mapped记录如果Mapped有用curl绕过代理直连后端如果还是没有检查编译产物必要时mvn clean install -U重新构建最后再怀疑Security权限和前端路由配置。最后再分享一个小技巧尤其是给刚接触RuoYi的新手新增一个模块时别急着写一大堆业务代码。先建一个最小化的Controller只写一个返回AjaxResult.success(ok)的空接口启动项目确认Mapped日志出现了再把业务代码往里填。这样就把接口注册和业务逻辑两个变量分开出问题以后直接知道该查哪一层。这个习惯在多人协作的大工程里尤其有用也让我少加了不少本来没必要的夜班。