本篇和大家分享的是一个磁盘文件查看系统,严格来说是使用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的代码:

  /// <summary>
/// 磁盘列表
/// </summary>
/// <param name="path">磁盘路径</param>
/// <returns></returns>
public IActionResult Index(string path)
{
Console.WriteLine($"IP:{HttpContext.Connection.RemoteIpAddress}正在查看磁盘:{path}");
var list = new List<FileSystemInfo>();
MoSearch moSerach = new MoSearch { Txt1 = path };
ViewData["Search"] = moSerach; if (string.IsNullOrWhiteSpace(path)) { return View(list); }
if (path.StartsWith("c:", StringComparison.OrdinalIgnoreCase)) { this.MsgBox($"无权限访问:{path}"); return View(list); }
if (!System.IO.Directory.Exists(path)) { this.MsgBox($"磁盘路径:{path}不存在!"); return View(list); }
DirectoryInfo dic = new DirectoryInfo(path);
list = dic.GetFileSystemInfos().OrderByDescending(b => b.LastWriteTime).ToList(); return View(list);
}

这里我默认限制了C盘,并且采用自带的文件对象FileSystemInfo来返回信息,仅仅只需要一段 dic.GetFileSystemInfos().OrderByDescending(b => b.LastWriteTime).ToList() 就能获取按照最新修改时间得到磁盘目录信息;对应的View布局如下:

 @using System.IO
@using ShenNiu.LogTool.Extension;
@using ShenNiu.LogTool.Controllers
@model List<FileSystemInfo>
@{
ViewData["Title"] = "日志搜索"; var moSearch = ViewData["Search"] as MoSearch;
}
<div>
<h4>@ViewData["Title"]</h4>
<hr />
<form id="form01" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="txt1">磁盘路径</label>
<input type="text" class="form-control" id="txt1" name="txt1" value="@moSearch.Txt1" style="max-width:100%" placeholder="会记录到后面的下拉框">
</div>
<div class="form-group">
<label for="sel1">常用地址</label>
<select name="sel1" id="sel1" class="form-control">
@*<option value="">==请选择==</option>
<optgroup label="日志">
<option value="D:\D\Joke">D:\D\Joke</option>
</optgroup>
<optgroup label="D盘">
<option value="D:\">D盘</option>
</optgroup>*@
</select> </div>
<div class="form-group ">
<input type="file" name="upFile" class="form-control" multiple placeholder="上传文件" />
</div>
<button type="button" id="btnSearch" class="btn btn-default">查 询</button>
<button type="button" id="btnUp" class="btn btn-default ">上 传</button>
<a href="javascript:window.history.go(-1);" class="btn btn-default">返 回</a>
<span id="span01" style="color:red">
@ViewData["msg"]
</span>
</form>
<hr />
<table class="table">
<thead>
<tr>
<th>文件名</th>
<th>磁盘路径</th>
<th>最后更新时间</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<tr>
<td>
@if (item.Attributes == FileAttributes.Archive)
{
<img src="/images/icon/@(item.Extension.GetExtensionIcon())" /><a href="/log/read?path=@item.FullName" target="_blank">@item.Name</a>
}
else if (item.Attributes == FileAttributes.Directory)
{
<img src="/images/icon/Directory1.jpg" /><a href="/log/index?path=@item.FullName">@item.Name</a>
}
else
{
<img src="/images/icon/@(item.Extension.GetExtensionIcon())" /><a href="/log/index?path=@item.FullName">@item.Name</a>
}
@item.Attributes
</td>
<td>@item.FullName</td>
<td>@item.LastWriteTime</td>
<td>@item.CreationTime</td>
<td>
@if (item.Attributes == FileAttributes.Archive)
{
<a href="/log/read?path=@item.FullName" target="_blank">查看</a>
}
</td>
</tr>
}
</tbody>
</table>
<div style="color:red">@ViewData["msg"]</div>
</div>
<script type="text/javascript">
$(function(){ $("#btnUp").on("click", function () {
var msg = $("#span01");
var form = document.getElementById("form01");
//console.log(form);
var data = new FormData(form); $.ajax({
type: "POST",
url: "/log/AjaxFileUp",
data: data, contentType: false,
processData: false,
success: function (data) {
if (data) {
msg.html(data.msg);
}
},
error: function () {
msg.html("上传文件异常,请稍后重试!");
}
});
}); $("#btnSearch").on("click",function(){ var sel1Val = $.trim($("select[name='sel1'] option:selected").val());
var txt1Val = $.trim($("#txt1").val()); var pathVal = sel1Val.length<=?txt1Val:sel1Val;
window.location.href="/log/index?path="+pathVal;
}); $.getJSON("/log/GetSelData",function(data){
console.log(data);
if(data){ var sel1 = $("select[name='sel1']");
var gArr = [];
gArr.push('<option value="">==请选择==</option>');
$.each(data,function(i,item){ gArr.push('<optgroup label="'+item.gname+'">'); $.each(item.gval,function(i2,item2){ gArr.push('<option value="'+item2.val+'">'+item2.name+'</option>');
}); gArr.push('</optgroup>');
}); sel1.html(gArr.join(''));
}
});
})
</script>

列表页面的常用地址来源有系统配置文件配置的,通过前端ajax调用接口获取配置的json内容,接口Action代码:

  public async Task<ContentResult> GetSelData()
{
var apiUrl = $"http://{Request.Host.Host}:{Request.Host.Port}/js/tooldata/logconf.json";
var str = string.Empty;
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri(apiUrl);
str = await client.GetStringAsync(apiUrl);
}
return Content(str);
}

配置文件格式和内容如:

 [
{
"gname": "日志",
"gval": [
{
"name": "JokeLog",
"val": "D:\\D\\Joke"
}
]
},
{
"gname": "D盘",
"gval": [
{
"name": "D盘",
"val": "D:\\"
}
]
}
]

指定磁盘目录上传文件和自动备份

通常咋们有这样的情况,我们没有直接访问服务器的权限,想上传个东西很麻烦,每次只能通过运维(当然这是正规的流程),可是往往一些特殊情况不得不自己传递个东西发布,因此这里增加了上传功能,并且上传时候如果已存在相同文件,那么在覆盖之前会自动增加备份到tempbak中去;

 /// <summary>
/// 本查看系统具有上传文件的功能
/// </summary>
/// <returns></returns>
[HttpPost]
public async Task<JsonResult> AjaxFileUp()
{
var data = new MoData { Msg = "上传失败" };
try
{
var upPath = Request.Form["txt1"];
if (string.IsNullOrWhiteSpace(upPath)) { data.Msg = "请在【磁盘路径】输入框输入上传路径。"; return Json(data); }
if (!System.IO.Directory.Exists(upPath)) { data.Msg = $"磁盘路径:{upPath}不存在!"; return Json(data); }
upPath = upPath.ToString().TrimEnd('\\'); var files = Request.Form.Files.Where(b => b.Name == "upFile");
//非空限制
if (files == null || files.Count() <= ) { data.Msg = "请选择上传的文件。"; return Json(data); } //格式限制
//var allowType = new string[] { "image/jpeg", "image/png" };
//if (files.Any(b => !allowType.Contains(b.ContentType)))
//{
// data.Msg = $"只能上传{string.Join(",", allowType)}格式的文件。";
// return Json(data);
//} //大小限制
var nMax = ;
if (files.Sum(b => b.Length) >= * * nMax)
{
data.Msg = $"上传文件的总大小只能在{nMax}M以下。"; return Json(data);
} //删除过去备份的文件
var basePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "tempbak");
DirectoryInfo dic = new DirectoryInfo(basePath);
var nCount = dic.GetFiles().Count();
var nMaxCount = ;
if (nCount > nMaxCount) //大于nMaxCount个文件清空临时目录
{
foreach (var item in dic.GetFiles().OrderBy(b => b.LastWriteTime).Take(nCount - nMaxCount))
{
try
{
item.Delete();
}
catch (Exception ex) { }
}
} //写入服务器磁盘
var upLog = new StringBuilder(string.Empty);
foreach (var file in files)
{ var fileName = file.FileName;
var path = Path.Combine(upPath, fileName);
upLog.AppendFormat("文件:{0};", path); //存在文件需要备份
if (System.IO.File.Exists(path))
{
FileInfo info = new FileInfo(path);
var tempPath = Path.Combine(basePath, info.Name); //备份目录
var newInfo = info.CopyTo(tempPath, true);
if (newInfo == null) { upLog.Append($"备份:失败,请稍后重试!"); }
else { upLog.Append($"备份:成功!"); }
} using (var stream = System.IO.File.Create(path))
{
await file.CopyToAsync(stream);
}
upLog.Append($"上传:成功;<br/>");
}
data.Msg = upLog.ToString();
data.Status = ;
}
catch (Exception ex)
{
data.Msg += ex.Message;
}
Console.WriteLine($"IP:{HttpContext.Connection.RemoteIpAddress}正在上传:{data.Msg}");
return Json(data);
}

关键点的逻辑代码已经有注释了这里就不多说了,主要满足咋们的业务:上传+备份;至于上传的js代码已经在上面的列表试图中了这里就不重复贴出来了;这里用到了几个自定义实体类:

 /// <summary>
/// 接口统一类
/// </summary>
public class MoData
{
public string Msg { get; set; } public int Status { get; set; }
} /// <summary>
/// 搜索类
/// </summary>
public class MoSearch
{
public string Txt1 { get; set; } public string Sel1 { get; set; }
} /// <summary>
/// 文件
/// </summary>
public class MoFile
{
public string Name { get; set; }
public string Path { get; set; }
public string Url { get; set; }
public string Content { get; set; }
public FileAttributes Attributes { get; set; }
}

直接查看内容

该系统可以直接查看如:txt,log等后缀的文件,因为这种类型的文件一般都有读,写同时操作的情况,所以这里我采用的方式是先拷贝当前访问的文件到temp临时目录中,然后在读取内容或下载文件;当满足超过10个文件的设置,那么自动删除修改时间最小的文件,避免拷贝文件一直增多导致磁盘空间的成本;下面是读取Action的内容:

 /// <summary>
/// 查看内容
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public async Task<IActionResult> Read(string path)
{
Console.WriteLine($"IP:{HttpContext.Connection.RemoteIpAddress}正在查看文件:{path}"); var moFile = new MoFile { Path = path };
if (string.IsNullOrWhiteSpace(path)) { this.MsgBox($"文件路径:{path}不存在。"); return View(moFile); }
if (!System.IO.File.Exists(path)) { this.MsgBox($"文件路径:{path}不存在!"); return View(moFile); } try
{
FileInfo info = new FileInfo(path);
//if (!ExtensionClass._AllowExtension.Any(b => b.ToUpper() == info.Extension.ToUpper()))
//{
// this.MsgBox($"无法访问{info.Extension}的文件"); return View(moFile);
// } var basePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "temp");
DirectoryInfo dic = new DirectoryInfo(basePath);
var nCount = dic.GetFiles().Count();
var nMaxCount = ;
if (nCount > nMaxCount) //大于nMaxCount个文件清空临时目录
{
foreach (var item in dic.GetFiles().OrderBy(b => b.LastWriteTime).Take(nCount - nMaxCount))
{
try
{
item.Delete();
}
catch (Exception ex) { }
}
} var tempPath = Path.Combine(basePath, info.Name);
var newInfo = info.CopyTo(tempPath, true);
if (newInfo == null) { this.MsgBox($"文件:{path}查看失败,请稍后重试!"); return View(moFile); } moFile.Name = newInfo.Name;
moFile.Url = $"/{moFile.Name}";
moFile.Attributes = newInfo.Attributes;
if (moFile.Attributes == FileAttributes.Archive && !ExtensionClass._FileExtension.Any(b => b == newInfo.Extension))
{
using (var stream = newInfo.OpenRead())
{
using (var reader = new StreamReader(stream))
{
moFile.Content = await reader.ReadToEndAsync();
}
}
}
}
catch (Exception ex)
{
this.MsgBox($"文件:{path}查看失败,请稍后重试!");
}
return View(moFile);
}

怎么使用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,端口,所以这里我们需要改改代码:

 public static void Main(string[] args)
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
Console.OutputEncoding = Encoding.GetEncoding("GB2312"); //Console.WriteLine("输入服务绑定的Ip:");
//var strHost = Console.ReadLine(); if (string.IsNullOrWhiteSpace(strHost)) { strHost = "127.0.0.1"; }
//Console.WriteLine("输入服务绑定的端口:");
//var strPort = Console.ReadLine(); if (string.IsNullOrWhiteSpace(strPort)) { strPort = "12345"; } //var hostAndPort = $"http://{strHost}:{strPort}";
var hostAndPort = "http://127.0.0.1:12345"; var host = new WebHostBuilder()
.UseKestrel()
.UseUrls(hostAndPort)
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.UseApplicationInsights()
.Build(); host.Run();
}

然后利用nssm工具,首先通过cmd命令执行如下命令:

执行后会弹出一个框,然后如图操作:

再点击“install server”,不出意外的话会弹出一个 successful的提示;再来咋们看看windows服务中我们注册的服务:

这个时候该服务是未启动状态,所以我们可以直接通过操作界面启动下(当然也可以通过nssm命令启动),能正常启动没问题的话,我们就可以在浏览器中访问:http://127.0.0.1:12345/Log:

好了本章到此就结束了,怎么样干货还是可以吧,不妨点个"推荐",谢谢。再发下git地址:https://github.com/shenniubuxing3/ShenNiu.LogTool

Asp.Net Core写个共享磁盘文件Web查看器的更多相关文章

  1. 使用Http-Repl工具测试ASP.NET Core 2.2中的Web Api项目

    今天,Visual Studio中没有内置工具来测试WEB API.使用浏览器,只能测试http GET请求.您需要使用Postman,SoapUI,Fiddler或Swagger等第三方工具来执行W ...

  2. 给 asp.net core 写一个简单的健康检查

    给 asp.net core 写一个简单的健康检查 Intro 健康检查可以帮助我们知道应用的当前状态是不是处于良好状态,现在无论是 docker 还是 k8s 还是现在大多数的服务注册发现大多都提供 ...

  3. 给 asp.net core 写个中间件来记录接口耗时

    给 asp.net core 写个中间件来记录接口耗时 Intro 写接口的难免会遇到别人说接口比较慢,到底慢多少,一个接口服务器处理究竟花了多长时间,如果能有具体的数字来记录每个接口耗时多少,别人再 ...

  4. ASP.NET Core中的jQuery Unobtrusive Ajax帮助器

    最近在ASP.NET Core下写文章管理系统时,准备在分页显示文章内容时,使用Ajax.网上找了篇帖文,简单翻一下,仅供自己查阅. 原链接:https://dotnetthoughts.net/jq ...

  5. 【ASP.NET Core】体验一下 Mini Web API

    在上一篇水文中,老周给大伙伴们简单演示了通过 Socket 编程的方式控制 MPD (在树莓派上).按照计划,老周还想给大伙伴们演示一下使用 Web API 来封装对 MPD 控制.思路很 Easy, ...

  6. ASP.NET Core 中文文档 第二章 指南(2)用 Visual Studio 和 ASP.NET Core MVC 创建首个 Web API

    原文:Building Your First Web API with ASP.NET Core MVC and Visual Studio 作者:Mike Wasson 和 Rick Anderso ...

  7. 使用静态基类方案让 ASP.NET Core 实现遵循 HATEOAS Restful Web API

    Hypermedia As The Engine Of Application State (HATEOAS) HATEOAS(Hypermedia as the engine of applicat ...

  8. DataProtection设置问题引起不同ASP.NET Core站点无法共享用户验证Cookie

    这是这两天ASP.NET Core迁移中遇到的一个问题.2个ASP.NET Core站点(对应于2个不同的ASP.NET Core Web应用程序),2个站点都可以登录,但在其中任1个站点登录后,在当 ...

  9. 在ASP.NET Core MVC中构建简单 Web Api

    Getting Started 在 ASP.NET Core MVC 框架中,ASP.NET 团队为我们提供了一整套的用于构建一个 Web 中的各种部分所需的套件,那么有些时候我们只需要做一个简单的 ...

随机推荐

  1. webstorm配置编译sass的输出目录

    关于这个的问题,虽说不是很难,但还是踩了点小坑,下面就来介绍下如何使用webstorm配置编译sass的输出目录. 1.下载Ruby 2.使用Ruby安装sass 3.检测是否安装成功. 前面的几步很 ...

  2. Patterns for application development with ASP.NET Core

    此文章翻译自 NDC { London } 16-20 January 2017 上, Damian Edwards和David Fowler的演讲,如果翻译不周,请大家指出错误. Logging 生 ...

  3. React的学习(下)

    摘要 众所周知,前端三大框架Angular.React.Vue,所以为了跟上时代的步伐,最近开始着手学习React,这时候就发现个大问题,框架一直在更新,提倡的编写语法也在更新,网上有许多教程都是一两 ...

  4. elasticsearch基础概念

    接近实时(NRT)        Elasticsearch是一个接近实时的搜索平台.这意味着,从索引一个文档直到这个文档能够被搜索到有一个轻微的延迟(通常是1秒).           集群(clu ...

  5. Vim安装YouCompletMe插件。

    1.Centos7.0自带含有支持python2.x的vim.(:version 后看python+则支持,python-则不支持)若不支持,卸载vim后源码编译安装. yum install pyt ...

  6. selenium IDE的3种下载安装方式

    第一种方式: 打开firefox浏览器-----点击右上角-----附加组件----插件----搜索框输入“selenium”-----搜索的结果中下拉到页面尾部,点击“查看全部的37项结果”---进 ...

  7. Swiper使用方法

    Swiper使用方法 1.首先加载插件,需要用到的文件有swiper.min.js和swiper.min.css文件. <!DOCTYPE html> <html> <h ...

  8. Android系统--输入系统(十二)Dispatch线程_总体框架

    Android系统--输入系统(十二)Dispatch线程_总体框架 1. Dispatch线程框架 我们知道Dispatch线程是分发之意,那么便可以引入两个问题:1. 发什么;2. 发给谁.这两个 ...

  9. scala和java的区别

    scala写程序跟说话一样,都是要把你的思想描述出来,但是scala最大的问题是api的符号标记太复杂, 很多都好难看懂,某些语法太过复杂,上手还是有些难度的.java很规范,但是代码太多,写逻辑很 ...

  10. hdu1151 Air Raid 二分匹配

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1151 求最小路径覆盖 二分图最小路径覆盖=点的个数-最大匹配. 代码: #include<ios ...