ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

若依框架新增模块接口404?从包扫描到动态路由的排查指南

若依框架新增模块接口404?从包扫描到动态路由的排查指南 如果你在若依RuoYi这类基于 Spring Boot 的后台管理框架上开发过新模块大概率撞过这样一堵墙代码明明照着别人写好了启动过程也没报错后端服务正常起来结果一访问新写的接口浏览器直接甩给你一个 404连点像样的日志都没有。这事的诡异之处在于它不是每次都能复现有时候重启一下好了有时候怎么调都没用。而且 404 这个状态码本身就有点“糊弄人”——它不像 500 那样明确告诉你“内部炸了”也不像 401 那样说“你没权限”它只是淡淡地告诉你“这里什么都没有”。但对于开发人员来说最头疼的就是这种“什么都有却又什么都没有”的状态。这篇文章我打算把若依框架中新增模块后接口 404 的排查思路完整捋一遍从前端后端的区别、包扫描和注解的坑、编译和缓存问题、URL 拼接、再到权限拦截和 RuoYi 特有的动态路由机制全部拆开讲清楚。末尾还会给出我平时新增模块时的标准操作流水线照着做基本能一次过。内容不多但都是实际踩过坑之后才攒下的经验。1. 别急着改代码先搞清楚 404 到底是谁返回的1.1 前后端分离下“接口404”和“页面404”是两件完全不同的事若依有单体版和前后端分离版如果你用的是 RuoYi-Vue 这类的分离架构那第一步就得先弄清楚你访问的到底是“页面地址”还是“接口地址”。这两个东西 404 的原因完全不相干查错方向的话能白忙一下午。接口 404指的是你通过 HTTP 请求一个后端 API 路径比如GET /api/system/user/list然后后端返回了 404。这种情况是 Spring Boot 的 DispatcherServlet 找不到对应的 HandlerMapping换句话说Spring 容器里压根没有能处理这个请求的方法。页面 404则是你在浏览器里输入了一个前端路由地址比如/system/user/index前端 Vue Router 找不到匹配的路由组件。这时候你看到的 404 页面往往是若依自带的那个 NotFound 页面而不是后端的错误响应。判断方法非常简单打开浏览器 F12 开发者工具看 Network 面板里的请求。如果请求发给了后端接口返回 404那是后端问题如果请求没有发出去或者只是前端路由自己兜底渲染了 404 组件那就是前端问题。响应内容的格式也能看出来Spring 返回的 404 通常是一段 JSON 或者 Whitelabel 错误页而 Vue 找不到路由时页面显示的是“抱歉你访问的页面不存在”之类的中文提示。1.2 先完整复现一次拿到请求地址、请求方法和响应体很多人一上来就翻代码找 Controller结果找了半天没发现问题回头一测才发现自己访问的路径根本不对。所以我的建议是先不急着看代码花两分钟把现场完整“取证”一遍。拿 Postman 或者直接 curl 请求一下接口把完整的 URL、请求方式、请求头都记录下来。重点看这几个地方路径里有没有漏掉context-path。有些若依配置里设置了server.servlet.context-path比如/ruoyi那你实际访问的地址就是http://ip:端口/ruoyi/模块路径/接口路径。漏了前缀直接 404。请求方法对不对。Controller 里写的是PostMapping你用 GET 去访问返回的会是 405 Method Not Allowed不是 404。但如果你用了不同路径那就可能 404。这个点看着小出错率不低。前后端分离时前端调用的是/dev-api/xxx这其实经过开发服务器的代理转发最后才打到后端。代理路径有没有配置、代理目标对不对也都是 404 的高发区。把复现的请求信息记下来之后再带着这些信息去看代码效率会高非常多。很多老手排查 404 比新手快不是因为他们眼光毒而是他们能在一分钟内判断出该往哪个方向查。2. 查后端接口真的被 Spring 管起来了吗2.1 包扫描是最常见的原因检查你的包路径是否在 com.ruoyi 下面Spring Boot 启动类上通常标注了SpringBootApplication而这个注解本质上包含了ComponentScan。它默认扫描的是“启动类所在包及其子包”下的所有组件。若依的启动类位置一般是com.ruoyi.RuoYiApplication或者类似的结构那么它会扫描com.ruoyi这个包及下面所有子包。这个机制的潜台词是如果你的新模块 Controller 放在了com.ruoyi.xxx里面那没问题能被扫到。但如果你建了个独立的包比如com.example.mybusiness或者直接把 Controller 放在某个不属于com.ruoyi分支的层级下Spring 就根本不会去加载它。为什么 Spring 扫不到就会 404因为 DispatcherServlet 在启动时会从容器里拿所有的 HandlerMapping把RequestMapping注解的 URL 和对应方法注册成映射表。没被扫描的 Controller 类压根没进容器URL 自然不在映射表里访问时直接 404而且启动过程毫无异常。快速判断方法启动后端时看日志有没有打印你新增 Controller 的 RequestMapping 信息。Spring Boot 启动时会在 DEBUG 级别下输出所有 URL 映射你也可以在配置里把logging.level.org.springframework.webDEBUG打开。如果日志里压根没有你这几个路径基本可以断定就是扫描范围的问题。解决办法是在启动类上加SpringBootApplication(scanBasePackages {com.ruoyi, com.yourmodule})或者用ComponentScan指定包路径。但这里我要多说一句能不改启动类就别改最好把新模块的包路径放到com.ruoyi下面这样最省心也符合团队协作时其他人的预期。2.2 Controller 注解没写对也会让你找不到接口包扫描没问题那再检查注解。这个听起来很基础但实际项目中经常见。第一种情况类上写的是Controller而不是RestController。在前后端分离的若依项目里接口基本都是返回 JSON所以要用RestController。如果只写了Controller方法上又没有ResponseBodySpring 会把这个接口当成返回视图处理虽然映射是存在的但访问的时候会返回 404 或者解析视图失败现象和 404 很像。第二种情况方法上忘了写GetMapping、PostMapping之类的请求映射注解。这种情况类上的RequestMapping存在但方法没有映射访问方法路径一样 404。第三种情况更隐蔽你在 Service 或者工具类上写了RestController或Controller然后里面没有请求映射方法。这种类虽然能进容器但没有映射不至于引发 404。麻烦的是它容易误导排查方向你以为接口在这个类里其实它根本没有。所以检查 Controller 注解时要确认三个点类上有没有RestController方法上有没有请求映射注解请求映射的 value 和 method 是否和访问方式匹配。2.3 用更直接的手段确认接口到底注册了没有如果不想靠猜有更硬核的验证方式直接把所有已注册的 URL 映射列出来看。第一种方法是在配置里开启 Spring MVC 的日志logging: level: org.springframework.web: trace重启后日志里会出现大量关于 RequestMappingHandlerMapping 的注册信息你直接搜自己新模块的路径关键词一搜就能看到注册成功与否。第二种方法是引入 Spring Boot Actuator然后请求/actuator/mappings拿到全部映射列表。如果你对 Actuator 不熟我建议还是用日志方式比较轻量。第三种方法最粗暴但也很有效临时在 Controller 里写一个非常简单的测试接口比如GetMapping(/test404)返回R.ok()然后访问一下。如果这个都能通说明环境没问题问题出在你之前的代码里。如果这个都 404那就不要再看业务代码了问题一定在扫描、依赖或者项目结构上。我踩过的一次最典型的坑是新增了一个 Maven 子模块里面写了 Controller但父 pom 里没有把这个子模块加进modules列表结果代码编译都没进主工程的 classpathSpring 自然什么也扫不到。这种问题靠改扫描包路径是没用的得去检查 Maven 依赖和模块引用关系。把新模块的依赖在ruoyi-admin的 pom 里加好之后重新构建一次再启动才正常。3. 代码明明存在为什么接口还是 4043.1 最坑的一类编译产物没更新如果说上面的问题是“真的没有这个接口”那这一节的问题是“接口明明在但跑起来的服务里没有”。这类现象非常迷惑人因为你翻代码、看类文件全都在但实际访问就是 404。最常见的原因是 IDEA 没触发重新编译。Spring Boot 项目在 IDEA 里启动时默认编译 target 目录下的 class 文件。如果你新增了 Controller 或者修改了 URL 映射之后没有执行 Build 或者 Rebuild Projecttarget 里跑的还是旧 class那新增的接口当然不存在。很多人在 IDEA 里点了重启按钮但 Spring Boot DevTools 的热重启有时会失灵或者根本没有引入 DevTools 依赖这时候“重启”只是把旧的 class 再加载一遍而已。处理方式遇到怎么改都没反应的 404先手动执行一次 Maven 的clean把 target 删掉再重新编译启动。命令就是mvn clean package -DskipTests在 IDEA 里对应的操作是Build - Rebuild Project然后再启动。另外还要检查一点你启动的进程到底是不是最新编译出来的。有时候旧进程没被完全杀掉新端口被占用IDEA 可能选择在已有进程上操作或者你起了多个实例访问的全是旧实例。用netstat -ano | findstr 端口号Windows或者lsof -i:端口号Linux/macOS看看端口对应的进程确认一下 PID 和启动时间是比较靠谱的做法。3.2 URL 拼错类级映射和方法级映射的组合这类问题尤其常见于“照着写”的场景。若依的 Controller 往往有类级别的RequestMapping然后方法上又有具体的GetMapping完整访问路径是两者拼接的结果。比如RestController RequestMapping(/system/user) public class SysUserController { GetMapping(/list) public TableDataInfo list(SysUser user) { // ... } }这个接口的完整访问路径就是/system/user/list。看起来很简单但出错的姿势有很多种。最常见的是类上写了/system/user方法上写了/list你访问的时候只带了其中一个还有一种是在类路径末尾加了斜杠方法路径开头也加了斜杠拼起来出现双斜杠/system/user//list。有些网关和代理会对双斜杠进行处理有时候能通有时候直接 404表现得非常不稳定。在若依项目里模块路径的设计也是有讲究的。系统管理是/system开头监控是/monitor开头业务模块如果你自己建建议也保持这种风格比如/business/xxx、/crm/xxx。这样从 URL 上就能一眼看出模块归属也方便在网关层做路径匹配和权限控制。另外如果你用了若依的代码生成器生成的代码路径是相对标准的。但手写时容易忽略的一点是方法级映射的路径建议用/list、/add、/edit这些有明确语义的单词不要搞成queryUserList这种动词英文既不符合 REST 风格也容易在拼接时出错。3.3 权限拦截和路径放行也会让“404”变得很迷惑在若依框架里如果你访问的是需要登录的接口但没带登录凭证通常会返回类似“未登录”或者 401 的提示。但如果你访问的是需要特定权限标识的接口而当前用户没有这个权限若依可能会返回“没有权限”的提示状态码可能是 403。但有的时候因为路径配置不正确某些权限拦截器把请求直接吞掉也可能出现 404 的现象。尤其在若依的 Shiro 或 Spring Security 配置里有一个对外匿名访问的路径列表。如果你新增的接口希望匿名访问但你忘了把这个路径加入匿名白名单拦截器就会把你的请求拦下来。拦下来之后如果拦截器没有很好的错误处理逻辑可能就返回了一个默认错误页看起来跟 404 一模一样。不过说实话如果只是权限问题更常见的现象是 401 或 403而不是 404。但如果你在排查时发现“接口在日志里已经注册了但访问就是 404”那可以留意一下是不是有过滤器过滤了路径。比如若依的 XSS 过滤器、身份认证过滤器等都有一定的路径匹配规则如果路径规则匹配到了不该匹配的接口或者路径被重写都可能造成接口访问异常。要确认这个方向直接看后端日志搜索请求的 URL看看它有没有进入某个拦截器的处理逻辑。如果没有基本可以排除权限拦截问题。4. 前端 404 的另一种场景菜单、路由与若依动态路由4.1 若依的动态路由机制如果你是前后端分离版那新增模块后即使后端接口已经可以正常访问了前端也可能出现“404”的假象。比如你新写了一个页面路由也加了但是页面访问的时候直接跳转到了若依自带的 404 页面。这个问题的根子在于若依的动态路由机制。若依的前端路由分两部分静态路由比如登录页、404 页、首页这些和动态路由。动态路由是根据数据库里sys_menu表的数据在用户登录后由前端动态生成的。菜单表里配置的每一个菜单项、路由地址、组件路径最终都会变成一个 Vue Router 的路由记录。所以你在前端代码里写了一个views/business/index.vue文件但如果没有在“菜单管理”里配置对应的菜单记录动态路由根本不会生成这个路由你直接访问路由地址就是 404。4.2 菜单配置了但页面还是 404 的几种常见原因第一种是“路由地址”和“组件路径”写错了。菜单管理里有一个“路由地址”字段对应前端 URL 路径和一个“组件路径”字段对应views下的文件路径。路由地址写的是business/index但组件路径写成了business/index这只是理想情况。实际开发中经常有目录层级对不上的情形组件路径写的是business/index但你的文件实际放在views/business/list.vue那系统会去加载views/business/index.vue找不到组件自然 404。第二种是路由地址的层级问题。你在菜单管理里配置菜单时如果父菜单的路由地址是parent子菜单的路由地址是child那么完整路由路径就是/parent/child。如果你在子菜单里写了/parent/child全路径反而可能生成出/parent/parent/child这样的嵌套从而 404。第三种是按钮权限标识错误导致菜单被隐藏。菜单管理里如果配置了“权限标识”那前端在渲染动态路由时会根据当前用户的权限过滤菜单。如果当前登录用户没有分配这个权限菜单就不显示直接访问路由地址也会因为路由不存在而 404。4.3 前端缓存和重新登录能解决一半的“404”这一点必须单独拿出来说因为它真的是被忽略的高频原因。若依前端在用户登录成功后会把动态路由存到 Vuex 和本地存储里。如果你在数据库里新增了一条菜单记录然后让当前在线用户刷新页面他可能仍然走的是旧的路由表因为刷新时前端判断已有用户信息就直接用旧的了。正确的方式是改完菜单后需要把浏览器缓存清一下或者直接退出登录重新登录。重新登录时前端会重新请求菜单接口拉取最新的菜单记录生成最新的动态路由。很多“菜单配好了但页面还是 404”的情况其实就是没有重新登录导致的。另外前端开发服务器比如 Webpack Dev Server也有自己的缓存。如果产品代码没变但引用的路由配置文件变了有时候需要重启一下前端 dev server 才能生效。这个跟后端编译问题有一点像本质都是“代码和运行产物不一致”。5. 我在若依里新增模块时的标准流水线5.1 后端从包结构到 Controller 的一份完整清单一次把项目搭对比遇到 404 再去排查要省心得多。我一般在若依里新增一个业务模块时会严格按照一套固定流程来。后端部分先确认包结构业务模块统一放在com.ruoyi.你的模块名下下面再分domain、mapper、service、controller四个字包。若依的代码生成器生成的也是这个结构保持统一的好处是后续看别人的代码、别人看你的代码都不费劲。然后写 Controller 的时候我会直接套用若依的规范RestController RequestMapping(/business/order) public class BizOrderController extends BaseController { Autowired private IBizOrderService bizOrderService; GetMapping(/list) public TableDataInfo list(BizOrder bizOrder) { startPage(); ListBizOrder list bizOrderService.selectBizOrderList(bizOrder); return getDataTable(list); } PostMapping(/add) public AjaxResult add(RequestBody BizOrder bizOrder) { return toAjax(bizOrderService.insertBizOrder(bizOrder)); } }如果你在 BaseController 里面已经有startPage()、getDataTable()这些方法直接继承就能用。这里还有个易错点RequestBody和PostMapping是配套的前端若依的 request.js 封装的默认 Content-Type 是 JSON所以后端接收对象参数时基本都用RequestBody。如果你用了 GET 请求方式去提交一个需要 body 的对象接口会报 415 或者干脆匹配不上映射。写完之后先不要启动项目直接执行一遍mvn clean install -DskipTests确认项目能完整编译通过。编译是最廉价的检查能提前拦住 80% 的代码级错误。5.2 前端从 API 文件到页面文件后端接口搞定后前端部分按顺序做三件事写 API 文件、写页面文件、配置菜单。API 文件放在src/api/你的模块名/xxx.js内容一般长这样import request from /utils/request export function listOrder(query) { return request({ url: /business/order/list, method: get, params: query }) }注意这里的 URL 要跟后端 Controller 的完整路径完全一致。若依开发环境有代理前端请求路径最前面并没有/api之类的统一前缀代理配置里已经把/dev-api这种前缀剥掉了再转发。你只需要写/business/order/list就行。如果你在这里画蛇添足加了别的统一前缀比如/api/business/order/list而后端没有这个/api前缀那就是 404。页面文件放到src/views/你命名的目录/xxx.vue下这个没有严格规范但建议目录名和菜单路由地址保持一致。比如菜单路由地址是business/order那页面文件放views/business/order/index.vue最不容易出错。菜单配置这里容易犯迷糊我说得细一点。在系统管理 - 菜单管理里点“新增”类型选“C 菜单”路由地址填order或者business/order取决于父菜单的路由地址组件路径填business/order/index权限字符填一个字符串如business:order:list然后在分配权限时给需要的人勾上。保存后退出登录再重新登录左边的菜单就会出现点击后能正常打开页面。5.3 验证清单改完这些才算完成我有一个几乎不变的新模块验收清单你按这个顺序走一遍能过滤掉绝大多数 404 问题后端启动无报错日志里能看到新 Controller 的所有请求映射路径。用 Postman 直接请求后端接口不带认证信息也能测通或者带着 Swagger/Knife4j 的调试环境测。能拿到 JSON 返回才算后端接口真正 OK。前端重新登录能通过菜单点击进入新页面。页面里的列表查询能拉到后端数据新增、修改按钮能正常工作。权限、按钮级别的控制按角色切换验证一下。这套流程单次跑下来也就十来分钟但如果跳过等出了问题再回头查时间成本至少翻三倍。6. 快速排查速查表与最后的避坑心得我把最常见的 404 场景整理成一个速查表方便你遇到问题时按图索骥现象可能原因排查手段解决建议访问任何新模块接口都 404日志无映射包路径不在扫描范围内查看启动类扫描包范围把包放进com.ruoyi或加 scanBasePackagesController 写了但启动日志没有该路径编译产物未更新执行 mvn clean package / Rebuild Project清掉 target 再重启日志有映射访问仍 404URL 拼接错误检查类级方法级请求映射按完整路径访问避免双斜杠前端页面访问 404接口正常动态路由没生成检查菜单管理配置配置菜单重新登录前端菜单配了但页面 404组件路径和路由地址不一致对比 views 目录文件路径修正菜单配置里的组件路径访问接口返回 404 但静态资源正常context-path 前缀遗漏查看 application.yml 配置请求地址加上 context-path新模块在其他电脑上能跑本机不行IDEA 缓存或 local 仓库依赖缺失查看 Maven 依赖重新导入 Maven 项目更新依赖最后分享几个我个人的经验。第一遇到 404 不要先怀疑框架有问题若依这种成熟框架的坑大概率是你自己踩出来的静下心按“有没有被扫描 - 有没有被编译 - 路径对不对 - 前端路由对不对”这个顺序查基本都能定位。第二多利用若依自带的代码生成器它能帮你把 Controller、Service、Mapper、前端页面和菜单 SQL 一次性生成出来模板本身是经过大量项目验证的比自己手写出错概率低得多。第三如果你经常在同一个框架里开发新模块建议把上面这套“标准流水线”写成团队文档让新人照着做能少踩很多 404 的坑。这东西说白了就是个知行合一的过程第一次遇到会觉得玄排查过三五次之后你就能条件反射般定位到问题。框架不会骗你404 背后一定有原因找到它改掉它下次就顺畅了。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进