简介这份文档面向Java Web开发者与运维人员聚焦SpringBootVue前后端分离项目在Tomcat上的部署实践帮助解决从本地运行到服务器上线过程中的打包、路径配置与跨域等常见问题。资源包共1个doc文件大小约72KB内容以文字步骤与配置代码为主便于对照操作。文档围绕WAR包改造、Vue构建产物放置、server.xml中Context节点配置、404刷新处理以及第三方API跨域配置等环节展开给出了可直接参考的代码片段与排错思路适合需要将前后端分离项目部署到WindowsTomcat环境的读者查阅。目前已有1450人学习下载可作为部署流程的速查手册使用。1. 为什么 war 包丢进 webapps 还是白屏一次 Tomcat 部署的完整复盘把 SpringBoot 打成 war 丢进 Tomcat 的 webapps再把 Vue 的 dist 拷进去浏览器一开——登录页出来了输完账号密码白屏。这个场景我在不同项目里见过太多次每次排查方向都不一样但根子往往就一个前端路由和后端上下文没对齐。这份文档记录的就是 Windows Tomcat 环境下不借助 Nginx纯靠 Tomcat 把 SpringBoot Vue 前后端分离项目跑起来的完整流程。它适合手头只有一台 Windows 服务器、不想额外装 Nginx、又必须把项目部署上线的后端或全栈同学。文档里提到的prod-api命名、server.xml的 Context 配置、WEB-INF/web.xml的 404 兜底都是实际部署中绕不开的环节。下面按「打包 → 放置 → 配置 → 排错」的顺序拆一遍中间会重点说清楚刷新 404 和跨域这两个最容易翻车的地方。2. 从 jar 到 warSpringBoot 打包方式切换与 Tomcat 目录规划2.1 为什么必须改 packaging以及改完之后 pom 里还要动什么SpringBoot 默认用内嵌 Tomcat 跑 jarjava -jar一条命令就完事。但一旦决定用外部 Tomcat 部署第一步就是把pom.xml里的packagingjar/packaging改成packagingwar/packaging。这个改动本身不复杂但改完之后如果直接mvn package打出来的 war 包丢进 Tomcat 大概率起不来因为内嵌的 Tomcat 依赖还在和外部的 Tomcat 容器冲突。常见做法是在spring-boot-starter-web依赖里把内嵌 Tomcat 排除掉同时补上spring-boot-starter-tomcat并标记为provided。这样打包时不会把 Tomcat 的 jar 塞进 war 的WEB-INF/lib里运行时由外部 Tomcat 提供容器能力。!-- pom.xml 关键改动 -- packagingwar/packaging dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId !-- 排除内嵌 Tomcat避免和外部 Tomcat 冲突 -- exclusions exclusion groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-tomcat/artifactId /exclusion /exclusions /dependency !-- 以 provided 方式引入编译时需要打包时不放入 lib -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-tomcat/artifactId scopeprovided/scope /dependency /dependencies参数说明scopeprovided/scope表示这个依赖只在编译和测试阶段有效打包时不会进入WEB-INF/lib。如果漏了这一步war 包里会同时存在内嵌 Tomcat 的类和外部 Tomcat 的类启动时经常报ClassNotFoundException或者端口冲突。另外启动类需要继承SpringBootServletInitializer并重写configure方法否则外部 Tomcat 找不到 SpringBoot 的入口。// 启动类改造让外部 Tomcat 能识别 SpringBoot 应用 SpringBootApplication public class Application extends SpringBootServletInitializer { Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { // 指向本来的启动类Tomcat 启动时会调用这里 return builder.sources(Application.class); } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }逻辑说明SpringBootServletInitializer是 SpringBoot 专门为 war 部署提供的适配器。外部 Tomcat 启动时通过 SPI 机制找到它再调用configure方法加载应用上下文。不继承这个类Tomcat 启动日志里只会看到 war 被解压但 Spring 容器根本没起来。2.2 打包命令、产物命名与 webapps 目录的放置策略改完 pom 和启动类之后执行打包命令。Maven 和 Gradle 的写法不同这里以 Maven 为例# 在项目根目录执行跳过测试加快打包速度 mvn clean package -DskipTests # 打包完成后target 目录下会生成 war 文件 # 默认名称通常是 artifactId-version.war比如 prod-api-0.0.1-SNAPSHOT.war打包完成后把 war 文件复制到 Tomcat 的webapps目录下并重命名为prod-api.war。重命名这一步不是必须的但建议做因为 Tomcat 会用 war 文件名作为上下文路径。如果保持默认的prod-api-0.0.1-SNAPSHOT.war访问路径就变成ip:端口/prod-api-0.0.1-SNAPSHOT/...前端配置接口地址时容易写错。Vue 项目这边执行npm run build生成dist目录。dist里的内容不需要打成 war直接整个文件夹复制到webapps下即可。最终webapps目录结构大致是这样webapps/ ├── prod-api/ # SpringBoot war 解压后的目录 │ ├── WEB-INF/ │ └── ... ├── dist/ # Vue 构建产物 │ ├── index.html │ ├── static/ │ └── ... └── ...提示war 包放入 webapps 后Tomcat 启动时会自动解压。如果修改了 war 包内容需要先删掉对应的解压目录再重启否则 Tomcat 可能继续用旧目录。3. server.xml 里 Context 配置前端路由与后端接口的路径对齐3.1 不配 Context 为什么会白屏配了之后又解决了什么文档里提到一个关键现象不添加Context配置登录能成功但登录之后是空白页。这个问题的根因是 Vue 的 history 路由模式和 Tomcat 的默认上下文路径不匹配。Vue 项目本地开发时跑在localhost:8080路由是/login、/dashboard这种形式。打包后如果直接通过ip:端口/dist/index.html访问Vue Router 在 history 模式下会认为当前路径是/dist/而它期望的 base 路径是/。结果就是路由匹配不上页面渲染为空。在conf/server.xml的Host节点下添加Context把path设为/docBase指向dist的绝对路径相当于告诉 Tomcat访问根路径时直接去dist目录找静态资源。这样 Vue Router 拿到的路径就是/路由匹配正常。!-- conf/server.xml在 Host 节点内添加 -- Context path/ docBaseC:/tomcat/webapps/dist reloadabletrue crossContexttrue /参数说明path/表示根路径映射docBase可以是绝对路径也可以是相对于webapps的路径比如docBasedistreloadabletrue方便开发阶段修改静态文件后自动重载生产环境建议设为false减少开销crossContexttrue允许不同上下文之间共享请求对前后端分离场景下的接口调用有帮助。3.2 刷新 404 的根因与 WEB-INF/web.xml 兜底方案配好 Context 之后首页能正常打开登录也能跳转。但一按 F5 刷新404。这个问题的本质是Vue Router 的 history 模式下/dashboard这种路径在服务器上并不存在对应的物理文件。首次进入时是 Vue 在前端接管路由刷新时浏览器直接向 Tomcat 请求/dashboardTomcat 找不到这个路径返回 404。解决办法是在dist目录下新建WEB-INF文件夹里面放一个web.xml把 404 错误重定向到index.html。这样刷新时 Tomcat 返回index.htmlVue Router 重新接管页面正常渲染。!-- 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-app逻辑说明error-page是 Servlet 规范里的错误页面映射。当 Tomcat 返回 404 时容器会内部转发到/index.html浏览器地址栏不变但内容变成了 Vue 的入口页面。metadata-completetrue表示不使用注解扫描加快启动速度。注意WEB-INF目录必须放在dist文件夹内部和index.html同级。放到webapps根目录下无效因为 Context 的docBase指向的是dist。4. 跨域与第三方 API 调用Tomcat 层面的处理思路4.1 跨域问题的来源前端 dist 和后端 war 不同上下文前后端分离部署时前端静态资源由 Tomcat 的dist上下文提供后端接口由prod-api上下文提供。浏览器发起请求时如果前端页面地址是ip:端口/接口地址是ip:端口/prod-api/...虽然同源策略下端口和协议一致但路径不同某些情况下仍会触发跨域限制。更常见的是前端直接调用第三方 API比如地图服务、短信服务这些接口的域名和当前页面域名不一致必然跨域。文档里提到的「调用第三方 API 如百度地图 API 所存在的跨域问题」本质是浏览器同源策略拦截了前端直接发起的跨域请求。Tomcat 本身不处理跨域需要在后端加过滤器或者在前端配置代理。由于没有 Nginx前端代理这条路走不通只能在 SpringBoot 侧解决。4.2 在 SpringBoot 里加全局 CORS 配置的实操常见做法是在 SpringBoot 里加一个全局 CORS 配置类允许指定来源的跨域请求。这种方式对第三方 API 调用同样有效因为请求先到 SpringBoot再由后端转发出去。// 全局跨域配置放在 config 包下 Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) // 拦截所有路径 .allowedOriginPatterns(*) // 允许的来源生产环境建议写具体域名 .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) // 允许携带 Cookie .maxAge(3600); // 预检请求缓存时间单位秒 } }参数说明allowedOriginPatterns在 SpringBoot 2.4 之后替代了allowedOrigins支持通配符allowCredentials(true)表示允许跨域请求携带认证信息此时allowedOriginPatterns不能设为*需要指定具体域名或使用模式匹配maxAge(3600)减少 OPTIONS 预检请求的次数。如果第三方 API 不支持 CORS前端直接调用会被浏览器拦截。这时候需要在 SpringBoot 里写一个转发接口由后端去调用第三方 API再把结果返回给前端。这样浏览器看到的是同源请求不涉及跨域。// 后端转发第三方 API 的示例 RestController RequestMapping(/proxy) public class ApiProxyController { GetMapping(/map) public String getMapData(RequestParam String keyword) { // 实际项目中用 RestTemplate 或 WebClient 调用第三方接口 // 这里只示意转发逻辑 String url https://third-party-api.example.com/search?q keyword; // 由后端发起请求不受浏览器同源策略限制 return restTemplate.getForObject(url, String.class); } }逻辑说明浏览器请求/proxy/map这是同源请求SpringBoot 收到后由后端服务器去调用第三方 API服务器之间的 HTTP 调用没有同源策略限制拿到结果后再返回给前端。这种方式虽然多了一跳但能彻底绕开跨域限制而且可以在后端做缓存、限流和鉴权。5. 避坑排查Tomcat 部署 SpringBoot Vue 的五个高频翻车点5.1 现象war 包放入 webapps 后Tomcat 启动日志报 ClassNotFoundException原因pom 里没有排除内嵌 Tomcat或者启动类没有继承SpringBootServletInitializer。war 包里的WEB-INF/lib同时存在内嵌 Tomcat 的类和 SpringBoot 的类类加载器冲突。解决检查 pom 的 exclusions 配置确认spring-boot-starter-tomcat的 scope 是provided检查启动类是否重写了configure方法。改完后重新打包删掉 webapps 下的旧解压目录再重启。5.2 现象登录页正常登录后白屏控制台无报错原因Vue Router 的 base 路径和 Tomcat 的 Context path 不一致。前端路由期望从/开始但实际访问路径带了/dist/前缀。解决在server.xml的Host节点下添加Context path/ docBasedist的绝对路径 /确保根路径直接映射到 dist 目录。如果项目部署在子路径下需要同步修改 Vue Router 的base配置。5.3 现象刷新页面 404直接访问路由地址也 404原因Vue Router 的 history 模式下服务器上没有对应的物理文件。Tomcat 默认按路径找文件找不到就返回 404。解决在dist目录下创建WEB-INF/web.xml配置 404 错误页面重定向到/index.html。注意WEB-INF必须在dist内部不能放在webapps根目录。5.4 现象接口请求返回 403 或 CORS 错误前端无法获取数据原因跨域配置缺失或配置不当。如果前端页面和接口不同源浏览器会先发 OPTIONS 预检请求后端没有正确响应就会拦截。解决在 SpringBoot 里加全局 CORS 配置确认allowedMethods包含 OPTIONSallowedHeaders允许前端实际发送的请求头。如果第三方 API 不支持跨域改用后端转发方案。5.5 现象修改了 dist 里的文件刷新页面没变化原因Tomcat 缓存了静态资源或者reloadable设为false时不会自动重载。解决开发阶段把Context的reloadable设为true生产环境修改后删掉webapps下的解压目录和work目录下的缓存再重启 Tomcat。浏览器端强制刷新Ctrl F5清除本地缓存。6. 验证部署是否真正生效三个必查项与一个习惯部署完成后不要只看首页能不能打开。我一般会按下面三个步骤验证一遍确保不是「看起来能用」。第一直接访问一个前端路由地址比如ip:端口/dashboard看是否返回index.html的内容。如果返回 404说明web.xml的 404 兜底没生效。第二打开浏览器开发者工具切到 Network 面板刷新页面看静态资源的加载路径是否正确。如果static/js/xxx.js的请求路径带了/dist/前缀说明 Context 配置的docBase和实际访问路径没对齐。第三调用一个后端接口确认返回的是 JSON 而不是 HTML。如果接口返回了index.html的内容说明 404 兜底把接口请求也重定向了需要调整web.xml的拦截范围或者把接口路径排除在外。# 快速验证接口是否正常返回 JSON curl -i http://ip:端口/prod-api/actuator/health # 预期返回类似 # HTTP/1.1 200 # Content-Type: application/json # {status:UP}如果接口返回的是 HTML检查web.xml里的error-page是否拦截了所有 404。有些项目会把接口路径也重定向到index.html导致前端拿到 HTML 却以为是 JSON解析报错。解决办法是在web.xml里只对特定路径做兜底或者在后端确保接口路径不会返回 404。还有一个习惯每次修改server.xml或web.xml之后先停 Tomcat删掉webapps下对应的解压目录和work目录下的缓存再启动。Tomcat 的缓存机制有时候会让人误以为配置没生效其实是旧文件还在起作用。从那以后我每次部署完都强制走一遍「停服务 → 清缓存 → 启服务 → 验证路由 → 验证接口」的流程省掉了很多来回折腾的时间。希望帮到你。本文还有配套的精品资源点击获取