简介:围绕若依前后端分离项目的实际部署,这份 Word 文档给出了一套可对照操作的流程记录,面向已具备 Spring Boot 与 Vue 基础、正准备把项目搬上服务器的初中级开发者。内容分三条主线展开:Linux 环境下打包 jar 与 dist、配置 Nginx 与 Redis 的完整步骤;Windows 下改用 war 包配合 Tomcat、不依赖 Nginx 的替代方案;以及调用百度地图等第三方接口时的跨域处理,并顺带交代打包后菜单打不开、刷新页面报 404 等常见故障的排查与修复思路。压缩包内仅 1 个 docx 文件,约 223KB,配有配置截图与 web.xml 示例代码,体量轻巧但关键节点标注具体,便于按模块检索。目前已有 7766 人学习下载,适合希望快速复现部署环境、减少试错成本的读者照单实践。
1. 若依前后端分离部署:版本没对齐,后面每一步都在还债
部署若依前后端分离项目,翻车最多的不是 Nginx 配置,而是版本。前阵子看一套环境:jar 打出来了,nginx 也起来了,浏览器输 IP 能出登录页,登进去菜单一片空白——查下来是打包机上 Node 版本高了一个大版本,dist 里的资源路径全错。若依这套 Vue + Element UI 加 Spring Boot + Spring Security + Redis + JWT 的组合在中小后台系统里太常见,抄来的步骤经常互相打架。这里按我自己装过的顺序写:先对齐 JDK、Maven、Node、Redis、Nginx 的版本,再分 Linux + Nginx 和 Windows + Tomcat 两条路线落地,最后把菜单打不开、刷新 404、跨域这些现场问题逐条拆开。
2. 打包环节:mvn clean package 和 npm run build:prod 的版本前提与产物目录
打包是整个部署里最容易被跳过的一步。很多人拿到 jar 和 dist 就直接往服务器上扔,出了问题再回头看,才发现打包机上的 JDK 是 17、Node 是 20,跟目标环境压根不是一回事。所以这一章不着急讲服务器,先把本机这条链路捋直。
2.1 版本对齐:JDK 1.8、Maven 3.6、Node 16 是上限
若依后端是 Spring Boot 2.x,包名还是javax.*,前端是 Vue 2 + Vue CLI 4 那一套。这两条线都有自己的版本天花板,跨过去不会报「版本不兼容」这种友好提示,而是各种莫名其妙的构建失败。
| 组件 | 建议版本 | 踩过的点 |
|---|---|---|
| JDK | 1.8 | 跳到 17 会碰到 javax/jakarta 包名和反射权限问题,改起来不止一处 |
| Maven | 3.6.3 及以上 | 3.8+ 默认拦截 http 仓库,接内网私服要补 mirror 配置 |
| Node | 14 或 16 | 17 及以上跑 Vue CLI 4 会抛ERR_OSSL_EVP_UNSUPPORTED |
| npm | 6.x / 8.x | npm 9+ 打老项目容易在 lockfile 上较劲 |
| MySQL | 5.7 或 8.0 | 8.0 的连接串要带serverTimezone=Asia/Shanghai |
| Redis | 5.0 或 6.2 | 若依只用到字符串结构,版本比较宽容 |
| Nginx | 1.18 及以上 | 需要try_files指令处理前端路由 |
版本自检我一般一次性跑完,四个命令,输出贴到部署记录里存档:
java -version # 确认 1.8.x,不是 11 也不是 17 mvn -v # 确认 3.6+,顺便看 MAVEN_HOME 对不对 node -v && npm -v # 确认 node 14/16,npm 6/8 mysql --version # 顺手看一眼客户端版本Node 这块多说一句。如果打包机上已经有 17 以上又不想降级,可以临时用export NODE_OPTIONS=--openssl-legacy-provider顶一下,能过构建。但我不推荐长期这么用,老项目配老 Node 更省事,用 nvm 切一个 16 的版本只要两分钟,比在 CI 里加各种环境变量干净得多。真不想装 nvm,就老老实实把 Node 16 写进部署文档的环境要求里。
2.2 后端打包:多模块项目里 jar 到底长在哪
若依是 Maven 多模块结构,根 pom 只是聚合,真正出可执行包的是ruoyi-admin这个模块。所以在根目录执行打包,产物不在根目录的target,而在ruoyi-admin/target下面,这一点第一次接触的人十个里有八个找错。
# 在项目根目录执行,跳过测试类,避免打包被单测拖住 mvn clean package -Dmaven.test.skip=true # 产物确认,正常会看到 ruoyi-admin.jar 和 ruoyi-admin.jar.original ls -lh ruoyi-admin/target/*.jarclean是清掉上一次的编译产物,不加的话改了代码可能打进去的还是旧 class;package走到打包阶段;-Dmaven.test.skip=true连测试代码都不编译,比-DskipTests更彻底。如果公司有内网私服、需要走特定 profile,再加-P prod,但若依本身没有强制的打包 profile,-P更多是给自己留的口子。
打包前必须确认两处配置,不然打出来的包在服务器上一定跑不起来:
第一处是数据库连接,在ruoyi-admin/src/main/resources/application-druid.yml里,把url、username、password改成生产库的值,别把开发机的localhost:3306原样带上去。
第二处是文件上传路径,application.yml里的profile字段,Windows 上通常是D:/ruoyi/uploadPath,Linux 上要改成/home/ruoyi/uploadPath,并且提前把这个目录建好、给运行用户写权限。这个坑很隐蔽——登录、查列表全都正常,只有上传头像和附件的时候报错,排查起来要翻日志才看得见。
2.3 前端打包:build:prod 与 dist 里装了什么
前端在ruoyi-ui目录下操作,先装依赖再打包。依赖装不上基本都是网络原因,换个国内镜像源就能解决:
# 换镜像源,这条只影响当前用户,不会污染全局 npm config set registry https://registry.npmmirror.com cd ruoyi-ui npm install # 首次装依赖,耗时几分钟 npm run build:prod --report # 生产构建,--report 额外生成体积报告build:prod会读取.env.production里的环境变量,把VUE_APP_BASE_API作为接口前缀编译进代码。若依默认给的是/prod-api,这个值决定了 Nginx 里location该怎么写,两边必须对上,对不上就是接口全 404。
--report会在dist下额外生成一个report.html,浏览器打开能看到各个 chunk 的体积排行。这个文件上线前记得删掉,别跟着 dist 一起传到服务器上——它会把你的目录结构和依赖版本全暴露出去。
打包完成后dist目录的结构大致是index.html、static/js/*.js、static/css/*.css、static/fonts/*。index.html里的资源引用路径,取决于vue.config.js里publicPath的取值。若依模板给的是'./',也就是相对路径——这个设定在本地开发时看着没问题,放到 Nginx 根目录下也能跑,但一旦你的前端不是挂在域名根路径上,或者后端换了部署方式,就会出现第 5 章要讲的那个菜单打不开的经典问题。打包完先别急着传,打开dist/index.html看一眼<script src>开头是./static还是/static,心里有个数。
2.4 传到服务器:固定目录约定,别每次现编
我习惯在服务器上固定一套目录,后面所有配置都围绕它写,换机器的时候照着建一遍就行:
| 路径 | 放什么 |
|---|---|
/home/ruoyi/server | 后端 jar 包和启动日志 |
/home/ruoyi/nginx/html | 前端 dist 的内容(不是 dist 目录本身) |
/home/ruoyi/uploadPath | 上传文件落盘目录 |
/home/ruoyi/logs | 后端 logback 输出的日志 |
传输用 scp 就够,不需要额外装工具:
# 传后端包 scp ruoyi-admin/target/ruoyi-admin.jar root@192.0.2.10:/home/ruoyi/server/ # 传前端,注意 dist/* 带星号,把内容铺进 html 目录,不要多一层 dist scp -r ruoyi-ui/dist/* root@192.0.2.10:/home/ruoyi/nginx/html/dist/*那个星号是重点。少写这个星号,服务器上会变成html/dist/index.html,Nginx 的root指向html就找不到首页,访问直接 403 或者目录列表。上传完ls一眼确认index.html就在html目录第一层。
3. Linux + Nginx 落地:redis 先起,nginx.conf 只改三处
Linux 这条路是主流打法:Nginx 同时干两件事,托管前端静态文件,把/prod-api开头的请求转给后端 8080。整条链路上只有三个角色——Redis、jar 进程、Nginx,启动顺序也按这个来,反了就会在后端日志里看到一堆连接超时。
3.1 Redis:装完别急着走,两项配置要对上
若依用 Redis 存登录 token、验证码和字典缓存,后端一起来就要连它,连不上直接启动失败。装 Redis 最快的方式是包管理器:
# CentOS / 麒麟一类用 yum,Ubuntu 系换成 apt install redis-server yum install -y redis systemctl enable redis --now # 验证,返回 PONG 才算活 redis-cli ping装完默认监听127.0.0.1:6379,无密码。如果后端 jar 和 Redis 在同一台机器上,这样就是可用的,不用改。但要跨机器访问,就必须改/etc/redis.conf里的bind和protected-mode,同时设requirepass——直接把无密码的 Redis 暴露在公网上,是运维里最不该犯的错误之一,历史上被挖矿程序扫掉的机器一大半是这个原因。
对应地,application.yml里这段要和服务器实际值一致:
spring: redis: host: 127.0.0.1 # Redis 不在本机就换成实际 IP port: 6379 password: # 设了 requirepass 就填在这,没设留空 database: 0 # 若依默认用 0 号库,别和别的项目抢database这一项值得留意。同一台 Redis 上跑着几个项目时,如果都用 0 号库,key 前缀又撞了,就会出现「A 项目登录把 B 项目顶下线」这种玄学现象。给每个项目分一个库号,或者至少确认 key 前缀不同,能省掉很多莫名其妙的排查。
3.2 Nginx:一个 server 块,两个 location
装 Nginx 用包管理器即可,版本 1.18 以上就够用:
yum install -y nginx nginx -v # 确认版本然后在conf.d下新建一个站点的配置文件,别直接改主配置。这样以后加站点、回滚都方便,主配置只留全局项。
server { listen 80; server_name 192.0.2.10; # 没有域名就写服务器 IP charset utf-8; # 前端静态资源,root 指向 dist 内容所在目录 location / { root /home/ruoyi/nginx/html; index index.html index.htm; # 关键:找不到文件时回落到 index.html,交给前端路由处理 try_files $uri $uri/ /index.html; } # 后端接口,把 /prod-api/ 前缀转发到 8080 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/; } }逐条说清楚,这几行是整份配置里最容易改错的:
try_files $uri $uri/ /index.html解决的是前端 history 路由的刷新问题。用户停在/system/user按 F5,浏览器真的会去请求/system/user这个路径,Nginx 在磁盘上找不到同名文件就返回 404。加上这条之后,找不到就回落到index.html,由 Vue Router 接管,页面正常渲染。少了这条,深层页面刷新必 404。
proxy_pass http://127.0.0.1:8080/结尾的斜杠决定了前缀去留。带斜杠,Nginx 会把/prod-api/剥掉再转发,后端收到的是/captchaImage;不带斜杠,后端收到的是/prod-api/captchaImage。若依后端默认context-path是/,所以这里必须带斜杠。反过来,如果你给后端配了/prod-api的 context-path,那这个斜杠就得去掉,两边只能有一边带前缀。
四行proxy_set_header主要是把真实客户端 IP 传给后端,不然后端日志里全是127.0.0.1,出问题根本查不到是谁在调。这一条在排查登录异常、限流误判的时候特别有用。
改完不要直接restart,先测语法再平滑重载:
nginx -t # 语法检查,输出 syntax is ok 才继续 nginx -s reload # 平滑重载,不断连接3.3 启动 jar:nohup 那一行里每个符号都有用
后端启动用nohup挂后台。前台跑的话,终端一断开进程就跟着没了,而且整个终端被占住干不了别的:
cd /home/ruoyi/server # 后台启动,标准输出和错误都写进 msg.log nohup java -jar ruoyi-admin.jar > msg.log 2>&1 & # 跟上日志,看启动过程 tail -f msg.log这一行里三部分缺一不可。nohup让进程忽略挂断信号,退出 SSH 也不会被杀;> msg.log把标准输出重定向到文件;2>&1把标准错误也并进同一个文件,不加的话异常堆栈照样打在终端里,等于没留日志;结尾的&才是丢到后台。
msg.log生成在执行这条命令时所在的目录,不是 jar 所在目录,也不是/var/log。所以先cd到/home/ruoyi/server再执行,日志位置才可预期。这个细节看起来小,但换个人来接手、找不到日志的时候就知道麻烦了。
内存参数按机器给,小机器别硬撑:
# 2G 内存的机器,堆开 512M 到 1G,留够给系统和 Redis 的余量 nohup java -Xms512m -Xmx1024m -Dfile.encoding=UTF-8 -jar ruoyi-admin.jar > msg.log 2>&1 &-Dfile.encoding=UTF-8在中文环境里建议加上,省得日志里出现一堆问号。
如果这套环境不是临时跑跑,建议直接写 systemd 单元,比nohup好管:进程挂了能自动拉起,systemctl status能看状态,开机自启也不用额外配。内容不长,ExecStart里的路径换成你自己的即可:
[Unit] Description=ruoyi-admin After=network.target redis.service [Service] WorkingDirectory=/home/ruoyi/server ExecStart=/usr/local/jdk1.8/bin/java -Xms512m -Xmx1024m -jar /home/ruoyi/server/ruoyi-admin.jar SuccessExitStatus=143 Restart=on-failure [Install] WantedBy=multi-user.targetSuccessExitStatus=143是因为 Java 进程收到 SIGTERM 时退出码是 143,不写这条,正常停机也会被记成失败。
3.4 启动顺序与端口确认
正确的顺序是 Redis → 后端 jar → Nginx。Redis 没起来后端启动会直接抛连接异常;Nginx 先起也没关系,只是这期间访问会 502。
三个都起完之后,用一条命令把关键端口全看一眼:
ss -lntp | grep -E ':(80|8080|6379)'正常应该能看到 80 被 nginx 占着、8080 被 java 占着、6379 被 redis 占着。哪个没出现,就是对应服务没起来,往下查那个即可,不用一上来就翻代码。
4. Windows + Tomcat 落地:war 包、prod-api 与 WEB-INF/web.xml 三件套
有些环境就是没有 Linux 机器,或者甲方只肯给一台 Windows Server,那就走 Tomcat 这条路。原理和 Nginx 方案一样:一份静态文件加一份后端服务,区别在于 Tomcat 同时承担了静态托管和接口转发的角色,配置从nginx.conf挪到了server.xml。
4.1 pom 改 war:不只是换个 packaging
第一步把ruoyi-admin/pom.xml里的打包类型改掉:
<!-- 由 jar 改为 war,外置 Tomcat 部署必须是 war --> <packaging>war</packaging>但只改这一行是不够的,两个连带动作必须一起做,否则打出来的 war 扔进 Tomcat 就是起不来。
第一,把内嵌的 Tomcat 排除掉,避免和外置容器打架:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency>第二,启动类要继承SpringBootServletInitializer并重写configure,这是外置容器能找到 Spring 上下文的入口。少了这一步,Tomcat 启动时不会报错,但访问任何接口都是 404,属于典型的「看起来部署成功了其实没有」:
@SpringBootApplication public class RuoYiApplication extends SpringBootServletInitializer { public static void main(String[] args) { SpringApplication.run(RuoYiApplication.class, args); } // 外置 Tomcat 通过这个方法加载应用上下文 @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(RuoYiApplication.class); } }改完重新mvn clean package,产物会从.jar变成.war。
4.2 目录布局与 server.xml 的两处改动
war 包丢进webapps时改名成prod-api,Tomcat 会按文件名自动解压出一个/prod-api的访问路径,这正好对上前端.env.production里的VUE_APP_BASE_API=/prod-api。两边名字必须一模一样,差一个字母就是接口 404。
前端 dist 的内容也放到webapps下,比如webapps/dist。然后改conf/server.xml——注意文件名是server.xml,不少流传的文档写成service.xml,照着找会找不到文件。
两处要动。第一处是 Connector 的端口,看你想让前端走 80 还是别的端口:
<!-- 想直接用 IP 访问就把 8080 改成 80,改完注意 80 是否被 IIS 之类占了 --> <Connector port="80" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />第二处是在Host节点里加一个 Context,把根路径指向 dist 的目录:
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"> <!-- path="/" 表示用根路径访问,docBase 是 dist 的绝对路径 --> <Context path="/" docBase="D:/tomcat/webapps/dist" reloadable="true" crossContext="true" /> </Host>path="/"让这个应用接管根路径,用户输http://ip:80/直接进前端。docBase写绝对路径最省心,写相对路径是相对webapps的,容易搞混。reloadable="true"是改了页面不用重启,调试期方便,上线前建议改回false少一点开销。
不加这段 Context 的典型症状是:登录页能出来,点登录也能成功,但进去之后一片空白。原因是前端 JS/CSS 的请求路径和后端的实际路径对不上,静态资源全 404。
4.3 WEB-INF/web.xml:把 404 兜回首页
前端路由刷新 404 的问题在 Tomcat 下同样存在,只是没有 Nginx 的try_files可用,得用错误页兜底。做法是在 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>error-page这段的意思是:任何 404 都把/index.html吐回去,浏览器拿到首页后由 Vue Router 根据地址栏路径渲染对应组件。效果和 Nginx 的try_files一致,只是实现方式不同。
一个前提别忘:metadata-complete="true"会让 Tomcat 跳过注解扫描,所以这个目录里不要再指望放别的 Servlet。它就是一个纯错误页配置。
4.4 Windows 下前后端路径对齐的两种取舍
Nginx 方案里,前端和后端同源,/prod-api由 Nginx 转发,所以不存在跨域。Tomcat 方案里两者也同源(同一个 80 端口),一样不跨域,但路径规则不同,有两种接法。
第一种是保持VUE_APP_BASE_API=/prod-api不变,靠 war 包改名成prod-api.war形成的/prod-api上下文来接。这种方式最省事,代码一行不用改,前提是 war 包名和前缀严格一致。
第二种是把VUE_APP_BASE_API改成完整地址,比如http://192.0.2.10:8080/prod-api。这种写法在前端直连后端时能用,但等于把后端地址硬编码进了构建产物,换服务器要重新打包,还引入了跨域配置的负担。除非有特殊网络结构,我不建议这么干。
改完.env.production一定要重新npm run build:prod,环境变量是编译期注入的,只改文件不重新打包,dist 里还是老值。这条听着像废话,但真有人改完配置直接刷浏览器,然后怀疑人生。
5. 部署排查:菜单打不开、登录白屏、跨域报错的 5 条现场记录
部署类问题有个共同特点:现象看着都像「后端挂了」,实际问题在打包、路径、端口、跨域各个层面。下面这五条是我在若依环境里重复遇到次数最多的,按「现象 → 原因 → 解决」记下来,照着对号入座能省掉大半天。
5.1 现象:登录能进,左侧菜单点开全是空白或 404
原因:vue.config.js里publicPath配的是'./',打包后index.html里的资源引用变成相对路径。挂在域名根路径下没事,一旦前端不在根路径、或者走了 Tomcat 的 Context、或者套了一层子目录,相对路径就会解析到错误的位置,异步组件加载全部失效。这个坑很典型——第一次打包放上去没问题,后来改了文件或菜单重新部署,就突然打不开了。
解决:把vue.config.js里的'./'改成'/',也就是去掉那个点,然后重新打包、重新上传。
// vue.config.js module.exports = { // 生产环境改成绝对根路径,避免子目录下资源解析错位 publicPath: process.env.NODE_ENV === 'production' ? '/' : '/', outputDir: 'dist', assetsDir: 'static' }改完必须重新npm run build:prod,只改源码不重新打包是没效果的。另外改完记得强刷浏览器(Ctrl+F5),index.html很可能被缓存了,你以为是配置没生效,其实是看的旧页面。
5.2 现象:Tomcat 部署后登录成功,主页面白屏,F5 刷新直接 404
原因:前端 history 路由的路径在服务器上找不到对应的物理文件,Tomcat 又没配回落到index.html的规则。登录那一刻是 JS 内部跳转所以能过,刷新就是真的向服务器要/index这个路径了。
解决:两件事一起做。在Host节点下补 Context,把根路径指到 dist;同时在 dist 里建WEB-INF/web.xml,把 404 错误页指向/index.html。两段配置在第 4 章都给了完整内容,直接抄。改完必须重启 Tomcat,server.xml不支持热加载。
5.3 现象:页面里调用第三方接口(地图、短信、物流之类)控制台报 CORS 跨域
原因:浏览器同源策略。前端页面在http://ip下,请求发到第三方域名,对方响应头里没有Access-Control-Allow-Origin,浏览器就拦了。这跟若依本身没关系,是浏览器的规矩。
解决:三条路,按场景选。
生产环境最稳的是让后端做一层转发,前端只调自己的域名。Nginx 里加一个 location 即可:
# 前端请求 /third-map/xxx,Nginx 转给第三方,浏览器看到的是同源 location /third-map/ { proxy_set_header Host api.example-third.com; proxy_pass https://api.example-third.com/; proxy_ssl_server_name on; }如果第三方接口是后端调的(不是浏览器直发),那压根不会有跨域,问题出在前端直接发了请求。这种情况改前端调用地址,走自己的后端接口。
后端加全局 CORS 配置是第二种做法,适合前后端确实分属不同域名的场景:
@Configuration public class CorsConfig { @Bean public CorsFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); // 开了凭证就不能用 "*",要用 allowedOriginPattern config.addAllowedOriginPattern("*"); config.addAllowedHeader("*"); config.addAllowedMethod("*"); config.setAllowCredentials(true); config.setMaxAge(3600L); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); } }这里有个高频报错:同时开allowCredentials(true)和addAllowedOrigin("*"),浏览器会直接拒绝,提示不能用通配符。必须换成addAllowedOriginPattern,或者把来源写成具体域名。生产环境别图省事用通配符,写死域名更安全。
第三种是前端vue.config.js里的devServer.proxy,但它只在npm run dev时生效,打包后的 dist 里根本没有这层转发。很多人本地调通了就以为线上也没问题,上线一看照样跨域,就是栽在这。本地开发可以这么写:
devServer: { port: 80, proxy: { // 只对本地开发服务器生效,生产构建不包含这段 '/dev-api': { target: 'http://localhost:8080', changeOrigin: true, pathRewrite: { '^/dev-api': '' } } } }5.4 现象:nohup 启动后进程几秒就没了,msg.log 里一堆异常
原因:排前三的是 Redis 没起、8080 端口被占、上传目录不存在没权限。这三种都会在启动阶段抛异常然后退出,但因为用了nohup,终端上看不到,只留个日志。
解决:先看日志尾部,再对症处理。
# 看最后 100 行,异常一般就在这里面 tail -n 100 msg.log # 端口被占的话,查一下是谁 ss -lntp | grep 8080 # 上传目录不存在就建,注意属主要和启动用户一致 mkdir -p /home/ruoyi/uploadPath && chown -R ruoyi:ruoyi /home/ruoyi日志里出现Unable to connect to Redis就去systemctl status redis;出现Port 8080 was already in use就kill掉旧进程,注意别把上一个版本的 jar 留着占端口,ps -ef | grep java看一眼再动手。
5.5 现象:接口 404,但后端日志里连请求记录都没有
原因:请求根本没到后端,卡在 Nginx 那一层。九成是location /prod-api/和proxy_pass结尾斜杠没配对上,前缀被多剥或者少剥了一段。
解决:先直接打后端,绕开 Nginx 定位问题在哪一层:
# 直连后端,如果能通说明后端没问题,问题在 Nginx curl -i http://127.0.0.1:8080/captchaImage # 再经 Nginx 打一次,对比返回 curl -i http://192.0.2.10/prod-api/captchaImage第一条通、第二条 404,就回头检查proxy_pass结尾那个斜杠。同时打开nginx -t和/var/log/nginx/error.log,Nginx 层的问题基本都能在这两处看到线索。
6. 装完怎么验证:四个检查点从后端往浏览器捋一遍
部署完之后最忌讳的就是「浏览器能打开登录页 = 装好了」。登录页能出来只说明静态文件到位,后端、Redis、接口转发一个都没验证。我习惯从后往前捋四个检查点,任何一环不通就停在那里排查,不往后走。
# 检查点一:进程和端口,三个都得在 ss -lntp | grep -E ':(80|8080|6379)' ps -ef | grep ruoyi-admin | grep -v grep # 检查点二:后端直连,返回 JSON 且 code 为 200 才算后端真正活了 curl -s http://127.0.0.1:8080/captchaImage | head -c 200 # 检查点三:经 Nginx 转发,返回内容必须和检查点二一致 curl -s http://192.0.2.10/prod-api/captchaImage | head -c 200 # 检查点四:首页静态文件是否到位 curl -I http://192.0.2.10/index.html检查点二的返回里有uuid和img两个字段,说明后端能正常连 Redis 生成验证码;如果返回{"msg":"...","code":500},那问题在后端到 Redis 这一段,别再折腾 Nginx 了。检查点三和检查点二结果不一致,问题一定在proxy_pass那几行。
上面四条过了之后,再做一遍手工动作,这步是自动脚本替代不了的:
| 动作 | 观察什么 | 出问题看哪 |
|---|---|---|
| 打开首页并 Ctrl+F5 强刷 | 静态资源是否全部 200 | 浏览器 Network 面板、publicPath |
| 输入错误验证码登录 | 是否返回验证码错误提示 | 后端日志、Redis 连接 |
| 正确登录进首页 | 左侧菜单是否完整加载 | /getRouters接口返回 |
| 停在任意子页面按 F5 | 是否 404 | try_files或WEB-INF/web.xml |
| 上传一次头像 | 文件是否落盘成功 | profile路径和目录权限 |
「停在子页面刷新」这一步是我认为最值得单独留意的。很多人部署完只验了登录,交付之后用户随手按了个 F5 就报 404,又变成一次线上事故。这个动作只要十秒,能提前把整类问题挡掉。
从那以后我每次部署若依,不管 Linux 还是 Windows,都会把这四个检查点连同上面那张表走一遍,一条不落,也不允许自己用「刚才本地试过了」代替。部署这件事没有后悔药,能提前验证的环节就别留到用户反馈的时候再补。希望帮到你。
本文还有配套的精品资源,点击获取