Jenkins 自动构建Job
1.创建Job
- 登录Jenkins,点击新建Item,创建项目
- 选择Pipeline,然后点击确定
- 接下来主要在Pipeline script中编写脚本
2.签出Git仓库
2.1配置Git账号
- Manage Jenkins->Security->Credentials 在凭据界面,选择全局
- 添加凭据,添加Git用户名和密码,ID是等下需要使用的。
2.2 编写Git签出脚本
- 点击 Build Now,就会执行刚刚的脚本。并在Jenkin主目录->workspace->项目名称 下签出对应的Git仓库
3.配置何时执行
3.1 手动执行
直接点击 Build Now 按钮,会立马执行。
3.2 间隔执行
- 定时获取Git代码,代码更新则构建 (每5分钟执行一次)
- 定时构建,不管代码有没有更新(每5分钟执行一次)
3.3 Git有更新主动推送构建
- 生成触发地址
- 在Git中配置Web 钩子
使用上面的地址(JENKINS_URL:替换成地址,TOKEN_NAME:替换为身份证令牌Vue1),配置到Git的Web钩子中。可以点击测试,看返回结果。
- 禁用跨站请求伪造(解决错误:No valid crumb was included in the request)
Git仓库的Web 钩子界面,点击测试推送,返回的响应信息中,提示:No valid crumb was included in the request。
在Manage Jenkins -> Script Console中执行
hudson.security.csrf.GlobalCrumbIssuerConfiguration.DISABLE_CSRF_PROTECTION = true
- 安装Build Authorization Token Root 插件(解决错误:Authentication required)
Git仓库的Web 钩子界面,点击测试推送,返回的响应信息中,提示:Authentication required。
Manage Jenkins->Plugins 搜索Build Authorization Token Root,并点击安装 - Git仓库添加允许列表
找到gitea的安装目录gitea\custom\conf,编辑app.ini文件
4.编写构建脚本
node {
try {
stage('SCM') {
git branch: 'dev', credentialsId: 'Test', url: 'http://127.0.0.1:3000/Test.git'
// 更新子模块
bat 'git submodule update --init --recursive'
bat 'git submodule update --remote'
}
stage('BUILD') {
bat 'dotnet build'
}
stage('DEPLOY') {
bat 'dotnet publish Test.csproj -c Release -o D:/publish/Test'
}
} catch(Exception e){
}
}
5.发送邮件
当构建失败时,发送邮件通知。
5.1 配置邮件信息
- 创建邮箱凭据,步骤参考【配置Git账号】
- 配置邮箱服务器信息 Manage Jenkins->System->Extended E-mail Notification
- 编写邮件发送脚本
node {
stage('SCM') {
emailext (
subject: "编译失败: ${env.JOB_NAME} [${env.BUILD_NUMBER}]",
body: """
<p><b>${env.JOB_NAME} [${env.BUILD_NUMBER}]</b> <span style="color:red;">编译失败</span>.</p>
<p>详情: <a href="${env.BUILD_URL}console">${env.BUILD_URL}console</a></p>
<br>
""",
to: "Test@163.com",
mimeType: 'text/html',
from: "codebuild@163.com"
)
}
}
6.发布.NET Core 程序
一般Jenkins与应用站点不在同一台服务器,我们可以通过共享文件夹的方式,再使用命令,复制文件到应用站点服务器。
- 编写powershell脚本文件 copy.ps1
if (!(Test-Path "Y:\")) {
net use Y: \\192.168.1.100\website /user:administrator 123456
}
Start-Sleep 5
$source = "D:\publish\Test"
$destination = "Y:\Test"
$timeThreshold = (Get-Date).AddHours(-1) # 设置时间阈值为一个小时前
# 定义要排除的文件扩展名或名称
$excludeExtensions = @(".pdb")
$excludeNames = @("tempfile.txt", "backup.log")
# 使用Get-ChildItem获取文件,并通过管道传递给Where-Object进行筛选
Get-ChildItem -Path $source -Recurse |
Where-Object {
# 检查文件的最后写入时间是否早于阈值
#$_.LastWriteTime -lt $timeThreshold -and
# 检查文件扩展名是否不在排除列表中
(-not $excludeExtensions.Contains($_.Extension.ToLower())) -and
# 检查文件名是否不在排除列表中
(-not $excludeNames.Contains($_.Name.ToLower()))
} |
ForEach-Object {
# 构造目标路径
$destinationPath = $_.FullName.Replace($source, $destination)
# 如果当前项是文件夹,则创建目标文件夹
if ($_.PSIsContainer) {
New-Item -ItemType Directory -Path $destinationPath -Force | Out-Null
} else {
# 否则,复制文件
Copy-Item -Path $_.FullName -Destination $destinationPath -Force
}
}
- 编写jenkins脚本
powershell "D:/publish/Test.ps1"
7.启动/停止应用站点
替换.NET Core程序时,需要先停止IIS之后才能替换。我们可以在IIS中部署另外一个程序,该程序可以用来停止/启动应用程序池和应用站点。当我们需要发布.NET Core程序时,我们可以先停止应用程序和应用站点,发布成功后,在启动应用程序池和应用站点。
以下代码需要安装Microsoft.Web.Administration、System.ServiceProcess.ServiceController类库
[Route("api/[controller]/[Action]")]
[ApiController]
public class IISController : ControllerBase
{
public string GetName()
{
return "IIS";
}
/// <summary>
/// 启动站点
/// </summary>
/// <param name="siteName"></param>
/// <returns></returns>
[HttpGet("{siteName}")]
public bool StartSite(string siteName)
{
var webManager = new ServerManager();
var startSite = webManager.Sites[siteName];
if (startSite == null)
{
return false;
}
if (startSite.State.Equals(ObjectState.Stopped))
{
startSite.Start();
}
return true;
}
/// <summary>
/// 停止站点
/// </summary>
/// <param name="siteName"></param>
/// <returns></returns>
[HttpGet("{siteName}")]
public string StopSite(string siteName)
{
var webManager = new ServerManager();
var startSite = webManager.Sites[siteName];
if (startSite == null)
{
return "不存在";
}
if (startSite.State.Equals(ObjectState.Started))
{
startSite.Stop();
}
return "成功了";
}
/// <summary>
/// 启动应用池
/// </summary>
/// <param name="poolName"></param>
/// <returns></returns>
[HttpGet("{poolName}")]
public bool StartPool(string poolName)
{
var webManager = new ServerManager();
var applicationPool = webManager.ApplicationPools[poolName];
if (applicationPool == null)
{
return false;
}
if (applicationPool.State.Equals(ObjectState.Stopped))
{
applicationPool.Start();
}
return true;
}
/// <summary>
/// 停止应用池
/// </summary>
/// <param name="poolName"></param>
/// <returns></returns>
[HttpGet("{poolName}")]
public bool StopPool(string poolName)
{
var webManager = new ServerManager();
var applicationPool = webManager.ApplicationPools[poolName];
if (applicationPool == null)
{
return false;
}
if (applicationPool.State.Equals(ObjectState.Started))
{
applicationPool.Stop();
}
return true;
}
/// <summary>
/// 启动服务
/// </summary>
/// <param name="serviceName"></param>
/// <returns></returns>
[HttpGet("{serviceName}")]
public IActionResult StartService(string serviceName)
{
try
{
using (var serviceController = new ServiceController(serviceName))
{
if (serviceController.Status == ServiceControllerStatus.Stopped || serviceController.Status == ServiceControllerStatus.Paused)
{
serviceController.Start();
serviceController.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(30));
}
return Ok($"Service {serviceName} started successfully.");
}
}
catch (Exception ex)
{
return StatusCode(500, $"An error occurred while trying to start the service: {ex.Message}");
}
}
/// <summary>
/// 启动服务
/// </summary>
/// <param name="serviceName"></param>
/// <returns></returns>
[HttpGet("{serviceName}")]
public IActionResult StopService(string serviceName)
{
try
{
using (var serviceController = new ServiceController(serviceName))
{
if (serviceController.Status == ServiceControllerStatus.Running)
{
serviceController.Stop();
serviceController.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(30));
}
return Ok($"Service {serviceName} stopped successfully.");
}
}
catch (Exception ex)
{
return StatusCode(500, $"An error occurred while trying to stop the service: {ex.Message}");
}
}
}
8.完整Jenkins脚本
node {
try {
stage('SCM') {
git branch: 'dev', credentialsId: 'Test1', url: 'http://127.0.0.1:3000/Test.git'
// 更新子模块
bat 'git submodule update --init --recursive'
bat 'git submodule update --remote'
}
stage('BUILD') {
bat 'dotnet build'
}
stage('DEPLOY') {
bat 'dotnet publish Test/Test.csproj -c Release -o D:/publish/Test'
}
stage('STOPSITE') {
bat "curl -X GET \"http://192.168.1.100:2000/api/iis/StopSite/Test\""
}
stage('STOPPOOL') {
bat "curl -X GET \"http://192.168.1.100:2000/api/iis/StopPool/Test\""
}
stage('COPY') {
powershell "D:/publish/test.ps1"
}
stage('STARTPOOL') {
bat "curl -X GET \"http://192.168.1.100:2000/api/iis/StartPool/Test\""
}
stage('STARTSITE') {
bat "curl -X GET \"http://192.168.1.100:2000/api/iis/StartSite/Test\""
}
} catch(Exception e){
// 构建失败时捕获异常并发送邮件
emailext (
subject: "编译失败: ${env.JOB_NAME} [${env.BUILD_NUMBER}]",
body: """
<p><b>${env.JOB_NAME} [${env.BUILD_NUMBER}]</b> <span style="color:red;">编译失败</span>.</p>
<p>详情: <a href="${env.BUILD_URL}console">${env.BUILD_URL}console</a></p>
<br>
""",
to: "test@163.com",
mimeType: 'text/html',
from: "test2@163.com"
)
}
}