增加功能:系统日志增加访问日志 closed #ITO8H
#Issue https://gitee.com/LongbowEnterprise/BootstrapAdmin/issues/ITO8H
This commit is contained in:
parent
7fe2763f8e
commit
45adc853e2
Binary file not shown.
|
@ -1,6 +1,6 @@
|
|||
using Bootstrap.Admin.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
|
||||
|
@ -53,6 +53,12 @@ namespace Bootstrap.Admin.Controllers
|
|||
/// <returns></returns>
|
||||
public ActionResult Logs() => View(new NavigatorBarModel(this));
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public ActionResult Traces() => View(new NavigatorBarModel(this));
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
|
@ -125,7 +131,7 @@ namespace Bootstrap.Admin.Controllers
|
|||
/// <summary>
|
||||
/// 用于测试ExceptionFilter
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <returns></returns>
|
||||
public ActionResult Error() => throw new Exception("Customer Excetion UnitTest");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
using Bootstrap.Admin.Query;
|
||||
using Bootstrap.DataAccess;
|
||||
using Longbow.Web.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Bootstrap.Admin.Controllers.Api
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class TracesController : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
public QueryData<Trace> Get([FromQuery]QueryTraceOptions value)
|
||||
{
|
||||
return value.RetrieveData();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,40 +20,41 @@ namespace Microsoft.AspNetCore.Builder
|
|||
/// <param name="builder"></param>
|
||||
/// <returns></returns>
|
||||
public static IApplicationBuilder UseOnlineUsers(this IApplicationBuilder builder) => builder.UseWhen(context => context.Filter(), app => app.Use(async (context, next) =>
|
||||
{
|
||||
await System.Threading.Tasks.Task.Run(() =>
|
||||
{
|
||||
var user = UserHelper.RetrieveUserByUserName(context.User.Identity.Name);
|
||||
if (user == null) return;
|
||||
{
|
||||
await System.Threading.Tasks.Task.Run(() =>
|
||||
{
|
||||
var user = UserHelper.RetrieveUserByUserName(context.User.Identity.Name);
|
||||
if (user == null) return;
|
||||
|
||||
var onlineUserSvr = context.RequestServices.GetRequiredService<IOnlineUsers>();
|
||||
var proxy = new Func<OnlineUserCache, Action, OnlineUserCache>((c, action) =>
|
||||
{
|
||||
var v = c.User;
|
||||
v.UserName = user.UserName;
|
||||
v.DisplayName = user.DisplayName;
|
||||
v.LastAccessTime = DateTime.Now;
|
||||
v.Method = context.Request.Method;
|
||||
v.RequestUrl = context.Request.Path;
|
||||
v.AddRequestUrl(context.Request.Path);
|
||||
action?.Invoke();
|
||||
return c;
|
||||
});
|
||||
onlineUserSvr.AddOrUpdate(context.Connection.Id ?? "", key =>
|
||||
{
|
||||
var agent = new UserAgent(context.Request.Headers["User-Agent"]);
|
||||
var v = new OnlineUser();
|
||||
v.ConnectionId = key;
|
||||
v.Ip = (context.Connection.RemoteIpAddress ?? IPAddress.IPv6Loopback).ToString();
|
||||
v.Location = onlineUserSvr.RetrieveLocaleByIp(v.Ip);
|
||||
v.Browser = $"{agent.Browser.Name} {agent.Browser.Version}";
|
||||
v.OS = $"{agent.OS.Name} {agent.OS.Version}";
|
||||
v.FirstAccessTime = DateTime.Now;
|
||||
return proxy(new OnlineUserCache(v, () => onlineUserSvr.TryRemove(key, out _)), null);
|
||||
}, (key, v) => proxy(v, () => v.Reset()));
|
||||
});
|
||||
await next();
|
||||
}));
|
||||
var onlineUserSvr = context.RequestServices.GetRequiredService<IOnlineUsers>();
|
||||
var proxy = new Func<OnlineUserCache, Action, OnlineUserCache>((c, action) =>
|
||||
{
|
||||
var v = c.User;
|
||||
v.UserName = user.UserName;
|
||||
v.DisplayName = user.DisplayName;
|
||||
v.LastAccessTime = DateTime.Now;
|
||||
v.Method = context.Request.Method;
|
||||
v.RequestUrl = context.Request.Path;
|
||||
v.AddRequestUrl(context.Request.Path);
|
||||
action?.Invoke();
|
||||
TraceHelper.Save(new Trace { Ip = v.Ip, RequestUrl = v.RequestUrl, LogTime = v.LastAccessTime, City = v.Location, Browser = v.Browser, OS = v.OS, UserName = v.UserName });
|
||||
return c;
|
||||
});
|
||||
onlineUserSvr.AddOrUpdate(context.Connection.Id ?? "", key =>
|
||||
{
|
||||
var agent = new UserAgent(context.Request.Headers["User-Agent"]);
|
||||
var v = new OnlineUser();
|
||||
v.ConnectionId = key;
|
||||
v.Ip = (context.Connection.RemoteIpAddress ?? IPAddress.IPv6Loopback).ToString();
|
||||
v.Location = onlineUserSvr.RetrieveLocaleByIp(v.Ip);
|
||||
v.Browser = $"{agent.Browser.Name} {agent.Browser.Version}";
|
||||
v.OS = $"{agent.OS.Name} {agent.OS.Version}";
|
||||
v.FirstAccessTime = DateTime.Now;
|
||||
return proxy(new OnlineUserCache(v, () => onlineUserSvr.TryRemove(key, out _)), null);
|
||||
}, (key, v) => proxy(v, () => v.Reset()));
|
||||
});
|
||||
await next();
|
||||
}));
|
||||
|
||||
private static bool Filter(this HttpContext context)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
using Bootstrap.DataAccess;
|
||||
using Longbow.Web.Mvc;
|
||||
using System;
|
||||
|
||||
namespace Bootstrap.Admin.Query
|
||||
{
|
||||
/// <summary>
|
||||
/// Query trace options.
|
||||
/// </summary>
|
||||
public class QueryTraceOptions : PaginationOption
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public DateTime? OperateTimeStart { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public DateTime? OperateTimeEnd { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public QueryData<Trace> RetrieveData()
|
||||
{
|
||||
var data = TraceHelper.Retrieves(this, OperateTimeStart, OperateTimeEnd);
|
||||
|
||||
var ret = new QueryData<Trace>();
|
||||
ret.total = data.TotalItems;
|
||||
ret.rows = data.Items;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
@model NavigatorBarModel
|
||||
@{
|
||||
ViewBag.Title = "访问日志";
|
||||
}
|
||||
@section css {
|
||||
<environment include="Development">
|
||||
<link href="~/lib/datetimepicker/css//bootstrap-datetimepicker.css" rel="stylesheet" />
|
||||
<link href="~/lib/bootstrap-table/bootstrap-table.css" rel="stylesheet" />
|
||||
</environment>
|
||||
<environment exclude="Development">
|
||||
<link href="~/lib/datetimepicker/css//bootstrap-datetimepicker.min.css" rel="stylesheet" />
|
||||
<link href="~/lib/bootstrap-table/bootstrap-table.min.css" rel="stylesheet" />
|
||||
</environment>
|
||||
}
|
||||
@section javascript {
|
||||
<environment include="Development">
|
||||
<script src="~/lib/bootstrap-table/bootstrap-table.js"></script>
|
||||
<script src="~/lib/bootstrap-table/extensions/export/bootstrap-table-export.js"></script>
|
||||
<script src="~/lib/bootstrap-table/locale/bootstrap-table-zh-CN.js"></script>
|
||||
<script src="~/lib/tablexport/tableExport.js"></script>
|
||||
<script src="~/lib/datetimepicker/js/bootstrap-datetimepicker.js"></script>
|
||||
</environment>
|
||||
<environment exclude="Development">
|
||||
<script src="~/lib/bootstrap-table/bootstrap-table.min.js"></script>
|
||||
<script src="~/lib/bootstrap-table/extensions/export/bootstrap-table-export.min.js"></script>
|
||||
<script src="~/lib/bootstrap-table/locale/bootstrap-table-zh-CN.min.js"></script>
|
||||
<script src="~/lib/tablexport/tableExport.min.js"></script>
|
||||
<script src="~/lib/datetimepicker/js/bootstrap-datetimepicker.min.js"></script>
|
||||
</environment>
|
||||
<script src="~/lib/datetimepicker/js/locales/bootstrap-datetimepicker.zh-CN.js"></script>
|
||||
<script src="~/js/traces.js" asp-append-version="true"></script>
|
||||
}
|
||||
<div class="card">
|
||||
<div class="card-header">查询条件</div>
|
||||
<div class="card-body">
|
||||
<form class="form-inline">
|
||||
<div class="row">
|
||||
<div class="form-group col-sm-auto">
|
||||
<label class="control-label" for="txt_operate_start">起始时间</label>
|
||||
<div class="input-group date">
|
||||
<input id="txt_operate_start" class="form-control" size="16" type="text" value="" readonly>
|
||||
<div class="input-group-append input-group-addon">
|
||||
<div class="input-group-text"><span class="fa fa-times"></span></div>
|
||||
</div>
|
||||
<div class="input-group-append input-group-addon">
|
||||
<div class="input-group-text"><span class="fa fa-calendar"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-sm-auto">
|
||||
<label class="control-label" for="txt_operate_end">终止时间</label>
|
||||
<div class="input-group date">
|
||||
<input id="txt_operate_end" class="form-control" size="16" type="text" value="" readonly>
|
||||
<div class="input-group-append input-group-addon">
|
||||
<div class="input-group-text"><span class="fa fa-times"></span></div>
|
||||
</div>
|
||||
<div class="input-group-append input-group-addon">
|
||||
<div class="input-group-text"><span class="fa fa-calendar"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-sm-auto flex-sm-fill justify-content-sm-end">
|
||||
<button type="button" id="btn_query" class="btn btn-primary btn-fill"><i class="fa fa-search" aria-hidden="true"></i><span>查询</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
查询结果
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table></table>
|
||||
</div>
|
||||
</div>
|
||||
@section modal {
|
||||
<div class="modal fade" id="dialogRequestData" tabindex="-1" role="dialog" data-backdrop="static" aria-labelledby="myModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="myModalLabel">请求数据明细窗口</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="form-inline">
|
||||
<div class="form-row">
|
||||
<div class="form-group col-12">
|
||||
<pre id="requestData"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
||||
<i class="fa fa-times"></i>
|
||||
<span>关闭</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
$(function () {
|
||||
var url = 'api/Traces';
|
||||
var $data = $('#requestData');
|
||||
var $dialog = $('#dialogRequestData');
|
||||
|
||||
$('.card-body table').smartTable({
|
||||
url: url,
|
||||
sortName: 'LogTime',
|
||||
sortOrder: 'desc',
|
||||
queryParams: function (params) { return $.extend(params, { OperateTimeStart: $("#txt_operate_start").val(), OperateTimeEnd: $("#txt_operate_end").val() }); },
|
||||
columns: [
|
||||
{ title: "用户名称", field: "UserName", sortable: true },
|
||||
{ title: "操作时间", field: "LogTime", sortable: true },
|
||||
{ title: "登录主机", field: "Ip", sortable: true },
|
||||
{ title: "操作地点", field: "City", sortable: true },
|
||||
{ title: "浏览器", field: "Browser", sortable: true },
|
||||
{ title: "操作系统", field: "OS", sortable: true },
|
||||
{ title: "操作页面", field: "RequestUrl", sortable: true }
|
||||
],
|
||||
exportOptions: {
|
||||
fileName: "访问日志数据",
|
||||
ignoreColumn: [8]
|
||||
}
|
||||
});
|
||||
});
|
|
@ -138,6 +138,17 @@ namespace Bootstrap.DataAccess.MongoDB
|
|||
return DBAccess.GetCollection<DataAccess.ResetUser>("ResetUsers");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static IMongoCollection<DataAccess.Trace> Traces
|
||||
{
|
||||
get
|
||||
{
|
||||
return DBAccess.GetCollection<DataAccess.Trace>("Traces");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
private static void InitDb()
|
||||
|
@ -232,15 +243,23 @@ namespace Bootstrap.DataAccess.MongoDB
|
|||
md.IdMemberMap.SetIgnoreIfDefault(true);
|
||||
md.UnmapMember(ex => ex.Period);
|
||||
});
|
||||
}
|
||||
if (!BsonClassMap.IsClassMapRegistered(typeof(DataAccess.Log)))
|
||||
}
|
||||
if (!BsonClassMap.IsClassMapRegistered(typeof(DataAccess.Trace)))
|
||||
{
|
||||
BsonClassMap.RegisterClassMap<DataAccess.Log>(md =>
|
||||
BsonClassMap.RegisterClassMap<DataAccess.Trace>(md =>
|
||||
{
|
||||
md.AutoMap();
|
||||
md.IdMemberMap.SetSerializer(new StringSerializer(BsonType.ObjectId));
|
||||
md.IdMemberMap.SetIgnoreIfDefault(true);
|
||||
});
|
||||
}
|
||||
if (!BsonClassMap.IsClassMapRegistered(typeof(DataAccess.Log)))
|
||||
{
|
||||
BsonClassMap.RegisterClassMap<DataAccess.Log>(md =>
|
||||
{
|
||||
md.AutoMap();
|
||||
md.AddKnownType(typeof(DataAccess.Trace));
|
||||
});
|
||||
}
|
||||
if (!BsonClassMap.IsClassMapRegistered(typeof(DataAccess.LoginUser)))
|
||||
{
|
||||
|
@ -259,7 +278,7 @@ namespace Bootstrap.DataAccess.MongoDB
|
|||
md.IdMemberMap.SetSerializer(new StringSerializer(BsonType.ObjectId));
|
||||
md.IdMemberMap.SetIgnoreIfDefault(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
using Longbow.Web.Mvc;
|
||||
using MongoDB.Driver;
|
||||
using PetaPoco;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Bootstrap.DataAccess.MongoDB
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class Trace : DataAccess.Trace
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override Page<DataAccess.Trace> Retrieves(PaginationOption po, DateTime? startTime, DateTime? endTime)
|
||||
{
|
||||
// filter
|
||||
var filterBuilder = Builders<DataAccess.Trace>.Filter;
|
||||
var filter = filterBuilder.Empty;
|
||||
if (startTime.HasValue) filter = filterBuilder.Gt("LogTime", startTime.Value);
|
||||
if (endTime.HasValue) filter = filterBuilder.Lt("LogTime", endTime.Value.AddDays(1).AddSeconds(-1));
|
||||
if (startTime == null && endTime == null) filter = filterBuilder.Gt("LogTime", DateTime.Today.AddDays(-7));
|
||||
|
||||
// sort
|
||||
var sortBuilder = Builders<DataAccess.Trace>.Sort;
|
||||
SortDefinition<DataAccess.Trace> sort = null;
|
||||
switch (po.Sort)
|
||||
{
|
||||
case "LogTime":
|
||||
sort = po.Order == "asc" ? sortBuilder.Ascending(t => t.LogTime) : sortBuilder.Descending(t => t.LogTime);
|
||||
break;
|
||||
case "IP":
|
||||
sort = po.Order == "asc" ? sortBuilder.Ascending(t => t.Ip) : sortBuilder.Descending(t => t.Ip);
|
||||
break;
|
||||
case "UserName":
|
||||
sort = po.Order == "asc" ? sortBuilder.Ascending(t => t.UserName) : sortBuilder.Descending(t => t.UserName);
|
||||
break;
|
||||
case "City":
|
||||
sort = po.Order == "asc" ? sortBuilder.Ascending(t => t.City) : sortBuilder.Descending(t => t.City);
|
||||
break;
|
||||
case "Browser":
|
||||
sort = po.Order == "asc" ? sortBuilder.Ascending(t => t.Browser) : sortBuilder.Descending(t => t.Browser);
|
||||
break;
|
||||
case "OS":
|
||||
sort = po.Order == "asc" ? sortBuilder.Ascending(t => t.OS) : sortBuilder.Descending(t => t.OS);
|
||||
break;
|
||||
case "RequestUrl":
|
||||
sort = po.Order == "asc" ? sortBuilder.Ascending(t => t.RequestUrl) : sortBuilder.Descending(t => t.RequestUrl);
|
||||
break;
|
||||
}
|
||||
|
||||
var traces = DbManager.Traces.Find(filter).Sort(sort).ToList();
|
||||
return new Page<DataAccess.Trace>()
|
||||
{
|
||||
Context = traces,
|
||||
CurrentPage = po.PageIndex,
|
||||
ItemsPerPage = po.Limit,
|
||||
TotalItems = traces.Count,
|
||||
TotalPages = (long)Math.Ceiling(traces.Count * 1.0 / po.Limit),
|
||||
Items = traces.Skip(po.Offset).Take(po.Limit).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="p"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Save(DataAccess.Trace p)
|
||||
{
|
||||
p.Id = null;
|
||||
DbManager.Traces.InsertOne(p);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -28,6 +28,7 @@ namespace Bootstrap.DataAccess
|
|||
database.AddMap<Menu>("Navigations", new string[] { "ParentName", "CategoryName", "Active", "Menus" });
|
||||
database.AddMap<Role>("Roles", new string[] { "Checked" });
|
||||
database.AddMap<Task>("Tasks");
|
||||
database.AddMap<Trace>("Traces");
|
||||
return database;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@ namespace Bootstrap.DataAccess
|
|||
/// <summary>
|
||||
/// 查询所有日志信息
|
||||
/// </summary>
|
||||
/// <param name="tId"></param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<Log> Retrieves() => CacheManager.GetOrAdd(RetrieveLogsDataKey, key => DbContextManager.Create<Log>().Retrieves());
|
||||
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
using Longbow.Data;
|
||||
using Longbow.Web.Mvc;
|
||||
using PetaPoco;
|
||||
using System;
|
||||
|
||||
namespace Bootstrap.DataAccess
|
||||
{
|
||||
public static class TraceHelper
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 保存访问历史记录
|
||||
/// </summary>
|
||||
/// <param name="p"></param>
|
||||
public static void Save(Trace p) => DbContextManager.Create<Trace>().Save(p);
|
||||
|
||||
/// <summary>
|
||||
/// 获得指定IP历史访问记录
|
||||
/// </summary>
|
||||
/// <param name="po"></param>
|
||||
/// <param name="startTime"></param>
|
||||
/// <param name="endTime"></param>
|
||||
/// <returns></returns>
|
||||
public static Page<Trace> Retrieves(PaginationOption po, DateTime? startTime, DateTime? endTime) => DbContextManager.Create<Trace>().Retrieves(po, startTime, endTime);
|
||||
}
|
||||
}
|
|
@ -6,53 +6,13 @@ namespace Bootstrap.DataAccess
|
|||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class Log
|
||||
public class Log : Trace
|
||||
{
|
||||
/// <summary>
|
||||
/// 获得/设置 操作日志主键ID
|
||||
/// </summary>
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 操作类型
|
||||
/// </summary>
|
||||
public string CRUD { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 用户名称
|
||||
/// </summary>
|
||||
public string UserName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 操作时间
|
||||
/// </summary>
|
||||
public DateTime LogTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 客户端IP
|
||||
/// </summary>
|
||||
public string Ip { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string City { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string Browser { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string OS { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取/设置 请求网址
|
||||
/// </summary>
|
||||
public string RequestUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 请求数据
|
||||
/// </summary>
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
using Longbow.Web.Mvc;
|
||||
using PetaPoco;
|
||||
using System;
|
||||
|
||||
namespace Bootstrap.DataAccess
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class Trace
|
||||
{
|
||||
/// <summary>
|
||||
/// 获得/设置 操作日志主键ID
|
||||
/// </summary>
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 用户名称
|
||||
/// </summary>
|
||||
public string UserName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 操作时间
|
||||
/// </summary>
|
||||
public DateTime LogTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 客户端IP
|
||||
/// </summary>
|
||||
public string Ip { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string City { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string Browser { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string OS { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取/设置 请求网址
|
||||
/// </summary>
|
||||
public string RequestUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 保存访问历史记录
|
||||
/// </summary>
|
||||
/// <param name="p"></param>
|
||||
public virtual bool Save(Trace p)
|
||||
{
|
||||
if (p == null) throw new ArgumentNullException(nameof(p));
|
||||
DbManager.Create().Save(p);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="ip"></param>
|
||||
/// <returns></returns>
|
||||
public virtual Page<Trace> Retrieves(PaginationOption po, DateTime? startTime, DateTime? endTime)
|
||||
{
|
||||
var sql = new Sql("select * from Traces");
|
||||
if (startTime.HasValue) sql.Append("where LogTime > @0", startTime.Value);
|
||||
if (endTime.HasValue) sql.Append("where LogTime < @0", endTime.Value.AddDays(1).AddSeconds(-1));
|
||||
if (startTime == null && endTime == null) sql.Append("where LogTime > @0", DateTime.Today.AddDays(-7));
|
||||
sql.Append($"order by {po.Sort} {po.Order}");
|
||||
|
||||
return DbManager.Create().Page<Trace>(po.PageIndex, po.Limit, sql);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -58,6 +58,7 @@ INSERT [dbo].[Navigations] ([ID], [ParentId], [Name], [Order], [Icon], [Url], [C
|
|||
INSERT [dbo].[Navigations] ([ID], [ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (13, 0, N'日志管理', 130, N'fa fa-gears', N'#', N'0')
|
||||
INSERT [dbo].[Navigations] ([ID], [ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (20, 13, N'操作日志', 10, N'fa fa-edit', N'~/Admin/Logs', N'0')
|
||||
INSERT [dbo].[Navigations] ([ID], [ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (21, 13, N'登录日志', 20, N'fa fa-user-circle-o', N'~/Admin/Logins', N'0')
|
||||
INSERT [dbo].[Navigations] ([ID], [ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (22, 13, N'访问日志', 30, N'fa fa-bars', N'~/Admin/Traces', N'0')
|
||||
INSERT [dbo].[Navigations] ([ID], [ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (14, 0, N'在线用户', 140, N'fa fa-users', N'~/Admin/Online', N'0')
|
||||
INSERT [dbo].[Navigations] ([ID], [ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (15, 0, N'程序异常', 150, N'fa fa-cubes', N'~/Admin/Exceptions', N'0')
|
||||
INSERT [dbo].[Navigations] ([ID], [ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (16, 0, N'工具集合', 160, N'fa fa-gavel', N'#', N'0')
|
||||
|
|
|
@ -535,3 +535,33 @@ GO
|
|||
|
||||
SET ANSI_PADDING OFF
|
||||
GO
|
||||
|
||||
/****** Object: Table [dbo].[Traces] Script Date: 03/16/2019 18:37:46 ******/
|
||||
SET ANSI_NULLS ON
|
||||
GO
|
||||
|
||||
SET QUOTED_IDENTIFIER ON
|
||||
GO
|
||||
|
||||
SET ANSI_PADDING ON
|
||||
GO
|
||||
|
||||
CREATE TABLE [dbo].[Traces](
|
||||
[ID] [int] IDENTITY(1,1) NOT NULL,
|
||||
[UserName] [varchar](50) NOT NULL,
|
||||
[LogTime] [datetime] NOT NULL,
|
||||
[Ip] [varchar](15) NOT NULL,
|
||||
[Browser] [varchar](50) NULL,
|
||||
[OS] [varchar](50) NULL,
|
||||
[City] [nvarchar](50) NULL,
|
||||
[RequestUrl] [nvarchar](500) NOT NULL,
|
||||
CONSTRAINT [PK_Traces] PRIMARY KEY CLUSTERED
|
||||
(
|
||||
[ID] ASC
|
||||
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
|
||||
) ON [PRIMARY]
|
||||
|
||||
GO
|
||||
|
||||
SET ANSI_PADDING OFF
|
||||
GO
|
|
@ -179,6 +179,18 @@
|
|||
"IsResource": NumberInt(0),
|
||||
"Application": "0"
|
||||
},
|
||||
{
|
||||
"_id": ObjectId("5bd9b3d868aa001661776f59"),
|
||||
"ParentId": "5bd7b8445fa31256f77e4b9c",
|
||||
"Name": "访问日志",
|
||||
"Order": NumberInt(30),
|
||||
"Icon": "fa fa-bars",
|
||||
"Url": "~/Admin/Traces",
|
||||
"Category": "0",
|
||||
"Target": "_self",
|
||||
"IsResource": NumberInt(0),
|
||||
"Application": "0"
|
||||
},
|
||||
{
|
||||
"_id": ObjectId("5bd7b8445fa31256f77e4b89"),
|
||||
"ParentId": "0",
|
||||
|
|
|
@ -55,6 +55,7 @@ INSERT INTO Navigations (ID, ParentId, Name, `Order`, Icon, Url, Category) VALUE
|
|||
INSERT INTO Navigations (ID, ParentId, Name, `Order`, Icon, Url, Category) VALUES (13, 0, '系统日志', 130, 'fa fa-gears', '#', '0');
|
||||
INSERT INTO Navigations (ID, ParentId, Name, `Order`, Icon, Url, Category) VALUES (20, 13, '操作日志', 10, 'fa fa-edit', '~/Admin/Logs', '0');
|
||||
INSERT INTO Navigations (ID, ParentId, Name, `Order`, Icon, Url, Category) VALUES (21, 13, '登录日志', 20, 'fa fa-user-circle-o', '~/Admin/Logins', '0');
|
||||
INSERT INTO Navigations (ID, ParentId, Name, `Order`, Icon, Url, Category) VALUES (21, 13, '访问日志', 30, 'fa fa-bars', '~/Admin/Traces', '0');
|
||||
INSERT INTO Navigations (ID, ParentId, Name, `Order`, Icon, Url, Category) VALUES (14, 0, '在线用户', 140, 'fa fa-users', '~/Admin/Online', '0');
|
||||
INSERT INTO Navigations (ID, ParentId, Name, `Order`, Icon, Url, Category) VALUES (15, 0, '程序异常', 150, 'fa fa-cubes', '~/Admin/Exceptions', '0');
|
||||
INSERT INTO Navigations (ID, ParentId, Name, `Order`, Icon, Url, Category) VALUES (16, 0, '工具集合', 160, 'fa fa-gavel', '#', '0');
|
||||
|
|
|
@ -166,4 +166,15 @@ CREATE TABLE ResetUsers(
|
|||
DisplayName VARCHAR (50) NOT NULL,
|
||||
Reason VARCHAR (500) NOT NULL,
|
||||
ResetTime DATETIME NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE Traces(
|
||||
ID INTEGER PRIMARY KEY Auto_increment,
|
||||
UserName VARCHAR (50) NOT NULL,
|
||||
LogTime DATETIME NOT NULL,
|
||||
IP VARCHAR (15) NOT NULL,
|
||||
Browser VARCHAR (50),
|
||||
OS VARCHAR (50),
|
||||
City VARCHAR (50),
|
||||
RequestUrl VARCHAR (500) NOT NULL
|
||||
);
|
|
@ -55,6 +55,7 @@ INSERT INTO Navigations (ParentId, Name, "order", Icon, Url, Category) VALUES (0
|
|||
INSERT INTO Navigations (ParentId, Name, "order", Icon, Url, Category) VALUES (0, '系统日志', 130, 'fa fa-gears', '#', '0');
|
||||
INSERT INTO Navigations (ParentId, Name, "order", Icon, Url, Category) VALUES (currval('navigations_id_seq') - 1, 0, '操作日志', 10, 'fa fa-edit', '~/Admin/Logs', '0');
|
||||
INSERT INTO Navigations (ParentId, Name, "order", Icon, Url, Category) VALUES (currval('navigations_id_seq') - 2, 0, '登录日志', 20, 'fa fa-user-circle-o', '~/Admin/Logins', '0');
|
||||
INSERT INTO Navigations (ParentId, Name, "order", Icon, Url, Category) VALUES (currval('navigations_id_seq') - 3, 0, '访问日志', 30, 'fa fa-bars', '~/Admin/Traces', '0');
|
||||
INSERT INTO Navigations (ParentId, Name, "order", Icon, Url, Category) VALUES (0, '在线用户', 140, 'fa fa-users', '~/Admin/Online', '0');
|
||||
INSERT INTO Navigations (ParentId, Name, "order", Icon, Url, Category) VALUES (0, '程序异常', 150, 'fa fa-cubes', '~/Admin/Exceptions', '0');
|
||||
INSERT INTO Navigations (ParentId, Name, "order", Icon, Url, Category) VALUES (0, '工具集合', 160, 'fa fa-gavel', '#', '0');
|
||||
|
|
|
@ -167,3 +167,14 @@ CREATE TABLE ResetUsers(
|
|||
Reason VARCHAR (500) NOT NULL,
|
||||
ResetTime DATE NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE Traces(
|
||||
ID SERIAL PRIMARY KEY,
|
||||
UserName VARCHAR (50) NOT NULL,
|
||||
LogTime DATE NOT NULL,
|
||||
IP VARCHAR (15) NOT NULL,
|
||||
Browser VARCHAR (50),
|
||||
OS VARCHAR (50),
|
||||
City VARCHAR (50),
|
||||
RequestUrl VARCHAR (500) NOT NULL
|
||||
);
|
|
@ -52,6 +52,7 @@ INSERT INTO [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category
|
|||
INSERT INTO [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (0, '系统日志', 130, 'fa fa-gears', '#', '0');
|
||||
INSERT INTO [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (last_insert_rowid(), '操作日志', 10, 'fa fa-edit', '~/Admin/Logs', '0');
|
||||
INSERT INTO [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (last_insert_rowid() - 1, '登录日志', 20, 'fa fa-user-circle-o', '~/Admin/Logins', '0');
|
||||
INSERT INTO [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (last_insert_rowid() - 2, '访问日志', 30, 'fa fa-bars', '~/Admin/Traces', '0');
|
||||
INSERT INTO [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (0, '在线用户', 140, 'fa fa-users', '~/Admin/Online', '0');
|
||||
INSERT INTO [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (0, '程序异常', 150, 'fa fa-cubes', '~/Admin/Exceptions', '0');
|
||||
INSERT INTO [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (0, '工具集合', 160, 'fa fa-gavel', '#', '0');
|
||||
|
|
|
@ -166,4 +166,15 @@ CREATE TABLE ResetUsers(
|
|||
[DisplayName] VARCHAR (50) NOT NULL,
|
||||
[Reason] VARCHAR (500) NOT NULL,
|
||||
[ResetTime] DATETIME NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE Traces(
|
||||
ID INTEGER PRIMARY KEY,
|
||||
UserName VARCHAR (50) NOT NULL COLLATE NOCASE,
|
||||
LogTime DATETIME NOT NULL,
|
||||
IP VARCHAR (15) NOT NULL,
|
||||
Browser VARCHAR (50),
|
||||
OS VARCHAR (50),
|
||||
City VARCHAR (50),
|
||||
RequestUrl VARCHAR (500) NOT NULL
|
||||
);
|
|
@ -0,0 +1,10 @@
|
|||
using Xunit;
|
||||
|
||||
namespace Bootstrap.Admin.Api.MySql
|
||||
{
|
||||
[Collection("MySqlContext")]
|
||||
public class TracesTest : Api.TracesTest
|
||||
{
|
||||
public TracesTest(MySqlBAWebHost factory) : base(factory) { }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
using Xunit;
|
||||
|
||||
namespace Bootstrap.Admin.Api.SQLite
|
||||
{
|
||||
[Collection("SQLiteContext")]
|
||||
public class TracesTest : Api.TracesTest
|
||||
{
|
||||
public TracesTest(SQLiteBAWebHost factory) : base(factory) { }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
using Bootstrap.DataAccess;
|
||||
using Longbow.Web.Mvc;
|
||||
using System;
|
||||
using Xunit;
|
||||
|
||||
namespace Bootstrap.Admin.Api
|
||||
{
|
||||
public class TracesTest : ControllerTest
|
||||
{
|
||||
public TracesTest(BAWebHost factory) : base(factory, "api/Traces") { }
|
||||
|
||||
[Fact]
|
||||
public async void Get_Ok()
|
||||
{
|
||||
var trac = new Trace() { Browser = "UnitTest", OS = "UnitTest", City = "本地连接", Ip = "::1", RequestUrl = "~/UnitTest", UserName = "UnitTest", LogTime = DateTime.Now };
|
||||
trac.Save(trac);
|
||||
|
||||
// 菜单 系统菜单 系统使用条件
|
||||
var query = "?sort=LogTime&order=desc&offset=0&limit=20&operateType=&OperateTimeStart=&OperateTimeEnd=&_=1547617573596";
|
||||
var qd = await Client.GetAsJsonAsync<QueryData<Trace>>(query);
|
||||
Assert.NotEmpty(qd.rows);
|
||||
|
||||
// clean
|
||||
DbManager.Create().Execute("Delete from Traces where LogTime = @0", trac.LogTime);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@ namespace Bootstrap.Admin.Controllers
|
|||
[InlineData("Roles", "角色管理")]
|
||||
[InlineData("Menus", "菜单管理")]
|
||||
[InlineData("Logs", "操作日志")]
|
||||
[InlineData("Traces", "访问日志")]
|
||||
[InlineData("Logins", "登录日志")]
|
||||
[InlineData("FAIcon", "图标集")]
|
||||
[InlineData("IconView", "图标分类")]
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
using Xunit;
|
||||
|
||||
namespace Bootstrap.DataAccess.MySql
|
||||
{
|
||||
[Collection("MySqlContext")]
|
||||
public class TracesTest : DataAccess.TracesTest
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
using Xunit;
|
||||
|
||||
namespace Bootstrap.DataAccess.SQLite
|
||||
{
|
||||
[Collection("SQLiteContext")]
|
||||
public class TracesTest : DataAccess.TracesTest
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
using Longbow.Web.Mvc;
|
||||
using System;
|
||||
using Xunit;
|
||||
|
||||
namespace Bootstrap.DataAccess
|
||||
{
|
||||
[Collection("SQLServerContext")]
|
||||
public class TracesTest
|
||||
{
|
||||
[Fact]
|
||||
public void Save_Ok()
|
||||
{
|
||||
var log = new Trace()
|
||||
{
|
||||
UserName = "UnitTest",
|
||||
Browser = "UnitTest",
|
||||
City = "本地连接",
|
||||
OS = "UnitTest",
|
||||
Ip = "::1",
|
||||
LogTime = DateTime.Now,
|
||||
RequestUrl = "~/Home/Index"
|
||||
};
|
||||
Assert.True(log.Save(log));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Retrieves_Ok()
|
||||
{
|
||||
var log = new Trace()
|
||||
{
|
||||
UserName = "UnitTest",
|
||||
Browser = "UnitTest",
|
||||
City = "本地连接",
|
||||
OS = "UnitTest",
|
||||
Ip = "::1",
|
||||
LogTime = DateTime.Now,
|
||||
RequestUrl = "~/Home/Index"
|
||||
};
|
||||
log.Save(log);
|
||||
Assert.NotEmpty(log.Retrieves(new PaginationOption() { Limit = 20, Offset = 0, Order = "desc", Sort = "LogTime" }, null, null).Items);
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
Loading…
Reference in New Issue