1. 程式人生 > >Asp.Net Core寫個共享磁盤文件Web查看器

Asp.Net Core寫個共享磁盤文件Web查看器

瀏覽器 服務器 文件夾 圖片 安全性

查看器功能說明與演示

本查看器主要是為了方便大家查看服務器上的日誌,這裏沒有考慮其他安全性問題,比如特定人員登錄才能查看,這個需要您們自己去增加;如果你服務器有對外開放了ip,那麽運行這個軟件的時候建議考慮配置成您們公司內網的ip,這裏可以避免一些安全性問題;下面是主要功能:

. 通過可以定義文件配置常用磁盤訪問地址

. 查看磁盤目錄下的文件夾和文件

. 部分可訪問行文件(如:txt,DLL,圖片等)可以在瀏覽器中打開或下載(訪問性格式由程序配置)

. 上傳多個文件到指定磁盤

. 文件備份(如果上傳的文件已經存在,會自動備份到bak文件夾中)

效果gif圖片,有點花多多包涵:

技術分享

效果還可以吧,不妨“推薦”下;

磁盤列表功能

首先,要明確的是在NetCore1.1中api已經和大部分能和framwork對應上了([email protected]和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="[email protected]" target="_blank">@item.Name</a> 60                         } 61                         else if (item.Attributes == FileAttributes.Directory) 62                         { 63                         <img src="/images/icon/Directory1.jpg" /><a href="[email protected]">@item.Name</a> 64                         } 65                         else 66                         { 67                         <img src="/images/icon/@(item.Extension.GetExtensionIcon())" /><a href="[email protected]">@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="[email protected]" 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             try10             {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                         try45                         {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 MoSearch15     {16         public string Txt1 { get; set; }17 18         public string Sel1 { get; set; }19     }20 21     /// <summary>22     /// 文件23     /// </summary>24     public class MoFile25     {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             try15             {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                         try31                         {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:

技術分享


Asp.Net Core寫個共享磁盤文件Web查看器