专栏首页liulunASP.NET CORE Linux发布工具(文件对比 只上传差异文件;自动启停WebServer命令;上传完成自动预热WebServer)

ASP.NET CORE Linux发布工具(文件对比 只上传差异文件;自动启停WebServer命令;上传完成自动预热WebServer)

最近这几日在搞一个小网站:教你啊 ;(感兴趣的朋友可以来捧场,在这个网站上有任何消费我都可以退还)

由于更新频繁,手动更新特别麻烦,于是开发了这个小工具

用了一段时间,还是挺顺手的,同时.NET CoreQQ群(225982985)的群友 @亡我之心不死  也推荐我分享出来

这就把代码公布在这里,有什么问题可以联系我:

先看配置文件App.Config:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <appSettings>
    <add key="ServerIPAddress" value="***.***.***.***"/>
    <add key="SSHUserName" value="***"/>
    <add key="SSHPassWord" value="*********"/>    
    <add key="ServerPath" value="/***/***/"/>
    <add key="ClientPath" value="D:\p\JiaoNiA\code\JNA\JNA.Web\bin\Release\netcoreapp2.0\centos.7-x64\publish\"/>
    <add key="IgnorFilePatten" value="System\..+;Microsoft\..+;.+\.so" />
    <add key="HttpServerStartCommand" value="systemctl start jna.service"/>
    <add key="HttpServerStopCommand" value="systemctl stop jna.service"/>
    <add key="WebSiteUrl" value="http://www.jiaonia.com"/>
    <add key="WebSiteAssertString" value="教你啊-知识复利制造平台"/>
  </appSettings>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/>
    </startup>
</configuration>

ServerIPAddress:服务器地址,服务器环境只支持linux

SSHUserName:服务器ssh的用户名(同时也得是sftp的用户名)

SSHPassWord:服务器ssh的密码(同时也得是sftp的密码)

ServerPath:服务器WEB程序的部署路径

ClientPath:你的开发环境,VS编译之后的路径,我用的编译命令是:

dotnet publish -c release -r centos.7-x64   

IgnorFilePatten:由于VS编译后的文件非常多,有些文件上传一次,一辈子也不用再上传了,那么就可以在这里设置一些正则表达式,过滤这些文件,减少比对工作量(正则表达式是用分号分割的)

HttpServerStopCommand:大部分时候更新程序都需要停机更新,这个命令就是停止WebServer的命令

HttpServerStartCommand:这个命令是升级完成后启动WebServer的命令

WebSiteUrl:升级完成后,并且WebServer也成功重启了,这个程序会请求一下你的web程序的URL,用来预热程序,要不然第一次访问很慢,这个URL就是在这里设置的

WebSiteAssertString:程序访问URL,会拿到服务端响应的HTML,然后判断响应的HTML是否包含这里设置的断言,有则证明升级成功;

好,来看代码:

先看几个私有的变量:(变量用户后面的注释有说明)

        static List<FileInfo> clientFileInfos = new List<FileInfo>();//用于存储本地待对比的文件
        static List<string> IgnorFilePattens = new List<string>();//用于存储过滤器,过滤器命中的文件不用参与对比
        static Dictionary<FileInfo,string> prepareFileInfo = new Dictionary<FileInfo, string>();//用于存储对比后待上传的文件        
        static NameValueCollection setting = ConfigurationManager.AppSettings;

再来看Main函数:(在几个关键点我都写了注释)

static void Main(string[] args)
{
            Console.WriteLine("开始比对文件(根据文件的修改时间)?y:开始。其他按键退出程序:");
            if (Console.ReadLine() != "y")
            {
                return;
            }
            Console.WriteLine("开始比对文件...");
            IgnorFilePattens.AddRange(setting["IgnorFilePatten"].Split(';'));//把过滤器先缓存起来
            getClientFileInfos(setting["ClientPath"]);//递归取本地文件,过滤器命中的文件跳过
            sftpCompareFile(sftpClient =>  //本地文件与服务器文件对比
            {
                if (prepareFileInfo.Count < 1)
                {
                    Console.WriteLine("没有需要更新的文件,按任意键退出程序!");
                    Console.ReadKey();
                    return;
                }
                Console.WriteLine("开始停机上传文件?y:开始。其他按键退出程序:");
                if (Console.ReadLine() != "y")
                {
                    return;
                }
                sshStartAndStopWebServer(() =>  //启停Web服务器
                {
                    foreach (var fileInfo in prepareFileInfo.Keys)
                    {
                        using (var fileStream = fileInfo.OpenRead())
                        {
                            sftpClient.BufferSize = 6 * 1024;
                            sftpClient.UploadFile(fileStream, prepareFileInfo[fileInfo],true);  //上传文件
                        }
                        Console.WriteLine("上传完成:" + prepareFileInfo[fileInfo]);
                    }
                });
                Thread.Sleep(918);   //留给服务器喘息的时间
                Console.WriteLine("开始请求目标网站...");
                var html = GetHtml(setting["WebSiteUrl"]);   //请求Web的URL
                if (html.Contains(setting["WebSiteAssertString"]))  //判断断言是否命中
                {
                    Console.WriteLine("升级成功,按任意键退出程序");
                }
                else
                {
                    Console.WriteLine("升级失败,请联系管理员!按任意键退出程序!");
                }
                Console.ReadKey();
            });
}

接下来,我们一点一点的看main函数里的几个关键点:

先看递归取本地文件,过滤器命中的文件跳过

        static void getClientFileInfos(string path)
        {
            var directoryPaths = Directory.GetDirectories(path);
            foreach (var directoryPath in directoryPaths)
            {
                getClientFileInfos(directoryPath);  //递归,自己调自己
            }
            var filePaths = Directory.GetFiles(path);
            foreach(var filePath in filePaths)
            {
                var fi = new FileInfo(filePath);
                var flag = false;
                foreach(var patten in IgnorFilePattens)
                {
                    flag = Regex.IsMatch(fi.Name, patten);
                    if (flag)
                    {
                        break;  //有一个过滤器命中,则不用管其他过滤器了
                    }
                }
                if (flag)
                {
                    continue; //命中的文件,则跳过
                }
                clientFileInfos.Add(fi);
            }
        }

本地文件与服务器文件对比:(按最后一次修改时间对比)

static void sftpCompareFile(Action<SftpClient> actor)
{
    using (var client = new SftpClient(setting["ServerIPAddress"], setting["SSHUserName"], setting["SSHPassWord"]))
    {
        client.Connect();
        foreach (var fileInfo in clientFileInfos)
        {
            var subName = fileInfo.FullName.Remove(0, setting["ClientPath"].Length);
            if (subName.StartsWith("\\"))
            {
                subName = subName.Remove(0, 1);
            }
            subName = subName.Replace('\\', '/');
            var serverPath = setting["ServerPath"] + subName;//前面几行代码都是为了拿到该文件在服务端的绝对路径,配置里的serverPath必须以/结尾,此处不做校验;
            var flag = client.Exists(serverPath);
            if (!flag)
            {
                prepareFileInfo.Add(fileInfo, serverPath); //如果服务端不存在这个文件,则这个文件是肯定要上传上去的,注意:这里没管目录存在不存在
                Console.WriteLine("待上传文件:" + subName);
            }
            else
            {
                var dt = client.GetLastWriteTime(serverPath);
                if (dt < fileInfo.LastWriteTime)  //文件最后一次更新时间比较,本地的时间比服务端的时间新,则需要上传
                {                            
                    prepareFileInfo.Add(fileInfo, serverPath);
                    Console.WriteLine("待上传文件:" + subName);
                }
            }
        }                
        actor(client);
    }
}

再来看启停Web服务器的代码:(就是直接执行配置文件中的命令,没什么特别的)

static void sshStartAndStopWebServer(Action actor)
{
    using (var sshclient = new SshClient(setting["ServerIPAddress"], setting["SSHUserName"], setting["SSHPassWord"]))
    {
        sshclient.Connect();
        using (var cmd = sshclient.CreateCommand(setting["HttpServerStopCommand"]))
        {
            cmd.Execute();
            Console.WriteLine("停用HttpServer");
        }
        actor();
        using (var cmd = sshclient.CreateCommand(setting["HttpServerStartCommand"]))
        {
            cmd.Execute();
            Console.WriteLine("启用HttpServer");
        }
        sshclient.Disconnect();
    }
}

请求HTML的代码

static string GetHtml(string url)
{
    HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
    request.Timeout = 16 * 1000;
    HttpWebResponse response = request.GetResponse() as HttpWebResponse;
    Stream stream = response.GetResponseStream();
    StreamReader reader = new StreamReader(stream, Encoding.UTF8);
    string html = reader.ReadToEnd();
    stream.Close();
    return html;
}

这个项目用到了一个关键的库:SSH.NET

在这里向作者致敬!

感兴趣的朋友,也可以加我的QQ群沟通:51021155

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 模仿angularjs写了一个简单的HTML模版和js数据填充的示例

    <!DOCTYPE html> <html lang="en" xmlns="http://www.w3.org/1999/xhtml"> <head> ...

    liulun
  • 博客园文章编辑器5.0版本发布(markdown版)

    (后来我自己发现了一些问题,于是偷偷发了博客园文章编辑器的4.0.1版本,也没通知大家,不过好在有自动升级功能)

    liulun
  • 30分钟泛型教程

    一、泛型入门: 我们先来看一个最为常见的泛型类型List<T>的定义 (真正的定义比这个要复杂的多,我这里删掉了很多东西) [Serializable] pub...

    liulun
  • NewLife.Net——网络压测单机2266万tps

    NewLife.Net压力测试,峰值4.2Gbps,50万pps,消息大小24字节,消息处理速度2266万tps!

    大石头
  • .NET Core 读取配置文件

    前面写过一篇《.NET Core类库中读取配置文件》 ,当时对于.NET Core读取配置文件了解有限,这里做下补充:

    雪飞鸿
  • c# 的析构以及垃圾回收2、3事!

    看书时,自己写的例子代码,了解到几个知识点,记载下来。同时发现自己手写代码的能力比较弱,还是得多写一下。

    申君健
  • 原 数据字典+匿名委托模拟switch/c

    魂祭心
  • 算法才是一个程序员最核心的竞争力(一)

    说到编程序,大家总是会想到各种酷炫的框架,只要掌握了新的技术框架,就觉得自己很牛了,其实真正体现一个程序牛逼的地方,就是掌握程序的灵魂:算法,今天教大家掌握一些...

    前端老鸟
  • netty案例,netty4.1中级拓展篇三《Netty传输Java对象》

    Netty在实际应用级开发中,有时候某些特定场景下会需要使用Java对象类型进行传输,但是如果使用Java本身序列化进行传输,那么对性能的损耗比较大。为此我们需...

    小傅哥
  • 23种设计模式详解(二)

    南风

扫码关注云+社区

领取腾讯云代金券