ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

若依前后端分离部署排障:白屏、404、菜单与跨域处理

若依前后端分离部署排障:白屏、404、菜单与跨域处理

简介:围绕若依前后端分离项目的部署实践,这份文档面向已具备 Java、Vue 与 Linux 基础的开发者,系统梳理从环境准备到上线排错的完整路径。内容按 Linux+Nginx 部署、Windows+Tomcat 无 Nginx 部署、第三方 API 跨域处理三部分展开,涉及前后端打包、Redis 缓存、反向代理、路由回退与登录后空白等关键节点,并给出部署后刷新 404 等常见问题的排查思路。资源包共 1 个 docx 文件,约 223KB,篇幅精炼,便于按章节对照操作,也可作为团队内部部署手册的参考底稿。目前已有 7766 人学习,适合需要快速跑通若依项目、理解前后端分离部署细节与跨域排错方法的中初级开发者查阅。

1. 部署后白屏、404、菜单打不开:若依前后端分离最容易翻车的三个瞬间

打包上传、nohup起 jar、nginx -s reload,浏览器一开——白屏。再刷新,404。换个账号登录,菜单点不动。这三连击几乎是每个第一次部署若依前后端分离项目的人都要交的学费,我在不同机器上重装过好几遍,坑一个没落下。

问题在于它不像单体的 Spring Boot 那样“起了就是起了”。前端 Vue 打出来的dist是静态资源,后端 Spring Boot 是接口服务,中间还夹着 Nginx 反向代理和 Redis 缓存,链路一长,任何一环配置错位都表现为“页面打不开”,报错信息还都藏在浏览器控制台或者msg.log里,不看日志根本猜不到。这篇就按 Linux+Nginx 和 Windows+Tomcat 两条部署路径,把白屏、404、菜单加载失败、第三方 API 跨域这几类高频故障逐个拆开,说清触发条件、根因,以及能直接抄的命令和配置。

2. 白屏与接口 404:Nginx 代理和打包配置错在哪

部署完第一眼看到白屏,很多人第一反应是前端打包坏了,回头重跑npm run build:prod --report,白跑几遍还是白。实际上白屏只是“HTML 加载成功了,但 JS/CSS 资源 404 或者接口全挂”的外在表现,根因基本锁定在两个地方:Nginx 的root/location指向不对,或者前端vue.config.js里的publicPath跟实际访问路径不匹配。

2.1 先分清是静态资源 404 还是接口 404

打开浏览器开发者工具,切到 Network 面板刷新页面,看红色请求的路径长什么样。如果是/static/js/xxx.js、/static/css/xxx.css这类静态资源 404,问题在前端打包路径或者 Nginx 的静态目录配置;如果是/prod-api/getInfo、/prod-api/login这类接口 404,问题在 Nginx 的proxy_pass或后端服务没起来。这一步分流决定了后面查哪个配置文件,别一上来就改代码。

另一种情况更隐蔽:请求返回 200,但响应体是一段 HTML。这说明 Nginx 把接口请求也当成静态资源去try_files了,最后 fallback 到index.html,前端拿到一坨 HTML 当 JSON 解析,自然报错。判断方法是看 Response 的 Content-Type,是text/html就实锤了。

2.2 Nginx 配置:root、location、proxy_pass 三段必须对齐

若依官方文档给的 Nginx 配置骨架大概是这样,我按实际排错时最常改的三个点加了注释:

server { listen 80; server_name localhost; location / { root /home/ruoyi/dist; # 必须指到 dist 目录本身,不是它的父目录 try_files $uri $uri/ /index.html; # history 路由必须回退到 index.html index index.html index.htm; } location /prod-api/ { proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://127.0.0.1:8080/; # 结尾的斜杠决定路径是否被截断 } }

root是最容易写错的一项。有人写成/home/ruoyi,结果访问/时 Nginx 去找/home/ruoyi/index.html,而文件其实在/home/ruoyi/dist/index.html里,直接 404 白屏。try_files那行是给 Vue 的 history 模式兜底的,缺了它,用户刷新非根路径(比如/system/user)就会 404,因为服务器上根本没有这个物理文件。

proxy_pass结尾的斜杠是个经典陷阱。写http://127.0.0.1:8080/时,/prod-api/login会被替换成/login转发给后端;如果漏掉斜杠写成http://127.0.0.1:8080,转发过去的路径是/prod-api/login,后端没有这个映射,直接 404。后端 Spring Boot 的 context-path 如果配了/prod-api,那反过来就不能带斜杠,否则变成/prod-api/prod-api/login。这两个配置是一对,改一个必须检查另一个。

改完配置不要急着重载,先nginx -t验证语法,通过再nginx -s reload。语法错的时候 reload 会失败但旧进程还在跑,页面表现跟没改一样,很容易误判成“改了没用”。

2.3 前端 publicPath:那个被很多人忽略的点

原文档里提到“后期修改了文件或菜单,再部署上去菜单打不开,去掉vue.config.js里/之前的.即可”。这个.指的是publicPath: './'这种相对路径写法。相对路径在本地npm run dev时没问题,但部署到 Nginx 后,如果访问路径带层级,相对路径解析会错位,静态资源全 404,页面白屏。

// vue.config.js module.exports = { publicPath: process.env.NODE_ENV === 'production' ? '/' : '/', // 部署在根路径用 '/';如果部署在子路径 /ruoyi/ 下才改成 '/ruoyi/' outputDir: 'dist', assetsDir: 'static' }

publicPath设成'/'表示资源从域名根开始找,这是部署在独立域名或独立端口下最稳的写法。只有当项目挂在http://ip/ruoyi/这种子路径下时,才需要改成'/ruoyi/',并且 Nginx 的root和location也要同步加前缀。改完必须重新npm run build:prod,只改配置不重新打包是无效的——dist 里的index.html引用路径是打包时写死的。

打包时加--report会额外生成一个体积分析页面,部署时它只是个静态文件,不影响运行,但如果发现 dist 特别大、上传慢,可以先看这个报告砍掉没用的依赖再传,能省不少等待时间。

3. 菜单打不开与登录后空白:路由、Redis 和 Tomcat 的连环坑

比白屏更让人抓狂的是“能登录,但登录完一片空白”,或者“菜单树加载不出来,点哪个都没反应”。这类问题往往不是前端坏了,而是后端返回的数据不对,或者路由回退没配好。Windows+Tomcat 部署路径下这个坑尤其密集,因为少了 Nginx 那层兜底,全靠 Tomcat 自己处理。

3.1 登录成功但空白页:Tomcat 缺少 Context 配置

原文档里写得很直白:war 包丢进webapps、改名为prod-api、dist 丢进去,然后必须改conf/server.xml,在 Host 节点下加 Context:

<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"> <Context path="/" docBase="dist的绝对路径或相对webapps的路径" reloadable="true" crossContext="true"></Context> </Host>

不加这段,登录接口能通(因为/prod-api是独立 war 应用,Tomcat 能自动映射),但前端页面路由一跳转就空白。原因在于 Vue 的 history 模式路由/system/user这类地址,Tomcat 找不到对应的物理目录,又没有回退规则,直接给个空响应。Context 把根路径指向 dist 目录,配合后面的web.xml做 404 回退,才形成完整闭环。

docBase写相对路径时是相对于webapps目录的,比如 dist 放在webapps/dist,这里就写dist;写绝对路径则要写全,D:\tomcat\webapps\dist这种,反斜杠注意转义习惯。reloadable="true"用于开发期改文件自动重载,生产环境建议关掉,每次都重载会拖慢响应。

3.2 刷新页面 404:WEB-INF/web.xml 的 404 回退

原文档给出的关键一步:在 dist 目录下建WEB-INF文件夹,里面放web.xml:

<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1" metadata-complete="true"> <display-name>Router for Tomcat</display-name> <error-page> <error-code>404</error-code> <location>/index.html</location> </error-page> </web-app>

这段配置的作用是:Tomcat 遇到 404 时把请求转发到/index.html,前端路由接管后再按 URL 渲染对应组件。功能上等价于 Nginx 的try_files $uri $uri/ /index.html。没有它,用户只要在任意非根页面按 F5,就是一个 Tomcat 的 404 页,看起来像服务挂了。

metadata-complete="true"表示不扫描注解,加快启动;version="3.1"要和 Tomcat 版本匹配,Tomcat 8 用 3.1、Tomcat 9 用 4.0 都行,版本写错会导致 web.xml 解析失败,症状是配置“完全没生效”。放好文件后必须重启 Tomcat,热部署不一定能识别新增的 WEB-INF。

3.3 菜单加载不出来:先看 Redis,再看动态路由

若依的菜单是动态权限菜单,登录后前端调/getRouters拿菜单树。这个接口挂了或者返回空数组,左侧菜单就是空白。排查顺序我一般是:先redis-cli ping确认 Redis 活着,再tail -f msg.log看有没有连接异常,最后看接口返回。

Redis 没起的时候,登录可能还能成功(JWT 校验不完全依赖它),但 token 缓存、验证码、字典数据全挂,菜单接口很容易超时或返回空。启动命令就是redis-server,确认redis.conf里bind和protected-mode没把本地连接挡掉。jar 包启动时如果 Redis 连不上,日志里会有Unable to connect to Redis明确的报错,别去前端找原因。

启动 jar 本身也有讲究,原文档用的是:

nohup java -jar ruoyi-admin.jar > msg.log 2>&1 &

nohup让进程脱离终端,> msg.log 2>&1把标准输出和错误都重定向到日志,&放后台。少了2>&1,错误日志不会进文件,出问题只能看到标准输出,排查时等于蒙眼。msg.log生成在当前执行命令的目录下,换目录启动就找不到日志了,这是新手常问“日志在哪”的原因。

4. 跨域报错与部署避坑清单:CORS、代理和那些反复踩的点

控制台一行红字has been blocked by CORS policy,前面所有部署工作看起来都白干了。跨域在若依项目里分两种:前后端之间的跨域(部署正确时不该出现),和调用第三方 API(比如地图服务)的跨域(必然出现,必须处理)。混在一起查会浪费大量时间。

4.1 前后端跨域:先确认代理是否真的生效

标准部署下,前端和后端同域名同端口,请求走/prod-api被 Nginx 转发,浏览器看来是同源,不会有跨域。如果部署后还是报跨域,说明请求根本没走代理——大概率是前端baseURL配错了,直接请求了http://ip:8080。检查.env.production里的VUE_APP_BASE_API,应该是/prod-api这种相对路径,不是完整域名。

# .env.production VUE_APP_BASE_API = '/prod-api'

改成完整地址后,浏览器就认为跨域了,Nginx 代理形同虚设。这个文件改完同样要重新打包。本地开发时vue.config.js里配devServer.proxy也是同理,只对npm run dev生效,生产环境靠 Nginx,两边别搞混。

4.2 第三方 API 跨域:三种处理方式怎么选

调用地图类第三方 API 时跨域无法避免,因为请求目标是别人的域名。常见做法有三种:

后端加 CORS 配置,让浏览器放行。Spring Boot 里可以全局配:

@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") // 生产环境换成具体域名 .allowedMethods("GET", "POST", "PUT", "DELETE") .allowCredentials(true) .maxAge(3600); } }

allowedOriginPatterns而不是allowedOrigins("*"),是因为开启allowCredentials(true)后通配符*会被拒绝,用 Pattern 版本才能兼容。这只解决自家后端接口的跨域,第三方 API 的响应头不归你管。

要绕开第三方 API 的跨域,就得让请求从服务器发出。Nginx 加一段反代:

location /third-api/ { proxy_pass https://第三方接口域名/; proxy_set_header Host 第三方接口域名; }

前端请求改成/third-api/xxx,浏览器同源,Nginx 服务器之间没有跨域限制。缺点是要为每个第三方域名加一段,且对方如果校验 Referer 或签名,可能还是过不去。

第三种是前端代理,只在开发期有效,生产环境不适用。三种方式里,需要长期稳定调用的走 Nginx 反代,临时调试用后端 CORS,别在vue.config.js的 proxy 上纠结生产问题。

4.3 部署避坑清单:五条血泪记录

  • 现象:改了配置重启后毫无变化。原因:Nginx 没 reload 或 reload 失败但没看输出,Tomcat 进程没真正重启。解决:nginx -t && nginx -s reload一步步来,Tomcat 用shutdown.sh后确认进程消失再startup.sh。
  • 现象:部署到子路径后所有资源 404。原因:publicPath还是/,Nginx 的location也没加前缀。解决:两处同步改成子路径,重新打包。
  • 现象:jar 起来几秒就退出。原因:端口被占或 Redis 连不上,异常打到msg.log里了。解决:tail -f msg.log看首个异常堆栈,netstat -tlnp | grep 8080查端口。
  • 现象:Windows 下 dist 里加了WEB-INF但刷新还是 404。原因:web.xml版本与 Tomcat 不匹配,或文件放错层级(应在 dist/WEB-INF/web.xml)。解决:核对版本号,重启 Tomcat。
  • 现象:菜单能显示但点进去接口 401。原因:token 没带上或 Redis 里 token 已过期。解决:看请求头有没有Authorization,清缓存重新登录,检查 Redis 服务时间与服务器是否一致。

5. 快速定位部署故障:我的分层排查顺序

折腾多了以后,我形成了一个固定的排查顺序,从外到内一层层剥,基本十分钟内能定位到是哪一层的问题。

第一步永远是看浏览器 Network 面板,把所有红色请求按类型分类:静态资源、接口、第三方。静态资源 404 查publicPath和 Nginxroot;接口 404 查proxy_pass斜杠和 context-path;接口 500 直接跳去看msg.log;第三方报 CORS 走第 4 章的处理。这一步能砍掉八成“感觉哪里都坏了”的假象。

第二步看后端日志的首个异常。tail -n 200 msg.log足够,异常堆栈的第一行和 caused by 那几行才是根因,后面的都是连锁反应。Redis 连不上、端口占用、数据库连不上、配置文件没加载,日志里都有明确关键字,比在前端猜快得多。

第三步做连通性验证,绕过前端直接打接口:

curl -i http://127.0.0.1:8080/getInfo curl -i http://127.0.0.1/prod-api/getInfo

第一条通第二条不通,问题在 Nginx 转发;两条都不通,后端没起来或者路径不对。这样能立刻把 Nginx 和后端两个嫌疑分开。

现象优先检查常见根因
整页白屏Network 静态资源publicPath、Nginx root
接口 404proxy_pass 结尾斜杠路径被截断或多一层前缀
登录后空白server.xml Context缺少根路径映射
刷新 404try_files / web.xmlhistory 路由无回退
菜单空Redis + /getRouters缓存服务未启动
跨域红字VUE_APP_BASE_API请求绕过代理直连后端

还有一个笨但有效的习惯:每次改完配置,先nginx -t、再 reload、再刷新,三步都确认了才继续下一步,绝不一次性改三处再一起重启。早期我图省事,一次改 Nginx 配置、vue.config.js和server.xml,结果页面好了但不知道是哪一项起的作用,下次换个环境又从头踩一遍。后来强制自己一次只动一个变量,改完记录一句“改了 X,现象从 A 变成 B”,排错速度反而快了很多。这套部署文档里 Linux+Nginx、Windows+Tomcat、跨域处理三部分的内容,配合上面这个顺序用,基本能覆盖绝大多数部署期的疑难杂症。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

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