最近这几日在搞一个小网站:教你啊 ;(感兴趣的朋友能够来捧场,在这个网站上有任何消费我均可以退还)html
因为更新频繁,手动更新特别麻烦,因而开发了这个小工具linux
用了一段时间,仍是挺顺手的,同时.NET CoreQQ群(225982985)的群友 @亡我之心不死 也推荐我分享出来git
这就把代码公布在这里,有什么问题能够联系我:github
先看配置文件App.Config:web
<?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的用户名)centos
SSHPassWord:服务器ssh的密码(同时也得是sftp的密码)缓存
ServerPath:服务器WEB程序的部署路径服务器
ClientPath:你的开发环境,VS编译以后的路径,我用的编译命令是:app
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