简介本资源是一份面向Java Web开发初学者与中级工程师的实战部署指南聚焦SpringBootVue前后端分离项目在Tomcat服务器上的完整落地流程。文档系统梳理了从后端WAR包改造、前端dist目录构建到Tomcat配置修改含Context路径设置、404路由重定向、跨域问题解决CORS配置示例等关键环节覆盖Windows环境下无Nginx的纯Tomcat部署场景特别适用于政务门户、企业官网等需自主可控部署的生产实践。资源为单文件Word文档.doc共1个文件大小72KB内容结构清晰、步骤详实含XML配置代码、Maven修改要点及web.xml错误页处理方案便于快速查阅与复用。目前已有1456人学习下载适合需要掌握前后端一体化部署、规避常见空白页与404问题、理解Tomcat上下文机制的开发者参考使用。1. Tomcat 部署 SpringBoot Vue不是“能跑就行”而是“刷新不404、路由不白屏、跨域不抓瞎”的完整闭环你有没有遇到过这种场景Vue 本地npm run serve一切丝滑SpringBoot 启动也秒开/api/login但一扔进 Tomcat——首页能进点个菜单就空白F5 刷新直接 404再试下百度地图 API控制台红字报CORS policy: No Access-Control-Allow-Origin header。这不是 Vue 或 SpringBoot 的锅是部署链路上三个被默认忽略的断点前端路由 fallback 缺失、上下文路径错配、后端跨域策略未生效于 WAR 容器环境。这份文档不是教你怎么把 war 包拖进webapps而是还原某高校实验室在政务系统迁移中踩过的 17 次重启、5 次重打包、3 次抓包验证后沉淀出的 Tomcat 部署 SpringBoot Vue 的最小可行闭环。它适用于所有需要纯 Tomcat无 Nginx/反向代理承载前后端分离项目的场景尤其适合对服务器权限受限、无法装额外中间件的政企内网环境。核心价值就三点第一让 Vue Router 的history模式在 Tomcat 下真正可用第二让 SpringBoot 的CrossOrigin或全局配置在 WAR 包里不被容器吞掉第三把pom.xml→war→dist→server.xml→web.xml这条链上每个环节的参数含义、修改依据、验证手段全摊开——不是“照着做”而是“知道为什么必须这么改”。2. 从 JAR 到 WARSpringBoot 打包方式切换的底层逻辑与三处关键改造SpringBoot 默认打包为可执行 JAR内嵌 Tomcat启动即服务。但当你明确要部署到外部 Tomcat 时JAR 包会直接被忽略Tomcat 只认 WAR 和目录结构。这步不是简单改packaging而是重构整个生命周期外部容器接管 Servlet 初始化、上下文加载、资源扫描。若只改 packaging 不动代码应用启动时会因找不到ServletWebServerFactory而报NoSuchBeanDefinitionException。2.1 修改 pom.xmlpackaging 依赖 scope 主类继承packagingwar/packaging这行只是表象。真正起作用的是以下三处联动spring-boot-starter-tomcat依赖 scope 必须设为provided原因外部 Tomcat 已提供 Servlet 容器实现若保留compile会引发类冲突如javax.servlet.*多版本共存导致java.lang.NoClassDefFoundError: javax/servlet/ServletContext。dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-tomcat/artifactId scopeprovided/scope /dependency主启动类必须继承SpringBootServletInitializer并重写configure()原因这是 SpringBoot 适配外部 Servlet 容器的钩子。SpringBootServletInitializer会在 Tomcat 启动时调用onStartup()将 Spring 上下文注册为 ServletContextListener否则你的RestController根本不会被扫描。SpringBootApplication public class ProdApiApplication extends SpringBootServletInitializer { Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(ProdApiApplication.class); } public static void main(String[] args) { SpringApplication.run(ProdApiApplication.class, args); } }移除spring-boot-maven-plugin的repackagegoal可选但推荐原因WAR 包不需要内嵌 Tomcat 启动脚本。保留该插件会导致生成BOOT-INF目录而标准 WAR 规范要求类在WEB-INF/classesTomcat 可能因扫描路径异常跳过 Spring 组件。!-- 删除或注释掉整个 plugin 块 -- !-- plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin --提示改完后务必执行mvn clean package -DskipTests。检查生成的prod-api.war解压后结构WEB-INF/classes/下应有你的ProdApiApplication.class和application.propertiesWEB-INF/lib/下不应出现tomcat-embed-*相关 jar。2.2 构建 WAR 包Maven profile 与生产环境隔离实践实际项目中开发环境用 JAR快速启动调试生产才切 WAR。建议用 Maven profile 实现一键切换profiles profile iddev/id properties packaging.typejar/packaging.type /properties activation activeByDefaulttrue/activeByDefault /activation /profile profile idprod-war/id properties packaging.typewar/packaging.type /properties build plugins plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-war-plugin/artifactId version3.3.2/version configuration failOnMissingWebXmlfalse/failOnMissingWebXml /configuration /plugin /plugins /build /profile /profiles构建命令变为# 开发环境默认 mvn clean package # 生产 WAR 包 mvn clean package -Pprod-war -DskipTestsfailOnMissingWebXmlfalse/failOnMissingWebXml是关键SpringBoot 2.0 不再强制要求web.xml但 Maven WAR 插件旧版本会报错此配置显式关闭校验。2.3 验证 WAR 包是否合格三步手工解压检查法不要等 Tomcat 启动失败才排查。拿到prod-api.war后立即执行解压并检查WEB-INF/web.xml是否存在SpringBoot WAR 不需要web.xml但 Tomcat 要求 WAR 包根目录下有WEB-INF文件夹。若缺失Tomcat 日志会报Failed to process JAR [file:/...]。此时需手动创建空WEB-INF目录哪怕里面没文件。检查WEB-INF/classes/application.properties中的server.port外部 Tomcat 管理端口SpringBoot 的server.port应设为-1或直接删除。否则启动时会尝试绑定 8080与 Tomcat 冲突日志出现Address already in use: bind。检查WEB-INF/lib/下是否有spring-boot-starter-tomcat若存在说明scopeprovided未生效。此时需检查 Maven 依赖树mvn dependency:tree | grep tomcat确认其 scope 显示为provided。3. Vue 前端部署dist 目录的路径陷阱与 history 模式 fallback 的两种实现Vue CLI 默认使用hash模式URL 带#部署到任何服务器都零配置。但政企项目普遍要求history模式URL 干净如/user/list这就强依赖服务器对 404 的 fallback 处理。Tomcat 本身不支持 SPA 的 history fallback必须通过web.xml或server.xml两种方式补足。很多人卡在“首页能进二级路由白屏”本质是 Tomcat 把/user/list当作真实文件去dist目录下找找不到就返回 404根本没机会交给 Vue Router。3.1 Vue 项目构建前的关键配置publicPath 与 outputDirvue.config.js中这两项决定 dist 目录如何被 Tomcat 定位module.exports { // 关键必须设为 /表示静态资源相对于 webapps 根路径 publicPath: /, // 输出目录名建议与后端 WAR 名一致避免混淆 outputDir: dist, // 若后端 API 在 /prod-api/ 下此处需配代理仅开发用 devServer: { proxy: { /api: { target: http://localhost:8080/prod-api, changeOrigin: true, } } } }publicPath: /的含义是index.html中引入的js/app.xxx.js路径为/js/app.xxx.js而非/dist/js/app.xxx.js。这样当dist目录被映射到 Tomcat 根路径时资源才能正确加载。若设为./或dist/则资源路径会多一层导致 404。构建命令npm run build # 生成 dist/ 目录结构应为 # dist/ # ├── index.html # ├── js/ # │ └── app.xxx.js # ├── css/ # │ └── app.xxx.css # └── favicon.ico3.2 方案一web.xml fallback推荐轻量、可控、无需改 server.xml这是最稳妥的方式原理是利用 Servlet 规范的error-page机制当 Tomcat 找不到/user/list对应的物理文件时触发 404 错误页将其重定向到/index.html由 Vue Router 解析 URL。操作步骤在dist目录下新建WEB-INF文件夹注意大小写必须全大写在WEB-INF内创建web.xml内容严格按文档提供XML 声明、schemaLocation、metadata-completetrue缺一不可?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-nameVue Router Fallback/display-name error-page error-code404/error-code location/index.html/location /error-page /web-app为什么必须加metadata-completetrueTomcat 8.5 默认扫描WEB-INF/classes下的WebServlet注解。若不加此属性Tomcat 会尝试解析dist目录下的 class实际没有导致启动变慢甚至失败。metadata-completetrue显式告知容器别扫描注解web.xml就是全部配置。3.3 方案二server.xml Context 配置灵活但有风险文档中提到的Context path/ docBase...是另一种方案但存在严重隐患docBase若写绝对路径如C:\tomcat\webapps\dist则 Tomcat 启动时会将该路径作为独立 WebApp 加载与prod-api.war形成两个上下文导致跨域问题重现/和/prod-api被视为不同源。正确做法是让 Vue 和 SpringBoot 共享同一上下文。安全配置推荐将dist目录内容直接复制到prod-api.war解压后的static目录下而非单独放webapps。这样 Vue 静态资源和 SpringBoot 接口同属/prod-api上下文天然同源。操作流程解压prod-api.war得到临时目录将dist/全部内容复制到temp/WEB-INF/classes/static/注意是classes/static不是webapps/static重新打包为prod-api.war启动 Tomcat访问http://ip:port/prod-api/此时index.html中的 API 请求地址应为/prod-api/api/login需在 Vue 中配置// vue.config.js devServer.proxy 仅用于开发生产环境需在 axios baseURL 中硬编码 axios.defaults.baseURL /prod-api;注意此方案下server.xml无需任何修改。Context配置是给独立 WebApp 用的混合部署时反而破坏同源性。4. 跨域问题排查为什么 CrossOrigin 失效Tomcat 的 CORS 拦截器真相文档末尾提到“解决调用第三方 API 如百度地图 API 的跨域问题”但没说清根源。这里存在一个经典误解CrossOrigin是 Spring MVC 的注解它只对当前应用接收的请求生效即浏览器发给http://ip:port/prod-api的请求而不负责转发出去的请求即prod-api后端调用https://api.map.baidu.com。真正的跨域发生在浏览器 →prod-api这一跳只要prod-api返回的响应头带Access-Control-Allow-Origin: *浏览器就允许前端 JS 读取响应。所以问题从来不在百度地图而在你的 SpringBoot 接口是否正确返回了 CORS 头。4.1 CrossOrigin 失效的三大原因与修复现象 1CrossOrigin注解写了但浏览器 Network 面板看不到Access-Control-Allow-Origin头原因SpringBoot 的CrossOrigin依赖CorsConfiguration而 WAR 包部署时若spring-webmvc版本与 Tomcat Servlet 版本不兼容如 Tomcat 9.0.83 SpringBoot 2.7.xHandlerMapping初始化失败CORS Filter 未注册。解决强制启用全局 CORS 配置在Configuration类中Configuration EnableWebMvc public class WebConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOrigins(*) // 生产环境请替换为具体域名 .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .maxAge(3600) .allowCredentials(true); // 若需携带 cookie } }现象 2接口返回 200但浏览器控制台仍报 CORS 错误原因前端发起的是POST请求但未设置Content-Type: application/json触发浏览器预检preflight请求OPTIONS方法。而你的 Controller 没有处理OPTIONSTomcat 直接返回 405 Method Not AllowedCORS 头未返回。解决在addCorsMappings中显式允许OPTIONS方法上面代码已包含或添加CrossOrigin到PostMapping方法上。现象 3CrossOrigin(origins http://localhost:8080)本地调试正常但部署后失效原因生产环境前端域名是http://your-domain.com但注解写死为localhost。CrossOrigin不支持正则或通配符。解决用全局配置替代单点注解并在allowedOrigins中传入运行时域名// 从 application.properties 读取 Value(${cors.allowed-origins:http://localhost:8080}) private String allowedOrigins; Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOrigins(allowedOrigins.split(,)) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS); }application.properties中cors.allowed-originshttps://your-domain.com,https://www.your-domain.com4.2 百度地图 API 调用的正确姿势后端代理而非前端直连文档提到“调用第三方 API 如百度地图”但没点破关键永远不要让前端 JS 直连百度地图 API。原因有三百度地图 API Key 必须绑定 Referer 白名单前端 JS 暴露 Key 即泄露浏览器同源策略限制若前端直连百度返回的响应头无Access-Control-Allow-Origin必然跨域失败政企内网常禁外网前端无法直连api.map.baidu.com。正确方案后端代理在 SpringBoot 中新增一个 Controller将前端请求转发给百度RestController RequestMapping(/api/map) public class MapProxyController { private final RestTemplate restTemplate new RestTemplate(); GetMapping(/geocoding/v3) public ResponseEntityString geocode(RequestParam String address) { String url https://api.map.baidu.com/geocoding/v3?address address outputjsonakYOUR_AK; HttpHeaders headers new HttpHeaders(); headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); HttpEntityString entity new HttpEntity(headers); return restTemplate.exchange(url, HttpMethod.GET, entity, String.class); } }前端调用axios.get(/prod-api/api/map/geocoding/v3?address北京)这样跨域问题彻底消失——浏览器只与prod-api通信prod-api与百度通信是服务端行为不受 CORS 限制。5. 避坑指南Tomcat 部署 SpringBoot Vue 的五个血泪现场部署不是线性流程而是不断重启、看日志、删缓存、重打包的循环。以下是某开发者在模拟项目 X 中记录的真实翻车现场每一条都对应一个catalina.out里的错误堆栈。5.1 现象Tomcat 启动成功但访问http://ip:port/返回 404http://ip:port/prod-api也 404原因prod-api.war文件名错误。Tomcat 要求 WAR 包名即为上下文路径。若你命名为prod-api-1.0.0.war则应用路径是/prod-api-1.0.0而非/prod-api。解决将 WAR 包重命名为prod-api.war去掉版本号和-或在server.xml的Context中显式指定path/prod-api。5.2 现象首页能打开点击菜单跳转后页面空白Network 面板显示index.html返回 200但js/app.xxx.js返回 404原因vue.config.js中publicPath设为./或dist/导致index.html中 script 标签的src是./js/app.xxx.js而 Tomcat 将dist映射到根路径实际应为/js/app.xxx.js。解决确认publicPath: /并检查dist/index.html源码确保所有src和href属性以/开头。5.3 现象dist目录下有WEB-INF/web.xml但刷新二级路由仍 404原因web.xml文件编码不是 UTF-8 无 BOM。Windows 记事本保存的 XML 常带 BOMTomcat 解析失败静默忽略整个文件。解决用 VS Code 或 Notepad 以 UTF-8 无 BOM 格式重存web.xml并在文件开头确认无隐藏字符可 hex 查看前 3 字节是否为EF BB BF。5.4 现象SpringBoot 接口返回数据但浏览器控制台报Failed to load resource: the server responded with a status of 401 (Unauthorized)原因EnableWebSecurity配置中未放行/**静态资源。Spring Security 默认拦截所有请求dist下的index.html、js/文件也被拦截返回 401。解决在SecurityConfig中放行静态资源Configuration EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { Override public void configure(WebSecurity web) throws Exception { web.ignoring().requestMatchers( /js/**, /css/**, /img/**, /favicon.ico, /**/*.html ); } }5.5 现象prod-api.war启动成功但dist目录下index.html中的 API 请求 404原因Vue 前端代码中写死了axios.defaults.baseURL http://localhost:8080部署后未改为相对路径/prod-api。解决前端构建时注入环境变量。在vue.config.js中const BASE_API process.env.NODE_ENV production ? /prod-api : http://localhost:8080/prod-api; module.exports { // ... chainWebpack: config { config.plugin(define).tap(args { args[0][process.env].VUE_APP_BASE_API JSON.stringify(BASE_API); return args; }); } }组件中使用axios.get(process.env.VUE_APP_BASE_API /login)。6. 验证与调试从日志定位、curl 模拟到 Chrome DevTools 的三层穿透法部署完成不等于稳定运行。我习惯用三层穿透法验证先看 Tomcat 日志确认容器级加载再用 curl 模拟请求验证网络层通路最后用 Chrome DevTools 审查资源加载与 JS 执行。这套组合拳能快速定位问题在哪个环节。6.1 第一层catalina.out 日志的黄金三行Tomcat 启动后第一时间tail -f logs/catalina.out关注三类关键日志日志关键词含义正常表现异常表现Deploying web application archiveWAR 包开始部署Deploying web application archive [/path/to/tomcat/webapps/prod-api.war]若无此行说明 WAR 包名错误或权限不足Starting ProtocolHandler [http-nio-8080]Tomcat HTTP 连接器启动Starting ProtocolHandler [http-nio-8080]若端口被占会报Failed to start end point associated with ProtocolHandlerStarted ProdApiApplicationSpringBoot 应用启动完成Started ProdApiApplication in 3.2 seconds若卡在此处检查SpringBootServletInitializer是否继承提示若日志中出现java.lang.ClassNotFoundException: javax.servlet.Filter立刻检查spring-boot-starter-tomcat的scope是否为provided。6.2 第二层curl 命令验证服务可达性绕过浏览器用 curl 直接测试各层# 1. 测试 Tomcat 根路径应返回 dist/index.html curl -I http://localhost:8080/ # 2. 测试 SpringBoot 接口应返回 JSON curl -I http://localhost:8080/prod-api/api/login # 3. 测试 Vue 静态资源应返回 200 Content-Type:text/html curl -I http://localhost:8080/js/app.xxx.js # 4. 测试 404 fallback故意请求不存在路径应返回 200 index.html curl -I http://localhost:8080/user/123关键看HTTP/1.1 200 OK和Content-Type。若第 4 条返回404 Not Found说明web.xmlfallback 未生效若第 2 条返回404说明prod-api.war未正确部署或RequestMapping路径错误。6.3 第三层Chrome DevTools 的 Network 与 Console 深度审查打开 Chrome按 F12切换到 Network 标签页Filter 输入doc查看document类型请求确认index.html的Status是 200Response Headers中有Content-Type: text/htmlFilter 输入js查看所有 JS 文件确认Status全为 200Initiator列显示index.html说明是 HTML 加载的非 XHRFilter 输入api查看 API 请求确认Status是 200Response Headers中有Access-Control-Allow-Origin: *Console 标签页若有Uncaught SyntaxError说明 JS 文件加载但解析失败常见于publicPath错误导致 script 路径 404浏览器加载了空文件若有TypeError: Cannot read property xxx of undefined说明 Vue 实例未正确挂载main.js中new Vue({el: #app})的#app元素在index.html中不存在。6.4 一个终极技巧用 Tomcat Manager 页面实时监控Tomcat 自带manager应用可直观看到部署状态。启用方法编辑conf/tomcat-users.xml添加用户role rolenamemanager-gui/ user usernameadmin password123456 rolesmanager-gui/重启 Tomcat访问http://localhost:8080/manager/html输入账号密码在 Applications 表格中找到prod-api行点击Start若为 stopped、Reload热重载、Server Status查看内存、线程。从那以后我每次部署新版本都强制走一遍这三层验证先盯 30 秒 catalina.out再敲 4 条 curl最后开 Chrome Network 刷三次页面。少走一次就可能在客户现场花 2 小时找那个漏掉的斜杠。希望帮到你。本文还有配套的精品资源点击获取