前言
这是一篇基于我真实项目经历沉淀的技术复盘。在最近的一次 CI/CD 搭建中,我需要从 Linux 版的 Jenkins 自动部署 Spring Boot 应用到 Windows 服务器,期间遭遇了 Maven 多模块依赖、本地 Jar 包路径错误、SSH 远程进程被杀、Windows 文件锁等一系列“水土不服”的顽疾。
本文由我提供实战场景和调试过程,并借助 AI 助手协助整理和润色,旨在还原最真实的排坑路径。文中提到的每一个报错都是我亲眼所见,每一个脚本都已经过生产环境验证。希望这套最终落地的“Windows 任务计划程序”方案,能为同样受困于 Windows 部署的开发者节省宝贵的时间。
一、 项目背景与目标
最近有一个维护项目,需要搭建一套自动化的 CI/CD 流水线。环境极其典型,也因此坑点重重:
- 构建环境:Jenkins 运行在 Linux 服务器的 Docker 容器中。
- 部署目标:一台 Windows Server 云服务器。
- 项目结构:一个老旧的 Spring Boot 多模块项目(包含
common,common-auth,wxApp等模块)。 - 特殊依赖:项目根目录下有个
lib文件夹,里面存放了一堆没有发布到 Maven 私服的第三方 Jar 包(system scope 依赖)。
核心目标:在 Jenkins 上点击一次“立即构建”,就能自动完成以下全流程:
- 编译
common等底层模块。 - 打包主程序
wxApp(包含本地 lib 包)。 - 将 Jar 包传输到 Windows 服务器。
- 在 Windows 上安全地重启服务。
二、 踩坑之路:问题逐个击破
整个过程并非一帆风顺,我们遇到了几个非常经典的问题。
坑点 1:Maven 多模块与本地 Jar 包的纠缠
刚开始构建时,Maven 频频报错。
现象 A:找不到兄弟模块依赖
主项目 wxApp 依赖 common 模块,构建时提示找不到 common 的 jar 包。
- 原因:在多模块项目中,必须先把被依赖的模块安装到本地 Maven 仓库。
- 解决:确保底层模块(如
common)的 Jenkins 构建命令使用的是mvn clean install,而不仅仅是package。
现象 B:本地 lib 包打包失败或路径错误
项目使用了 <scope>system</scope> 引入本地 lib 下的 Jar 包。最初尝试在 pom.xml 中使用 <resource> 标签手动将 lib 目录复制到 BOOT-INF/lib。
结果在 Linux Jenkins 环境下报错 NoSuchFileException,因为它试图将文件复制到 Linux 系统根目录的 /BOOT-INF/ 下。
- 解决:放弃手动的
<resource>复制配置,使用 Spring Boot 插件的标准官方支持。
在pom.xml的spring-boot-maven-plugin插件配置中添加一行即可完美解决:<configuration> <includeSystemScope>true</includeSystemScope> </configuration>
坑点 2:Windows SSH 的路径“水土不服”
为了传输文件,我们在 Windows 上安装了 OpenSSH Server,并使用了 Jenkins 的 Publish Over SSH 插件。
现象:配置 Remote Directory 时,如果写绝对路径(如 C:\Users\Administrator\Downloads)或带反斜杠的路径,文件传输经常失败。
- 解决:Windows 的 OpenSSH 对路径支持比较微妙。最稳妥的方式是使用相对于用户主目录的相对路径,例如直接填写
temp或Downloads。
坑点 3:令人抓狂的 “0kb 日志” (Jenkins 进程杀手)
这是最耗时的一个坑。文件传过去了,Bat 脚本也执行了,但是 Java 服务就是起不来,生成的日志文件永远是 0kb。
- 原因:这是 Jenkins 的机制问题。为了防止僵尸进程,Jenkins 在 SSH 会话断开时,会通过 “Process Tree Killer” 杀死该会话衍生出的所有子进程。我们启动的 Java 进程成了牺牲品。
- 尝试过的(不完美 ai提供的)方案:
- 在 Bat 脚本头部设置
set BUILD_ID=dontKillMe(旧版 Jenkins)。 - 设置
set JENKINS_NODE_COOKIE=dontKillMe(新版 Jenkins)。 - 实测结果:都不行。
- 在 Bat 脚本头部设置
坑点 4:Windows 文件锁 (Permission denied)
当 Java 程序在 Windows 上运行时,它的 Jar 包文件会被系统锁定。此时如果 Jenkins 试图上传新文件覆盖旧文件,就会报错。
- 解决思路:不能直接覆盖。必须采用 “上传到临时目录 -> 杀掉旧进程解锁 -> 移动新文件覆盖 -> 启动” 的迂回策略。
三、 终极方案:脱离 Jenkins 的“任务计划程序”
为了彻底解决 SSH 进程被杀和文件锁的问题,我们最终采用了最稳妥的方案:让 Jenkins 只负责“发令”,让 Windows 系统自己负责“执行”。
我们利用了 Windows 自带的 “任务计划程序 (Task Scheduler)”。
步骤 1:在 Windows 上准备“任务”
- 远程登录 Windows 服务器,打开“任务计划程序”。
- 创建一个新任务,命名为
RestartWxApp(名字随意,稍后要用到)。 - 关键配置:
- 【常规】:勾选“不管用户是否登录都要运行”,勾选“使用最高权限运行”。
- 【操作】:启动程序,指向我们接下来要写的 Bat 脚本(例如
D:\Downloads\restart.bat)。起始于一定要填脚本所在的文件夹。 - 【设置】:务必取消勾选“如果运行时间超过以下时间,停止任务”。(否则3天后你的服务会被系统干掉)。
步骤 2:编写极简版部署脚本 (restart.bat)
由于该脚本将由 Windows 系统直接托管运行,与 Jenkins 的 SSH 会话无关,因此不需要任何“免死金牌”变量,脚本可以写得非常纯净。
该脚本实现了:杀进程解锁 -> 从临时文件夹移动新包 -> 自动识别 Jar 包名 -> 后台启动。
文件位置示例:
D:\Downloads\restart.bat
@echo off
setlocal enabledelayedexpansion
:: 确保进入正确的目录
cd /d C:\Users\Administrator\Downloads
:: ================= 1. 停止旧服务 =================
:: 强制杀掉 java 进程以释放文件锁
taskkill /F /IM java.exe >nul 2>&1
:: 使用 ping 命令进行延时等待(约3秒),确保句柄释放。
:: 在非交互式后台环境下,ping 比 timeout 命令更稳定。
ping -n 4 127.0.0.1 >nul
:: ================= 2. 更新文件 =================
:: 如果 Jenkins 上传了新包到 temp 目录,则移动过来覆盖
if exist temp\*.jar (
move /Y temp\*.jar . >nul
:: 再次等待文件系统索引更新
ping -n 4 127.0.0.1 >nul
)
:: ================= 3. 自动查找 Jar 包名 =================
set "JAR_NAME="
:: 查找当前目录下以 wx 开头的 jar 包,按时间倒序,取最新的一个
for /f "delims=" %%i in ('dir /b /o-d wx*.jar') do (
set "JAR_NAME=%%i"
goto :Found
)
:Found
:: ================= 4. 启动新服务 =================
if not "!JAR_NAME!"=="" (
:: 使用 start /b 在后台启动 Java。
:: >nul 2>&1 表示丢弃控制台输出(假设你的应用已经配置了 Logback/Log4j 写日志文件)
start /b java -jar "!JAR_NAME!" >nul 2>&1
)
exit
步骤 3:配置 Jenkins 构建后操作
最后,回到 Jenkins 的任务配置中。
在 “Send build artifacts over SSH” 的配置里:
- Source files:
target/*.jar - Remove prefix:
target/ - Remote directory:
temp(先把文件传到临时目录,避开锁定) - Exec command:
这里只需要执行一条极其简单的命令,告诉 Windows 触发我们刚才创建的任务即可:
schtasks /Run /TN "RestartWxApp"
四、 总结
通过这套方案,我们成功实现了目标:
- Jenkins 负责最擅长的编译和打包,并将文件安全传输到 Windows 的临时区。
- Jenkins 发送一个瞬间完成的触发指令,然后立刻断开 SSH 连接,不留后患。
- Windows 接收指令后,通过系统级的“任务计划程序”在后台稳定地执行更新和重启脚本。
这套方案完美绕过了 SSH 会话管理和文件锁的限制,是目前 Linux Jenkins 部署到 Windows 环境下成功的方案之一。希望这篇记录能帮助大家少踩几个坑!
评论区