目前公司初步涉及微服务架构,那么持续集成就比较重要了,采用的CI的流程如图:

流程说明:

公司的项目需要使用到windows服务器,所以初步就这样做了,从svn拉取最新的项目进行构建,构建完成之后调用脚本文件发送最新的项目war包至开发和测试的应用启动目录,再配置一个监控目录文件改动的脚本,进而实现启动最新的项目,但每次去服务器的jenkins控制台启动项目不方便,而且不是每个开发者都会jenkins,所以在控制jenkins上我又写了一个ps脚本,通过远程在公司内网调用jenkins实现项目的构建,这样一套流程下来,每个开发者提交了项目之后只需启动我给的脚本过一会就能看到上线的效果了.

具体实现流程:

  1. jenkins安装 进入: Jenkin下载页面 选择 Generic Java package(.war)
  2. jenkins配置(maven,jdk,svn路径) ,maven项目支持需安装 Maven Release Plug-in Plug-in 插件
  3. 创建maven项目,配置好就行(开始不需要填写构建后操作),立即构建会在jenkins安装目录下生成workspace和项目目录,我们需要的就是 workspace目录,
  4. 将项目构建成功后的ps脚本拷贝至workspace目录,在项目配置页里面配置调用脚本命令(cmd 调用ps脚本需要特殊配置,下文会讲解)
  5. 到这一步就完成了将最新的项目war包拷贝至开发,测试服务器的功能了,然后在启动远程调用的ps脚本,就可以通过调用jenkins api启动项目的构建了

脚本讲解:

  1. 首先是构建成功后的ps脚本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# ps version 2.0 scripts
# this script is used to copy project to $PROJECTS_HOME after it loaded by jenkins projects
#################### HOW TO USE IT ##########################
# first copy this script to $JENKINS_HOME (default= "C:\Users\Administrator\.jenkins")
# then in jenkins project config page ( url = "http://server:port/job/project/configure" it must be maven project) find Post Steps block
# choose " Execute Windows batch command " and write next code in the input area
### code in jenkins config page start #####
# @echo off
# cd ..
# powershell.exe -noprofile Set-ExecutionPolicy Unrestricted
# powershell.exe -file automate.ps1 -job %JOB_NAME%
### code end ###############################
# this script will auto close after execute over
param(
[string]$job=$(throw "Parameter missing: -job %JOB_NAME%")
)
$configFile="ps.json"
$targetDrive="D:"
$targetDir="project-dev"
# 运行中脚本的绝对路劲
$scriptPath=Split-Path -Parent $MyInvocation.MyCommand.Definition
if(!(Test-Path $configFile)){
throw "$configFile 配置文件不存在!"
}
# 初始化函数
function init{
#write-host "init 方法开始执行"
$targetPath=$targetDrive+"\"+$targetDir
if(!(Test-Path -path $targetPath)){
if(!(Test-Path -path $targetDrive)){
throw " $targetDrive 盘符不存在,请创建一个再重试。 "
}
New-Item -path $targetPath -type directory
}
return $targetPath
}
function main{
#write-host "main 方法开始执行"
$targetPath=init
write-debug "$targetPath"
Start-Sleep -Seconds 10
$modules=Get-Content $configFile -encoding utf8 | ConvertFrom-Json
foreach($module in $modules){
write-debug "$module.server : $module.port"
if($module.server -eq $job){
# stopserver
stopServer $module
copyFile $module $targetPath
}
}
}
function copyFile($module,$targetPath){
#Write-Debug "copyFile 方法开始执行"
if(($module -eq "") -or ($module -eq $null)){
throw "$module must not be null"
}
$date=get-date -format MMdd_HHmm
$bacName=$module.server+"_"+$date+"_back"+$module.suffix
Write-Host "当前脚本执行目录: $scriptPath"
$proFloder=$module.server
$copyFileName=$module.fullName+"-"+$module.version+$module.suffix
Write-Host "备份文件名: $bacName"
if(Test-Path -path $targetPath){
$copyFilePath=$targetPath+"\"+$proFloder
if(Test-Path -path $copyFilePath){
$fileName=$module.fullName+"-"+$module.version+$module.suffix
if(Test-Path -path $module.server){
$copy1=$copyFilePath+"\"+$fileName
$copy2=$copyFilePath+"\"+$bacName
Copy-Item $copy1 $copy2
}else{
Write-Debug "无需备份"
}
}else{
Write-Debug "目标文件夹不存在"
New-Item -type directory -Path $copyFilePath
}
cd $scriptPath
$loadProPath=""
# 公司项目结构不统一,加上这个判断获取文件路径
if($module.hasSecond){
$loadProPath=$proFloder+"\"+$module.fullName+"\target"
}else{
$loadProPath=$proFloder+"\target"
}
if(Test-Path -path $loadProPath){
cd $loadProPath
if(Test-Path -path $copyFileName){
Copy-Item $copyFileName $copyFilePath
}else{
write-host "待复制的文件不存在,请重新构建"
}
}
}else{
Write-Debug "$targetPath 未创建成功" #可能权限问题,没遇到过
}
}
# jenkins和开发服务器放在一起的做法,否则关闭功能单独放在一个脚本里实现
function stopServer($module){
#Write-Debug "stopServer 方法开始执行"
$port=$module.port
$content=(netstat -ano | findstr $port) #这里调用的是cmd语法,ps的没这么直接的功能
if(($content -eq "") -or ($content -eq $null)){
echo "当前端口 $port 没程序正在运行"
return
}
$content=$content.split("\n")[0]
$proPID=$content.split(" ")[-1]
if($proPID -match "[\d]+"){
Stop-Process -Id $proPID -Force
}else{
echo "进程id为: $proPID 的进程停止不了,请手动关闭后再试" #这种情况还没遇到过
}
}
main

main 函数为脚本的入口,write-debug 是测试的时候输出用的,write-host是正式运行时输出用的
由于直接在jenkins里面运行项目会导致关闭困难,所以我们就在外面运行了,上面实现了文件的复制(开发服务器 + 测试服务器),下面就来实现文件的监控,运行,关闭了。文件拷贝至服务器,我们需要监控文件夹的改变才能知道文件更新了,进而才能重启项目,实现更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
write-host "jar监听"
$floder="X:\project-test" # 测试服务器上的监控文件
#文件类型
$jar="*.jar"
#时间间隔
$timeout = 1 # 60s
$projects_path = "configure.txt"
$bat_path = "..\project-test\bats"
#创建监控对象
if(test-path "$floder"){
Write-Host "文件夹已存在,不需要创建!"
}else{
md $floder
}
#监控所有jar文件的改变
$jar_watch = new-object System.IO.FileSystemWatcher $floder,$jar
$jar_watch.IncludeSubDirectories = $true
Write-Host "Ctrl+C 退出文件夹的监控"
while($true){
$jar_result = $jar_watch.WaitForChanged('Changed',$timeout)
if ($jar_result.TimedOut -eq $false)
{
$lastof = $jar_result.Name.LastIndexOf("\")
#覆盖的项目名称
$jar_name = $jar_result.Name.Substring($lastof + 1)
#write-host $jar_name
#读取项目数组
$array_projects = Get-Content $projects_path
#write-host $array_projects.Contains($war_name)
foreach($pa in $array_projects) {
write-host $pa
if($jar_name.contains($pa)) {
$bat_name = "start"+$pa.Split(".")[0]+".bat"
write-host $jar_name
cd $bat_path
start $bat_name
}
}
}
}
write-host "取消监控"

这是jar类型文件的监控,只监控change事件的文件改变。然后先启动关闭脚本关闭项目,再启动启动脚本启动项目。启动和关闭就不贴了。上面有关闭的部分代码,启动的话就和普通命令一样了。接下来再看看远程api操作jenkins进行构建:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
##################### HOW TO USE IT ########################
## 自动构建项目脚本,通过jenkins的远程api调用来实现的
## 构建的项目是根据文件名字而变化的,所以必须保证文件名的正确性(大小写敏感)
$user = "xx"
$password = "xx"
$pair = "$($user):$($password)"
$job = $MyInvocation.MyCommand.Name.Split('.')[0]
$url = "//serverurl/job/"+ $job + "/build"
$encodedCreds = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($pair))
$basicAuthValue = "Basic $encodedCreds"
$basicHeader = @{
Authorization = $basicAuthValue
}
Invoke-WebRequest -Uri http:$url -Method Post -Headers $basicHeader

这是通过设置headers来进行basic authentication的。/job/jobname/build/ 来进行执行构建请求。jobname是根据文件名来获取的,所以只需要一个文件,改个名字,其他微服务就都能跑了。

在有些系统里powershell脚本默认是记事本打开的,需要改变默认打开方式,在打开方式这里选择

C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe 为默认打开方式,首次执行powershell脚本还需要在cmd窗口执行以下命令 powershell.exe -noprofile Set-ExecutionPolicy Unrestricted 开启执行权限