# 用 Maven 构建 Java 应用

本教程将向你展示如何使用Pipeline编排并构建一个使用Maven管理的简单Java应用程序。
如果您是一名使用Maven的Java开发人员,并且之前并没有接触过持续集成/持续部署的概念,或者您可能熟悉这些概念但并不清楚如何使用CORNERSTONE Pipeline实现这样的构建,那么本教程适合您。

参照示例:HelloWord

# 前置要求

  1. 需要使用版本控制工具,如:SVN/Git
  2. 需及时向版本库提交代码和从代码库中更新代码
  3. 准备两台服务器:
    • 持续集成部署服务器:获取最新代码执行代码编译、代码质量检查等

      安装SVN/Git 客户端

      安装JDK,并设置好环境变量:JAVA_HOME

      安装Maven,并设置好环境变量:MAVEN_HOME

      服务器开放SSH端口(默认:22)

    • 应用服务器:产品服务器支持远程程序发布及备份。

      安装JDK,并设置好环境变量:JAVA_HOME

      服务器开放SSH端口(默认:22)、应用访问端口(默认:80)

  4. SonarQube 服务器

# 发布流程图

# 实现步骤

# 步骤一、基础
  1. 登录CORNERSTONE
  2. 工作台->选择工程->DevOps
# 步骤二、主机
# 进入主机 , 创建编译、发布主机

# 进入主机 , 创建应用主机

# 步骤三、创建应用程序
  1. 新建一个工程 File->New->Project

  2. 选择Spring Initializr(通过https:///start.spring.io中模板,初始化项目)

  3. 配置项目相关参数

  4. 选择项目的名称和项目存放的路径

  5. 应用程序结构

  6. 创建 DemoController

package io.itit.demo;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

/**
 *
 * @author skydu
 *
 */
@RestController
@RequestMapping("/demo")
public class DemoController {


	@RequestMapping("/test")
	@ResponseBody
	public String test(){
		return "Hello SpringBootDemo!";
	}


	@RequestMapping("/hello")
	@ResponseBody
	public String helloWorld(){
		return "HelloWorld";
	}

	@RequestMapping("/compare/{val}")
	@ResponseBody
	public boolean compare(@PathVariable Integer val){
		return val > 100 ? true : false;
	}

}
  1. 修改pom文件,加入 TestNG 测试框架
<!-- 依赖节点 -->
<dependency>
    <groupId>org.testng</groupId>
    <artifactId>testng</artifactId>
    <version>6.14.3</version>
</dependency>

<!-- 插件节点 -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>3.0.0-M3</version>
    <configuration>
        <suiteXmlFiles>
            <suiteXmlFile>testng.xml</suiteXmlFile>
        </suiteXmlFiles>
        <argLine>-Dfile.encoding=UTF-8</argLine>
    </configuration>
</plugin>
  1. 创建 DemoTestNGTests.java 文件
package io.itit.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.Assert;
import org.testng.annotations.Test;

@SpringBootTest(classes = DemoApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class DemoTestNGTests extends AbstractTestNGSpringContextTests {
    @Autowired
    private TestRestTemplate restTemplate;


    @Test
    public void testHello(){
        ResponseEntity<String> entity = this.restTemplate.getForEntity("/demo/hello", String.class);
        System.out.println(entity);
        Assert.assertEquals(entity.getStatusCode(),HttpStatus.OK);
        Assert.assertTrue((entity.getBody()).equals("HelloWorld"));
    }

    @Test
    public void testCompare(){
        ResponseEntity<Boolean> entity = this.restTemplate.getForEntity("/demo/compare/101", Boolean.class);
        Assert.assertEquals(entity.getStatusCode(),HttpStatus.OK);
        System.out.println(entity);
        Assert.assertTrue(entity.getBody());
    }

    @Test
    public void testCompareMin(){
        ResponseEntity<Boolean> entity = this.restTemplate.getForEntity("/demo/compare/2", Boolean.class);
        Assert.assertEquals(entity.getStatusCode(),HttpStatus.OK);
        System.out.println(entity);
        Assert.assertTrue(entity.getBody());
    }
}

  1. 创建 testng.xml 文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="All Test Suite">
    <test
          name="测试用例">
        <classes>
            <class name="io.itit.demo.DemoTestNGTests">
                <methods>
                    <include name="testHello"/>
                    <include name="testCompare"/>
                    <include name="testCompareMin"/>
                </methods>
            </class>
        </classes>
    </test>
</suite>

  1. 提交版本
# 步骤四、Pileline
# 进入Pileline , 创建Pipeline
var pipeline={
    stages:{
        compile:{
            node:"compile.example.com",
            workspace:'/data/website/repo',
            steps:{
                svnCheckOut(){
                    $.sh('svn co svn://svn.example.com/repo/SpringBootDemo /data/website/repo/SpringBootDemo');
                    $.sh('svn update SpringBootDemo');
                },
                mvnTest(){
                    $.sh('cd SpringBootDemo;mvn test;');
                    $.sh('cp target/surefire-reports/emailable-report.html src/main/resources/static/test.html');
                }
                mvnInstall(){
                    ret=$.sh('cd SpringBootDemo;mvn clean install -Dmaven.test.skip=true');
                    if(ret!=0){
                        $.abort('编译失败')
                    }
                },
                archive(){
                    $.copyTo('SpringBootDemo/target/SpringBootDemo-1.0.0.jar',
                     'uat.example.com',
                     '/data/website/SpringBootDemo/SpringBootDemo-1.0.0.jar');
                },
                sonar(){
                    $.sh('cd SpringBootDemo;mvn sonar:sonar \
                    -Dsonar.projectKey=SpringBootDemo \
                    -Dsonar.host.url=http://sonar.example.com \
                    -Dsonar.login=66fc023f54d20c447017e620143145321d0d5d5e')
                    $.print('sonar地址:http://sonar.example.com')
                },
            }
        },
        deploy:{
            node:"uat.example.com",
            workspace:'/data/website/SpringBootDemo',
            steps:{
                deploy(){
                    $.sh('./restart.sh')
                    $.print('访问地址:http://demo.example.com/')
                    $.printQRCode('http://demo.example.com/')
                    $.print('测试结果地址:http://demo.example.com/test.html')
                    $.printQRCode('http://demo.example.com/test.html')
                }
            }
        }
    }
};

# 应用服务器目录说明

SpringBootDemo
└── config -- 配置文件夹
     └── application.yml -- SpringBoot 配置文件
├── SpringBootDemo-0.0.1-SNAPSHOT.jar -- 应用打包文件
├── logs -- 日志文件夹
├── restart.sh -- 重启脚本
├── stop.sh -- 终止脚本
└── pid -- 进程Id文件

restart.sh

stop.sh

# 触发执行

[完]

# Pipeline 说明:

  • stages:示例有两个stage,一个是compile,另外一个是deploy。

    • compile:编译stage。[ stage名称可以随便定义,不一定是compile ]
    • deploy: 发布,运行stage。
  • node:对应第一步里的主机名称。在comple stage里node是compile,也就编译服务器的意思。

  • workspace:登录主机后的默认目录。

  • steps:stage里的步骤。示例中compile stage包含三步。

    • svnCheckOut:编译服务器从svn服务器拉取最新的代码用于编译。
    • mvnTest:使用TestNG 进行测试。
    • mvnInstall:使用mvn编译打包。
    • archive:把编译后生成的jar包拷贝到uat服务器指定目录。
    • sonar: 使用 SonarQube 进行代码质量分析。
    • deploy:登录发布祝主机,执行重启命令。
  • $表示pipeline自身,可以调用的函数列表如下:

# 补充说明

一条完整的Pipeline交付流水线通常会包括代码获取、单元测试、静态检查、打包部署、接口测试、 页面测试、性能测试、人工检验等 研发测试环节,还会包括灰度发布、正式发布等发布环境。

项目相关的Pipeline脚本只是正对该项目的场景展开并尽可能做到通用,部分项目请根据自己项目的情况进行脚本修改或参数调整。