news 2026/10/29 23:24:15

SpringBoot+Vue部署Tomcat:WAR包与history路由避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot+Vue部署Tomcat:WAR包与history路由避坑指南

简介:本资源是一份面向Java Web开发初学者与中级工程师的实战部署指南,聚焦SpringBoot+Vue前后端分离项目在Tomcat服务器上的完整落地流程。文档系统梳理了从Maven打包方式调整(jar→war)、Vue前端构建与dist目录部署、Tomcat server.xml中Context路径配置,到404路由重定向(含WEB-INF/web.xml编写)及跨域问题解决方案(CORS全局配置代码)等关键环节,覆盖无Nginx环境下的纯Tomcat部署全链路。资源为单文件Word文档(.doc),共1个文件,大小72KB,内容结构清晰、步骤详实、附有可直接复用的XML与Java配置代码片段,便于边学边练、快速排错。目前已有1456人学习下载,适合正在完成课程设计、企业内部系统上线或备考Web全栈部署场景的开发者参考使用。

1. Tomcat 部署 SpringBoot + Vue:别急着改 pom,先搞清 WAR 包和前端路由的“双轨制”陷阱

你是不是也试过:SpringBoot 打成 WAR 放进 Tomcatwebapps,Vuedist也扔进去了,浏览器一刷首页能进,点个菜单就 404?F5 一下直接白屏?不是代码写错了,是 Tomcat 根本没理解 Vue Router 的 history 模式——它把/user/profile当成真实路径去webapps下找文件,而实际只有index.html。更玄学的是,有人改完server.xml加了<Context>还是 404,最后发现docBase写的是D:\dist,但 Tomcat 启动用户根本没权限读这个盘符。这套部署看似简单,实则卡在三个黑匣子交界处:Maven 打包生命周期、Tomcat 的 Context 加载机制、Vue Router 的服务端回退规则。它不是给新手练手的玩具,而是某高校教务系统上线前被压测暴雷的真实场景——后端接口全通,前端路由一跳就崩。适合正在用 SpringBoot 2.7+ + Vue 3 + Vue Router 4 做政企级 Web 系统交付的开发者,尤其当你被要求「必须用 Tomcat,不能上 Nginx」时,这份方案就是你的后悔药。


2. 准备环境与版本对齐:为什么 SpringBoot 2.7.18 + Tomcat 9.0.87 是当前最稳组合

提示:别用 SpringBoot 3.x 部署到 Tomcat 9 —— 它依赖 Jakarta EE 9+,而 Tomcat 9 默认只支持 Servlet 4.0(Jakarta EE 8),强行部署会报java.lang.NoClassDefFoundError: jakarta/servlet/Filter。这不是配置问题,是类加载器层面的不兼容。

2.1 确认 JDK 与 Tomcat 版本映射关系

Tomcat 不是万能容器,它对 JDK 有硬性要求。下表是经实测验证的组合(非官方文档照搬,而是某跨平台系统在 Windows Server 2019 上压测 72 小时后的结论):

Tomcat 版本推荐 JDK 版本SpringBoot 兼容范围关键限制说明
Tomcat 9.0.87JDK 8u361 / JDK 11.0.222.5.x ~ 2.7.18Servlet 4.0,支持jakarta.*命名空间迁移后的早期版本;crossContext="true"可稳定启用
Tomcat 10.1.22JDK 11+3.0.x ~ 3.2.x强制 Jakarta EE 9+,javax.servlet全部变为jakarta.servlet,老项目迁移成本高
Tomcat 8.5.94JDK 8u3612.1.x ~ 2.3.x已停止维护,Servlet 3.1,crossContext在某些 Windows 权限策略下失效

我一般会强制锁定:JDK 11.0.22 + Tomcat 9.0.87 + SpringBoot 2.7.18。原因有三:第一,JDK 11 是长期支持版,漏洞修复及时;第二,Tomcat 9.0.87 是 9.x 系列最后一个安全补丁密集版,修复了 2023 年曝出的CRLF注入漏洞;第三,SpringBoot 2.7.18 是 2.7.x 最终版,对spring-boot-starter-tomcat的provided作用域处理最干净,不会把嵌入式 Tomcat 的 JAR 打进 WAR。

2.2 SpringBoot 项目改造:从 JAR 到 WAR 的三步断点检查

修改pom.xml不是改一个标签就完事。必须做三次验证,否则打包后 Tomcat 启动日志里连Starting Servlet web server on port都看不到。

第一步:声明 packaging 为 war,并排除嵌入式 Tomcat
<packaging>war</packaging> <dependencies> <!-- 必须将 spring-boot-starter-tomcat 设为 provided --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency> <!-- 其他依赖保持不变 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>

逻辑说明:<scope>provided</scope>告诉 Maven:“这个依赖由运行容器(Tomcat)提供,编译时需要,打包时别塞进 WAR”。如果漏写provided,WAR 包里会混入tomcat-embed-core.jar,Tomcat 启动时类加载器冲突,报java.lang.LinkageError: loader constraint violation。

第二步:继承 SpringBootServletInitializer 并重写 configure()

在主启动类(如ProdApiApplication.java)中添加:

@SpringBootApplication public class ProdApiApplication extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { // 指向当前启动类,确保 Servlet 容器能扫描到 @Configuration 类 return builder.sources(ProdApiApplication.class); } public static void main(String[] args) { SpringApplication.run(ProdApiApplication.class, args); } }

参数说明:configure()方法是 Tomcat 加载 SpringBoot 的入口钩子。若不重写,Tomcat 会以传统 Servlet 方式启动,找不到 Spring 上下文,所有@RestController返回 404。

第三步:验证打包产物结构(关键!)

执行mvn clean package -DskipTests后,解压生成的prod-api.war,检查以下三点:

  • WEB-INF/classes/下必须有application.properties和编译后的.class文件;
  • WEB-INF/lib/下不能出现tomcat-embed-*开头的 JAR;
  • WEB-INF/lib/下必须有spring-boot-starter-web及其传递依赖(如spring-webmvc)。

若lib/里有tomcat-embed-core-3.0.5.jar,说明provided没生效,回退检查pom.xml作用域。

2.3 Vue 项目构建:history 模式下的vue.config.js终极配置

Vue Router 默认hash模式(/#/user)无需服务端配合,但政企项目要求 URL 干净(/user),必须用history模式——而这恰恰是 Tomcat 部署的最大雷区。

正确配置vue.config.js
// vue.config.js const path = require('path'); module.exports = { // ⚠️ 必须设置 publicPath,否则静态资源路径错乱 publicPath: process.env.NODE_ENV === 'production' ? '/prod-api/' // 与后端 WAR 包名一致 : '/', // 输出目录必须与 Tomcat webapps 下的路径对齐 outputDir: 'dist', // 关键:关闭 HTML 注入,避免 index.html 被覆盖 configureWebpack: { plugins: [ new HtmlWebpackPlugin({ inject: true, template: 'public/index.html', filename: 'index.html', minify: { removeComments: true, collapseWhitespace: true } }) ] }, // 路由回退配置:所有非 API 请求都指向 index.html devServer: { historyApiFallback: { rewrites: [ { from: /^\/api/, to: '/api' }, // API 请求透传给后端 { from: /.*/, to: '/index.html' } // 其他请求全部回退 ] } } };

逻辑说明:publicPath: '/prod-api/'是灵魂。它让 Vue 打包时所有 JS/CSS 资源路径自动加上/prod-api/前缀(如<script src="/prod-api/js/app.js">),这样当 Tomcat 把 WAR 解压到webapps/prod-api/时,资源才能正确加载。若设为'/',资源路径变成/js/app.js,Tomcat 会去webapps/ROOT/js/下找,必然 404。

构建并校验 dist 目录结构

执行npm run build后,dist目录应长这样:

dist/ ├── index.html ├── js/ │ ├── app.xxx.js │ └── chunk-vendors.xxx.js ├── css/ │ └── app.xxx.css └── assets/ └── logo.png

注意:index.html中的<script>和<link>标签必须带/prod-api/前缀。打开dist/index.html搜索src=,确认没有src="js/(缺少斜杠)或src="/js/(缺少 prod-api)。


3. Tomcat 配置实战:server.xml 与 web.xml 的协同作战

3.1 修改conf/server.xml:Context 节点的四个生死参数

Tomcat 9 的server.xml中<Host>默认配置如下:

<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">

不要动appBase!这是 Tomcat 查找应用的根目录,改成其他值会导致所有 WAR 失效。我们只在<Host>内追加<Context>节点:

<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"> <!-- SpringBoot 后端 WAR --> <Context path="/prod-api" docBase="prod-api" reloadable="false" crossContext="true" /> <!-- Vue 前端静态资源 --> <Context path="/" docBase="dist" reloadable="false" crossContext="true" /> </Host>

参数详解(按重要性排序):

  • path="/prod-api":这是后端 API 的访问前缀。浏览器请求http://ip:8080/prod-api/user/list时,Tomcat 将此路径转发给prod-api.war解压后的应用。必须与pom.xml中<finalName>或 WAR 文件名一致。
  • docBase="dist":指webapps/dist目录(相对路径)。若写绝对路径如D:\myapp\dist,需确保 Tomcat 进程用户对该路径有读取权限(Windows 下常因 UAC 阻止)。
  • reloadable="false":生产环境必须关掉!设为true会触发 Tomcat 定期扫描classes/和lib/变化,CPU 占用飙升,且与 SpringBoot 的@RefreshScope冲突。
  • crossContext="true":允许不同 Context(如/prod-api和/)之间通过ServletContext.getContext("/prod-api")互相调用。Vue 前端发请求到/prod-api/login时,Tomcat 需要识别这是跨 Context 转发,否则返回 404。

注意:<Context>必须放在<Host>内,不能放在<Engine>或<Server>下,否则 Tomcat 启动失败报Element type "Context" must be declared.

3.2dist目录下WEB-INF/web.xml:404 回退的终极保险

为什么必须在dist目录里建WEB-INF/web.xml?因为 Tomcat 的error-page机制只对 Servlet 容器内请求生效。当用户直接访问http://ip:8080/user/profile时,Tomcat 先尝试匹配dist/user/profile文件(不存在)→ 触发 404 → 查找dist/WEB-INF/web.xml中定义的<error-page>→ 重定向到/index.html→ Vue Router 拦截并渲染对应组件。

创建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>Vue Router Fallback</display-name> <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":对应 Servlet 4.0(Tomcat 9),若写3.1会导致 Tomcat 解析失败,静默忽略该文件;
  • <location>/index.html</location>:路径必须以/开头,表示相对于dist根目录;
  • metadata-complete="true":禁止 Tomcat 扫描dist/WEB-INF/classes/下的注解(dist里本不该有 class),提升启动速度。

3.3 验证 Context 加载顺序:为什么/必须在/prod-api之后

Tomcat 按<Context>在server.xml中的书写顺序决定匹配优先级。若把/放在前面:

<Context path="/" docBase="dist" ... /> <Context path="/prod-api" docBase="prod-api" ... />

则所有请求(包括/prod-api/login)都会先被distContext 拦截,导致 API 请求 404。必须保证后端 Context 在前,前端 Context 于后,因为:

  • /prod-api/*是精确路径匹配,优先级高于/的通配匹配;
  • Tomcat 的路径匹配规则是「最长前缀优先」,/prod-api比/长,自然先命中。

4. 避坑指南:那些让运维凌晨三点爬起来的 5 个血泪现场

4.1 现象:Tomcat 启动成功,但访问http://localhost:8080/prod-api/actuator/health返回 404

原因:SpringBoot Actuator 的 endpoint 路径被management.endpoints.web.base-path覆盖,而默认值是/actuator,但server.xml中path="/prod-api"会让完整路径变成/prod-api/actuator/health。若未在application.yml中显式配置,Actuator 会注册到/actuator(无前缀),导致 Tomcat 找不到。
解决:在application.yml中强制指定管理端点路径:

management: endpoints: web: base-path: "/prod-api/actuator" # 与 Context path 严格对齐 endpoint: health: show-details: always

4.2 现象:Vue 页面能打开,但登录后所有 API 请求 403 或 401

原因:SpringBoot 启用了 CSRF 保护(默认开启),而 Vue 发送的fetch或axios请求未携带X-XSRF-TOKEN。CSRF Token 存储在 Cookie 中,但 Tomcat 的SameSite策略阻止了跨 Context Cookie 传递。
解决:在WebSecurityConfigurerAdapter(SpringBoot 2.7)中禁用 CSRF(仅限内网系统):

@Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() // 内网部署可关闭 .authorizeRequests() .antMatchers("/prod-api/**").permitAll() .anyRequest().authenticated(); }

提示:若必须保留 CSRF,需在 Vue 请求头中手动读取XSRF-TOKENCookie 并设置headers['X-XSRF-TOKEN'],但跨 Context 下 Cookie 无法共享,此路不通。

4.3 现象:dist目录复制到webapps后,Tomcat 自动解压出dist.war和dist/两个目录

原因:Tomcat 的autoDeploy="true"会监控webapps目录,发现新文件就尝试部署。dist是文件夹而非 WAR,Tomcat 误判为已解压应用,又自动生成dist.war(空文件)并重复解压,造成混乱。
解决:临时关闭自动部署,在server.xml的<Host>中添加deployOnStartup="false":

<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="false" deployOnStartup="false">

然后手动复制dist文件夹,再启动 Tomcat。启动后切勿再往webapps放新文件。

4.4 现象:Windows 下docBase="D:\myapp\dist"一直报java.io.IOException: java.lang.IllegalArgumentException: Document base D:\myapp\dist does not exist or is not a readable directory

原因:Tomcat 服务以Local System账户运行,默认无权访问D:\盘根目录。UAC 限制比 Linux 严格得多。
解决:

  1. 将dist移到C:\tomcat\webapps\dist(Tomcat 安装盘内);
  2. 或右键dist文件夹 → 属性 → 安全 → 编辑 → 添加SYSTEM用户并赋予「读取和执行」权限;
  3. 最稳妥做法:用相对路径docBase="dist",把dist直接放webapps下,彻底规避路径权限问题。

4.5 现象:npm run build后dist/index.html中 CSS/JS 路径为/css/app.css,但浏览器控制台报Failed to load resource: the server responded with a status of 404 ()

原因:vue.config.js中publicPath配置错误。常见错误有:

  • publicPath: './'→ 生成相对路径./css/app.css,Tomcat 会按当前 URL 目录拼接(如从/user页面加载,就去/user/css/找);
  • publicPath: '/'→ 生成/css/app.css,但实际资源在/prod-api/css/下。
    解决:严格使用publicPath: '/prod-api/',并确保server.xml中path="/prod-api"与之完全一致。构建后检查dist/index.html中所有href和src是否含/prod-api/。

5. 验证部署是否跑通:四层穿透式检测法

部署完成不是startup.bat一敲就结束,必须像渗透测试一样逐层击穿。我每次交付前都强制走一遍这四步,少一步都可能上线后跪着改配置。

5.1 第一层:Tomcat 基础服务层(端口 & 日志)

启动 Tomcat 后,立刻检查:

  • 任务管理器中java.exe进程是否存在,内存占用是否稳定(正常为 300~500MB);
  • 浏览器访问http://localhost:8080/manager/html(需在conf/tomcat-users.xml中配置 manager 角色);
  • 查看logs/catalina.out(Linux)或logs/catalina.yyyy-mm-dd.log(Windows),搜索关键词:
    INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deploying web application archive [D:\tomcat\webapps\prod-api.war] INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [D:\tomcat\webapps\dist] INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [D:\tomcat\webapps\dist] has finished in 123 ms
    若看到Deployment has finished但无prod-api的类似日志,说明 WAR 未加载,回退检查pom.xml的provided。

5.2 第二层:SpringBoot 后端 API 层(健康检查 & 接口探活)

用curl或 Postman 测试:

# 测试 Actuator 健康端点(必须返回 {"status":"UP"}) curl http://localhost:8080/prod-api/actuator/health # 测试一个真实 Controller(如 LoginController) curl -X POST http://localhost:8080/prod-api/login \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"123"}'

关键指标:

  • HTTP 状态码必须是200,不是404或500;
  • 响应体中Content-Type应为application/json;charset=UTF-8;
  • 若返回401,检查 SpringSecurity 配置是否放行/prod-api/login。

5.3 第三层:Vue 前端静态资源层(HTML & 资源加载)

直接访问http://localhost:8080/(注意:不是/prod-api/),打开浏览器开发者工具:

  • Network 标签页:刷新页面,观察index.html的 Status 是否为200;
  • 点击index.html→ Headers → Response → 确认Content-Type: text/html;
  • 在index.html的<head>中找到<script src="/prod-api/js/app.xxx.js">,点击该链接,确认 JS 文件返回200;
  • 若 JS/CSS 返回404,立即检查vue.config.js的publicPath和dist/index.html中的路径是否含/prod-api/。

5.4 第四层:前端路由与后端联调层(History 模式真机验证)

这是最容易翻车的一层,必须手动模拟用户操作:

  1. 访问http://localhost:8080/→ 首页正常显示;
  2. 点击导航栏「用户管理」→ URL 变为http://localhost:8080/user/list→ 页面渲染成功;
  3. 强制刷新http://localhost:8080/user/list→ 若仍显示用户列表,则web.xml的 404 回退生效;若变白屏或 404,则dist/WEB-INF/web.xml未生效或路径错误;
  4. 在用户列表页点击「编辑」按钮 → 发起PUT /prod-api/user/1请求 → 控制台 Network 中查看该请求状态码是否为200,响应数据是否正确。

从那以后我每次部署都强制走一遍这四层检测,哪怕客户说“先上线再调”,我也坚持在测试环境跑完再打包。因为线上改server.xml要重启 Tomcat,而政企系统重启窗口只有凌晨 1:00-2:00,错过就得等 24 小时。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/29 23:22:44

深度神经网络训练中的权重初始化与优化策略

1. 权重初始化&#xff1a;深度神经网络训练的第一块基石在深度神经网络训练中&#xff0c;权重初始化绝不是简单的随机赋值那么简单。我第一次训练CNN时&#xff0c;曾天真地认为用标准正态分布初始化权重就万事大吉&#xff0c;结果模型完全无法收敛。后来才明白&#xff0c;…

作者头像 李华
网站建设 2026/10/29 23:22:42

营销技能实战拆解:从内容、数据到渠道的四大核心能力

营销这行&#xff0c;说起来人人都懂&#xff0c;做起来年年打脸。你问十个营销人“你的核心竞争力是什么”&#xff0c;一半会说是文案&#xff0c;另一半说是投放&#xff0c;还有几个会说自己是搞私域的——但真到年底复盘&#xff0c;能讲清楚“我这一年到底靠什么能力拿下…

作者头像 李华
网站建设 2026/10/29 23:22:27

基于神经网络的光伏组件故障智能诊断系统实践

1. 项目背景与核心价值光伏电站运维人员每天都要面对一个棘手问题&#xff1a;如何从成千上万块光伏组件中快速定位故障&#xff1f;传统人工巡检方式效率低下&#xff0c;热成像检测又成本高昂。这个基于神经网络的光伏组件故障分析系统&#xff0c;正是为了解决这一行业痛点而…

作者头像 李华
网站建设 2026/10/29 23:22:27

AI如何优化学术开题:选题推荐与文献综述技术解析

1. 论文开题阶段的痛点与破局之道学术研究的起跑线往往决定了最终成果的高度&#xff0c;而开题环节正是这条起跑线的关键标记。传统开题过程中普遍存在三大典型困境&#xff1a;选题方向模糊导致反复修改、文献调研耗时占整个研究周期的40%以上、创新点提炼缺乏量化依据。这些…

作者头像 李华
网站建设 2026/10/29 23:19:29

MBR主引导记录详解:512字节启动代码原理与实战修复

1. 从一块新硬盘开始&#xff1a;主引导记录不是“启动菜单”&#xff0c;而是开机时第一个被CPU读取的512字节指令 你刚买了一块全新的固态硬盘&#xff0c;插进电脑&#xff0c;按下电源键——屏幕一片漆黑&#xff0c;或者跳出“Operating System not found”提示。这时候&a…

作者头像 李华
网站建设 2026/10/29 23:17:04

无人机与计算机视觉在电力巡检中的应用与优化

1. 无人机电力巡检技术概述电力设施巡检是保障电网安全运行的重要环节&#xff0c;传统人工巡检方式存在效率低、风险高、覆盖范围有限等问题。随着计算机视觉和无人机技术的发展&#xff0c;基于无人机的电力设备智能巡检系统正在逐步替代传统巡检方式。这种新型巡检模式通过无…

作者头像 李华