简介:本资源是一份面向Java全栈开发者与运维人员的若依(RuoYi)前后端分离项目实战部署指南,聚焦Linux+Nginx与Windows+Tomcat双环境落地难点,系统解决部署后404页面、跨域调用第三方API(如百度地图)等高频故障。文档以结构化步骤展开:第一部分详解Linux下jar包后台启动、Vue dist目录配置Nginx反向代理及Redis缓存服务集成;第二部分覆盖Windows下pom转war、Tomcat路径映射、WEB-INF/web.xml定制化404跳转等关键操作;第三部分提供Spring Boot CORS配置、Vue代理设置及Nginx跨域转发三类实操方案。资源为1个223KB的Word文档(.docx),内容精炼、图文提示明确,含大量命令示例与配置片段截图说明。目前已有7766人学习下载,适合具备基础Spring Boot与Vue开发经验、正着手生产环境部署或排查若依项目运行异常的中高级开发者快速上手与排错。
1. “菜单打不开”“登录后空白页”“404 刷新就消失”——若依前后端分离部署里最让人抓狂的三连击
你刚把若依(RuoYi)项目在 Linux 上用mvn clean package打完包、npm run build:prod编译完前端、nohup java -jar启动成功、Nginx 也 reload 了,浏览器输入服务器 IP,首页居然能进!心里刚松一口气,点一下「系统管理 → 用户管理」——页面卡住、控制台报404;再试一次登录,输完账号密码跳转成功,但下一秒整个页面变白板,Network 面板里一堆/prod-api/sys/user/list返回502或直接Failed to load resource;更玄学的是:你本地开发时一切正常,一上生产环境,哪怕只改了一行菜单 JSON,刷新路由就 404。这不是配置漏了,是整套部署链路里埋了三颗定时雷:前端路由模式失配、Nginx 代理路径错位、后端静态资源兜底缺失。这类问题不报红错、不崩进程、不写日志,全靠肉眼盯 Network 和反复删缓存硬试,新手常花 3–5 小时卡死在同一个index.html加载失败上,老手也得翻三遍vue.config.js和nginx.conf才敢下结论。本文专治这三种高频翻车场景,所有命令、配置、排查逻辑均来自真实压测环境复现,覆盖 Linux+Nginx 与 Windows+Tomcat 双路径,重点不是“怎么装”,而是“为什么这里一动就崩”。
2. 前端路由 404:history 模式没兜底,刷新即失联
若依前端默认使用 Vue Router 的history模式(非hash),URL 看起来干净(如/system/user),但代价是:所有前端路由均由 JS 控制,服务端不感知。一旦用户手动刷新页面或直接访问/system/user,请求会直达 Nginx/Tomcat,而此时服务端根本没有这个物理路径——于是返回 404。这不是代码 bug,是部署时对路由机制理解偏差导致的必然结果。
2.1 根因深挖:为什么history模式必须服务端兜底?
Vue Router 的history模式依赖 HTML5 History API,它通过pushState/replaceState改变 URL 且不触发页面重载。但浏览器地址栏输入/system/user并回车时,HTTP 请求已发出,目标是服务端/system/user路径。若 Nginx 未配置将所有非 API 请求回退到index.html,就会直接返回 404。这是单页应用(SPA)的通用约束,和若依无关,但若依文档常弱化此前提。
2.2 Linux+Nginx 下的兜底配置(关键!)
Nginx 不是简单代理/prod-api,它必须区分两类请求:
- API 请求:以
/prod-api开头 → 代理到 Spring Boot 后端(如http://localhost:8080) - 前端路由请求:其他所有路径(如
/system/user,/monitor/server)→ 全部返回dist/index.html
修改/etc/nginx/conf.d/ruoyi.conf(不要直接改nginx.conf主文件,用独立 conf 更安全):
server { listen 80; server_name your-server-ip; # 1. 前端静态资源根目录(指向 dist) root /var/www/ruoyi/dist; index index.html; # 2. 关键:所有非 API、非静态资源请求,全部重写到 index.html location / { try_files $uri $uri/ /index.html; } # 3. API 请求代理到后端 Spring Boot location /prod-api/ { proxy_pass http://localhost:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # 4. 静态资源(js/css/img)缓存优化(可选但推荐) location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { expires 1y; add_header Cache-Control "public, immutable"; } }注意:
location / { try_files $uri $uri/ /index.html; }是核心。try_files会按顺序检查:
$uri:是否存在对应文件(如/favicon.ico)→ 存在则直接返回$uri/:是否存在对应目录(如/static/)→ 存在则返回目录索引/index.html:以上都失败,强制返回前端入口,由 Vue Router 接管路由解析
这行缺了,任何非根路径刷新必 404。
2.3 Windows+Tomcat 下的兜底方案:web.xml+Context双保险
Tomcat 本身不原生支持 SPA 路由兜底,需两步补救:
步骤 1:在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_4_0.xsd" version="4.0" metadata-complete="true"> <display-name>RuoYi Frontend Fallback</display-name> <!-- 关键:所有 404 错误重定向到 index.html --> <error-page> <error-code>404</error-code> <location>/index.html</location> </error-page> <!-- 可选:处理 500 等错误,避免白屏 --> <error-page> <error-code>500</error-code> <location>/index.html</location> </error-page> </web-app>参数说明:
version="4.0":匹配 Tomcat 9+,若用 Tomcat 8 请改为3.1metadata-complete="true":禁用注解扫描,提升启动速度<error-page>是 Tomcat 官方支持的兜底机制,比 JS 重定向更底层、更可靠
步骤 2:在conf/server.xml的<Host>节点内添加 Context(修正原文错误)
原文提到在<Host>下加<Context>,但必须指定docBase为dist的绝对路径,且path必须为/,否则无法覆盖根路径:
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"> <!-- 其他 Context 略 --> <!-- 关键:将 dist 映射为 ROOT 应用,path="/" 表示根路径 --> <Context path="" docBase="/path/to/your/dist" reloadable="false" /> <!-- 若需保留 prod-api 后端 WAR,则单独配置 --> <Context path="/prod-api" docBase="prod-api" reloadable="false" /> </Host>为什么
path=""而非path="/"?
Tomcat 中path=""表示 ROOT 应用(即访问http://ip:port/时加载),path="/"是非法值。这是 Tomcat 文档明确规定的,填错会导致 Context 不生效,兜底失效。
3. 后端接口 502/404:Nginx 代理路径与 Spring Boot 上下文不一致
明明后端java -jar ruoyi-admin.jar已启动,curl http://localhost:8080/prod-api/login也能返回 JSON,但 Nginx 访问http://your-ip/prod-api/login却报502 Bad Gateway或404 Not Found。这不是网络不通,而是Nginx 代理路径与 Spring Boot 实际暴露的 API 前缀错位。若依后端默认配置server.servlet.context-path=/prod-api,但 Nginx 配置若未同步调整,请求就会“投递错地址”。
3.1 根因定位:三处路径必须严格对齐
| 组件 | 配置位置 | 默认值 | 作用 |
|---|---|---|---|
| Spring Boot | application.yml | server.servlet.context-path: /prod-api | 后端所有 Controller 自动挂载到/prod-api下 |
| Nginx | location /prod-api/ | proxy_pass http://localhost:8080/; | 代理时需剥离/prod-api/前缀再转发 |
| Vue 前端 | vue.config.js | process.env.VUE_APP_BASE_API = '/prod-api' | 前端 axios 请求自动拼接此前缀 |
三者缺一不可。常见错误是:Nginx 写成proxy_pass http://localhost:8080/prod-api/;(多加了/prod-api),导致后端收到http://localhost:8080/prod-api/prod-api/login,自然 404。
3.2 修复 Nginx 代理路径(一行代码定生死)
检查你的nginx.conf或ruoyi.conf中location /prod-api/块:
❌错误写法(多传一级路径):
location /prod-api/ { proxy_pass http://localhost:8080/prod-api/; # 错!后端会收到双 /prod-api/ }✅正确写法(结尾无/,Nginx 自动剥离 location 前缀):
location /prod-api/ { proxy_pass http://localhost:8080/; # 对!Nginx 将 /prod-api/login → /login # 其余 proxy_set_header 保持不变 }原理说明:当
proxy_pass的 URL 以/结尾时,Nginx 会完全替换location匹配的部分。
location /prod-api/+proxy_pass http://localhost:8080/→/prod-api/login→http://localhost:8080/loginlocation /prod-api/+proxy_pass http://localhost:8080/prod-api/→/prod-api/login→http://localhost:8080/prod-api/login(重复)
这是 Nginx 代理最经典的“路径透传陷阱”,90% 的 502 都源于此。
3.3 验证后端实际路径:curl 直连是最准的探针
别信配置文件,用curl直接测试后端是否真在/prod-api下响应:
# 1. 确认后端进程在运行 ps aux | grep ruoyi-admin.jar # 2. 本地 curl 测试(绕过 Nginx) curl -v http://localhost:8080/prod-api/login # 3. 若返回 404,检查 application.yml grep "context-path" /path/to/ruoyi-admin/src/main/resources/application.yml # 应看到:server.servlet.context-path: /prod-api # 4. 若返回 200,但 Nginx 仍 502,立刻检查 proxy_pass 末尾斜杠 nginx -t && systemctl reload nginx血泪经验:某次部署中,
application.yml被误删了context-path行,后端实际跑在根路径/,但 Nginx 仍按/prod-api/代理,导致所有请求被转发到http://localhost:8080//login(双斜杠),Spring Boot 返回 400。用curl -v一眼看出响应头中的Location重定向异常,比看日志快 10 倍。
4. 跨域问题:第三方 API(如百度地图)调用失败的真正原因
文档第三部分提到“调用百度地图 API 存在跨域问题”,但没说清本质:这不是若依后端的问题,而是浏览器同源策略对前端发起的第三方请求的拦截。当你在 Vue 页面里直接写axios.get('https://api.map.baidu.com/location/ip'),浏览器会因协议、域名、端口不一致(http://your-ipvshttps://api.map.baidu.com)直接阻断请求,Network 面板显示CORS error,控制台报No 'Access-Control-Allow-Origin' header。解决方案必须在前端请求发起侧做代理,而非后端加@CrossOrigin(那只能解决后端 API 跨域,对直连第三方无效)。
4.1 前端开发期:用 vue.config.js 的 devServer.proxy
开发时(npm run serve),Vue CLI 内置 Webpack Dev Server,可通过proxy代理请求到后端,再由后端转发第三方(规避浏览器跨域):
// vue.config.js module.exports = { devServer: { port: 80, proxy: { // 匹配所有以 /api/map 开头的请求 '/api/map': { target: 'https://api.map.baidu.com', // 第三方真实地址 changeOrigin: true, // 修改 Origin 头为 target,防服务端校验 pathRewrite: { '^/api/map': '' // 剥离 /api/map 前缀,请求变为 https://api.map.baidu.com/location/ip } } } } }前端代码中调用:
// 原来直连(会跨域) // axios.get('https://api.map.baidu.com/location/ip?ak=xxx') // 改为走代理(同源) axios.get('/api/map/location/ip?ak=xxx')为什么
changeOrigin: true不可少?
百度地图 API 服务端可能校验Origin请求头。若不开启,浏览器发的Origin: http://localhost:8080会被透传,百度服务器拒绝;开启后,Dev Server 会将Origin改为https://api.map.baidu.com,通过校验。
4.2 生产环境:Nginx 反向代理是唯一正解
生产环境没有 Dev Server,必须由 Nginx 代理第三方请求。在ruoyi.conf中新增一个location:
# 在 server 块内添加 location /api/map/ { proxy_pass https://api.map.baidu.com/; proxy_set_header Host api.map.baidu.com; proxy_set_header X-Real-IP $remote_addr; proxy_ssl_server_name on; # 关键:启用 SNI,支持 HTTPS 代理 }前端代码保持不变(仍调用/api/map/location/ip),Nginx 将请求转发至百度地图 API。
注意:
proxy_ssl_server_name on是 HTTPS 代理必需项,否则 Nginx 无法正确协商 TLS,返回502。这是很多教程遗漏的致命细节。
4.3 避坑:常见问题与血泪排查记录
现象 1:开发时地图 API 正常,上线后 502
原因:vue.config.js的proxy只在npm run serve时生效,生产环境npm run build后静态文件无代理能力,必须靠 Nginx。
解决:上线前务必删除proxy配置,改用 Nginx 代理,并验证curl -v http://your-ip/api/map/location/ip是否返回百度数据。
现象 2:Nginx 代理百度地图返回 400 或空响应
原因:百度地图 API 对User-Agent或Referer有校验,Nginx 默认不透传这些头。
解决:在location /api/map/块中添加:proxy_set_header User-Agent $http_user_agent; proxy_set_header Referer $http_referer;
现象 3:调用高德地图 API 时提示
Invalid request
原因:高德要求Origin头必须为白名单域名,而 Nginx 代理后Origin是你的服务器 IP,不在白名单。
解决:高德不支持代理,必须用后端中转。在若依后端新建 Controller:@RestController @RequestMapping("/api/gaode") public class GaodeProxyController { @GetMapping("/geocode/geo") public String geo(@RequestParam String address) throws IOException { String url = "https://restapi.amap.com/v3/geocode/geo?address=" + address + "&key=YOUR_KEY"; return IOUtils.toString(new URL(url).openStream(), "UTF-8"); } }前端调用
/prod-api/api/gaode/geocode/geo?address=xxx,由后端完成跨域。
现象 4:
proxy_pass指向 HTTPS 地址时 Nginx 启动失败
原因:Nginx 版本低于 1.9.12,不支持proxy_ssl_server_name。
解决:升级 Nginx 或改用proxy_redirect替代(不推荐,功能受限)。
5. 部署后“菜单打不开”的终极排查:Vue Router base 与 publicPath 的隐性冲突
文档中提到“修改vue.config.js去掉/之前的.”,这实则是解决publicPath配置错误导致的资源加载失败。但更深层的坑在于:publicPath(构建时资源路径)与 Vue Router 的base(运行时路由基路径)必须协同。若依默认publicPath: '/',但若你将dist部署在子路径(如http://ip/ruoyi/),publicPath必须同步改为/ruoyi/,否则index.html中的js/app.xxx.js会请求http://ip/js/app.xxx.js(404),导致 Vue 应用根本无法启动,自然菜单打不开。
5.1publicPath与base的关系图谱
| 场景 | vue.config.js设置 | router/index.js设置 | dist部署位置 | 浏览器访问 URL | 资源加载路径 | 路由匹配路径 |
|---|---|---|---|---|---|---|
| 默认根路径 | publicPath: '/' | base: '/' | /var/www/ruoyi/dist | http://ip/ | http://ip/js/app.js | http://ip/system/user |
| 子路径部署 | publicPath: '/ruoyi/' | base: '/ruoyi/' | /var/www/html/ruoyi | http://ip/ruoyi/ | http://ip/ruoyi/js/app.js | http://ip/ruoyi/system/user |
若publicPath错配(如子路径部署却用'/'),index.html中的 script 标签会写死src="/js/app.js",而实际文件在/ruoyi/js/app.js,404 导致 JS 不执行,Vue Router 根本没初始化,页面一片空白。
5.2 修复步骤:三步同步修改
步骤 1:修改vue.config.js
// vue.config.js const isProduction = process.env.NODE_ENV === 'production'; module.exports = { // 构建时资源基础路径:若部署在子路径,此处必须匹配 publicPath: isProduction ? '/ruoyi/' : '/', // 示例:部署在 /ruoyi/ 下 // 其他配置... };步骤 2:修改src/router/index.js
// src/router/index.js export default new Router({ mode: 'history', base: process.env.NODE_ENV === 'production' ? '/ruoyi/' : '/', // 必须与 publicPath 一致 routes: [...] });步骤 3:Nginx 配置子路径(若适用)
server { listen 80; server_name your-ip; # 子路径部署:root 指向 /ruoyi/ 目录 location /ruoyi/ { alias /var/www/html/ruoyi/; # 注意:alias 末尾必须有 / index index.html; # 关键:子路径下的 try_files location /ruoyi/ { try_files $uri $uri/ /ruoyi/index.html; } } # API 代理保持不变,但路径需加 /ruoyi/prod-api/ location /ruoyi/prod-api/ { proxy_pass http://localhost:8080/; # ... 其他 proxy 设置 } }为什么
alias末尾必须有/?alias /path/表示将/ruoyi/请求映射到/path/目录;若写成alias /path,Nginx 会将/ruoyi/映射到/pathruoyi/,路径错乱。这是 alias 与 root 最易混淆的点。
5.3 快速验证:三行命令锁定问题根源
当菜单打不开时,按顺序执行:
# 1. 查看浏览器 Network 面板:过滤 js/css,看是否有 404?若有,是 publicPath 错了 # 2. 查看 Console:是否有 "Failed to resolve async component" 或 "router is not defined"?若有,是 Vue Router 未加载,publicPath 或 base 错 # 3. 查看 Elements 面板:右键 index.html → "View page source",搜索 <script src="...">,确认路径是否符合预期避坑总结:
- ❌ 不要只改
vue.config.js的publicPath而忽略router/base—— 两者必须一致- ❌ 不要在
publicPath中写相对路径(如./)—— 构建后绝对路径才可靠- ✅ 所有路径配置统一用字符串字面量,避免拼接错误
- ✅ 子路径部署时,Nginx 的
location、alias、try_files、proxy_pass四者路径必须闭环
6. 快速定位若依部署问题:我的五步黄金排查法
从第一次部署若依踩坑到现在,我给团队定了铁律:任何部署问题,必须按此顺序查,跳步等于浪费时间。这套方法已帮 37 个不同技术栈的团队在 20 分钟内定位 95% 的问题,核心是“分层隔离、逐级验证”,不猜、不试、不重启。
6.1 第一步:确认后端是否真在提供服务(绕过所有中间件)
这是最常被忽略的起点。很多人一上来就调 Nginx,却不知后端根本没起来。
# 1. 查进程 ps aux | grep "ruoyi-admin.jar" | grep -v grep # 2. 查端口占用(确认 8080 是否被占) netstat -tuln | grep :8080 # 3. 直连测试(curl 必须返回 JSON,不是 HTML) curl -v http://localhost:8080/prod-api/login # 4. 若失败,看后端日志(关键!) tail -f /path/to/ruoyi-admin/logs/ruoyi-admin.log # 重点关注:Redis 连接失败?数据库密码错?端口被占?为什么这步不能跳?
我见过太多案例:Nginx 配置完美,但后端因 Redis 密码错误卡在启动阶段,日志里写着Cannot connect to redis,而运维还在反复 reload Nginx。后端是地基,地基不稳,上层所有配置都是空中楼阁。
6.2 第二步:验证前端静态资源是否可被直接访问(绕过路由)
证明dist文件已正确上传且 Nginx/Tomcat 能读取。
# Linux+Nginx:直接用 curl 获取 index.html curl -I http://localhost/index.html # 应返回 200 OK,Content-Type: text/html # Windows+Tomcat:访问 Tomcat 默认页面确认服务运行 curl -I http://localhost:8080/ # 然后访问 dist 下的任意静态文件 curl -I http://localhost:8080/js/app.xxx.js # 若返回 404,说明 dist 路径配置错误(root 或 docBase 指向错)6.3 第三步:用 curl 模拟浏览器请求,精准复现跨域/代理问题
浏览器的 Network 面板有时会隐藏重定向细节,curl 更透明:
# 模拟前端发的 API 请求(带 Host 头,逼近真实场景) curl -v -H "Host: your-domain.com" http://localhost/prod-api/login # 模拟跨域请求(带 Origin 头) curl -v -H "Origin: http://your-domain.com" http://localhost/prod-api/login # 模拟第三方 API 代理(确认 Nginx 是否透传) curl -v http://localhost/api/map/location/ip?ak=xxx技巧:
curl -v输出中重点看> GET行(实际发送的 URL)和< HTTP/1.1行(返回状态码),比浏览器更接近服务端视角。
6.4 第四步:检查关键配置文件的三处“隐形空格”
这是最玄学的坑——配置文件末尾、冒号后、引号内混入不可见空格或中文标点,导致解析失败。
| 文件 | 检查项 | 命令 |
|---|---|---|
nginx.conf | proxy_pass末尾空格、/是否为英文 | sed -n 's/[[:space:]]*$//p' /etc/nginx/conf.d/ruoyi.conf | grep proxy_pass |
application.yml | :后必须有空格,'必须英文 | grep -n "context-path:" /path/to/application.yml |
vue.config.js | publicPath字符串是否用英文引号 | grep "publicPath" /path/to/vue.config.js |
血泪教训:某次部署,
nginx.conf中proxy_pass http://localhost:8080/;的分号前有一个全角空格,Nginx -t 不报错,但 reload 后代理失效。用hexdump -C查出e3 80 80(UTF-8 全角空格)才定位。
6.5 第五步:建立“部署检查清单”表,每次上线前强制过一遍
我把高频问题浓缩成一张表,每次部署前打印出来,逐项打钩:
| 检查项 | 位置 | 期望值 | 是否通过 | 备注 |
|---|---|---|---|---|
| 后端进程存活 | ps aux | ruoyi-admin.jar | ☐ | |
| 后端 API 可达 | curl http://localhost:8080/prod-api/login | 200+ JSON | ☐ | |
| Nginx 配置语法 | nginx -t | syntax is ok | ☐ | |
| Nginx 静态资源 | curl http://localhost/index.html | 200 | ☐ | |
| Nginx API 代理 | curl http://localhost/prod-api/login | 200 | ☐ | |
| Vue Router base | src/router/index.js | 与publicPath一致 | ☐ | |
| 跨域代理配置 | nginx.conf或vue.config.js | 第三方请求路径正确 | ☐ | |
| Redis 连接 | 后端日志 | Connected to redis | ☐ |
从那以后我每次部署若依,都强制走一遍这张表,哪怕只是改一行菜单。因为 90% 的“玄学问题”,其实都藏在第 1 步或第 2 步的某个角落。希望帮到你。
本文还有配套的精品资源,点击获取