最近這幾日在搞一個小網站:教你啊 ;(感興趣的朋友能夠來捧場,在這個網站上有任何消費我均可以退還)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