Jenkins 实战指南-项目自动构建部署全流程通关

在现代软件开发中,"自动化" 是提升效率的核心密码。当你还在手动编译、打包、上传、部署 Java 项目时,高效团队早已通过 CI/CD 工具实现了 "代码提交即发布" 的无缝流程。Jenkins 作为开源 CI/CD 领域的标杆工具,凭借其强大的扩展性和灵活性,成为 Java 团队实现自动化的首选。本文将带你从零开始,一步步搭建 Jenkins 环境,编写 Pipeline 脚本,最终实现 Java 项目从代码提交到自动部署的全流程自动化,让你彻底告别重复的手动操作。

一、为什么需要 Jenkins?------CI/CD 的核心价值

在没有自动化工具的年代,一个 Java 项目的发布流程可能是这样的:

  1. 开发人员在本地编译代码,解决依赖冲突
  2. 手动运行单元测试,检查是否通过
  3. 使用 Maven/Gradle 打包成 jar/war 包
  4. 通过 FTP/SCP 将包上传到服务器
  5. 登录服务器,停止旧服务,备份旧版本
  6. 启动新服务,检查日志确认是否正常
  7. 发现问题后,重复步骤 3-6 回滚版本

这个过程不仅耗时(中小型项目每次发布至少 15-30 分钟),更重要的是充满人为错误风险------ 可能少传一个配置文件,可能忘记备份旧版本,可能在多台服务器部署时版本不一致。

Jenkins 通过持续集成(CI)持续部署(CD) 解决这些问题:

  • 持续集成:代码提交后自动触发编译、测试,及时发现集成问题
  • 持续部署:测试通过后自动部署到目标环境,减少人工干预
  • 流程标准化:将部署流程固化到脚本,确保每次执行一致
  • 快速反馈:构建 / 部署结果实时通知,问题早发现早解决

对于 Java 团队,Jenkins 能带来的具体收益包括:

  • 开发效率提升:开发者专注编码,无需关注部署细节
  • 发布频率提高:从每周一次到每天多次,加速迭代
  • 故障恢复更快:一键回滚机制,减少故障影响
  • 团队协作更顺:透明的构建流程,便于跨角色协作

二、环境准备 ------ 搭建基础运行环境

工欲善其事,必先利其器。在安装 Jenkins 前,需要准备好基础环境。本文以CentOS 7为例(Windows 环境会标注差异),目标是构建一个能运行 Java 17 项目、连接 MySQL、使用 Git 管理代码的 CI/CD 环境。

2.1 安装 JDK 17

Jenkins 本身基于 Java 开发,且我们的项目需要 JDK 17,因此首先安装 JDK 17:

bash

复制代码
# 下载JDK 17(注意:实际下载链接需从Oracle官网或OpenJDK镜像获取)
wget https://download.java.net/java/GA/jdk17.0.2/dfd4a8d09854a74b96786e1b17c7d6/13/GPL/openjdk-17.0.2_linux-x64_bin.tar.gz

# 解压到/usr/local目录
tar -zxvf openjdk-17.0.2_linux-x64_bin.tar.gz -C /usr/local/

# 重命名为jdk17(方便后续引用)
mv /usr/local/jdk-17.0.2 /usr/local/jdk17

# 配置环境变量
echo 'export JAVA_HOME=/usr/local/jdk17' >> /etc/profile
echo 'export PATH=$JAVA_HOME/bin:$PATH' >> /etc/profile
echo 'export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar' >> /etc/profile

# 使环境变量生效
source /etc/profile

# 验证安装(输出java version "17.0.2"即为成功)
java -version

Windows 环境:从 Oracle 官网下载 JDK 17 安装包,按向导安装,在 "高级系统设置" 中配置 JAVA_HOME 环境变量,指向安装目录。

2.2 安装 Maven 3.8+

Maven 用于构建 Java 项目,需要与 JDK 17 兼容的版本(推荐 3.8.x 及以上):

bash

复制代码
# 下载Maven
wget https://archive.apache.org/dist/maven/maven-3/3.8.8/binaries/apache-maven-3.8.8-bin.tar.gz

# 解压到/usr/local
tar -zxvf apache-maven-3.8.8-bin.tar.gz -C /usr/local/

# 配置环境变量
echo 'export MAVEN_HOME=/usr/local/apache-maven-3.8.8' >> /etc/profile
echo 'export PATH=$MAVEN_HOME/bin:$PATH' >> /etc/profile

# 生效配置
source /etc/profile

# 验证(输出Apache Maven 3.8.8即为成功)
mvn -v

配置 Maven 镜像(加速依赖下载):

bash

复制代码
# 编辑Maven配置文件
vi /usr/local/apache-maven-3.8.8/conf/settings.xml

# 在<mirrors>标签内添加阿里云镜像
<mirror>
  <id>aliyunmaven</id>
  <mirrorOf>*</mirrorOf>
  <name>阿里云公共仓库</name>
  <url>https://maven.aliyun.com/repository/public</url>
</mirror>

2.3 安装 Git

用于拉取代码仓库中的项目:

bash

复制代码
# CentOS安装Git
yum install -y git

# 验证(输出git version 1.8.3.1及以上即可)
git --version

Windows 环境:从 Git 官网下载安装包,勾选 "Add Git to PATH" 选项,方便命令行调用。

2.4 安装 MySQL 8.0

我们的 Java 示例项目会用到 MySQL,因此需要安装数据库(若已有可跳过):

bash

复制代码
# 安装MySQL源
rpm -Uvh https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm

# 安装MySQL
yum install -y mysql-community-server

# 启动MySQL服务
systemctl start mysqld

# 设置开机自启
systemctl enable mysqld

# 获取初始密码(用于首次登录)
grep 'temporary password' /var/log/mysqld.log

# 登录MySQL(输入上述初始密码)
mysql -u root -p

# 修改密码(MySQL8.0要求强密码)
ALTER USER 'root'@'localhost' IDENTIFIED BY 'YourStrongPassword123!';

# 允许远程连接(开发环境方便测试,生产环境需限制IP)
use mysql;
update user set host = '%' where user = 'root';
flush privileges;

# 创建示例项目数据库
create database jenkins_demo character set utf8mb4 collate utf8mb4_unicode_ci;

三、Jenkins 安装与初始化 ------ 搭建自动化引擎

3.1 安装 Jenkins

推荐使用 war 包方式安装(灵活且版本可控):

bash

复制代码
# 下载Jenkins(LTS长期支持版,更稳定)
wget https://get.jenkins.io/war-stable/2.401.3/jenkins.war

# 启动Jenkins(--httpPort指定端口,避免与其他服务冲突)
nohup java -jar jenkins.war --httpPort=8080 > jenkins.log 2>&1 &

# 查看启动日志(确认是否成功)
tail -f jenkins.log

启动成功后,日志中会显示初始管理员密码,类似:
Jenkins initial setup is required. An admin user has been created and a password generated.
Please use the following password to proceed to installation: xxxxxxxx

3.2 首次访问与初始化

  1. 浏览器访问http://服务器IP:8080,进入解锁页面
  2. 输入日志中的初始密码(或通过cat /root/.jenkins/secrets/initialAdminPassword获取)
  3. 选择 "安装推荐的插件"(首次使用推荐,后续可按需添加)
  4. 等待插件安装完成(耗时取决于网络,可跳过暂时失败的插件)
  5. 创建管理员用户(输入用户名、密码、邮箱)
  6. 确认 Jenkins URL(保持默认或修改为实际访问地址)
  7. 完成初始化,进入 Jenkins 首页

3.3 安装必备插件

Jenkins 的强大之处在于插件生态,针对 Java 项目自动化,需要安装以下核心插件:

  1. 进入 "系统管理" → "插件管理" → "可选插件"
  2. 搜索并安装以下插件:
    • Maven Integration:支持 Maven 项目构建
    • Pipeline:核心插件,支持 Pipeline 脚本
    • Pipeline Utility Steps:提供 Pipeline 常用工具方法
    • Git:支持从 Git 仓库拉取代码
    • Publish Over SSH:通过 SSH 部署到远程服务器
    • SonarQube Scanner:集成代码质量检查(可选)
    • Email Extension:邮件通知(可选)
  3. 安装完成后重启 Jenkins(http://IP:8080/restart

3.4 全局工具配置

告诉 Jenkins 我们安装的 JDK、Maven、Git 位置:

  1. 进入 "系统管理" → "全局工具配置"
  2. JDK 配置
    • 取消 "安装自动安装"
    • 名称:JDK17
    • JAVA_HOME:/usr/local/jdk17(Linux)或C:\Program Files\Java\jdk-17(Windows)
  3. Maven 配置
    • 取消 "安装自动安装"
    • 名称:Maven3.8
    • MAVEN_HOME:/usr/local/apache-maven-3.8.8(Linux)或C:\apache-maven-3.8.8(Windows)
  4. Git 配置
    • 名称:Git
    • Path to Git executable:/usr/bin/git(Linux)或C:\Program Files\Git\bin\git.exe(Windows)
  5. 点击 "保存"

四、Java 项目准备 ------ 构建可部署的示例应用

为了演示 Jenkins 自动部署,我们需要一个 Java 项目。下面创建一个基于 Spring Boot 3.x 的 Web 应用,包含简单的 REST 接口、数据库操作和单元测试。

4.1 项目结构

plaintext

复制代码
jenkins-demo/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/
│   │   │       └── example/
│   │   │           ├── JenkinsDemoApplication.java
│   │   │           ├── controller/
│   │   │           │   └── UserController.java
│   │   │           ├── service/
│   │   │           │   ├── UserService.java
│   │   │           │   └── impl/
│   │   │           │       └── UserServiceImpl.java
│   │   │           ├── mapper/
│   │   │           │   └── UserMapper.java
│   │   │           └── entity/
│   │   │               └── User.java
│   │   └── resources/
│   │       ├── application.yml
│   │       └── mybatis/
│   │           └── UserMapper.xml
│   └── test/
│       └── java/
│           └── com/
│               └── example/
│                   ├── JenkinsDemoApplicationTests.java
│                   └── service/
│                       └── UserServiceTest.java
├── pom.xml
└── README.md

4.2 核心代码实现

pom.xml(关键依赖配置):

xml

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.3</version>
        <relativePath/>
    </parent>
    
    <groupId>com.example</groupId>
    <artifactId>jenkins-demo</artifactId>
    <version>1.0.0</version>
    <name>jenkins-demo</name>
    <description>Jenkins自动部署示例项目</description>
    
    <properties>
        <java.version>17</java.version>
        <mybatis.version>3.5.13</mybatis.version>
    </properties>
    
    <dependencies>
        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!-- MyBatis -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>3.0.2</version>
        </dependency>
        
        <!-- MySQL驱动 -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        
        <!-- Lombok(简化代码,提供@Slf4j) -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        
        <!-- 单元测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        
        <!-- 测试数据库用 -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <!-- Spring Boot打包插件 -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
            
            <!-- 单元测试插件 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.0.0-M9</version>
                <configuration>
                    <!-- 跳过测试的参数(Jenkins中可动态控制) -->
                    <skipTests>${skipTests}</skipTests>
                </configuration>
            </plugin>
        </plugins>
        
        <!-- 打包后的文件名格式:项目名-版本号.jar -->
        <finalName>${project.artifactId}-${project.version}</finalName>
    </build>
</project>

实体类 User.java

java

运行

复制代码
package com.example.entity;

import lombok.Data;

import java.time.LocalDateTime;

/**
 * 用户实体类
 */
@Data
public class User {
    private Long id;
    private String username;
    private String email;
    private Integer age;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}

Mapper 接口 UserMapper.java

java

运行

复制代码
package com.example.mapper;

import com.example.entity.User;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;

/**
 * 用户数据访问接口
 */
@Mapper
public interface UserMapper {
    /**
     * 查询所有用户
     */
    List<User> selectAll();
    
    /**
     * 新增用户
     */
    int insert(User user);
}

UserMapper.xml(MyBatis 映射文件):

xml

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
    <select id="selectAll" resultType="com.example.entity.User">
        select id, username, email, age, create_time, update_time from user
    </select>
    
    <insert id="insert" parameterType="com.example.entity.User">
        insert into user (username, email, age, create_time, update_time)
        values (#{username}, #{email}, #{age}, now(), now())
    </insert>
</mapper>

服务接口 UserService.java

java

运行

复制代码
package com.example.service;

import com.example.entity.User;
import java.util.List;

/**
 * 用户服务接口
 */
public interface UserService {
    /**
     * 获取所有用户
     */
    List<User> getAllUsers();
    
    /**
     * 创建用户
     */
    boolean createUser(User user);
}

服务实现类 UserServiceImpl.java

java

运行

复制代码
package com.example.service.impl;

import com.example.entity.User;
import com.example.mapper.UserMapper;
import com.example.service.UserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;

/**
 * 用户服务实现类
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class UserServiceImpl implements UserService {

    private final UserMapper userMapper;
    
    @Override
    public List<User> getAllUsers() {
        log.info("查询所有用户信息");
        return userMapper.selectAll();
    }
    
    @Override
    public boolean createUser(User user) {
        log.info("创建新用户: {}", user.getUsername());
        int rows = userMapper.insert(user);
        return rows > 0;
    }
}

控制器 UserController.java

java

运行

复制代码
package com.example.controller;

import com.example.entity.User;
import com.example.service.UserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.List;

/**
 * 用户接口控制器
 */
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
@Slf4j
public class UserController {

    private final UserService userService;
    
    /**
     * 获取所有用户
     */
    @GetMapping
    public List<User> getAllUsers() {
        log.info("接收查询所有用户请求");
        return userService.getAllUsers();
    }
    
    /**
     * 创建用户
     */
    @PostMapping
    public String createUser(@RequestBody User user) {
        log.info("接收创建用户请求: {}", user.getUsername());
        boolean success = userService.createUser(user);
        return success ? "用户创建成功" : "用户创建失败";
    }
    
    /**
     * 健康检查接口(用于部署后验证服务是否正常)
     */
    @GetMapping("/health")
    public String healthCheck() {
        return "Service is running";
    }
}

配置文件 application.yml

yaml

复制代码
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/jenkins_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    username: root
    password: YourStrongPassword123!
    driver-class-name: com.mysql.cj.jdbc.Driver
  profiles:
    active: dev

mybatis:
  mapper-locations: classpath:mybatis/*.xml
  type-aliases-package: com.example.entity
  configuration:
    map-underscore-to-camel-case: true # 下划线转驼峰

server:
  port: 8081 # 避免与Jenkins的8080端口冲突

单元测试 UserServiceTest.java

java

运行

复制代码
package com.example.service;

import com.example.entity.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;

/**
 * 用户服务测试类
 */
@SpringBootTest
class UserServiceTest {

    @Autowired
    private UserService userService;
    
    @Test
    void getAllUsers_ShouldReturnUserList() {
        List<User> users = userService.getAllUsers();
        // 初始数据库为空,返回空列表(非null)
        assertNotNull(users);
    }
    
    @Test
    void createUser_WithValidData_ShouldReturnTrue() {
        User user = new User();
        user.setUsername("testuser");
        user.setEmail("test@example.com");
        user.setAge(25);
        
        boolean result = userService.createUser(user);
        assertTrue(result);
    }
}

4.3 提交代码到 Git 仓库

将项目推送到 Git 仓库(以 Gitee 为例,GitHub 操作类似):

bash

复制代码
# 初始化Git仓库
git init

# 添加文件
git add .

# 提交
git commit -m "初始化Jenkins演示项目"

# 关联远程仓库(替换为你的仓库地址)
git remote add origin https://gitee.com/your-username/jenkins-demo.git

# 推送代码
git push -u origin master

五、Jenkins Pipeline 实战 ------ 编写自动化脚本

Pipeline 是 Jenkins 中最强大的功能,通过代码(Jenkinsfile)定义整个构建部署流程,支持版本控制和复用。我们将创建一个包含 "拉取代码→编译→测试→打包→部署" 的完整 Pipeline。

5.1 创建 Pipeline 项目

  1. 登录 Jenkins,点击左侧 "新建 Item"
  2. 输入项目名称(如jenkins-demo-pipeline),选择 "Pipeline",点击 "确定"
  3. 在项目配置页面,拉到 "Pipeline" 配置区域

5.2 编写基础 Pipeline 脚本

在 "Pipeline" 配置中选择 "Definition" 为 "Pipeline script",输入以下脚本:

groovy

复制代码
pipeline {
    // 指定在哪个节点执行(默认在Jenkins所在节点)
    agent any
    
    // 环境变量定义
    environment {
        // 项目名称
        PROJECT_NAME = 'jenkins-demo'
        // 项目版本(与pom.xml一致)
        PROJECT_VERSION = '1.0.0'
        // 打包后的文件名
        JAR_FILE = "${PROJECT_NAME}-${PROJECT_VERSION}.jar"
        // 部署目录
        DEPLOY_DIR = '/opt/deploy'
        // 服务端口(与application.yml一致)
        SERVICE_PORT = 8081
    }
    
    // 工具配置(关联全局工具配置中的名称)
    tools {
        maven 'Maven3.8'
        jdk 'JDK17'
        git 'Git'
    }
    
    // 构建阶段定义
    stages {
        // 1. 拉取代码
        stage('拉取代码') {
            steps {
                echo "开始拉取代码..."
                // 替换为你的Git仓库地址
                git url: 'https://gitee.com/your-username/jenkins-demo.git',
                    branch: 'master'
            }
        }
        
        // 2. 编译代码
        stage('编译代码') {
            steps {
                echo "开始编译代码..."
                sh 'mvn clean compile'
            }
        }
        
        // 3. 运行单元测试
        stage('运行单元测试') {
            steps {
                echo "开始运行单元测试..."
                sh 'mvn test'
            }
            post {
                // 测试失败后保存测试报告
                always {
                    junit '**/target/surefire-reports/*.xml'
                }
            }
        }
        
        // 4. 打包构建
        stage('打包构建') {
            steps {
                echo "开始打包构建..."
                sh 'mvn package -DskipTests'
            }
        }
        
        // 5. 部署应用
        stage('部署应用') {
            steps {
                echo "开始部署应用..."
                
                // 创建部署目录
                sh "mkdir -p ${DEPLOY_DIR}"
                
                // 停止旧服务(如果存在)
                sh """
                    if [ -f "${DEPLOY_DIR}/${PROJECT_NAME}.pid" ]; then
                        PID=\$(cat ${DEPLOY_DIR}/${PROJECT_NAME}.pid)
                        echo "停止旧服务,PID: \$PID"
                        kill -9 \$PID || true
                    fi
                """
                
                // 复制新包到部署目录
                sh "cp target/${JAR_FILE} ${DEPLOY_DIR}/"
                
                // 启动新服务,记录PID
                sh """
                    cd ${DEPLOY_DIR}
                    nohup java -jar ${JAR_FILE} > ${PROJECT_NAME}.log 2>&1 &
                    echo \$! > ${PROJECT_NAME}.pid
                    echo "新服务启动成功,PID: \$(cat ${PROJECT_NAME}.pid)"
                """
            }
        }
        
        // 6. 验证部署
        stage('验证部署') {
            steps {
                echo "验证服务是否正常启动..."
                // 循环检查健康接口,最多等待30秒
                sh """
                    count=0
                    while true; do
                        if curl -s "http://localhost:${SERVICE_PORT}/api/users/health" | grep -q "Service is running"; then
                            echo "服务验证成功"
                            exit 0
                        fi
                        count=\$((count + 1))
                        if [ \$count -ge 6 ]; then
                            echo "服务启动超时"
                            exit 1
                        fi
                        sleep 5
                    done
                """
            }
        }
    }
    
    // 构建结果通知
    post {
        success {
            echo "构建部署成功!"
        }
        failure {
            echo "构建部署失败!"
        }
        always {
            echo "构建流程结束"
        }
    }
}

5.3 脚本关键步骤解析

  1. agent any:表示在任何可用的 Jenkins 节点上执行(后续可扩展为多节点)
  2. environment:定义环境变量,避免硬编码,方便维护
  3. tools:指定构建工具,关联全局配置中的 JDK、Maven、Git
  4. stages :核心构建阶段,每个 stage 完成一个独立任务:
    • 拉取代码:从 Git 仓库克隆代码,支持指定分支
    • 编译代码mvn clean compile清理并编译源码
    • 运行单元测试mvn test执行测试,junit插件收集测试报告
    • 打包构建mvn package生成 jar 包,-DskipTests跳过测试(已单独执行)
    • 部署应用
      • 停止旧服务(通过 PID 文件)
      • 复制新 jar 包到部署目录
      • 启动新服务,将进程 ID 写入 PID 文件
    • 验证部署:通过健康检查接口确认服务启动成功
  5. post:构建结束后执行的操作,支持成功 / 失败 / 总是执行的逻辑

5.4 运行 Pipeline 并查看结果

  1. 点击项目页面的 "Build Now" 触发构建
  2. 点击左侧 "Build History" 中的构建编号(如 #1)进入构建详情
  3. 点击 "Console Output" 查看实时构建日志
  4. 构建成功后,访问http://服务器IP:8081/api/users/health,显示 "Service is running" 即为部署成功

六、自动化进阶 ------ 让部署更智能

基础 Pipeline 实现了自动部署,但在实际生产中还需要更多功能,如代码提交触发构建、远程部署、代码质量检查、邮件通知等。

6.1 配置代码提交自动触发构建

无需手动点击 "Build Now",代码提交到 Git 后自动触发构建:

  1. 安装 "Generic Webhook Trigger" 插件
  2. 进入项目配置,在 "构建触发器" 中勾选 "Generic Webhook Trigger"
  3. 设置 "Token"(如jenkins-demo-trigger
  4. 在 Git 仓库中配置 WebHook:
    • Gitee:仓库 → 管理 → WebHooks → 添加
    • Payload URL:http://JenkinsIP:8080/generic-webhook-trigger/invoke?token=jenkins-demo-trigger
    • 触发事件:选择 "推送事件"
  5. 保存配置,后续提交代码到 master 分支会自动触发构建

6.2 远程服务器部署(通过 SSH)

实际场景中,Jenkins 和应用服务器通常分离,需要通过 SSH 部署:

  1. 进入 "系统管理" → "系统" → "Publish over SSH"
  2. 配置 SSH 服务器:
    • Name:app-server(自定义名称)
    • Hostname:应用服务器 IP
    • Username:登录用户名(如 root)
    • 选择认证方式(密码或密钥)
    • 点击 "Test Configuration" 验证连接
  3. 修改 Pipeline 的 "部署应用" 阶段:

groovy

复制代码
stage('部署应用') {
    steps {
        echo "开始部署到远程服务器..."
        
        // 通过SSH执行命令
        sshPublisher(publishers: [sshPublisherDesc(
            configName: 'app-server', // 对应配置的SSH服务器名称
            transfers: [sshTransfer(
                cleanRemote: false,
                excludes: '',
                execCommand: """
                    # 停止旧服务
                    if [ -f "/opt/deploy/${PROJECT_NAME}.pid" ]; then
                        PID=\$(cat /opt/deploy/${PROJECT_NAME}.pid)
                        kill -9 \$PID || true
                    fi
                    
                    # 启动新服务
                    cd /opt/deploy
                    nohup java -jar ${JAR_FILE} > ${PROJECT_NAME}.log 2>&1 &
                    echo \$! > ${PROJECT_NAME}.pid
                """,
                execTimeout: 120000,
                flatten: false,
                makeEmptyDirs: false,
                noDefaultExcludes: false,
                patternSeparator: '[, ]+',
                remoteDirectory: '/opt/deploy', // 远程服务器目录
                remoteDirectorySDF: false,
                removePrefix: 'target', // 移除本地的target前缀
                sourceFiles: "target/${JAR_FILE}" // 本地文件路径
            )],
            usePromotionTimestamp: false,
            useWorkspaceInPromotion: false,
            verbose: true
        )])
    }
}

6.3 集成 SonarQube 代码质量检查

SonarQube 用于检测代码中的漏洞、异味和重复代码,在 Pipeline 中添加质量门禁:

  1. 安装 SonarQube 服务器(参考官方文档)
  2. Jenkins 中安装 "SonarQube Scanner" 插件
  3. 进入 "系统管理" → "系统" → "SonarQube servers" 配置:
    • Name:SonarQube
    • Server URL:SonarQube 访问地址(如http://sonarIP:9000
    • Server authentication token:在 SonarQube 中生成的令牌
  4. 在 Pipeline 中添加代码质量检查阶段:

groovy

复制代码
stage('代码质量检查') {
    steps {
        echo "开始代码质量检查..."
        withSonarQubeEnv('SonarQube') {
            sh """
                mvn sonar:sonar \
                -Dsonar.projectKey=${PROJECT_NAME} \
                -Dsonar.projectName=${PROJECT_NAME} \
                -Dsonar.projectVersion=${PROJECT_VERSION} \
                -Dsonar.sources=src/main/java \
                -Dsonar.java.binaries=target/classes
            """
        }
    }
}

// 添加质量门禁检查(需安装Sonar Quality Gates插件)
stage('质量门禁检查') {
    steps {
        script {
            def qualityGate = waitForQualityGate()
            if (qualityGate.status != 'OK') {
                error "代码质量检查未通过: ${qualityGate.status}"
            }
        }
    }
}

6.4 邮件通知构建结果

构建完成后自动发送邮件通知相关人员:

  1. 安装 "Email Extension" 插件
  2. 进入 "系统管理" → "系统" → "Extended E-mail Notification" 配置:
    • SMTP 服务器:如smtp.163.com
    • SMTP 端口:25(或 465 用于 SSL)
    • 用户名:发件人邮箱
    • 密码:邮箱授权码
    • 默认收件人:developer@example.com
  3. 在 Pipeline 的 post 部分添加邮件通知:

groovy

复制代码
post {
    success {
        echo "构建部署成功!"
        emailext(
            subject: "构建成功: ${PROJECT_NAME} #${BUILD_NUMBER}",
            body: """
                <h3>构建信息</h3>
                <p>项目名称: ${PROJECT_NAME}</p>
                <p>构建编号: ${BUILD_NUMBER}</p>
                <p>构建结果: 成功</p>
                <p>查看详情: <a href="${BUILD_URL}">${BUILD_URL}</a></p>
            """,
            to: 'developer@example.com'
        )
    }
    failure {
        echo "构建部署失败!"
        emailext(
            subject: "构建失败: ${PROJECT_NAME} #${BUILD_NUMBER}",
            body: """
                <h3>构建信息</h3>
                <p>项目名称: ${PROJECT_NAME}</p>
                <p>构建编号: ${BUILD_NUMBER}</p>
                <p>构建结果: 失败</p>
                <p>查看详情: <a href="${BUILD_URL}">${BUILD_URL}</a></p>
            """,
            to: 'developer@example.com'
        )
    }
}

6.5 版本回滚机制

部署失败时能快速回滚到上一版本:

  1. 在部署阶段保存历史版本:

groovy

复制代码
stage('部署应用') {
    steps {
        echo "开始部署应用..."
        
        // 创建历史版本目录
        sh "mkdir -p ${DEPLOY_DIR}/history"
        
        // 备份当前版本(如果存在)
        sh """
            if [ -f "${DEPLOY_DIR}/${JAR_FILE}" ]; then
                cp ${DEPLOY_DIR}/${JAR_FILE} ${DEPLOY_DIR}/history/${JAR_FILE}.bak.${BUILD_TIMESTAMP}
                echo "备份当前版本到历史目录"
            fi
        """
        
        // 后续部署步骤...
    }
}
  1. 配置参数化构建:
    • 项目配置中勾选 "This project is parameterized"
    • 添加 "Choice Parameter",名称ACTION,选项:deployrollback
    • 添加 "String Parameter",名称ROLLBACK_VERSION,默认值为空
  2. 修改 Pipeline 支持回滚逻辑:

groovy

复制代码
stage('部署或回滚') {
    steps {
        script {
            if (params.ACTION == 'deploy') {
                // 执行正常部署步骤
                echo "执行部署操作..."
                // 部署脚本...
            } else if (params.ACTION == 'rollback') {
                // 执行回滚操作
                echo "执行回滚操作,版本: ${params.ROLLBACK_VERSION}"
                sh """
                    # 停止当前服务
                    if [ -f "${DEPLOY_DIR}/${PROJECT_NAME}.pid" ]; then
                        PID=\$(cat ${DEPLOY_DIR}/${PROJECT_NAME}.pid)
                        kill -9 \$PID || true
                    fi
                    
                    # 恢复历史版本
                    cp ${DEPLOY_DIR}/history/${params.ROLLBACK_VERSION} ${DEPLOY_DIR}/${JAR_FILE}
                    
                    # 启动服务
                    cd ${DEPLOY_DIR}
                    nohup java -jar ${JAR_FILE} > ${PROJECT_NAME}.log 2>&1 &
                    echo \$! > ${PROJECT_NAME}.pid
                """
            }
        }
    }
}

七、常见问题与解决方案

在 Jenkins 使用过程中,可能会遇到各种问题,以下是高频问题及解决方法:

7.1 构建失败:找不到 JDK/Maven

现象 :日志中出现java: command not foundmvn: command not found

原因:全局工具配置错误,Jenkins 无法找到 JDK/Maven 路径

解决

  1. 确认 JDK/Maven 实际安装路径(which javawhich mvn
  2. 进入 "全局工具配置",检查路径是否正确(绝对路径)
  3. 确保 Jenkins 用户有访问该路径的权限

7.2 权限问题:无法创建目录或执行命令

现象 :日志中出现Permission denied

原因:Jenkins 运行用户(通常是 jenkins)权限不足

解决

  1. 查看 Jenkins 运行用户:ps -ef | grep jenkins
  2. 为部署目录授权:chown -R jenkins:jenkins /opt/deploy
  3. 必要时赋予 sudo 权限(谨慎操作):编辑/etc/sudoers添加jenkins ALL=(ALL) NOPASSWD: ALL

7.3 测试失败:单元测试不通过导致构建中断

现象mvn test执行失败,构建终止

解决

  1. 查看测试报告:构建详情 → "Test Result"
  2. 修复代码中的测试用例
  3. 紧急情况下可临时跳过测试(不推荐):将mvn package改为mvn package -DskipTests

7.4 插件冲突:安装插件后 Jenkins 无法启动

现象:Jenkins 启动失败,日志中有插件相关错误

解决

  1. 进入 Jenkins 插件目录(通常是/root/.jenkins/plugins
  2. 删除冲突的插件目录(如problem-plugin/
  3. 重启 Jenkins:systemctl restart jenkins

7.5 远程部署超时:SSH 连接失败

现象 :远程部署阶段提示Connection timed out

解决

  1. 检查网络:在 Jenkins 服务器上ping 应用服务器IP
  2. 检查端口:telnet 应用服务器IP 22确认 SSH 端口开放
  3. 检查认证:重新配置 "Publish over SSH" 的密钥或密码
  4. 增加超时时间:在 sshPublisher 中设置execTimeout: 300000(5 分钟)

八、总结与展望

通过本文的步骤,可以掌握从 0 搭建 Jenkins 环境、配置自动化工具、编写 Pipeline 脚本、实现 Java 项目自动部署的完整流程。一个成熟的 CI/CD 流水线能为团队带来显著的效率提升,让开发者从繁琐的部署工作中解放出来,专注于代码质量和业务逻辑。