实在是没有系统学习找工具,只能自己造,代替部分人工操作,实现提交代码后测试环境拉取测试,发布版本后正式环境拉取编译 发布。

环境

- -
代码环境 .Net Core 3.1
版本管理器 Gitea(Docker)搭建
操作系统 Linux(以下服务器均为 Centos 7)
正式服务器IP .15
镜像服务器IP
(作为正式服务镜像用于测试)
.38
测试服务器IP .43
代码版本服务器IP .44(就是Gitea所在服务)

提交策略(略)

提交策略

1. 服务器拉取的脚本

测试服务器中有一个脚本,从git上拉取最新代码(配置git过程略),然后将发布的代码覆盖,并启动服务。

这里我在VS中将项目发布,并一同提交,脚本直接拉取发布的文件夹进行本地覆盖,不在服务器上二次编译,但是 正式环境需要二次编译以保证编译通过才能发布。

该脚本在/home/gitpull.sh 下

注意 dev 为测试用代码分支

echo 

mainpath=/home/MES #git路径 主程序路径
cd $mainpath
echo ----流水线开始获取代码----

#reset是重置 强制拉取的目的
git reset --hard origin/dev
git pull


cd $mainpath
project=/home/MES/02_Source/MES/MES/bin/Release/netcoreapp3.1/publish #项目路径 需要发布的路径
deploy=/home/MESpublish #发布后路径
dllname=MES.dll #应用名称


echo ------执行结束命令-----
cd $deploy
./ShutDown.sh

echo ------复制发布的文件-----
cp -rf $project $deploy


echo ------执行start----
./Start.sh


echo ------结束------

ShutDown.sh 代码 用于停止.net core 项目

port=80 #项目端口号
#根据端口号查询对应的pid
pid=$(netstat -nlp | grep -w :$port  | awk '{print $7}' | awk -F"/" '{ print $1 }');
#杀掉对应的进程,如果pid不存在,则不执行
if [ -n  "$pid" ];  then
    kill -9 $pid;
fi

Start.sh 代码 用于启动项目

#!/bin/sh
#启动MVC
#先到目录下 否则报错
cd /home/MESpublish/publish
#不显示日志
nohup dotnet HD.MES.dll >/dev/null 2>&1&
~                                              

如果不cd到发布路径虽然会启动项目,但是访问会报错!原因未知

在发布后手动执行这个脚本,就可以愉快的测试了

拉取

2. Gitea Web钩子配置

这里的钩子就是当Git触发动作时向指定的Web服务推送一段JSON,动作包括 拉取 推送 发布 分支 等,JSON中包含项目信息 提交人 版本信息 等。

这里我主要配置了发布钩子

钩子配置
发布配置

保存后发送的内容

保存后发送的内容

可以看到我的 测试服务器上有一个服务8070 ,是一个自己写的WebAPI服务,核心代码如下:

3. 触发脚本的服务

该服务主要接收发布的事件,触发sh脚本(/home/MESReleases/pullPublish.sh)

using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;

namespace GitWebApiTest.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class GiteaController : ControllerBase
    {
        [HttpPost]
        public ActionResult<string> Post(dynamic _data)
        {
            dynamic data = JsonConvert.DeserializeObject(Convert.ToString(_data));
            Task.Run(() =>
            {
                string path = data["repository"]["name"].ToString();
                if (path == "项目名称")
                {
                    try
                    {
                        //Console.WriteLine(data);
                        var tag_name = data["release"]["tag_name"].ToString();

                        JArray assets = JArray.FromObject(data["release"]["assets"]);
                        var urls = assets.AsJEnumerable().Select(x => x).ToArray()
                        .Select(x =>
                            $"{x["browser_download_url"]}|{x["name"]}"
                          );

                        var prerelease = data["release"]["prerelease"].ToString() == "True" ? "1" : "0";
                        //Console.WriteLine(string.Join(",", urls));

                        if (prerelease == "true") { return; }//拦截预览版 稳定版才执行

                        //创建一个ProcessStartInfo对象 使用系统shell 指定命令和参数 设置标准输出
                        var sh = $"/home/MESReleases/pullPublish.sh {tag_name} {prerelease} {string.Join(",", urls)}";
                        Console.WriteLine(sh);

                        var psi = new ProcessStartInfo("sh", sh)
                        {
                            RedirectStandardOutput = true
                        };
                        //启动
                        var proc = Process.Start(psi);
                        if (proc == null)
                        {
                            Console.WriteLine("Can not exec.");
                        }
                        else
                        {
                            Console.WriteLine("-------------Start read standard output--------------");
                            //开始读取
                            using (var sr = proc.StandardOutput)
                            {
                                while (!sr.EndOfStream)
                                {
                                    Console.WriteLine(sr.ReadLine());
                                }

                                if (!proc.HasExited)
                                {
                                    proc.Kill();
                                }
                            }
                        }
                    }
                    catch (Exception ex) { }
                }
            });
            //}
            return "OK";
        }
    }
}

4. 推送到正式服务器

pullPublish.sh 脚本 用于将发布后的源码下载,解压,编译,打包(压缩),发送到正式服务器的指定目录,这样我在发布时点击保存,正式服务器就有包了

代码较多, 这里进行拆解

4.1 判断参数

脚本接收三个参数:

  • 版本号,必须 用于创建目录,拼接下载地址
  • 是否是预览版,必须 判断代码包是否发送到正式服务器(预览版不发送)
  • 附件地址,非必须 以逗号分割
if [ 2 -gt $# ]
   then
     echo  "缺少参数:第1个参数是版本号,第2个参数 1 预览版 0 稳定版"
     exit;
fi

4.2 创建发布目录和log目录

也可以不赋权限

mainpath=/home/MESReleases/$1
if [ ! -d $mainpath  ];then
    mkdir $mainpath
    chmod 777 $mainpath
fi
Logpath=/home/MESReleases/log`date '+%Y%m%d'`.log
echo " " >> $Logpath
echo "———————$1————————–" >> $Logpath

4.3 下载到刚才建立的文件夹

之间将版本号 $1 拼接到下载的地址

使用 wget 进行下载 -P 表示指定目录

#下载
wgeturl=http://192.168.123.44:9000/……/archive/$1.tar.gz
echo "开始下载" $wgeturl >> $Logpath
echo "开始下载" $wgeturl
wget -N $wgeturl -P $mainpath

下载文件超过1G会很慢,改为通过Git命令获取源码,git会自动检测更新项不用每次都下载 代码如下

在指定目录下载源码,执行一次就行

 cd /home/MESReleases/Source/
 git init
 git config --local core.sparsecheckout true
 git remote add origin http://192.168.123.44:9000/XXXX
 #只获取指定目录 即代码所在目录
 echo '/02_Source/' >> .git/info/sparse-checkout
 git pull origin master

将下载代码替换为获取

//到源码目录下
cd /home/MESReleases/Source/
#重置
git fetch
#拉取
git reset --hard origin/master
git pull

#如果有先删除
if [ -f $1/02_Source ]; then
 rm -rf $1/02_Source
fi
#复制 注意-r的使用,没有提示 cp 略过目录
cp -r 02_Source $1/02_Source

#此代码会让代码获取速度提示一半,并且留存了版本代码,但是只会获取最新的代码,对于发布最新代码也可以

4.4 解压

--exclude 是跳过文件的意思
--strip-components 是跳级的意思

#解压

echo "开始解压到" $mainpath >> $Logpath
echo "开始解压到" $mainpath
tar -zxf $mainpath/$1.tar.gz --exclude=/hd_mes/01_设计书说明文档 --exclude=/hd_mes/03_其他资料 -C $mainpath hd_mes/02_Source/MES/ --strip-components 1

4.5 编译发布

-nowarn 表示不弹出编号为XXX的提示
-o 发布到指定目录

echo "开始发布" $project >> $Logpath
echo '开始发布' $project

project=$mainpath/02_Source/…….csproj
publish="$mainpath/publish"
if [ ! -d $publish  ];then
    mkdir $publish
    chmod 777 $publish
fi

#增加本地变量,指定执行目录,dotnet在后台执行时会失去运行上下文导致报错
export DOTNET_CLI_HOME=/home

dotnet publish $project -c Release -o $publish --self-contained false -nowarn:msb3202,nu1503,cs1591,cs0168,cs1998,cs4014,cs0219,cs0472,MSB3277,CS8632,CS0108,CS0162,CS8321,CS1570,CS1587
echo "发布成功" >> $Logpath
echo '发布成功'

4.6 打包压缩

注意这里也是 cd 之后再压缩,否则会按路径压缩

echo "开始打包" >> $Logpath
echo '开始打包'
file=$1.tar.gz
cd $mainpath
tar czf $mainpath/$file ./publish
echo "打包完成" >> $Logpath
echo '打包完成'

4.7 发送文件

rsync -a 和 scp 的效果是一样的,发送到指定服务器的指定目录

向服务器发送 需要两个服务器设置免密

mainserver=root@192.168.2.15
imageserver=root@192.168.123.38
if [ $2 -eq 0 ];then
    echo "发送" $mainpath/$file >> $Logpath
    echo '发送' $mainpath/$file
    ssh $mainserver "mkdir $mainpath"
    ssh $imageserver "mkdir $mainpath"
    rsync -a $mainpath/$file $mainserver:$mainpath
    scp $mainpath/$file $imageserver:$mainpath
fi

4.8 附件的下载与发送

echo "得到附件" $3  >> $Logpath
if [ -n "$3" ];then
    assetpath=$mainpath/assets
    if [ ! -d $assetpath  ];then
        mkdir $assetpath
        chmod 777 $assetpath
    fi
    assets=$3
    assets=${assets//,/ }
    for element in $assets
    do
        asseturl=`echo $element | cut -d '|' -f 1`
        assetname=`echo $element | cut -d '|' -f 2`
        echo "下载附件" $asseturl "|" $assetname >> $Logpath
        wget -N $asseturl -O $assetpath/$assetname -P $assetpath
    done
    ssh $mainserver "mkdir $mainpath"
    ssh $mainserver "mkdir $assetpath"

    ssh $imageserver "mkdir $mainpath"
    ssh $imageserver "mkdir $assetpath"

    echo "发送文件" $assetpath >> $Logpath
    scp -r $assetpath $mainserver:$mainpath
    scp -r $assetpath $imageserver:$mainpath
fi
发布

5. 正式服务器更新

更新需要经历:

  • 备份当前版本
  • 解压新版本 覆盖
  • 关闭服务
  • 开启服务

得到当前版本我直接在项目中 增加了API 获取版本

version=$( curl -s http://localhost/api/Version| awk '{print $0}')

具体脚本解释参照上面

#启动
#!/bin/bash
if [ 1 -gt $# ]
   then
     echo  "缺少参数:第1个参数是版本号"
     exit;
fi
mainpath=/home/MESReleases/$1
if [ ! -f $mainpath/$1.tar.gz  ];then
     echo  "没有版本文件 $mainpath/$1.tar.gz 退出"
     exit;
fi
Logpath=/home/MESReleases/log`date '+%Y%m%d'`.log
echo " " >> $Logpath
echo "———————发布 $1————————–" >> $Logpath

version=$( curl -s http://localhost/api/Version| awk '{print $0}')
echo "得到版本信息 $version" >> $Logpath
echo "得到版本信息 $version" 
echo "备份 $version" >> $Logpath
echo "备份 $version" 
/home/MESBackup.sh $version
echo "停止 $version" >> $Logpath
echo "停止 $version" 
/home/MESpublish/Shutdown.sh
echo "更新文件 $1" >> $Logpath
echo "更新文件 $1" 
tar -zxf $mainpath/$1.tar.gz  --exclude=publish/log --exclude=publish/appsettings.json -C /home/MESpublish/
echo "启动 $1" >> $Logpath
echo "启动 $1" 
更新

之后会结合自动测试命令,让发布更敏捷

dotnet test --configuration release --logger:"trx;LogFilePrefix=testResults" -v normal -noconlog