首页
/
行业洞察
/
正文
INDUSTRY INSIGHT · 深度
若依前后端分离部署排错:白屏、404、502与跨域实战
📅 2026/11/3 11:00:11
✍️ 爱科研究院
👁 阅读 3,247
简介围绕若依前后端分离项目部署编写的实操文档面向具备Spring Boot与Vue基础、需要把项目迁移到Linux或Windows服务器的开发者集中解决双环境部署、第三方接口跨域及部署后页面空白或404等常见问题。压缩包内含一个docx文件约223KB按Linux配合Nginx与Windows配合Tomcat两套方案组织并补充跨域配置、异常页面处理等章节内容以命令、配置项和关键文件路径为主线便于逐项对照排查。文档结构按部署环境分章步骤之间配有排查提示便于按问题定位配置。已有7766人学习适合作为项目上线前的核对清单与排错索引。读者可获取前后端打包、反向代理、访问路径设置及兜底页面配置等操作参照也能理解前端代理、后端跨域策略与代理转发三种处理思路减少部署反复试错提升项目落地效率。1. 部署完打开是白屏这类问题为什么总在最后一步爆出来前后端分离项目部署到服务器上最容易出事的不是写代码阶段而是打包上传之后——浏览器打开 IP 只有一个白屏或者登录按钮点下去一直转圈F12 里一堆红色 404 和 CORS 报错。若依这类 Spring Boot Vue 的组合前后端分属两个进程、中间还隔着一层 Nginx 或 Tomcat任何一个环节的路径、端口、代理规则差一个字符最后呈现给用户的都是「页面打不开」。写这份部署笔记的初衷就是把这套流程里最容易翻车的地方按故障类型拆开让第一次上手的同学少走几圈弯路也让已经踩过坑的人能快速对上号。2. 打包阶段就翻车mvn clean package 失败与 dist 目录是空的部署的第一步不是传文件是本地能打出干净可用的产物。我见过太多人是「jar 打出来了但没内容」「dist 传上去发现只有一层空目录」然后在服务器上折腾半天其实问题全在本机。这一章把打包阶段的高频失败场景过一遍每一步都给出可以直接抄的命令和验证方式。2.1 BUILD FAILURE 先分清是编译挂还是测试挂mvn clean package报BUILD FAILURE时控制台往上翻十几行一定会有一行[ERROR]开始的真正原因。最常见的两类一类是编译错误比如某个实体类字段改名后 mapper 里还引用着旧名字另一类是测试用例失败很多人的单元测试里写死了本地数据库连接一换环境就连不上导致整个打包流程中断。定位方式是先把错误行数看清楚# 打包并输出完整日志失败后直接搜 [ERROR] 定位第一处真实原因 mvn clean package -DskipTestsfalse build.log 21 # 只看第一处报错后面的往往是连锁反应 grep -n ERROR build.log | head -5-DskipTestsfalse表示照常跑测试用来确认问题是不是测试引起的如果确认与业务无关、只是本机环境问题改成-DskipTests跳过测试先出包。逻辑上package阶段依赖test阶段测试挂掉整个生命周期就终止所以很多人看到的「打包失败」其实是「测试失败」两者在日志里的位置和形态完全不同不要混为一谈。2.2 target 目录里没有 jar先看模块结构若依是多模块工程真正能启动的是带启动类的那个子模块一般是ruoyi-admin根目录直接mvn package打出来的是聚合工程的产物jar 不一定在你以为的位置。我一般打完包后先确认一遍# 列出所有子模块下生成的 jar确认哪一个带启动类 find . -name *.jar -path */target/* -exec ls -lh {} \; # 确认 jar 里确实包含启动类避免拿到的是空壳 unzip -l ruoyi-admin/target/*.jar | grep -i Application.class如果find什么都没输出多半是pom.xml里的packaging被改过比如为了 Tomcat 部署改成了war却忘了改回来或者打包插件被注释掉了。还有一种情况是 IDE 自动构建把 target 目录清掉了此时重新跑一次mvn clean package即可别在空目录上反复纠结。2.3 npm run build:prod 出来的 dist 是空的或直接内存溢出前端打包失败的表现比后端隐蔽因为npm有时会返回 0 退出码但dist里只有一个index.html或者干脆没生成完整静态资源。先确认命令跑的是生产模式而不是开发模式# 生产环境打包--report 会额外生成体积分析报告便于排查包过大 npm run build:prod --report # 检查产物完整性正常应有 index.html 和 static 目录 ls -lh dist du -sh dist如果报JavaScript heap out of memory是 Node 默认堆内存不够Vue 项目依赖一多就会触发# 临时调大 Node 堆内存上限到 4G再重新打包 export NODE_OPTIONS--max-old-space-size4096 npm run build:prod这个环境变量只在当前终端会话有效重新开窗口就没了所以别指望它在 CI 里也生效CI 里要在构建脚本中显式设置。--report生成的报告只是辅助不影响产物本身但能看出是哪个依赖把包撑大了。2.4 传上服务器之前先在本地把产物验一遍这一步很多人跳过结果把问题带到了服务器上排查成本翻好几倍。我的做法是在本地起一个静态服务把dist挂上去直接看页面能不能加载# 用 npx 起一个静态服务器模拟 Nginx 直接托管 dist 的效果 npx serve -s dist -l 5000 # 浏览器访问 http://localhost:5000F12 看是否有 404 的 js/css-s参数表示单页应用模式所有未命中的路径回退到index.html这正是 Nginx 里try_files $uri $uri/ /index.html做的事情。如果本地这么访问页面正常、只是接口请求失败说明静态资源没问题问题一定在反向代理或后端服务如果本地打开就是白屏那和服务器无关是打包配置的问题先改vue.config.js里的publicPath再说。3. Linux Nginx 部署502 Bad Gateway、接口 404 与 Redis 连接被拒这是最常见的一套生产环境组合jar 包由java -jar拉起前端dist交给 Nginx 托管/prod-api反向代理到后端端口。这一章按「先看现象再倒推」的顺序展开每个故障都对应一个具体报错方便你直接对号入座。3.1 nginx.conf 里三个位置决定成败root、try_files、proxy_pass大部分 502 和 404 都能在这一段配置里找到答案先把一份可用的最小配置摆出来server { listen 80; server_name _; charset utf-8; # 前端静态资源目录放哪台机器就写哪个绝对路径 location / { root /home/app/dist; # 单页应用必须加这一行否则刷新子路由直接 404 try_files $uri $uri/ /index.html; index index.html; } # 后端接口前缀必须和前端 .env.production 里 VUE_APP_BASE_API 一致 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; # 末尾这个斜杠决定 /prod-api/ 前缀是否被剥掉 proxy_pass http://127.0.0.1:8080/; } }proxy_pass末尾有没有斜杠是这套配置里最阴的一个点写成http://127.0.0.1:8080/Nginx 会把/prod-api/user/list转成/user/list写成http://127.0.0.1:8080转过去仍然是/prod-api/user/list后端没有这个映射直接 404。这就是很多人「接口路径明明没错却报 404」的根因。改完配置别忘nginx -t校验语法再nginx -s reload平滑重载直接restart会把正在处理的请求掐断。3.2 502 Bad Gateway先确认后端进程还活着502 的含义很死板——Nginx 找不到能连上的上游。所以排查顺序是进程在不在、端口通不通、配置指的对不对。# 第一步看 java 进程是否还在 ps -ef | grep java | grep -v grep # 第二步在本机直接探测后端端口排除 Nginx 干扰 curl -i http://127.0.0.1:8080/ # 第三步确认端口确实被监听而不是被防火墙挡了 ss -lntp | grep 8080如果进程不在去看启动日志如果进程在但curl超时多半是应用启动到一半卡住了常见于数据库连不上、Redis 连不上如果本机curl通、外网访问还是 502那就要看 Nginx 的error.log通常是connect() failed (111: Connection refused)说明proxy_pass里的 IP 或端口写错了或者后端只监听了127.0.0.1而 Nginx 里写的是公网 IP。3.3 接口报 401 或 404/prod-api 前缀的连锁反应接口 404 分两种一种是真的路径不对另一种是前端打包时用的接口前缀和 Nginx 里配的对不上。打开 F12 看请求 URL如果请求是http://ip/user/list而不是http://ip/prod-api/user/list说明前端的VUE_APP_BASE_API没生效打包时读的环境文件不对。401 则是另一回事它说明请求已经到了后端只是 token 没带或者过期了。若依用 JWT 做认证token 存在请求头里如果 Nginx 配置中漏掉了某些 header 透传或者前端跨域时allowCredentials与allowedOriginPatterns配错token 就会丢。定位方式是看响应头里有没有WWW-Authenticate以及后端日志里有没有「认证失败」的记录。注意location /prod-api/结尾的斜杠不能省写成/prod-api会导致/prod-api-x这类路径也被匹配进来产生难以理解的串包问题。3.4 nohup 启动后进程莫名消失msg.log 里写着答案后台启动命令本身没错问题往往在启动后几分钟进程就没了# 后台启动并把标准输出、错误输出都重定向到日志 nohup java -jar ruoyi-admin.jar msg.log 21 # 紧接着确认进程真的起来了而不是启动瞬间退出 sleep 5 ps -ef | grep ruoyi-admin | grep -v grep21的作用是把标准错误合并到标准输出缺了它异常堆栈不会进msg.log你看到的日志永远是干净的实际进程早就因为OutOfMemoryError或数据库连接失败退出了。另外nohup只防挂断信号不防人为kill服务器重启后进程也不会自动拉起生产环境常见做法是配 systemd 或者用守护脚本别指望一条nohup顶到底。3.5 Redis 连不上Unable to connect to Redis 的两种成因若依的缓存、验证码、在线用户都依赖 Redis启动时连不上会直接抛异常退出。日志里看到Unable to connect to Redis时先分清楚是「没装」还是「装了连不上」# 确认 Redis 服务在跑 systemctl status redis # 本机连一下验证端口和密码 redis-cli -h 127.0.0.1 -p 6379 ping redis-cli -h 127.0.0.1 -p 6379 -a 你的密码 ping第一种情况是服务没启动systemctl start redis即可第二种是密码或库号配错检查application.yml里spring.redis下的host、port、password、database四项password为空时不要写成空字符串加引号直接留空即可。还有一种容易忽略的Redis 配了bind 127.0.0.1而应用部署在另一台机器上这时候连不上但ping本机是通的需要改绑定地址或走内网。4. Windows Tomcat 部署登录成功却空白页、刷新就 404没有 Nginx、只能上 Tomcat 的场景在内部系统里很常见。这套部署方式多出来的坑集中在两处war 包结构与 Context 配置。前者决定应用能不能起来后者决定前端路由能不能用。4.1 从 jar 改 warpom 里要动的不止 packaging 一行只把packagingjar/packaging改成war是不够的内置 Tomcat 会和外部容器冲突必须把它标记为 provided否则启动时端口抢占、类加载冲突一起来!-- 打包方式改为 war供外部 Tomcat 部署 -- packagingwar/packaging dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-tomcat/artifactId !-- provided 表示编译时需要运行时由外部 Tomcat 提供 -- scopeprovided/scope /dependency改完之后重新mvn clean package产物从.jar变成.war。另外确认启动类继承了SpringBootServletInitializer并重写了configure方法这是 Spring Boot 支持外部容器的入口若依默认工程一般已经带上了如果是从别的模板改过来的项目要自己补。少了这一步的表现是war 丢进webapps后 Tomcat 能起来但访问根路径直接 404日志里没有任何应用启动记录。4.2 登录成功却空白页server.xml 的 Context 少一行这是 Windows Tomcat 部署里最经典的一个坑。登录接口返回 200跳转之后页面一片空白F12 里能看到GET /system/user之类的请求返回的是 404因为它被当成前端路由去请求静态资源了。根因是前端dist被直接放进webapps后Tomcat 把它当成一个普通目录不认识 SPA 的路由回退规则。解决办法是在conf/server.xml的Host节点下补一条 Context把根路径指到distHost namelocalhost appBasewebapps unpackWARstrue autoDeploytrue !-- path/ 表示根路径直接访问前端docBase 指向 dist 所在目录 -- Context path/ docBaseD:/tomcat/webapps/dist reloadabletrue crossContexttrue/ /Hostpath/是把前端提到根路径docBase写dist的绝对路径最稳妥写相对路径时是相对于webapps的。reloadabletrue只建议在调试期开它会监控类变化并重启应用生产环境开着会增加开销。这一步不做表现就是「能登录、进不去」。4.3 刷新页面 404WEB-INF/web.xml 兜底Context 配好之后点菜单能跳转了但用户一按 F5 刷新又是 404。原因是浏览器的地址栏变成了/system/userTomcat 找不到这个物理路径下的文件也不会自动回退到index.html。解决办法是在dist目录下新建WEB-INF文件夹放一个web.xml?xml version1.0 encodingUTF-8? web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd version3.1 metadata-completetrue display-nameRouter for Tomcat/display-name error-page !-- 所有 404 统一回退到单页应用入口 -- error-code404/error-code location/index.html/location /error-page /web-app这段配置的作用是把 404 错误页指向index.html由前端路由自己解析当前地址本质上是替代了 Nginx 里try_files的职责。metadata-completetrue表示不再扫描注解能稍微加快启动。注意这个文件必须放在dist/WEB-INF/下放在别处不生效。4.4 改名 prod-api 之后前后端路径要同步Tomcat 部署时通常会把 war 重命名为prod-api让后端接口落在http://ip:端口/prod-api/下与 Linux 方案保持一致的接口前缀。改名之后有三处必须同时对上少一处就 404war 包文件名prod-api.war部署后目录是webapps/prod-api前端打包时的VUE_APP_BASE_API必须是/prod-api如果后端application.yml里配了server.servlet.context-path要确认它和 war 名是否重复重复了会变成/prod-api/prod-api我遇到过一次改完 war 名忘了重新打前端包的情况前端还带着旧的接口前缀结果登录一直 404排查了半小时才想起来。从那以后我养成习惯改任何路径相关配置先grep一遍全工程的接口前缀确认没有残留。5. 常见问题避坑改菜单打不开、跨域报错、静态资源 404 的五个高频坑这一章挑的都是部署「成功之后」才暴露的问题——第一次部署一切正常改了点东西重新发布页面就不对了。这类问题最难查因为流程你已经走过一遍会下意识认为步骤没错。下面五条按「现象 → 原因 → 解决」写遇到对得上的直接照做。5.1 改完菜单重新打包菜单点不开也进不去现象第一次部署完全正常后来在后台加了几个菜单、改了路由重新打包上传登录后左侧菜单能显示但点击任意菜单右侧内容区空白控制台有静态资源 404。原因前端vue.config.js里的publicPath是相对路径./打包后index.html里引用的 js/css 变成./static/js/xxx.js。一级路由下没问题一旦地址栏深度变成/system/user相对路径就会去/system/static/js/xxx.js找文件自然 404。这个问题和「加菜单」没有因果关系只是恰好在你改完菜单后才重新打包让你误以为是菜单配置写错了。解决把publicPath改成绝对根路径重新打包上传// vue.config.js module.exports { // 部署在域名根路径下用 /部署在子路径下用 /子路径/ publicPath: process.env.NODE_ENV production ? / : /, outputDir: dist }改完一定要rm -rf dist再重新npm run build:prod因为旧的dist可能残留着上一次的文件覆盖不干净会继续命中旧路径。判断是否改对的方法很简单打开dist/index.html看引用的资源是不是/static/js/...开头。5.2 调用第三方接口报 Access-Control-Allow-Origin 缺失现象页面里调用外部地图、短信、OCR 之类的开放接口浏览器控制台报has been blocked by CORS policy: No Access-Control-Allow-Origin header is present on the requested resource。本地开发时好好的上线就报。原因浏览器同源策略只认「协议 域名 端口」三元组只要目标接口和你页面不同源就要求对方响应头里明确允许。跨域限制是浏览器施加的不是服务器拒绝了你所以用 Postman 请求同一地址完全正常这就是很多人「明明接口能通」的错觉来源。解决三条路按推荐顺序排。第一条最省事用 Nginx 反向代理把第三方接口变成同源请求# 前端请求 /third-api/xxxNginx 转发到真实第三方地址浏览器看到的是同源 location /third-api/ { proxy_pass https://第三方接口域名/; proxy_set_header Host 第三方接口域名; # 对方是 https 时通常需要这行否则可能握手失败 proxy_ssl_server_name on; }第二条是在后端配全局 CORS适合第三方接口由自己另一个服务提供的场景Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) // 开启 allowCredentials 时不能再用 allowedOrigins(*) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }maxAge(3600)是让浏览器缓存预检结果一小时减少OPTIONS请求次数。这条配置最容易翻车的是同时用了allowedOrigins(*)和allowCredentials(true)浏览器会直接拒绝必须换成allowedOriginPatterns。第三条是在前端vue.config.js里配devServer.proxy但它的有效期只到npm run serve为止// 只在本地开发时生效打包后这段配置不会进入 dist devServer: { proxy: { /dev-api: { target: http://localhost:8080, changeOrigin: true, pathRewrite: { ^/dev-api: } } } }changeOrigin: true是改写请求头里的 Host避免被目标服务按域名做校验拦掉。这条配置最坑的地方在于「本地好使、线上失效」——因为它压根不参与打包线上必须靠 Nginx 或后端 CORS 解决。5.3 上线后接口全部 404本地 npm run serve 却正常现象本地开发一切正常打包部署到服务器后所有接口都 404Network 面板里请求地址是http://ip/dev-api/xxx。原因把开发环境的接口前缀原样打进了生产包。若依前端用环境文件区分.env.development里通常是/dev-api.env.production里是/prod-api如果打包命令走了开发模式或者环境文件被改错前缀就会带错。解决确认打包命令是npm run build:prod对应 production 模式然后检查.env.production里的接口前缀并保证 Nginx 或 Tomcat 里存在同名映射。三处名称必须完全一致差一个字符都是 404。验证方式是打包完成后全局搜一遍产物# 在 dist 里搜接口前缀确认打进去的是生产环境的值 grep -ro prod-api dist/static/js/ | head -35.4 上传的图片、头像部署后不显示现象后台上传文件提示成功但页面上头像裂图F12 里请求返回 404或者重启服务后之前上传的文件全没了。原因若依的文件上传路径由ruoyi.profile配置如果配的是相对路径或者临时目录重启或者换机器后路径就失效了另外 Nginx 没有为上传目录配置映射请求根本到不了文件。解决把上传路径配成固定的绝对路径并在 Nginx 里映射出来# application.yml ruoyi: # 上传文件的根目录指向磁盘上真实存在且可写的路径 profile: /home/app/uploadPath# 把 /profile/ 开头的请求直接映射到文件目录不需要经过后端 location /profile/ { alias /home/app/uploadPath/; }alias和root的区别要分清alias是替换匹配到的前缀root是拼接完整路径用错会导致路径多一层或少一层。改完记得给目录写权限chown -R到你运行 jar 的那个用户。5.5 Tomcat 下控制台中文乱码、日志变问号现象Windows 上部署到 Tomcat 后后台日志里的中文全部变成???接口返回的中文也可能是乱码。原因Windows 控制台默认编码是 GBK而 JVM 和 Tomcat 默认按 UTF-8 输出两边不一致就会乱码。解决在 Tomcat 的bin/catalina.bat里加上编码参数并确认server.xml的 Connector 节点带URIEncodingUTF-8rem 在 catalina.bat 的 set JAVA_OPTS 行追加文件编码参数 set JAVA_OPTS%JAVA_OPTS% -Dfile.encodingUTF-8 -Dsun.jnu.encodingUTF-8这只是显示层的问题不影响数据本身。但如果数据库里存进去的就是乱码那要回头查数据库连接串有没有带characterEncodingutf8别把两个问题混在一起查。6. 快速定位把「页面不对」拆成可验证的三层部署出问题的时候最忌讳的是凭感觉改配置。我现在的习惯是把它拆成三层从外往里一层层验证每层都有明确的判断标准验证不通就停在这一层不要跳。第一层静态资源层。打开浏览器 F12 的 Network只过滤js和css。如果这些请求本身 404问题在publicPath或 Nginx 的root路径如果状态码是 200 但页面还是白屏说明资源加载了但路由没匹配上问题在try_files或web.xml的 404 回退。这一层能用 curl 独立验证# 直接请求首页和一个静态资源确认 Nginx 能返回内容 curl -I http://你的服务器IP/ curl -I http://你的服务器IP/static/js/app.js第二层接口层。Network 里过滤XHR看请求 URL、状态码、响应体三个东西。URL 前缀不对是打包环境问题502 是后端没起来404 是代理路径或 war 名不对401 是 token 没带200 但返回业务错误码那就是后端逻辑了和部署无关。这一层的关键动作是拿同一条命令去服务器上直接打后端# 绕过 Nginx直接打后端区分是代理问题还是服务问题 curl -i -X POST http://127.0.0.1:8080/login \ -H Content-Type: application/json \ -d {username:admin,password:admin123}第三层服务层。前两层都通还不对就去看后端日志和 Redis。msg.log里搜ERROR、Exception看有没有启动失败的残留redis-cli ping确认缓存可用ss -lntp确认端口监听正常。这三条命令我一般写在部署脚本的开头每次发布先跑一遍比事后排查省事得多。层级典型现象首先检查静态资源页面白屏、js/css 404publicPath、Nginxroot接口代理502、接口 404、401proxy_pass斜杠、war 名、token后端服务启动即退出、接口 500msg.log、Redis、数据库连接还有一个习惯是从那以后我每次改完路径相关配置都会先把dist整个删掉重新打包再用grep搜一遍产物里的接口前缀和资源路径确认和服务器上的 Nginx 配置一字不差。这套动作前后不到两分钟但省下来的排查时间往往是几个小时——血泪经验告诉我部署问题九成不是玄学而是某个字符没对上。这份部署文档把 Linux Nginx 和 Windows Tomcat 两套环境的命令、nginx.conf、server.xml、web.xml按执行顺序整理在了一起卡住的时候对着改就行希望帮到你。本文还有配套的精品资源点击获取
📌 标签:
工业官网
设计趋势
AI 建站
SEO
获取完整报告 →
RELATED ARTICLES
推荐阅读
2026/11/3 10:55:10
中文评论情感分析实战:LSTM+Attention落地指南
2026/11/3 10:55:10
Hough-Contour两级串联实现瞳孔直径99.8%精确测量
2026/11/3 10:55:10
机器学习大作业:医疗保健智能系统设计与评估全流程
2026/11/3 14:02:23
LiberoSoc数字逻辑仿真与74HC实验箱上板验证
2026/11/3 14:02:23
Web前端工程师进阶:从基础到架构的全栈成长路径
2026/11/3 14:02:23
SARIMA模型在医疗时序预测中的应用与优化
2026/11/3 14:02:23
双栈法实现队列:数据结构转换的核心技巧
2026/11/3 14:02:23
LangGraph节点设计与性能优化实战指南
2026/11/3 13:57:20
IP5365寄存器手册实战:I2C控制与避坑指南
2026/11/3 0:03:28
淘宝商品详情API开发实战与性能优化指南
2026/11/3 0:03:28
Unity Package Manager刷新错误排查与解决方案
2026/11/3 0:03:28
Java record 规范构造器能否被反序列化或反射绕过
2026/11/3 4:05:41
面向复杂光照与角度的宠物面部识别鲁棒性增强策略
2026/11/3 4:05:35
DRV8818PWPR与R7FA8M1AHECBD工业步进驱动实战指南
2026/11/3 4:05:30
STM32与DRV8818双极步进电机驱动方案:从选型到调试全流程解析
2026/11/3 4:05:41
面向复杂光照与角度的宠物面部识别鲁棒性增强策略
2026/11/3 4:05:35
DRV8818PWPR与R7FA8M1AHECBD工业步进驱动实战指南
2026/11/3 4:05:30
STM32与DRV8818双极步进电机驱动方案:从选型到调试全流程解析