ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

RuoYi 若依 Nginx 子路径部署全链路避坑

RuoYi 若依 Nginx 子路径部署全链路避坑 1. 子路径部署到底难在哪先把 RuoYi 的三层路径捋清楚接手过好几个把若依往二级目录里塞的活儿最典型的一次是内网一台服务器80 端口已经被公司门户站占了运维只甩过来一句话——给你个/ruoyi/目录自己想办法。看着像是把文件拷过去、nginx 加个 location 就完事真动手才发现前端白屏、接口 404、验证码裂图、头像不显示一个接一个往外冒。所以这篇不聊虚的就把 RuoYi 从根路径搬到 nginx 子路径这件事按我自己趟过的顺序完整拆一遍改了哪些文件、为什么这么改、哪几个地方一字之差就翻车。适合谁看已经能把若依跑起来、但对 nginx 和前端构建配置只是照着教程点过一遍的同学也适合手上有多个 Web 项目要挤在同一个域名、同一个 80 端口下需要给每个项目分配/app1/、/app2/这种子路径的运维和全栈。看完你至少能自己判断这次改造到底该改前端、改后端还是只在 nginx 里转一道以及改完之后怎么验证它真的没漏。1.1 一个请求从浏览器到 Java 方法中间拼了几段字符串很多人改路径改崩根本原因是脑子里只有一个路径的概念。实际上一个带子路径的若依请求URL 是被四段拼起来的nginx 的 location 前缀前端 axios 的 baseURL后端server.servlet.context-pathController 上的RequestMapping。任何两段重复或者任何一段被漏掉结果就是 404。拿一个真实地址举例http://10.0.0.15/ruoyi/prod-api/system/user/list。拆开来看/ruoyi/是这整套应用对外的门牌号由 nginx 决定/prod-api/是若依前端约定的接口前缀写在.env.production的VUE_APP_BASE_API里/system/user/list才是后端 Controller 真正认的地址。至于后端那个context-path若依默认是/也就是不占位。所以整件事的本质是你要决定/ruoyi/这一段在哪一层被吃掉。是在 nginx 里用proxy_pass的斜杠规则剥掉还是在后端的context-path里声明出来。这个决定一旦定下来后面所有的配置都是围着它转的。1.2 三个路径必须分开命名别混着叫我习惯把这三个概念在文档里写死避免和同事沟通时互相误解概念配置位置示例值作用范围公开部署路径public pathnginxlocation/ 前端publicPath/ruoyi/浏览器地址栏能看到的那一段接口前缀API prefix.env.production的VUE_APP_BASE_API/ruoyi/prod-api前端所有 ajax 请求的公共前缀上下文路径context-pathapplication.yml的server.servlet.context-path默认/可改为与公开前缀一致后端 Servlet 容器层面的前缀之所以强调这个是因为.env.production里写的VUE_APP_BASE_API里其实已经包含了/ruoyi这一段而前端的publicPath也包含/ruoyi——这两个是同一个/ruoyi的两处声明不是叠加关系。我见过有人在publicPath已经设为/ruoyi/的情况下又把VUE_APP_BASE_API写成/ruoyi/prod-api结果 nginx 那边再叠一层最终变成/ruoyi/ruoyi/prod-api/system/user/list后端直接懵掉。1.3 两条改造路线怎么选路线 Anginx 剥离前缀后端一行不改。前端publicPath设为/ruoyi/接口前缀设为/ruoyi/prod-apinginx 里location /ruoyi/prod-api/用带斜杠的proxy_pass把这段前缀换成/请求到后端时已经是干净的/system/user/list。后端context-path保持默认jar 包不用重新打、不用重启。路线 B让后端 context-path 完全对齐公开前缀。把server.servlet.context-path直接设成/ruoyi/prod-apinginx 那边proxy_pass http://127.0.0.1:8080;原样透传不做任何路径改写。两条路线我实际都用过选择逻辑很简单对比项路线 Anginx 剥离路线 Bcontext-path 对齐后端改动量零改一行 yml 重新打包含配置的 jarnginx 复杂度需要理解 proxy_pass 斜杠语义一个location加原样转发最简单前缀调整成本改 nginx 配置 reload 即可要改 yml 重新发版附属入口Druid、Swagger可以单独放行或干脆不暴露全部被绑到新前缀下路径变长适合场景运维配合度高、希望后端保持纯净nginx 由别的团队管只允许原样转发我个人默认推路线 A。原因很实际路径这种东西是对外契约的一部分改起来最频繁把它留在 nginx 这一层出问题时nginx -s reload两秒钟生效不用惊动后端发版流程。2. 动手前的准备先确认版本再列改造清单2.1 你手里的到底是哪一版若依配置项完全不一样这一步必须先做因为 RuoYi-VueVue2 Vue CLI和 RuoYi-Vue3Vite在构建配置上的写法完全不同抄错教程会浪费一整个下午。最直接的判断方法看前端工程的根目录# 进到前端工程目录 cd ruoyi-ui # 看有没有这几个文件 ls -l | grep -E vue.config.js|vite.config.js|package.json # 再看 package.json 里用的构建工具 grep -E \(vite|vue/cli-service)\ package.json出现vue.config.js并且依赖里有vue/cli-service那是Vue2 / Vue CLI 版配置项叫publicPath路由里读的是process.env.BASE_URL。出现vite.config.js并且依赖里有vite那是Vue3 / Vite 版配置项叫base环境变量前缀也从VUE_APP_变成了VITE_APP_前端读取方式从process.env.XXX变成import.meta.env.XXX。这个前缀差异特别容易踩你在.env.production里写了VUE_APP_BASE_APIVite 版根本不会读接口前缀会变成undefined请求直接飞到一个叫undefined/system/user/list的地址上。至于 RuoYi-Cloud微服务版路径处理还要多一层网关本文主要讲单体前后端分离版的思路微服务版可以参考同样的原理把前缀放在网关的routes配置里。2.2 全链路改造清单照着表格一项项划掉我把改造点整理成了一张表建议动手前先复制出来改一项划一项最后再统一验证序号文件/位置原值典型新值说明1vue.config.js的publicPath//ruoyi/决定静态资源引用前缀2vite.config.js的base//ruoyi/Vue3 版等价项3.env.production的VUE_APP_BASE_API/prod-api/ruoyi/prod-api接口前缀4.env.development的VUE_APP_BASE_API/dev-api保持不变推荐本地联调不要动5路由baseprocess.env.BASE_URL通常不用改Vue CLI 会自动跟随 publicPath6nginx 静态 location无location /ruoyi/指向 dist 目录7nginx 接口 location无location /ruoyi/prod-api/带斜杠转发到后端8后端context-path//路线 A 不改路线 B 才需要动注意第 3 项和第 4 项一定要分开看。开发环境用devServer的 proxy走的是本机 8080跟子路径部署没关系如果你顺手把.env.development也改了本地起服务时接口会 404然后你会怀疑人生。2.3 上线前先备份给自己留条退路子路径改造涉及前端重新打包和 nginx 配置变更两件事都可能出问题。我的习惯是动手前做好三份备份dist目录整体打 tar 包留一份、当前生效的 nginx 配置文件复制成.bak、后端 jar 包如果改了 yml 也要留旧版本。nginx 侧还有一个很实用的技巧新配置先不覆盖用nginx -t -c /etc/nginx/conf.d/ruoyi-new.conf单独校验语法确认没问题再替换、再nginx -s reload。reload 是平滑的不会断掉正在处理的连接但如果配置本身有语法错误reload 会被拒绝并保留旧配置这比直接重启服务安全得多。# 备份 tar -czf /backup/ruoyi-dist-$(date %F).tar.gz /data/www/ruoyi/dist cp /etc/nginx/conf.d/ruoyi.conf /etc/nginx/conf.d/ruoyi.conf.bak.$(date %F) # 校验新配置 nginx -t # 平滑重载 nginx -s reload3. 前端改造publicPath、路由 base 与接口前缀3.1 Vue2 版一行 publicPath 决定所有静态资源的生死若依 Vue2 版的前端工程里vue.config.js顶部就有一个publicPath// ruoyi-ui/vue.config.js module.exports { publicPath: process.env.NODE_ENV production ? / : /, outputDir: dist, assetsDir: static, // ... }默认三个环境都是/意思是打包出来的index.html里所有资源引用都写成/static/js/app.xxxx.js这种绝对根路径。放在根目录访问没问题一旦搬到/ruoyi/下浏览器会去请求http://域名/static/js/app.xxxx.js——这个路径在 nginx 上根本没有对应的 location返回 404页面白屏控制台一片红。改法就是把它改成module.exports { publicPath: process.env.NODE_ENV production ? /ruoyi/ : /, // ... }为什么只改生产环境的因为开发环境你是用npm run dev在本地 5173 或 8080 端口起服务访问地址是http://localhost:80/没有任何子路径改成/ruoyi/反而会让本地 dev server 的资源路径全部错乱。所以保留三元表达式只让 production 分支生效。这里的assetsDir: static也值得说一句它决定了 js、css、字体、图片被打包到dist/static/下面所以最终资源地址是/ruoyi/static/...。这个名字可以改但没必要改了反而要留意有些第三方组件里硬编码了路径。3.2 Vue3 版配置项换成了 base变量前缀也换成了 VITE_APP_Vite 版的对应配置在vite.config.js的defineConfig里// ruoyi-ui/vite.config.js import { defineConfig, loadEnv } from vite export default defineConfig(({ mode, command }) { const env loadEnv(mode, process.cwd()) return { // 生产环境部署在 /ruoyi/ 子路径下 base: command build ? /ruoyi/ : /, // ... } })用command build判断比用mode更稳因为mode你可以自定义成staging之类的值而command只有serve和build两种。另外还有一个几乎不用改代码的办法直接在构建命令上加参数# 临时指定子路径不修改配置文件 npm run build -- --base/ruoyi/这在 CI 流水线里特别顺手——同一套代码根据部署环境传不同的--base不用为每个环境维护一份配置。Vite 版还有一处必改环境变量文件里的前缀。Vite 只认VITE_开头的变量# .env.production VITE_APP_BASE_API /ruoyi/prod-api前端代码里对应改成import.meta.env.VITE_APP_BASE_API。如果你是从 Vue2 版迁移过来的项目或者抄了一份 Vue2 的配置这一处不改接口会请求到字符串undefined上报错信息看起来像是后端挂了实际上前端根本没拼对地址。3.3 路由 baseVue2 版自动跟随Vue3 版要看模式Vue2 版若依的src/router/index.js里是这么写的export default new Router({ mode: history, base: process.env.BASE_URL, scrollBehavior: () ({ y: 0 }), routes: constantRoutes })process.env.BASE_URL在 Vue CLI 里就等于你配置的publicPath。所以你把publicPath改成/ruoyi/之后路由的 base 会自动变成/ruoyi/不用再手改一处。这是一个设计得很贴心的地方很多人不知道反而去手动写死base: /ruoyi/结果和 publicPath 冲突。Vue3 版默认用的是 hash 模式createWebHashHistory()地址形如http://域名/ruoyi/#/system/user。hash 模式的好处是路由切换完全在前端完成#后面的内容不会发给服务器所以刷新页面永远不会 404也不需要 nginx 的try_files兜底。缺点是地址里带个#有些客户觉得不好看。如果你把 Vue3 版改成了 history 模式createWebHistory(/ruoyi/)那就必须配合 nginx 的try_files兜底否则用户在/ruoyi/system/user页面按 F5nginx 会去找/data/www/ruoyi/system/user这个文件找不到就 404。这一点在下一章的 nginx 配置里会详细说。3.4 接口前缀改生产环境文件别碰开发环境.env.production是前端打包时读取的接口前缀配置# 改造前 VUE_APP_BASE_API /prod-api # 改造后 VUE_APP_BASE_API /ruoyi/prod-api改完之后所有走src/utils/request.js里那个 axios 实例的请求都会自动带上/ruoyi/prod-api前缀。这一点很关键——若依把 axios 实例封装得很干净baseURL: process.env.VUE_APP_BASE_API全项目共用一个实例所以只需要改一处。但有一个例外要注意项目里如果有直接写死/prod-api的地方或者用img src/prod-api/...这种写法就改不到。头像、验证码、部分导出链接都可能踩到。排查方法很土但有效# 在构建后的产物里搜索硬编码的接口前缀 grep -rn prod-api dist/ --include*.js | head -20如果产物里搜出一堆/prod-api且不是以/ruoyi/prod-api形式出现的说明有地方漏改了回到源码里搜grep -rn \/prod-api\|\/prod-api\|/prod-api src/3.5 构建完之后先自己验一遍产物打完包别急着往服务器扔本地先做一次静态检查cd ruoyi-ui npm run build # 1. 看 index.html 里的资源引用前缀对不对 grep -o src[^]* dist/index.html | head -5 grep -o href[^]* dist/index.html | head -5正确的结果应该长这样每个引用都带/ruoyi/src/ruoyi/static/js/chunk-vendors.8f3a2b1c.js href/ruoyi/static/css/app.4d2e1f0a.css如果还是/static/...说明publicPath没生效大概率是你改错了文件或者NODE_ENV判断没走到 production 分支。第二种情况可以用一个笨办法验证把三元表达式先临时改成固定值/ruoyi/重新打包看结果如果这次对了那就是NODE_ENV的问题。# 2. 确认产物目录结构 ls dist/ # 期望看到: index.html static/ favicon.ico4. 后端改造context-path、静态资源与那些编外入口4.1 走路线 A 的话后端一行都不用改这是我推荐路线 A 的核心理由后端零改动。因为所有/ruoyi和/ruoyi/prod-api这些前缀都在 nginx 那一层被处理掉了请求到达 Spring Boot 的时候路径跟原来一模一样Controller 该怎么映射还怎么映射。后端唯一需要确认的是application.yml里的context-path保持原样。若依默认配置里这一项通常是不写的或者写成/server: port: 8080 servlet: context-path: /如果你们项目的 yml 里被前人改动过写成了别的前缀那就得先把它恢复成/否则 nginx 剥完前缀后后端还会再剥一层照样 404。用一条命令就能确认线上的实际情况# 本地起服务时看启动日志里有没有 context path 的打印 grep -i context path /data/logs/ruoyi/ruoyi-admin.log # 或者直接探一个不需要鉴权的接口 curl -i http://127.0.0.1:8080/captchaImage # 返回 200 说明 context-path 是 / # 返回 404 说明有前缀需要进一步确认4.2 上传文件和/profile静态映射反而是最省心的很多人担心上传的头像、附件在子路径下访问不到。实际情况比想象中好若依的后端有一个ResourcesConfig把/profile/**映射到了本地磁盘的上传目录而前端呢拼接方式是process.env.VUE_APP_BASE_API user.avatar。也就是说头像地址会自动变成/ruoyi/prod-api/profile/avatar/2024/06/xx.png。这个请求走的是我们配置的/ruoyi/prod-api/这个 locationnginx 剥掉前缀后后端收到的是/profile/avatar/2024/06/xx.png正好命中ResourcesConfig的映射规则。所以整个链路是自动打通的不需要为上传文件单独加 location。但有两处细节要盯一下第一ruoyi.profile这个配置项指向的是服务器上的物理目录比如/data/upload。它跟部署路径完全无关不要看到要改路径就把这个也改了改错了会导致历史文件全部找不到。第二如果你们的上传目录单独挂在另一个域名或者 CDN 上那就得在前端调用处做替换。这种情况没法靠改publicPath解决得在src/utils/request.js或者专门的download工具函数里处理。我一般会留一个环境变量来放这个外部前缀方便切换。4.3 接口文档、监控页这些编外入口别漏也别全放出去若依后端还带了一些非业务入口它们在子路径下的表现不太一样值得单独列一下入口默认路径子路径部署后的行为建议Druid 监控/druid/**不在/ruoyi/下走原根路径生产环境直接关闭或加 IP 白名单Swagger 文档/swagger-ui/**、/v2/api-docs同上生产环境关闭验证码/captchaImage前端带/ruoyi/prod-api前缀正常无需处理导出下载/system/user/export等前端拼接前缀正常注意 nginx 超时设置这里的重点是这些入口不在你的/ruoyi/门牌号下面它们是暴露在域名根路径上的。也就是说http://域名/druid/index.html理论上还是能打开的。这在很多人的认知里是个盲区——以为把应用收到子路径下就藏起来了其实只是业务接口藏起来了这些附属入口还敞着。处理办法很简单两条路要么在 yml 里把 Druid 和 Swagger 的开关关掉生产环境本来就不该开要么在 nginx 层给这些路径加一层访问控制# 明确拒绝根路径下的监控和文档入口 location ~ ^/(druid|swagger-ui|swagger-resources|v2/api-docs) { deny all; return 403; }4.4 定时任务会不会因为路径变化不执行搜这个话题的时候看到不少人在问若依任务不执行顺手把这层关系理一下若依的定时任务Quartz调度逻辑是通过反射直接调用 Spring 容器里的 Bean 方法不走 HTTP 请求。也就是说invokeTarget写的是ryTask.ryParams(ry)这种形式它跟部署路径、nginx、context-path 全都没关系。所以子路径改造不会让定时任务失效。真正会导致任务不执行的原因通常是另外几个cron 表达式写错、任务被暂停状态不是正常、或者 Bean 名字写错导致反射找不到目标方法日志里会有ClassNotFoundException或NoSuchMethodException。唯一会被路径改造影响的场景是任务逻辑里硬编码了后端自身的 HTTP 地址比如某个任务里手动写了HttpUtil.get(http://localhost:8080/system/config/list)去调自己的接口。这种写法本身就很不推荐应该直接注入 Service 调方法但如果项目里真有路径变了确实会挂。排查方法就是全局搜一下http://localhost或http://127.0.0.1。4.5 会话、Cookie 与 Sa-Token 的路径属性若依默认用的是 JWTtoken 放在请求头里跟路径无关所以路线 A 下完全不用操心会话问题。但如果你们项目改成了基于 Cookie 的会话比如用了 Sa-Token 的会话模式或者自己接了一套 Session 方案那就要留意 Cookie 的Path属性。Cookie 的 Path 决定了浏览器在什么路径下会带上这个 Cookie默认是当前请求的路径。如果前端页面在/ruoyi/下而后端接口在/ruoyi/prod-api/下路径层级不同可能出现登录成功但下一个请求不带 Cookie的诡异现象。解决办法是在后端设置 Cookie 时明确指定Path/ruoyi或者干脆指定为/。Sa-Token 可以通过配置文件调整 Cookie 相关参数。这个问题不常遇到但一旦遇到特别难查因为浏览器开发者工具里明明能看到 Cookie 存在就是不发送。5. Nginx 配置location 前缀、proxy_pass 斜杠与 try_files5.1 整体骨架先搭好细节再往里填一份能让若依跑在/ruoyi/下的 nginx 配置结构上只需要两个location一个管前端静态文件一个管后端接口。nginx 的 location 匹配规则是前缀匹配取最长所以/ruoyi/prod-api/会比/ruoyi/优先命中不需要写任何正则顺序也可以随便放。server { listen 80; server_name _; # 前端静态资源 location /ruoyi/ { root /data/www; index index.html; try_files $uri $uri/ /ruoyi/index.html; } # 后端接口 location /ruoyi/prod-api/ { proxy_pass http://127.0.0.1:8080/; } }配合目录结构/data/www/ruoyi/里面直接放index.html、static/这一小段就能跑起来。剩下的都是锦上添花和避坑。5.2 proxy_pass 带不带斜杠一字之差决定 404这是整篇里最容易翻车、也最值得单独拎出来讲的一点。nginx 的规则是proxy_pass后面如果带了 URI哪怕只有一个/就会用这个 URI 替换掉 location 匹配到的那一段如果后面只有host:port没有 URI就原样透传整个请求路径。对照着看更清楚# 写法一带斜杠做替换 location /ruoyi/prod-api/ { proxy_pass http://127.0.0.1:8080/; } # 请求 /ruoyi/prod-api/system/user/list # 后端收到 /system/user/list ← 命中 Controller正确 # 写法二不带斜杠原样透传 location /ruoyi/prod-api/ { proxy_pass http://127.0.0.1:8080; } # 请求 /ruoyi/prod-api/system/user/list # 后端收到 /ruoyi/prod-api/system/user/list ← 404 # 写法三带路径替换成另一段 location /ruoyi/prod-api/ { proxy_pass http://127.0.0.1:8080/ruoyi/; } # 请求 /ruoyi/prod-api/system/user/list # 后端收到 /ruoyi/system/user/list ← 只有 context-path 是 /ruoyi 时才正确三种写法没有对错取决于你后端context-path怎么配。但绝不能混用写了带斜杠的proxy_pass又给后端加了context-path就会变成两层剥离或者两层叠加最终地址怎么拼都是错的。我的建议是一旦定下用写法一就把这条规则写进团队文档谁都别改。还有一个隐藏细节location 里如果用了正则比如location ~ ^/ruoyi/prod-api/那proxy_pass就不允许带 URI否则 nginx 启动直接报错。所以接口转发这一块务必用前缀 location别用正则。5.3 前端静态文件用 root 别用 alias这是个大坑网上很多教程写的是alias /data/www/ruoyi/dist/;。这个写法在你只是想返回一个固定文件时没问题但一旦和try_files配合就会出问题——这也是我当年白屏排查了两个小时的元凶。原因是用了alias之后try_files $uri里的$uri是完整的请求路径包含/ruoyi/这一段nginx 会把它拼到 alias 目录后面变成/data/www/ruoyi/dist/ruoyi/index.html多了一层/ruoyi/自然找不到。正确做法是用rootlocation /ruoyi/ { root /data/www; # 注意这里是父目录不是 dist 目录本身 index index.html; try_files $uri $uri/ /ruoyi/index.html; }这样请求/ruoyi/static/js/app.jsnginx 拼出来就是/data/www/ruoyi/static/js/app.js正好是 dist 内容拷贝后的位置。所以部署时把 dist 里的内容整个拷到/data/www/ruoyi/下而不是建一个/data/www/ruoyi/dist/再往里面放。提示如果你想保留 dist 这个目录名有些发布脚本会按这个命名那就把 root 设成/data/www/ruoyi并让 location 保持/ruoyi/同时把 dist 内容拷到/data/www/ruoyi/ruoyi/——非常绕我强烈建议直接用前一种简单清晰。5.4 刷新 404history 模式必须配 try_filestry_files $uri $uri/ /ruoyi/index.html;这一行的作用是先尝试把请求当成真实文件去找找$uri文件再找$uri/目录都找不到就返回/ruoyi/index.html交给前端路由去处理。没有这一行会怎样用户访问http://域名/ruoyi/system/usernginx 去找/data/www/ruoyi/system/user这个文件不存在直接 404。用户会以为系统坏了其实只是刷新时少了个兜底规则。Vue3 版默认的 hash 模式不需要这一行因为#后面的内容压根不会发给服务器。但如果你改成 history 模式这一行就是必需的。判断方法很简单在浏览器里手动输一个带子路径的业务地址然后按 F5能正常显示就对了。5.5 上传大小、超时、gzip 与缓存策略这四项属于不改也能跑改了能少接很多投诉的部分。上传大小nginx 默认的client_max_body_size是 1MB。若依的上传功能、Excel 导入功能很容易超过这个值表现出来就是上传大文件时报 413而且前端捕获不到明确错误只显示失败。放到server块里设成 100m 比较稳妥。这个限制在 nginx 和 Spring Boot 两层都有后端的spring.servlet.multipart.max-file-size也要同步放大只改一层照样失败。超时默认proxy_read_timeout是 60 秒。若依的导出功能、数据量大的报表查询很容易超过这个时间表现出来是导出文件下载到一半变成 0 字节或者直接失败。后端接口要 3 分钟的话nginx 这边得设成 300s并且三个超时都要一起改只改proxy_read_timeout可能还是会被proxy_send_timeout卡住。gzip前端打包后最大的两个文件是chunk-vendors.js和app.css未压缩时可能有 1MB 以上。开启 gzip 后通常能压到 30% 左右内网访问感知不明显外网访问提速很明显。缓存这里有个反直觉的点。给js、css加长缓存是好事因为它们带 hash内容变了文件名就变了但index.html绝对不能加长缓存否则用户浏览器一直用旧的 index.html里面引用的还是已经删掉的旧 hash 文件结果就是白屏。很多发版之后部分用户白屏强制刷新一下就好了的故障根源都在这里。# 带 hash 的静态资源放心长缓存 location ~* ^/ruoyi/static/.*\.(js|css|png|jpg|jpeg|gif|woff2?|ttf|svg)$ { root /data/www; expires 30d; add_header Cache-Control public, immutable; access_log off; } # 入口文件坚决不缓存 location /ruoyi/index.html { root /data/www; add_header Cache-Control no-cache, no-store, must-revalidate; add_header Pragma no-cache; }注意正则 location 的优先级高于前缀 location所以上面这两条会覆盖掉location /ruoyi/但不会影响/ruoyi/prod-api/接口路径不以static/开头。写正则 location 时一定要把/ruoyi/前缀带上否则可能误伤其他子项目。5.6 一份可以直接抄的完整配置把上面的东西合起来这份配置我自己在好几个项目里用过改一下目录和端口就能上server { listen 80; server_name app.example.internal; client_max_body_size 100m; server_tokens off; gzip on; gzip_min_length 1k; gzip_comp_level 5; gzip_types text/plain text/css application/javascript application/json application/xml image/svgxml; gzip_vary on; # ---------- 后端接口 ---------- location /ruoyi/prod-api/ { proxy_pass http://127.0.0.1:8080/; proxy_http_version 1.1; 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; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_connect_timeout 30s; proxy_send_timeout 300s; proxy_read_timeout 300s; proxy_hide_header X-Powered-By; } # ---------- 带 hash 的静态资源 ---------- location ~* ^/ruoyi/static/.*\.(js|css|png|jpg|jpeg|gif|woff2?|ttf|svg|ico)$ { root /data/www; expires 30d; add_header Cache-Control public, immutable; access_log off; } # ---------- 入口文件 ---------- location /ruoyi/index.html { root /data/www; add_header Cache-Control no-cache, no-store, must-revalidate; } # ---------- 前端应用 ---------- location /ruoyi/ { root /data/www; index index.html; try_files $uri $uri/ /ruoyi/index.html; } # ---------- 屏蔽根路径下的附属入口 ---------- location ~ ^/(druid|swagger-ui|swagger-resources|v2/api-docs) { deny all; } }对应的目录结构/data/www/ └── ruoyi/ # dist 里的内容直接放这里 ├── index.html ├── favicon.ico ── static/ ├── js/ ├── css/ └── fonts/6. 排障实录从白屏到 401 的高频问题6.1 白屏控制台一堆 404 和 MIME 类型错误白屏是子路径部署最常见的症状而且它有三个完全不同的成因得分开看。第一种资源路径没带前缀。打开浏览器 Network 面板如果看到请求的是http://域名/static/js/app.js而不是http://域名/ruoyi/static/js/app.js那就是publicPath或 Vite 的base没改或者改了没重新打包。这种情况的特征是所有 js、css 都是 404。第二种路径带了前缀但类型报错。控制台报Refused to apply style from ... because its MIME type (text/html) is not supported。这个更隐蔽——请求确实打到/ruoyi/static/js/app.js了但 nginx 找不到这个文件try_files把请求兜底给了index.html所以返回的是 HTML 内容浏览器按 js 解析失败。根因是文件路径没对上通常是用了alias或者 dist 放错了一层目录。解决办法就是回到 5.3 节检查root和实际目录结构。第三种服务器返回了 200 但还是白屏。这种情况多半是 index.html 缓存了老版本。用强制刷新 无痕窗口验证一下如果无痕能开、普通窗口白屏那就百分之百是缓存问题去检查/ruoyi/index.html的缓存头配置。6.2 接口通了但一直 401 或者直接 404404 的先看路径拼接。在浏览器 Network 里看失败请求的完整地址对着 5.2 节的三种proxy_pass写法自己算一遍后端收到了什么。如果你的地址里出现了两遍/ruoyi那就是前缀被叠了检查.env.production和publicPath是不是都往前缀里塞了/ruoyi。还有一种 404 是因为前端请求打到了前端 location 上而不是接口 location。比如接口前缀写成/ruoyi/api而不是/ruoyi/prod-apinginx 里没有对应的接口 location请求就被location /ruoyi/接走当成静态文件找不到兜底返回 index.html。表现是接口返回 200 但内容是 HTML解析 JSON 时报语法错误。这个特征很好认。401 的话优先怀疑路径丢了对 token 的携带而不是鉴权逻辑本身。若依的 token 放在请求头Authorization里只要请求正常发出就一定会带。所以 401 大概率是两种情况一是前面说的接口返回了 HTML前端把 HTML 当响应处理没拿到正常业务码二是 Cookie/Session 模式下路径不匹配导致会话丢失参考 4.5 节排查。顺带说一个隐藏福利子路径部署之后跨域问题自动消失了。因为前端页面和接口都在同一个域名、同一个端口下浏览器认为是同源请求。所以那些为了跨域折腾的Access-Control-Allow-Origin配置在这个方案下反而可以不要。6.3 验证码裂图、头像 404、导出文件 0 字节验证码裂图若依的登录页验证码用的是img :srccodeUrlcodeUrl由process.env.VUE_APP_BASE_API /captchaImage拼出来。所以路线 A 下它自动变成/ruoyi/prod-api/captchaImage正常。裂图基本只有一个原因——接口前缀没生效图片地址是/prod-api/captchaImagenginx 没这个 location。回到 3.4 节检查。头像 404参考 4.2 节正常情况下不用额外配置。如果头像真的 404先确认后端ruoyi.profile目录下文件是否真实存在再确认ResourcesConfig有没有被项目的其他配置类覆盖。导出文件 0 字节这是超时问题不是路径问题特征特别明显——文件能创建但大小是 0。改proxy_read_timeout和后端对应的异步超时配置。还有一种情况是大文件下载时 gzip 反而拖慢甚至中断可以在接口的 location 里单独关掉 gzip。6.4 WebSocket 连不上一直重连如果项目里集成了 WebSocket比如做了站内消息、实时通知或者接了 MQTT 再通过 WS 推给前端那子路径改造后一定要检查两件事。第一连接地址的前缀要跟着改。前端连的地址如果是ws://域名/ruoyi/prod-api/websocket/xxx那就得保证 nginx 的/ruoyi/prod-api/这个 location 里有Upgrade和Connection两个头上面 5.6 的配置里已经带了。少了这两个握手会失败。第二注意Connection upgrade不要写死。如果你的 server 块里还有其他普通接口也共用这个 location硬编码Connection: upgrade会让普通请求也带上这个头虽然大多数情况下无害但更规范的写法是用map指令按请求头动态设置。简单场景下直接写死也能用我自己的项目里就是写死的没出过问题。6.5 常见问题速查表现象最可能的原因快速验证修复方向整页白屏全部资源 404publicPath/base 未改或未重新打包Network 面板看资源 URL 前缀改配置后重新 build白屏报 MIME type 错误root/alias 用错文件路径不匹配看响应内容是不是 HTML改用 root核对目录层级接口 404地址里出现两次 /ruoyi前缀被重复拼接直接看请求 URL检查 env 与 publicPath接口 200 但内容是一段 HTML请求被前端 location 截走看 Response 内容核对接口前缀与 location 是否对应上传大文件 413client_max_body_size 太小上传 10MB 文件试试nginx 与后端两处同步放大导出文件 0 字节proxy 读超时看耗时是否接近 60 秒调大三个 timeout刷新页面 404history 模式缺 try_files在业务页面按 F5加 try_files 兜底发版后部分人白屏index.html 被缓存无痕窗口是否正常index.html 加 no-cache验证码裂图接口前缀未生效看 img 的 src改 .env.production 后重打包Cookie 模式下登录后立刻失效Cookie Path 不匹配看浏览器是否发送 Cookie设置 Cookie Path 为上级目录6.6 几条用时间换来的经验第一先改前端、再调 nginx、最后动后端。这个顺序的好处是每一步的变量都可控。前端改完打包扔到本地 nginx 试一下静态能不能开静态通了再接接口接口通了再考虑要不要动 context-path。反过来先动后端前端还没打包出问题你分不清是哪一层的锅。第二用curl -I而不是浏览器做第一轮验证。浏览器有缓存、有 Service Worker、有各种兜底会掩盖真实状态。一条命令就能看清楚# 看前端入口 curl -I http://127.0.0.1/ruoyi/ # 期望 200Content-Type: text/html # 看静态资源 curl -I http://127.0.0.1/ruoyi/static/js/app.xxxx.js # 期望 200Content-Type 是 javascript 而不是 text/html # 看后端接口不带 token 期望 401但绝不能是 404 curl -I http://127.0.0.1/ruoyi/prod-api/captchaImage # 期望 200captchaImage是个特别好用的探针因为它不需要登录、不需要参数返回的又是图片一眼就能看出后端通没通。我每次改完路径第一件事就是 curl 它。第三别在 location 里用正则匹配接口路径。前面提过一次proxy_pass带 URI 和正则 location 是互斥的nginx 会在启动时直接报错。有人为了让接口 location 更精确去写正则结果nginx -t不过排查半天才发现是这两条规则打架。第四把路径前缀集中管理在一个变量里nginx 侧可以用set前端侧用环境变量别在十几个文件里各写一遍/ruoyi。这一点在项目从/ruoyi/换到/admin/的时候体现得特别明显——集中管理的项目改一处就完事散落的项目要全局搜索替换然后重新全量测试。我自己现在会习惯性地在 nginx 配置顶部加一行注释写明本项目的公开前缀下次改的时候一眼就能定位到所有相关 location。最后分享一个小习惯每次改完 nginx我都会把改动前的地址、改动后的地址、以及curl -I的返回状态码记在一张便签上贴到部署文档里。看起来有点多余但当三个月后另一个同事再问这个子路径当时是怎么配的你翻出这张便签五分钟就能讲清楚比重新推一遍快得多。
返回列表