若依前后端分离部署全解析:Nginx、Tomcat与Redis协同实战
简介一份面向 Java 后端开发者和运维人员的若依前后端分离部署指南覆盖 LinuxNginx、WindowsTomcat 两种典型部署环境从后台 jar 打包、前端 npm 构建生成 dist到 Nginx 代理、Redis 缓存服务、Tomcat WAR 发布与路径映射均有清晰说明同时针对部署后刷新页面出现 404、调用百度地图等第三方 API 遇跨域报错等高频问题提供了可直接对照的解决思路与配置方法。资源为单个 docx 文档压缩包大小约 223KB结构紧凑便于查阅、检索和打印。文档按三大任务组织第一部分梳理 LinuxNginx 环境下的完整落地流程包含 mvn 打包、dist 产物部署、nginx.conf 代理配置和 nohup 后台启动 jar 包第二部分切换到 WindowsTomcat 无 Nginx 场景讲解 pom 改 war、webapps 目录重命名、service.xml 上下文配置以及用 WEB-INF/web.xml 避免刷新后 404第三部分汇总跨域问题的前端代理、后端跨域策略与 Nginx 转发三种处理方式。目前已有 7757 人学习下载适合计划搭建若依生产环境或正在对照排查部署异常的开发者参考。1. 若依前后端分离项目部署为什么总卡在“最后一步”把若依RuoYi这套开源 Java 后台管理框架从 IDEA 搬到 Linux 服务器绝大多数人不是卡在写代码而是卡在打包、代理、刷新 404、Redis 连接这些“最后一公里”。本地开发时 Vite 代理和后端 Spring Boot 天然同源一旦切到生产环境前端静态资源、后端 API 进程、Redis、Nginx 或 Tomcat 这四个角色必须按正确的时序和前后的路径规则拼起来任何一环对不上表现都是页面转圈、验证码不刷新、登录后白屏。这篇文档从若依前后端分离版的实际部署链路出发把 jar/war 两种打包方式、dist 目录、Nginx 代理、Tomcat 的 Context 配置和跨域处理逐个拆开讲适合已经跑通若依本地开发、准备上测试或生产环境的 Java 后端和全栈开发。2. Linux Nginxjar 包、dist 目录与 Redis 的协同时序2.1 后端 jar 的构建fat jar 与原始 jar 的区别若依是一个 Maven 多模块工程Web 启动模块通常是ruoyi-admin。在 IDEA 的 Terminal 中执行mvn clean package会在ruoyi-admin/target下生成两个文件一个带.original后缀的原始 jar一个 Spring Boot 插件重新打包过的可执行 fat jar。部署时取不带.original后缀的那个因为它把 Tomcat 内嵌进了BOOT-INF/lib可以直接java -jar运行。# 跳过单测缩短打包时间 mvn clean package -DskipTests # 确认产物 ls -lh ruoyi-admin/target/*.jar-DskipTests只是不执行测试用例不会影响编译日常部署我会习惯加上。如果生产机器 JDK 版本和开发环境不一致启动时会抛UnsupportedClassVersionError这是最常见的jar 包没问题但启动失败原因。可以先java -version确认再决定要不要重新指定maven.compiler.source/target。2.2 前端打包build:prod 与 dist 目录结构前端项目执行npm run build:prod --report会在根目录生成dist。--report会额外输出一个report.html用来分析打包体积首次部署可以留着看日常更新建议去掉因为会拖慢构建。dist 目录里只有index.html和static目录static/js、static/css下的文件名带内容 hash这是 Webpack 的产物特征浏览器可以放心缓存更新时不会因为文件名不变而拿到旧文件。# 前端工程根目录下执行 npm install npm run build:prod构建脚本对应的环境变量在.env.production里关键一项是VUE_APP_BASE_API /prod-api这个前缀决定了前端所有请求都发到http://你的域名/prod-api/xxxNginx 和 Tomcat 的路径转发都要围绕它来配。2.3 Redis 是若依启动的前置条件若依的验证码、JWT 会话、字典数据、在线用户都依赖 Redis。很多人第一次部署把 jar 启动了但 Redis 没装登录时验证码图片能出提交登录就 500。因为验证码校验是先从 Redis 里取取不到直接报错。生产环境建议给 Redis 设置requirepass然后同步修改若依application.yml里spring.redis.password代码里不写明文密码的用环境变量注入。# 启动前先确认 Redis 可用 redis-cli ping # 期望返回 PONG注意 Redis 默认只绑定127.0.0.1如果 jar 和 Redis 在同一台机器没问题分开部署就要改bind 0.0.0.0同时安全组放行 6379 端口。这里的坑在于jar 启动本身不报错只有访问登录接口时才报连接超时排查方向容易跑偏。2.4 Nginx 配置四个关键点Nginx 在这里干两件事托管 dist 静态文件把/prod-api/反代到后端 jar。一份最小可用的 server 配置如下server { listen 80; server_name _; root /opt/ruoyi/dist; index index.html; client_max_body_size 50m; # 前端路由刷新页面时不出现 404 location / { try_files $uri $uri/ /index.html; } # 后端 API 反代 location /prod-api/ { proxy_pass http://127.0.0.1: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_pass http://127.0.0.1:8080/;末尾的斜杠很关键。加了斜杠请求/prod-api/captchaImage会被剥离前缀转发成http://127.0.0.1:8080/captchaImage去掉斜杠则会把/prod-api原样带过去后端找不到这个路径返回 404。若依后端接口本身就带/prod-api前缀的部署方式另说前后端分离版的默认约定是前端带前缀、后端不带所以这里必须加斜杠。配置项作用易错点try_files $uri $uri/ /index.html前端 history 路由刷新兜底缺少它刷新子页面报 404proxy_pass末尾/控制是否剥离/prod-api前缀带不带斜杠后端收到的 URL 完全不同client_max_body_size限制上传文件大小不配置时默认 1m图片上传会失败root指定 dist 目录的绝对路径写成相对路径启动不报错但访问全是 4032.5 后台启动 jar 与日志定位jar 必须后台运行否则关掉 SSH 窗口进程就没了。标准的启动方式是nohup java -jar /opt/ruoyi/ruoyi-admin.jar \ --server.port8080 \ --spring.profiles.activeprod \ /opt/ruoyi/msg.log 21 msg.log把标准输出写进日志21把错误输出也合并到同一个文件让命令在后台执行。nohup保证即使当前终端退出进程也不会收到挂断信号。启动后先看端口和日志# 确认端口在监听 netstat -tlnp | grep 8080 # 实时看日志 tail -f /opt/ruoyi/msg.log最后在 Windows 浏览器直接访问http://服务器IP。如果页面能开但接口报 502多半是后端没起来或 Nginx 反代地址写错如果接口 403优先看 Redis 和数据库连接。3. Windows Tomcat 无 Nginx 部署war 化、Context 路径与刷新 4043.1 为什么还要保留 Tomcat 部署方式不是所有内网环境都有 Linux 服务器很多公司已有的 Windows 服务器上跑着 Tomcat再引入 Nginx 会多一个维护点。把若依从 jar 改成 war 丢进 Tomcat让 Tomcat 同时承担静态资源服务和后端 Servlet 容器是这种情况下最省事的方式。Tomcat 8.5 及以上版本支持 Servlet 3.1配合 Java 8 和若依当前的 Spring Boot 版本没问题建议直接用 Tomcat 9。3.2 pom 改 war三个动作缺一不可若依 web 启动模块的pom.xml需要做三处修改packaging改成war、排除 Spring Boot 内嵌 Tomcat、确保启动类继承SpringBootServletInitializer。常见做法是!-- ruoyi-admin/pom.xml -- packagingwar/packaging dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-tomcat/artifactId scopeprovided/scope /dependencyscopeprovided表示打包时排除这个依赖因为外置 Tomcat 会提供 Servlet 容器。启动类方面若依较新版本的RuoYiApplication已经继承了SpringBootServletInitializer并重写了configure老版本或二次开发的工程要检查一下否则 war 部署后 Spring 容器不会初始化Tomcat 访问直接 404。打包方式不变mvn clean package -DskipTests3.3 server.xml 与 Context 路径映射打包出的 war 复制到 Tomcat 的webapps目录重命名为prod-api.war对应前端请求前缀/prod-api。假设 Tomcat 启动端口改成 8080那么前端请求http://ip:8080/prod-api/captchaImageTomcat 会自动把/prod-api这个上下文路径映射到prod-api.war应用不需要额外写转发规则。dist 目录的处理方式是复制到webapps下然后在conf/server.xml的 Host 节点添加 Context 映射把根路径指向 dist。注意Tomcat 实际文件名是server.xml有些部署文档会误写成service.xml。Host namelocalhost appBasewebapps unpackWARstrue autoDeploytrue Context path/ docBaseD:/tomcat/webapps/dist reloadabletrue crossContexttrue /Context /HostdocBase可以写绝对路径也可以写相对webapps的路径。path/表示把根路径映射到 dist这样访问http://ip:8080/就直接打开前端页面。访问地址实际命中的资源http://ip:8080/dist/index.htmlhttp://ip:8080/static/js/xxx.jsdist/static 下的静态文件http://ip:8080/prod-api/loginprod-api.war 里的后端接口http://ip:8080/prod-api/captchaImage后端验证码接口3.4 刷新 404 与登录后白屏的处理Tomcat 部署方式下前端页面用的是 HTML5 history 路由直接访问http://ip:8080/index或刷新页面Tomcat 找不到/index这个物理路径就返回 404。解决办法是在 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 error-code404/error-code location/index.html/location /error-page /web-appTomcat 收到 404 后会转交给/index.html然后前端路由再接管 URL。这个机制和 Nginx 里的try_files $uri $uri/ /index.html是同一件事。登录成功但页面白屏的情况要分两类排查。第一类Network 面板里 JS/CSS 文件 404说明publicPath不对静态资源按错误路径加载了第二类静态资源正常但用户信息接口/prod-api/system/user/getInfo返回 401 或 403说明 Token 没写进请求头通常是本地存储逻辑或网关路径问题。先看 Network能省大量时间。4. 跨域问题第三方 API 同源策略与前后端联调的解耦思路4.1 先分清是哪一层跨域浏览器同源策略限制的是XMLHttpRequest和fetchscript、img、link标签天然不受限制。百度地图 JS API 走的是动态 script 标签加载所以它不存在传统意义的 CORS 问题但如果你在前端代码里用fetch直接请求某个第三方 HTTP 接口跨域错误就出现了。另一种场景是后端 Java 代码通过RestTemplate或HttpClient调用第三方接口这是服务器到服务器的通信浏览器完全不参与不存在跨域。真正需要处理的跨域是浏览器直接访问第三方 API或者前端页面部署域名和后端接口域名不一致。解决思路只有一个把跨域请求变成同源请求要么用 Nginx 代理要么让后端加 CORS 响应头。4.2 Nginx 代理转发第三方 API以百度地图 API 为例前端请求自己的域名/map-api/geocodingNginx 把这个前缀转发给百度location /map-api/ { proxy_pass https://api.map.baidu.com/; proxy_ssl_server_name on; proxy_set_header Host api.map.baidu.com; }请求https://你的域名/map-api/geocoding会被转发成https://api.map.baidu.com/geocoding浏览器视角看请求是同源的自然不会触发 CORS。proxy_ssl_server_name on是给 HTTPS 上游传 SNI不加的话部分服务器会握手失败这是反向代理 HTTPS 接口时容易忽略的参数。4.3 Spring Boot 侧统一配置 CORS如果没有 Nginx或者前端资源放在 CDN 上和后端域名完全隔离可以在若依后端加一个全局 CORS 配置。常见做法是实现WebMvcConfigurerConfiguration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }注意allowedOriginPatterns(*)和allowedOrigins(*)的区别当allowCredentials(true)时allowedOrigins不能使用通配符必须写具体域名allowedOriginPatterns在 Spring 5.3 支持用模式匹配。如果若依的接口全部走 JWT本质上无状态可以不设置allowCredentials直接allowedOrigins(*)更省事。方案适用场景注意点Nginx 反向代理有 Nginx 的生产环境同一域名下转发改造最小后端 CORS 配置前后端域名分离、CDN 部署OPTIONS预检请求需要被 Spring Security 放行前端 devServer.proxy仅本地开发联调生产构建后完全不生效4.4 预检请求与调试方法若依集成了 Spring Security浏览器发起跨域请求前会先发一个OPTIONS预检请求如果 Security 配置里没有放行请求会在到达 Controller 前就被拦截表现为接口报 403 而不是 CORS 报错。需要在 Security 配置中加上http.authorizeHttpRequests() .requestMatchers(HttpMethod.OPTIONS, /**).permitAll()调试时先看浏览器 Network 面板的响应头有没有Access-Control-Allow-Origin。没有这个头说明请求根本没走到后端 CORS 过滤器有这个头但浏览器还报错检查 Allow-Origin 的值是否和当前页面域名匹配。这个排查顺序能区分问题是出在 Nginx、Security 还是 Controller。5. 部署后的版本更新与配置收敛publicPath、日志与缓存5.1 publicPath 与“菜单打不开”的真实原因部署完成后改了菜单或页面重新上传 dist 后菜单打不开大多数情况不是代码问题而是vue.config.js里的publicPath和实际部署路径不一致。若依小程序、管理端等多路由场景下前端跳转到/index后刷新资源路径加载不到表现就是白屏或菜单点击无反应。生产环境部署在域名根路径时保持publicPath: /是对的如果需要部署在子路径下改成/子路径/而不是./。// vue.config.js module.exports { // 根路径部署用 / // 子路径部署用 /ruoyi/并保证 Nginx 或 Tomcat 的 Context 也对应 publicPath: /, outputDir: dist, assetsDir: static }./是相对路径本地 file 协议打开没问题但放到服务器后BrowserRouter 会使当前 URL 变成http://ip/index相对路径拼出来是http://ip/index/static/js/xxx.js资源 404。把publicPath收敛成与部署环境一致的绝对路径这类问题能直接消失。5.2 一次标准的前后端更新流程后端更新走“打包 → 上传 → 杀进程 → 重启”前端更新走“打包 → 清空旧 dist → 同步新文件”。前端不要直接覆盖旧 dist因为带 hash 的旧 JS/CSS 文件会残留积累多了磁盘占用不说还可能因 index.html 缓存问题加载到半新半旧的文件。# 后端定位并停止旧进程 ps -ef | grep ruoyi-admin.jar | grep -v grep kill $(pgrep -f ruoyi-admin.jar) # 启动新 jar nohup java -jar /opt/ruoyi/ruoyi-admin.jar \ --server.port8080 \ --spring.profiles.activeprod \ /opt/ruoyi/msg.log 21 # 前端清空并同步 rm -rf /opt/ruoyi/dist/* scp -r dist/* rootserver-ip:/opt/ruoyi/dist/ # 验证后端接口 sleep 15 curl -s -o /dev/null -w %{http_code} \ http://127.0.0.1:8080/prod-api/captchaImage5.3 三个高频排错命令# 实时日志看请求进来后在哪一步断掉 tail -f /opt/ruoyi/msg.log # 静态资源是否被正确托管 curl -s -o /dev/null -w %{http_code} http://127.0.0.1/index.html # Redis 连接是否正常 redis-cli ping如果 curl 后端返回 200 但浏览器访问 404问题在 Nginx 的root路径写错如果后端日志有请求记录但前端一直转圈检查前端请求的VUE_APP_BASE_API前缀是否和后端上下文一致。另外前端更新后浏览器还在用旧资源先强刷一次再在 Nginx 给index.html加Cache-Control: no-cache给带 hash 的static目录开长缓存这是静态资源部署的基本收敛动作。本文还有配套的精品资源点击获取