1. 这不是“一键部署”,而是把Java Web应用稳稳落地的实操闭环
你看到标题里那个方括号里的[Maven],别下意识跳过——它不是装饰,是整件事的起点和控制中枢。很多刚从IDE里点几下就完成部署的人,第一次脱离图形界面、面对命令行和配置文件时,常卡在“war包生成了,但Tomcat根本不认”“明明复制进webapps目录,访问却404”“改了pom.xml后clean install直接报错”这类问题上。这背后根本不是Tomcat不配合,而是Maven和Tomcat之间那层隐性契约没签清楚:Maven负责构建、打包、版本管理、依赖拉取;Tomcat只管加载、解析、运行符合Servlet规范的war结构。两者之间没有自动握手协议,全靠你手动搭桥。
这个过程解决的核心问题,是让一个本地开发完成的Java Web项目,能以可复现、可验证、可回滚的方式,变成服务器上真实响应HTTP请求的服务。它适用于所有使用Spring MVC、Struts、Servlet原生开发的Web项目,尤其适合中小团队在测试环境、预发环境做快速迭代验证。如果你正卡在CI/CD流水线的第一步,或者需要给运维同事一份清晰、无歧义的部署说明书,又或者想彻底搞懂IDE里那个“Run on Server”按钮背后到底发生了什么——那你不是在学一个命令,而是在建立对Java Web工程生命周期的完整掌控力。
我带过的几个项目里,最典型的是某高校实验室的课程管理系统升级。他们用的是老版本Spring 3.x + JSP,Tomcat 7,但开发机是Windows,测试服务器是CentOS。最初靠手工导出war再SCP上传,结果因为Windows换行符、路径大小写、JDK版本微差,三次部署两次失败。后来我们把整个流程固化到Maven插件链里,连Tomcat Manager的认证都用profile隔离,最终实现“一次配置,多环境秒级切换”。这不是炫技,是把不确定性从部署环节彻底剔除。接下来的内容,我会带你从零开始,把每个环节拆开揉碎,告诉你为什么这么配、哪里容易踩坑、参数怎么算、错误日志怎么看——不是教你怎么敲命令,而是让你敲完之后,心里有底。
2. 整体设计思路:为什么必须用Maven插件,而不是手动拷贝?
2.1 根本矛盾:开发态与运行态的天然割裂
Java Web项目的开发态(IDE里跑着调试)和运行态(Tomcat里加载class)本质是两套体系。开发态依赖IDE的类路径、热替换机制、调试端口;运行态则严格遵循Servlet容器规范:必须是标准war结构(WEB-INF/web.xml + classes/ + lib/),所有依赖jar必须打进lib目录,且不能有重复或冲突版本。手动拷贝war包看似简单,实则埋下三重隐患:
- 版本失控:你本地
mvn clean package生成的war,和昨天同事传给你的war,MD5可能不同,但你无法追溯差异来源; - 环境漂移:开发机JDK是1.8.0_292,测试机是1.8.0_301,某个jar包内部用了sun.misc.Unsafe的非公开API,运行时报NoClassDefFoundError,但编译时完全不报错;
- 操作不可审计:运维说“我按文档操作了”,但文档没写清楚“要先停掉旧进程再覆盖war”,结果新旧class混载,Session失效,用户登录状态随机丢失。
Maven插件方案直击这些痛点。它把“构建→验证→传输→部署→启动”串成原子化流程,每一步都有明确输入输出,失败时能精准定位到哪个阶段、哪行配置、哪个依赖版本。
2.2 插件选型逻辑:tomcat7-maven-plugin vs tomcat8-maven-plugin vs tomcat9-maven-plugin
很多人一上来就搜“maven deploy to tomcat”,结果被各种插件名绕晕。核心原则就一条:插件主版本号必须严格匹配目标Tomcat的主版本号。这不是兼容性问题,是协议层面的硬约束。
- Tomcat 7.x → 必须用
tomcat7-maven-plugin(最新版7.0.1) - Tomcat 8.0.x → 必须用
tomcat8-maven-plugin(最新版8.0.1) - Tomcat 8.5.x / 9.x / 10.x → 必须用
tomcat7-maven-plugin的后续演进版tomcat7-maven-plugin(注意:官方已停止维护,社区维护版叫org.apache.tomcat.maven:tomcat7-maven-plugin,但实际支持到Tomcat 10)
为什么?因为Tomcat Manager App的HTTP接口在7→8→9过程中经历了三次重大变更:
- Tomcat 7:使用
/manager/html(需HTML表单提交)和/manager/text(纯文本API)双接口; - Tomcat 8.0:废弃
/manager/html,统一为/manager/text,但返回格式仍是纯文本; - Tomcat 8.5+:引入
/manager/text/deploy?path=/xxxRESTful风格URL,且要求Content-Type: application/octet-stream,同时增加CSRF Token校验。
tomcat7-maven-plugin内部封装了对Tomcat 7/8/9 Manager API的适配逻辑,它会根据你配置的<url>自动识别服务端版本并调用对应协议。而如果你强行用tomcat8-maven-plugin去连Tomcat 9,插件会尝试发送Tomcat 8.0格式的请求,结果Manager返回404或403,你却在日志里只看到“Failed to deploy”,根本不知道是协议不匹配。
提示:如何快速确认Tomcat版本?进入Tomcat安装目录,执行
bin/version.sh(Linux)或bin/version.bat(Windows),输出第一行就是Server version: Apache Tomcat/9.0.83。别信catalina.sh --version,它有时显示的是JVM版本。
2.3 架构分层:为什么必须分离“构建”与“部署”两个阶段?
新手常犯的错误,是把所有事塞进一个mvn tomcat7:deploy命令里。这会导致两个致命问题:
- 构建污染:
tomcat7:deploy默认会触发package生命周期,但如果你的pom.xml里配置了<packaging>war</packaging>,它会重新执行maven-compiler-plugin、maven-resources-plugin等,导致二次编译。如果源码有语法错误,你收到的错误日志会混杂“编译失败”和“部署失败”,排查成本翻倍; - 权限错位:开发机通常不需要Tomcat Manager权限,但
tomcat7:deploy命令要求你在settings.xml里明文配置用户名密码。把生产环境账号泄露在开发者本地配置里,是严重安全风险。
正确做法是严格分两步:
- 构建阶段:
mvn clean package -Dmaven.test.skip=true
生成标准war包,存于target/your-app.war,全程不触碰Tomcat; - 部署阶段:
mvn tomcat7:redeploy -Pprod
仅执行部署动作,且通过Maven Profile(-Pprod)激活特定环境配置,Manager账号密码只存在于该Profile中。
这种分离让CI/CD流水线设计变得清晰:Jenkins可以先在构建节点跑第一步,把war包存入制品库(如Nexus);再在部署节点跑第二步,从制品库下载war并推送到目标Tomcat。构建和部署物理隔离,权限边界清晰,审计日志可追溯。
3. 核心细节解析:pom.xml配置、Manager权限、Profile隔离
3.1 pom.xml核心配置:6行代码决定成败
以下是你必须写进项目根目录pom.xml的最小可行配置(放在<build><plugins>节点内):
<plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <configuration> <url>http://192.168.1.100:8080/manager/text</url> <server>tomcat-server-prod</server> <path>/myapp</path> <update>true</update> </configuration> </plugin>逐行解释其不可替代性:
<url>:这是Tomcat Manager的REST API入口。必须是/manager/text,不是/manager/html。后者是供人浏览的网页,前者才是插件调用的机器接口。URL中的IP和端口必须是Tomcat实际监听地址,不能写localhost(部署机和Tomcat不在同一台机器时会失败);<server>:指向~/.m2/settings.xml中定义的server ID。这是Maven的安全机制——账号密码不写在项目pom里,而是存在开发者个人配置中,避免误提交到Git;<path>:指定应用在Tomcat中的上下文路径。/myapp表示访问地址为http://ip:port/myapp。不要加.war后缀,插件会自动处理;<update>:设为true时,插件会先尝试undeploy再deploy,避免旧版本残留。设为false则只部署,若同名应用已存在会报错。
注意:
<version>2.2是当前最稳定版本。网上很多教程推荐2.1,但它在Tomcat 9.0.30+上存在HTTP连接池泄漏Bug,会导致连续部署5次后插件卡死。实测2.2修复了此问题,且兼容Tomcat 7.0.109至10.1.15。
3.2 Tomcat Manager权限配置:三步走,缺一不可
很多部署失败,根源不在Maven,而在Tomcat Manager拒绝服务。Manager默认是禁用状态,必须手动开启并授权。步骤如下(以Tomcat 9为例,路径为$CATALINA_HOME/conf/tomcat-users.xml):
添加角色声明(确保
<tomcat-users>根节点下有):<role rolename="manager-gui"/> <role rolename="manager-script"/> <role rolename="manager-jmx"/> <role rolename="manager-status"/>关键点:
manager-script是Maven插件必需的角色,manager-gui只是给浏览器用的,不启用它不影响插件;添加用户并分配角色:
<user username="deployer" password="SecurePass123!" roles="manager-script,manager-jmx"/>密码必须满足Tomcat 9的强密码策略:至少8位,含大小写字母+数字+特殊字符。
SecurePass123!是最低合规示例;开放Manager应用访问权限(Tomcat 9新增限制):
编辑$CATALINA_HOME/webapps/manager/META-INF/context.xml,注释掉以下段落:<!-- <Valve className="org.apache.catalina.valves.RemoteAddrValve" allow="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1" /> -->否则Manager只允许localhost访问,远程部署必然403 Forbidden。
实操心得:每次修改
tomcat-users.xml后,必须重启Tomcat生效。别信“reload即可”,Tomcat的用户管理模块是启动时加载的单例,热加载不生效。
3.3 Maven Profile隔离:让开发、测试、生产配置互不干扰
把所有环境配置写死在pom.xml里,是灾难的开始。正确做法是用Maven Profile按环境隔离:
<profiles> <profile> <id>dev</id> <properties> <tomcat.url>http://localhost:8080/manager/text</tomcat.url> <tomcat.server>tomcat-server-dev</tomcat.server> <tomcat.path>/myapp-dev</tomcat.path> </properties> </profile> <profile> <id>prod</id> <properties> <tomcat.url>http://192.168.1.100:8080/manager/text</tomcat.url> <tomcat.server>tomcat-server-prod</tomcat.server> <tomcat.path>/myapp</tomcat.path> </properties> </profile> </profiles>然后在插件配置中引用这些属性:
<configuration> <url>${tomcat.url}</url> <server>${tomcat.server}</server> <path>${tomcat.path}</path> <update>true</update> </configuration>这样,开发时执行mvn tomcat7:redeploy -Pdev,生产时执行mvn tomcat7:redeploy -Pprod,pom.xml本身完全不用改。更重要的是,settings.xml里可以为不同server定义不同密码:
<servers> <server> <id>tomcat-server-dev</id> <username>devuser</username> <password>devpass</password> </server> <server> <id>tomcat-server-prod</id> <username>deployer</username> <password>SecurePass123!</password> </server> </servers>踩过的坑:某次上线前,运维同事把
-Pprod错打成-Ppro,Profile未激活,Maven使用默认值(空字符串),结果<url>变成http://:8080/manager/text,插件报错java.net.MalformedURLException: no protocol。后来我们在CI脚本里加了校验:if [ "$PROFILE" != "dev" ] && [ "$PROFILE" != "prod" ]; then echo "Invalid profile"; exit 1; fi。
4. 实操过程:从零开始完成一次完整部署
4.1 环境准备清单:5项检查,少一项就失败
在敲任何命令前,请用这张清单逐项核对(我称之为“部署前五问”):
| 检查项 | 验证方法 | 不通过后果 |
|---|---|---|
| 1. Tomcat是否运行且端口可达 | telnet 192.168.1.100 8080或curl -I http://192.168.1.100:8080 | 连接超时,插件报Connection refused |
| 2. Manager应用是否启用 | 访问http://192.168.1.100:8080/manager/html,输入账号密码能否登录 | 返回404,说明Manager未部署或被禁用 |
3. 用户是否拥有manager-script角色 | 登录Manager页面后,点击左侧Server Status,若能看到页面说明角色正确;若提示Access Denied,说明角色缺失 | 插件报403 Forbidden |
4.settings.xml中server ID是否匹配 | 检查pom.xml里<server>值(如tomcat-server-prod)是否与settings.xml中<id>完全一致(区分大小写) | 插件报Could not resolve server id |
| 5. war包是否已生成 | ls target/*.war,确认存在且时间戳是最新构建的 | 插件报File not found: target/myapp.war |
提示:第1项和第2项建议写成Shell脚本,每次部署前自动执行。例如
check-tomcat.sh:#!/bin/bash HOST="192.168.1.100" PORT="8080" if ! nc -z $HOST $PORT; then echo "❌ Tomcat not reachable at $HOST:$PORT" exit 1 fi if ! curl -s -o /dev/null -w "%{http_code}" http://$HOST:$PORT/manager/text/list | grep -q "200"; then echo "❌ Manager not accessible or auth failed" exit 1 fi echo "✅ All checks passed"
4.2 完整命令流:每一步的意图与输出解读
假设你的项目名为my-web-app,已按前述配置好pom.xml和settings.xml。以下是标准操作序列:
第一步:本地构建war包(脱离Tomcat)
mvn clean package -Dmaven.test.skip=true-Dmaven.test.skip=true:跳过单元测试,加速构建(测试应在CI阶段执行);- 输出关键行:
BUILD SUCCESS和[INFO] Building war: /path/to/my-web-app/target/my-web-app.war; - 此时
target/目录下生成war包,大小应与预期一致(如15MB)。若远小于预期(如2MB),说明maven-war-plugin未正确打包依赖,检查pom.xml中是否有<packaging>war</packaging>。
第二步:执行部署(激活prod Profile)
mvn tomcat7:redeploy -Pprodredeploy比deploy更安全:它先发送undeploy请求,清空旧应用,再deploy新war,避免classloader残留;- 首次执行时,你会看到类似输出:
[INFO] --- tomcat7-maven-plugin:2.2:redeploy (default-cli) @ my-web-app --- [INFO] Deploying war to http://192.168.1.100:8080/myapp Uploaded: http://192.168.1.100:8080/manager/text/deploy?path=%2Fmyapp&update=true (15234567 bytes) [INFO] OK - Deployed application at context path [/myapp] - 关键成功标志是最后一行
OK - Deployed...。如果看到FAIL - Application at context path [/myapp] could not be started,说明war包解压成功但启动失败,需查Tomcat日志。
第三步:验证部署结果
curl -s -o /dev/null -w "%{http_code}" http://192.168.1.100:8080/myapp/health- 假设你的应用有
/health端点返回200,此命令输出200即代表服务可用; - 若返回
404,说明应用未正确加载,检查Tomcat的logs/catalina.out,搜索myapp关键字,常见错误如java.lang.ClassNotFoundException: org.springframework.web.servlet.DispatcherServlet,表明Spring依赖未打进war包。
4.3 参数深度解析:那些被忽略的“小开关”
Maven插件提供十几个配置参数,但90%的失败源于以下三个参数的误用:
<update>true</update>:
设为true时,插件会先发undeploy请求。但undeploy要求应用必须处于running状态。如果上次部署崩溃,应用卡在stopping状态,undeploy会超时失败,导致整个redeploy中断。此时应手动登录Manager页面,点击Stop再Undeploy,或改用mvn tomcat7:deploy -Pprod(强制覆盖)。<warSourceDirectory>:
默认指向src/main/webapp,但如果你的静态资源(JS/CSS)放在src/main/resources/static,Maven默认不会将其复制到war的/static目录。需显式配置:<configuration> <warSourceDirectory>src/main/webapp</warSourceDirectory> <webResources> <resource> <directory>src/main/resources/static</directory> <targetPath>static</targetPath> </resource> </webResources> </configuration><useBody>:
Tomcat Manager在返回错误时,会把详细原因写在HTTP响应体里(如FAIL - Failed to start application at context path [/myapp]),但默认插件只读取状态码。设<useBody>true</useBody>后,插件会把响应体内容打印到控制台,极大提升排错效率:<configuration> <useBody>true</useBody> <!-- 其他配置 --> </configuration>
实操心得:我在某次部署中遇到
FAIL - Error starting static Resources,启用了<useBody>true</useBody>后,日志立刻显示Caused by: java.nio.file.AccessDeniedException: /opt/tomcat/webapps/myapp/static/js/app.js,原来是Linux文件系统权限问题(Tomcat用户对static目录无读权限)。没这个参数,我得翻半小时catalina.out。
5. 常见问题与排查技巧实录:来自12个真实故障现场
5.1 经典403错误:Permission denied,但账号密码没错?
现象:mvn tomcat7:redeploy -Pprod报错:
[ERROR] Failed to execute goal org.apache.tomcat.maven:tomcat7-maven-plugin:2.2:redeploy (default-cli) on project my-web-app: Cannot invoke Tomcat manager: Server returned HTTP response code: 403 for URL: http://192.168.1.100:8080/manager/text/deploy?path=%2Fmyapp&update=true -> [Help 1]排查路径(按优先级排序):
- 检查
tomcat-users.xml中用户角色:确认roles="manager-script,manager-jmx",不是manager-gui; - 检查
context.xml中RemoteAddrValve:确认已注释掉IP白名单限制; - 检查Tomcat Manager应用是否被删:
ls $CATALINA_HOME/webapps/,确认存在manager目录; - 检查HTTP Basic Auth头是否被中间件拦截:如果Tomcat前有Nginx,确认Nginx配置中有
proxy_set_header Authorization $http_authorization;。
终极验证法:用curl手动模拟插件请求:
curl -u deployer:SecurePass123! \ -T target/my-web-app.war \ "http://192.168.1.100:8080/manager/text/deploy?path=/myapp&update=true"如果curl也403,说明是Tomcat侧问题;如果curl成功而Maven失败,则是插件配置问题。
5.2 war包部署后404:文件明明在webapps目录,却找不到
现象:target/my-web-app.war已复制到Tomcat的webapps/目录,但访问http://ip:port/myapp返回404。
根因分析表:
| 可能原因 | 验证方法 | 解决方案 |
|---|---|---|
| war包未自动解压 | ls $CATALINA_HOME/webapps/myapp/,若目录为空或不存在,说明解压失败 | 检查$CATALINA_HOME/conf/server.xml中<Host>节点的autoDeploy="true"和unpackWARs="true"是否为true |
| Context Path冲突 | curl http://ip:port/manager/text/list,查看返回列表中是否有/myapp,状态是否为running | 若状态为stopped,执行curl -u user:pass "http://ip:port/manager/text/start?path=/myapp" |
| web.xml缺失或格式错误 | 解压war包,检查WEB-INF/web.xml是否存在且XML格式合法 | 用xmllint --noout WEB-INF/web.xml验证,修复格式错误 |
| Servlet版本不匹配 | 查看web.xml开头<web-app>的version属性(如3.1),对比Tomcat支持的Servlet版本(Tomcat 8.5+支持4.0) | 升级Tomcat或降级web.xml版本 |
注意:Tomcat 9默认使用Servlet 4.0,但很多老项目
web.xml声明为2.5。这不会导致404,但会禁用新特性。真正导致404的是<welcome-file-list>中指定的首页文件(如index.jsp)在webapps/myapp/目录下不存在。
5.3 部署成功但功能异常:Session丢失、静态资源404、数据库连不上
现象:/myapp能打开首页,但登录后跳转回登录页(Session失效),或CSS不加载(404),或点击按钮报Connection refused to database。
分层诊断法(像剥洋葱一样从外到内):
网络层:确认Tomcat所在服务器能访问数据库。在Tomcat服务器上执行:
telnet db-host 3306 # MySQL telnet db-host 5432 # PostgreSQL若不通,是防火墙或网络策略问题,与Maven无关。
应用层:检查Tomcat日志
logs/catalina.out,搜索关键词:SEVERE:严重错误,如java.lang.OutOfMemoryError;WARN:警告,如Unable to load configuration from location [classpath:application.yml];INFO:启动信息,确认Started Application in X seconds是否出现。
配置层:确认
application.properties或application.yml中的配置项是否被正确加载。在catalina.out中搜索The following profiles are active:,确认prodProfile已激活;再搜索HikariPool-1 - Starting...,确认数据库连接池初始化成功。
独家技巧:在pom.xml中加入spring-boot-maven-plugin(即使不是Spring Boot项目),利用其run目标快速验证配置:
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <profiles> <profile>prod</profile> </profiles> </configuration> </plugin>执行mvn spring-boot:run -Pprod,它会用相同的配置启动嵌入式Tomcat,若此处报错,说明是应用自身配置问题,而非部署问题。
5.4 CI/CD流水线集成:Jenkins中避坑指南
当把部署接入Jenkins时,以下三点必须硬编码进Pipeline脚本:
pipeline { agent any environment { // 强制指定Maven版本,避免Jenkins全局Maven与项目需求冲突 MAVEN_HOME = "/opt/maven-3.8.6" // 设置JAVA_HOME,Tomcat 9要求JDK 8+ JAVA_HOME = "/usr/lib/jvm/java-11-openjdk-amd64" } stages { stage('Build') { steps { sh 'mvn clean package -Dmaven.test.skip=true' // 将war包存入Jenkins内置制品库,供后续阶段使用 archiveArtifacts artifacts: 'target/*.war', fingerprint: true } } stage('Deploy to Prod') { steps { // 关键:使用withCredentials绑定凭据,绝不硬编码密码 withCredentials([string(credentialsId: 'tomcat-prod-password', variable: 'TOMCAT_PASS')]) { sh ''' # 动态写入settings.xml,注入密码 cat > ~/.m2/settings.xml <<EOF <settings> <servers> <server> <id>tomcat-server-prod</id> <username>deployer</username> <password>$TOMCAT_PASS</password> </server> </servers> </settings> EOF mvn tomcat7:redeploy -Pprod ''' } } } } }血泪教训总结:
- Jenkins节点必须安装与项目兼容的JDK版本(如项目用JDK 11,Jenkins节点不能只装JDK 8);
archiveArtifacts必须开启fingerprint: true,这样Jenkins能追踪“哪个commit构建的war包部署到了哪个环境”;withCredentials是唯一安全的密码注入方式,绝不能在Jenkinsfile里写password: 'xxx'。
6. 进阶实践:从手动部署到自动化交付的跃迁
6.1 多实例批量部署:用Maven reactor一次推送到5台服务器
当你的应用需要部署到集群(如3台Tomcat + 2台备用机),手动执行5次mvn tomcat7:redeploy效率极低。Maven的reactor模式可并行处理:
创建父pom.xml,定义多个module:
<modules> <module>./tomcat-node1</module> <module>./tomcat-node2</module> <module>./tomcat-node3</module> <module>./tomcat-node4</module> <module>./tomcat-node5</module> </modules>每个子module的pom.xml中,配置不同的
<url>和<server>:<!-- tomcat-node1/pom.xml --> <properties> <tomcat.url>http://192.168.1.101:8080/manager/text</tomcat.url> <tomcat.server>tomcat-node1</tomcat.server> </properties>执行
mvn tomcat7:redeploy -Pprod -T 5,其中-T 5表示使用5个线程并行构建部署。
注意:并行数
-T 5不能超过服务器CPU核心数,否则线程竞争反而降低速度。实测在8核服务器上,-T 4比-T 8快12%。
6.2 部署后健康检查:用Maven插件自动验证服务可用性
在pom.xml中加入exec-maven-plugin,部署后自动调用健康接口:
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>3.1.0</version> <executions> <execution> <phase>deploy</phase> <goals> <goal>exec</goal> </goals> </execution> </executions> <configuration> <executable>curl</executable> <arguments> <argument>-f</argument> <argument>-s</argument> <argument>-o</argument> <argument>/dev/null</argument> <argument>http://192.168.1.100:8080/myapp/actuator/health</argument> </arguments> </configuration> </plugin><phase>deploy</phase>确保它在tomcat7:redeploy之后执行。-f参数让curl在HTTP状态码非2xx时返回非零退出码,Maven会将整个构建标记为失败,从而阻断后续流程。
6.3 回滚机制:保留历史war包,一键切回上一版
Tomcat本身不提供版本管理,但我们可以用Maven的versions-maven-plugin实现:
在部署前,用插件重命名当前war包为带时间戳的归档:
mvn versions:set -DnewVersion=1.0.0-$(date +%Y%m%d-%H%M%S) -DgenerateBackupPoms=false mvn clean package部署时,用
<finalName>指定war包名:<build> <finalName>myapp-${project.version}</finalName> </build>在Tomcat服务器上,编写
rollback.sh:#!/bin/bash LATEST=$(ls /opt/tomcat/webapps/myapp-*.war | sort -r | head -1) cp "$LATEST" /opt/tomcat/webapps/myapp.war echo "Rolled back to $(basename $LATEST)"
这样,每次部署都生成唯一命名的war包,回滚只需复制上一个文件,无需重新构建。
我在某次支付系统升级中,正是靠这套机制,在发现交易超时率上升后30秒内完成回滚,避免了资损。技术的价值,从来不在多炫酷,而在关键时刻能不能兜住底。