本篇和大家分享的是一個磁碟檔案檢視系統,嚴格來說是使用NetCore寫的一個Web系統應用,由於NetCore跨平臺特性,我生成了exe的執行包,只需要配置執行電腦ip+埠,即可在瀏覽器中通過IP+埠的方式訪問目標呼叫上的所有目錄,不錯是所有目錄(如果您有:C,D,E,F盤都可以訪問),當然為了安全最好限制下;還有上傳,備份功能,具體看下面的分享內容吧;git地址:https://github.com/shenniubuxing3/ShenNiu.LogTool
檢視器功能說明與演示
本檢視器主要是為了方便大家檢視伺服器上的日誌,這裡沒有考慮其他安全性問題,比如特定人員登入才能檢視,這個需要您們自己去增加;如果你伺服器有對外開放了ip,那麼執行這個軟體的時候建議考慮配置成您們公司內網的ip,這裡可以避免一些安全性問題;下面是主要功能:
. 通過可以定義檔案配置常用磁碟訪問地址
. 檢視磁碟目錄下的資料夾和檔案
. 部分可訪問行檔案(如:txt,DLL,圖片等)可以在瀏覽器中開啟或下載(訪問性格式由程式配置)
. 上傳多個檔案到指定磁碟
. 檔案備份(如果上傳的檔案已經存在,會自動備份到bak資料夾中)
效果gif圖片,有點花多多包涵:
效果還可以吧,不妨“推薦”下;
磁碟列表功能
首先,要明確的是在NetCore1.1中api已經和大部分能和framwork對應上了(據@善友一篇部落格簡單介紹說NetCore2.0的api已經能夠和framwork持平了),因此這裡我們能夠直接使用DirectoryInfo,來檢視磁碟路徑的資料夾和檔案,所以就有了檢視列表Action的程式碼:
1 /// <summary> 2 /// 磁碟列表 3 /// </summary> 4 /// <param name="path">磁碟路徑</param> 5 /// <returns></returns> 6 public IActionResult Index(string path) 7 { 8 Console.WriteLine($"IP:{HttpContext.Connection.RemoteIpAddress}正在檢視磁碟:{path}"); 9 var list = new List<FileSystemInfo>(); 10 MoSearch moSerach = new MoSearch { Txt1 = path }; 11 ViewData["Search"] = moSerach; 12 13 if (string.IsNullOrWhiteSpace(path)) { return View(list); } 14 if (path.StartsWith("c:", StringComparison.OrdinalIgnoreCase)) { this.MsgBox($"無許可權訪問:{path}"); return View(list); } 15 if (!System.IO.Directory.Exists(path)) { this.MsgBox($"磁碟路徑:{path}不存在!"); return View(list); } 16 DirectoryInfo dic = new DirectoryInfo(path); 17 list = dic.GetFileSystemInfos().OrderByDescending(b => b.LastWriteTime).ToList(); 18 19 return View(list); 20 }
這裡我預設限制了C盤,並且採用自帶的檔案物件FileSystemInfo來返回資訊,僅僅只需要一段 dic.GetFileSystemInfos().OrderByDescending(b => b.LastWriteTime).ToList() 就能獲取按照最新修改時間得到磁碟目錄資訊;對應的View佈局如下:
1 @using System.IO 2 @using ShenNiu.LogTool.Extension; 3 @using ShenNiu.LogTool.Controllers 4 @model List<FileSystemInfo> 5 @{ 6 ViewData["Title"] = "日誌搜尋"; 7 8 var moSearch = ViewData["Search"] as MoSearch; 9 } 10 <div> 11 <h4>@ViewData["Title"]</h4> 12 <hr /> 13 <form id="form01" method="post" enctype="multipart/form-data"> 14 <div class="form-group"> 15 <label for="txt1">磁碟路徑</label> 16 <input type="text" class="form-control" id="txt1" name="txt1" value="@moSearch.Txt1" style="max-width:100%" placeholder="會記錄到後面的下拉框"> 17 </div> 18 <div class="form-group"> 19 <label for="sel1">常用地址</label> 20 <select name="sel1" id="sel1" class="form-control"> 21 @*<option value="">==請選擇==</option> 22 <optgroup label="日誌"> 23 <option value="D:\D\Joke">D:\D\Joke</option> 24 </optgroup> 25 <optgroup label="D盤"> 26 <option value="D:\">D盤</option> 27 </optgroup>*@ 28 </select> 29 30 </div> 31 <div class="form-group "> 32 <input type="file" name="upFile" class="form-control" multiple placeholder="上傳檔案" /> 33 </div> 34 <button type="button" id="btnSearch" class="btn btn-default">查 詢</button> 35 <button type="button" id="btnUp" class="btn btn-default ">上 傳</button> 36 <a href="javascript:window.history.go(-1);" class="btn btn-default">返 回</a> 37 <span id="span01" style="color:red"> 38 @ViewData["msg"] 39 </span> 40 </form> 41 <hr /> 42 <table class="table"> 43 <thead> 44 <tr> 45 <th>檔名</th> 46 <th>磁碟路徑</th> 47 <th>最後更新時間</th> 48 <th>建立時間</th> 49 <th>操作</th> 50 </tr> 51 </thead> 52 <tbody> 53 @foreach (var item in Model) 54 { 55 <tr> 56 <td> 57 @if (item.Attributes == FileAttributes.Archive) 58 { 59 <img src="/images/icon/@(item.Extension.GetExtensionIcon())" /><a href="/log/read?path=@item.FullName" target="_blank">@item.Name</a> 60 } 61 else if (item.Attributes == FileAttributes.Directory) 62 { 63 <img src="/images/icon/Directory1.jpg" /><a href="/log/index?path=@item.FullName">@item.Name</a> 64 } 65 else 66 { 67 <img src="/images/icon/@(item.Extension.GetExtensionIcon())" /><a href="/log/index?path=@item.FullName">@item.Name</a> 68 } 69 @item.Attributes 70 </td> 71 <td>@item.FullName</td> 72 <td>@item.LastWriteTime</td> 73 <td>@item.CreationTime</td> 74 <td> 75 @if (item.Attributes == FileAttributes.Archive) 76 { 77 <a href="/log/read?path=@item.FullName" target="_blank">檢視</a> 78 } 79 </td> 80 </tr> 81 } 82 </tbody> 83 </table> 84 <div style="color:red">@ViewData["msg"]</div> 85 </div> 86 <script type="text/javascript"> 87 $(function(){ 88 89 $("#btnUp").on("click", function () { 90 var msg = $("#span01"); 91 var form = document.getElementById("form01"); 92 //console.log(form); 93 var data = new FormData(form); 94 95 $.ajax({ 96 type: "POST", 97 url: "/log/AjaxFileUp", 98 data: data, 99 100 contentType: false, 101 processData: false, 102 success: function (data) { 103 if (data) { 104 msg.html(data.msg); 105 } 106 }, 107 error: function () { 108 msg.html("上傳檔案異常,請稍後重試!"); 109 } 110 }); 111 }); 112 113 $("#btnSearch").on("click",function(){ 114 115 var sel1Val = $.trim($("select[name='sel1'] option:selected").val()); 116 var txt1Val = $.trim($("#txt1").val()); 117 118 119 var pathVal = sel1Val.length<=0?txt1Val:sel1Val; 120 window.location.href="/log/index?path="+pathVal; 121 }); 122 123 $.getJSON("/log/GetSelData",function(data){ 124 console.log(data); 125 if(data){ 126 127 128 129 var sel1 = $("select[name='sel1']"); 130 var gArr = []; 131 gArr.push('<option value="">==請選擇==</option>'); 132 $.each(data,function(i,item){ 133 134 gArr.push('<optgroup label="'+item.gname+'">'); 135 136 $.each(item.gval,function(i2,item2){ 137 138 gArr.push('<option value="'+item2.val+'">'+item2.name+'</option>'); 139 }); 140 141 gArr.push('</optgroup>'); 142 }); 143 144 sel1.html(gArr.join('')); 145 } 146 }); 147 }) 148 </script>
列表頁面的常用地址來源有系統配置檔案配置的,通過前端ajax呼叫介面獲取配置的json內容,介面Action程式碼:
1 public async Task<ContentResult> GetSelData() 2 { 3 var apiUrl = $"http://{Request.Host.Host}:{Request.Host.Port}/js/tooldata/logconf.json"; 4 var str = string.Empty; 5 using (HttpClient client = new HttpClient()) 6 { 7 client.BaseAddress = new Uri(apiUrl); 8 str = await client.GetStringAsync(apiUrl); 9 } 10 return Content(str); 11 }
配置檔案格式和內容如:
1 [ 2 { 3 "gname": "日誌", 4 "gval": [ 5 { 6 "name": "JokeLog", 7 "val": "D:\\D\\Joke" 8 } 9 ] 10 }, 11 { 12 "gname": "D盤", 13 "gval": [ 14 { 15 "name": "D盤", 16 "val": "D:\\" 17 } 18 ] 19 } 20 ]
指定磁碟目錄上傳檔案和自動備份
通常咋們有這樣的情況,我們沒有直接訪問伺服器的許可權,想上傳個東西很麻煩,每次只能通過運維(當然這是正規的流程),可是往往一些特殊情況不得不自己傳遞個東西釋出,因此這裡增加了上傳功能,並且上傳時候如果已存在相同檔案,那麼在覆蓋之前會自動增加備份到tempbak中去;
1 /// <summary> 2 /// 本檢視系統具有上傳檔案的功能 3 /// </summary> 4 /// <returns></returns> 5 [HttpPost] 6 public async Task<JsonResult> AjaxFileUp() 7 { 8 var data = new MoData { Msg = "上傳失敗" }; 9 try 10 { 11 var upPath = Request.Form["txt1"]; 12 if (string.IsNullOrWhiteSpace(upPath)) { data.Msg = "請在【磁碟路徑】輸入框輸入上傳路徑。"; return Json(data); } 13 if (!System.IO.Directory.Exists(upPath)) { data.Msg = $"磁碟路徑:{upPath}不存在!"; return Json(data); } 14 upPath = upPath.ToString().TrimEnd('\\'); 15 16 var files = Request.Form.Files.Where(b => b.Name == "upFile"); 17 //非空限制 18 if (files == null || files.Count() <= 0) { data.Msg = "請選擇上傳的檔案。"; return Json(data); } 19 20 //格式限制 21 //var allowType = new string[] { "image/jpeg", "image/png" }; 22 //if (files.Any(b => !allowType.Contains(b.ContentType))) 23 //{ 24 // data.Msg = $"只能上傳{string.Join(",", allowType)}格式的檔案。"; 25 // return Json(data); 26 //} 27 28 //大小限制 29 var nMax = 20; 30 if (files.Sum(b => b.Length) >= 1024 * 1024 * nMax) 31 { 32 data.Msg = $"上傳檔案的總大小隻能在{nMax}M以下。"; return Json(data); 33 } 34 35 //刪除過去備份的檔案 36 var basePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "tempbak"); 37 DirectoryInfo dic = new DirectoryInfo(basePath); 38 var nCount = dic.GetFiles().Count(); 39 var nMaxCount = 10; 40 if (nCount > nMaxCount) //大於nMaxCount個檔案清空臨時目錄 41 { 42 foreach (var item in dic.GetFiles().OrderBy(b => b.LastWriteTime).Take(nCount - nMaxCount)) 43 { 44 try 45 { 46 item.Delete(); 47 } 48 catch (Exception ex) { } 49 } 50 } 51 52 //寫入伺服器磁碟 53 var upLog = new StringBuilder(string.Empty); 54 foreach (var file in files) 55 { 56 57 var fileName = file.FileName; 58 var path = Path.Combine(upPath, fileName); 59 upLog.AppendFormat("檔案:{0};", path); 60 61 //存在檔案需要備份 62 if (System.IO.File.Exists(path)) 63 { 64 FileInfo info = new FileInfo(path); 65 var tempPath = Path.Combine(basePath, info.Name); //備份目錄 66 var newInfo = info.CopyTo(tempPath, true); 67 if (newInfo == null) { upLog.Append($"備份:失敗,請稍後重試!"); } 68 else { upLog.Append($"備份:成功!"); } 69 } 70 71 using (var stream = System.IO.File.Create(path)) 72 { 73 await file.CopyToAsync(stream); 74 } 75 upLog.Append($"上傳:成功;<br/>"); 76 } 77 data.Msg = upLog.ToString(); 78 data.Status = 2; 79 } 80 catch (Exception ex) 81 { 82 data.Msg += ex.Message; 83 } 84 Console.WriteLine($"IP:{HttpContext.Connection.RemoteIpAddress}正在上傳:{data.Msg}"); 85 return Json(data); 86 }
關鍵點的邏輯程式碼已經有註釋了這裡就不多說了,主要滿足咋們的業務:上傳+備份;至於上傳的js程式碼已經在上面的列表試圖中了這裡就不重複貼出來了;這裡用到了幾個自定義實體類:
1 /// <summary> 2 /// 介面統一類 3 /// </summary> 4 public class MoData 5 { 6 public string Msg { get; set; } 7 8 public int Status { get; set; } 9 } 10 11 /// <summary> 12 /// 搜尋類 13 /// </summary> 14 public class MoSearch 15 { 16 public string Txt1 { get; set; } 17 18 public string Sel1 { get; set; } 19 } 20 21 /// <summary> 22 /// 檔案 23 /// </summary> 24 public class MoFile 25 { 26 public string Name { get; set; } 27 public string Path { get; set; } 28 public string Url { get; set; } 29 public string Content { get; set; } 30 public FileAttributes Attributes { get; set; } 31 }
直接檢視內容
該系統可以直接檢視如:txt,log等字尾的檔案,因為這種型別的檔案一般都有讀,寫同時操作的情況,所以這裡我採用的方式是先拷貝當前訪問的檔案到temp臨時目錄中,然後在讀取內容或下載檔案;當滿足超過10個檔案的設定,那麼自動刪除修改時間最小的檔案,避免拷貝檔案一直增多導致磁碟空間的成本;下面是讀取Action的內容:
1 /// <summary> 2 /// 檢視內容 3 /// </summary> 4 /// <param name="path"></param> 5 /// <returns></returns> 6 public async Task<IActionResult> Read(string path) 7 { 8 Console.WriteLine($"IP:{HttpContext.Connection.RemoteIpAddress}正在檢視檔案:{path}"); 9 10 var moFile = new MoFile { Path = path }; 11 if (string.IsNullOrWhiteSpace(path)) { this.MsgBox($"檔案路徑:{path}不存在。"); return View(moFile); } 12 if (!System.IO.File.Exists(path)) { this.MsgBox($"檔案路徑:{path}不存在!"); return View(moFile); } 13 14 try 15 { 16 FileInfo info = new FileInfo(path); 17 //if (!ExtensionClass._AllowExtension.Any(b => b.ToUpper() == info.Extension.ToUpper())) 18 //{ 19 // this.MsgBox($"無法訪問{info.Extension}的檔案"); return View(moFile); 20 // } 21 22 var basePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "temp"); 23 DirectoryInfo dic = new DirectoryInfo(basePath); 24 var nCount = dic.GetFiles().Count(); 25 var nMaxCount = 10; 26 if (nCount > nMaxCount) //大於nMaxCount個檔案清空臨時目錄 27 { 28 foreach (var item in dic.GetFiles().OrderBy(b => b.LastWriteTime).Take(nCount - nMaxCount)) 29 { 30 try 31 { 32 item.Delete(); 33 } 34 catch (Exception ex) { } 35 } 36 } 37 38 var tempPath = Path.Combine(basePath, info.Name); 39 var newInfo = info.CopyTo(tempPath, true); 40 if (newInfo == null) { this.MsgBox($"檔案:{path}檢視失敗,請稍後重試!"); return View(moFile); } 41 42 moFile.Name = newInfo.Name; 43 moFile.Url = $"/{moFile.Name}"; 44 moFile.Attributes = newInfo.Attributes; 45 if (moFile.Attributes == FileAttributes.Archive && !ExtensionClass._FileExtension.Any(b => b == newInfo.Extension)) 46 { 47 using (var stream = newInfo.OpenRead()) 48 { 49 using (var reader = new StreamReader(stream)) 50 { 51 moFile.Content = await reader.ReadToEndAsync(); 52 } 53 } 54 } 55 } 56 catch (Exception ex) 57 { 58 this.MsgBox($"檔案:{path}檢視失敗,請稍後重試!"); 59 } 60 return View(moFile); 61 }
怎麼使用ShenNiu.LogTool工具呢
我這裡只提供了一個windows x64平臺的執行exe包ShenNiu.LogTool(不用安裝什麼執行環境),只需要雙擊“ShenNiu.LogTool.exe”-》配置Ip+埠(預設IP:127.0.0.1,埠:12345):
-》瀏覽器中輸入:http://127.0.0.1:12345/Log 即可訪問檢視系統,剩下的操作就如上gif截圖了;
使用nssm工具把NetCore生成的exe轉成windows服務
本篇到這裡還要講一個工具nssm(這裡不提供下載地址,個位網搜吧),因為就windows平臺而言netcore生成如果不用iis釋出,那麼大多數都是通過exe來執行的,但是我們不可能再伺服器上開很多個黑屏cmd一樣的窗體,那這樣伺服器每次關閉的話那就用不了服務了;因此我們使用nssm把這個netcore上傳的exe轉成windows服務中去,這樣就算關機重啟也能及時啟動;
由於windows服務不會提示讓咋們輸入繫結的ip,埠,所以這裡我們需要改改程式碼:
1 public static void Main(string[] args) 2 { 3 Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); 4 Console.OutputEncoding = Encoding.GetEncoding("GB2312"); 5 6 //Console.WriteLine("輸入服務繫結的Ip:"); 7 //var strHost = Console.ReadLine(); if (string.IsNullOrWhiteSpace(strHost)) { strHost = "127.0.0.1"; } 8 //Console.WriteLine("輸入服務繫結的埠:"); 9 //var strPort = Console.ReadLine(); if (string.IsNullOrWhiteSpace(strPort)) { strPort = "12345"; } 10 11 //var hostAndPort = $"http://{strHost}:{strPort}"; 12 var hostAndPort = "http://127.0.0.1:12345"; 13 14 var host = new WebHostBuilder() 15 .UseKestrel() 16 .UseUrls(hostAndPort) 17 .UseContentRoot(Directory.GetCurrentDirectory()) 18 .UseIISIntegration() 19 .UseStartup<Startup>() 20 .UseApplicationInsights() 21 .Build(); 22 23 host.Run(); 24 }
然後利用nssm工具,首先通過cmd命令執行如下命令:
執行後會彈出一個框,然後如圖操作:
再點選“install server”,不出意外的話會彈出一個 successful的提示;再來咋們看看windows服務中我們註冊的服務:
這個時候該服務是未啟動狀態,所以我們可以直接通過操作介面啟動下(當然也可以通過nssm命令啟動),能正常啟動沒問題的話,我們就可以在瀏覽器中訪問:http://127.0.0.1:12345/Log:
好了本章到此就結束了,怎麼樣乾貨還是可以吧,不妨點個"推薦",謝謝。再發下git地址:https://github.com/shenniubuxing3/ShenNiu.LogTool