简介:本资源是一份面向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.87 | JDK 8u361 / JDK 11.0.22 | 2.5.x ~ 2.7.18 | Servlet 4.0,支持jakarta.*命名空间迁移后的早期版本;crossContext="true"可稳定启用 |
| Tomcat 10.1.22 | JDK 11+ | 3.0.x ~ 3.2.x | 强制 Jakarta EE 9+,javax.servlet全部变为jakarta.servlet,老项目迁移成本高 |
| Tomcat 8.5.94 | JDK 8u361 | 2.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: always4.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 严格得多。
解决:
- 将
dist移到C:\tomcat\webapps\dist(Tomcat 安装盘内); - 或右键
dist文件夹 → 属性 → 安全 → 编辑 → 添加SYSTEM用户并赋予「读取和执行」权限; - 最稳妥做法:用相对路径
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 msDeployment 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 模式真机验证)
这是最容易翻车的一层,必须手动模拟用户操作:
- 访问
http://localhost:8080/→ 首页正常显示; - 点击导航栏「用户管理」→ URL 变为
http://localhost:8080/user/list→ 页面渲染成功; - 强制刷新
http://localhost:8080/user/list→ 若仍显示用户列表,则web.xml的 404 回退生效;若变白屏或 404,则dist/WEB-INF/web.xml未生效或路径错误; - 在用户列表页点击「编辑」按钮 → 发起
PUT /prod-api/user/1请求 → 控制台 Network 中查看该请求状态码是否为200,响应数据是否正确。
从那以后我每次部署都强制走一遍这四层检测,哪怕客户说“先上线再调”,我也坚持在测试环境跑完再打包。因为线上改
server.xml要重启 Tomcat,而政企系统重启窗口只有凌晨 1:00-2:00,错过就得等 24 小时。希望帮到你。
本文还有配套的精品资源,点击获取