diff --git a/Bootstrap.Admin/BootstrapAdmin.db b/Bootstrap.Admin/BootstrapAdmin.db
index 7e0fbc80..796d982e 100644
Binary files a/Bootstrap.Admin/BootstrapAdmin.db and b/Bootstrap.Admin/BootstrapAdmin.db differ
diff --git a/Bootstrap.Admin/Controllers/AdminController.cs b/Bootstrap.Admin/Controllers/AdminController.cs
index 9204f65b..584999d9 100644
--- a/Bootstrap.Admin/Controllers/AdminController.cs
+++ b/Bootstrap.Admin/Controllers/AdminController.cs
@@ -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
///
public ActionResult Logs() => View(new NavigatorBarModel(this));
+ ///
+ ///
+ ///
+ ///
+ public ActionResult Traces() => View(new NavigatorBarModel(this));
+
///
///
///
@@ -125,7 +131,7 @@ namespace Bootstrap.Admin.Controllers
///
/// 用于测试ExceptionFilter
///
- ///
+ ///
public ActionResult Error() => throw new Exception("Customer Excetion UnitTest");
}
}
diff --git a/Bootstrap.Admin/Controllers/Api/TracesController.cs b/Bootstrap.Admin/Controllers/Api/TracesController.cs
new file mode 100644
index 00000000..b6caa4da
--- /dev/null
+++ b/Bootstrap.Admin/Controllers/Api/TracesController.cs
@@ -0,0 +1,26 @@
+using Bootstrap.Admin.Query;
+using Bootstrap.DataAccess;
+using Longbow.Web.Mvc;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Bootstrap.Admin.Controllers.Api
+{
+ ///
+ ///
+ ///
+ [Route("api/[controller]")]
+ [ApiController]
+ public class TracesController : ControllerBase
+ {
+ ///
+ ///
+ ///
+ ///
+ ///
+ [HttpGet]
+ public QueryData Get([FromQuery]QueryTraceOptions value)
+ {
+ return value.RetrieveData();
+ }
+ }
+}
diff --git a/Bootstrap.Admin/OnlineUsers/OnlineUsersMiddlewareExtensions.cs b/Bootstrap.Admin/OnlineUsers/OnlineUsersMiddlewareExtensions.cs
index e8ca286a..36a17ef0 100644
--- a/Bootstrap.Admin/OnlineUsers/OnlineUsersMiddlewareExtensions.cs
+++ b/Bootstrap.Admin/OnlineUsers/OnlineUsersMiddlewareExtensions.cs
@@ -20,40 +20,41 @@ namespace Microsoft.AspNetCore.Builder
///
///
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();
- var proxy = new Func((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();
+ var proxy = new Func((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)
{
diff --git a/Bootstrap.Admin/Query/QueryTraceOptions.cs b/Bootstrap.Admin/Query/QueryTraceOptions.cs
new file mode 100644
index 00000000..abf70168
--- /dev/null
+++ b/Bootstrap.Admin/Query/QueryTraceOptions.cs
@@ -0,0 +1,36 @@
+using Bootstrap.DataAccess;
+using Longbow.Web.Mvc;
+using System;
+
+namespace Bootstrap.Admin.Query
+{
+ ///
+ /// Query trace options.
+ ///
+ public class QueryTraceOptions : PaginationOption
+ {
+ ///
+ ///
+ ///
+ public DateTime? OperateTimeStart { get; set; }
+
+ ///
+ ///
+ ///
+ public DateTime? OperateTimeEnd { get; set; }
+
+ ///
+ ///
+ ///
+ ///
+ public QueryData RetrieveData()
+ {
+ var data = TraceHelper.Retrieves(this, OperateTimeStart, OperateTimeEnd);
+
+ var ret = new QueryData();
+ ret.total = data.TotalItems;
+ ret.rows = data.Items;
+ return ret;
+ }
+ }
+}
diff --git a/Bootstrap.Admin/Views/Admin/Traces.cshtml b/Bootstrap.Admin/Views/Admin/Traces.cshtml
new file mode 100644
index 00000000..384dfc37
--- /dev/null
+++ b/Bootstrap.Admin/Views/Admin/Traces.cshtml
@@ -0,0 +1,103 @@
+@model NavigatorBarModel
+@{
+ ViewBag.Title = "访问日志";
+}
+@section css {
+
+
+
+
+
+
+
+
+}
+@section javascript {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+}
+
+
+@section modal {
+
+}
\ No newline at end of file
diff --git a/Bootstrap.Admin/wwwroot/js/traces.js b/Bootstrap.Admin/wwwroot/js/traces.js
new file mode 100644
index 00000000..0694b6c8
--- /dev/null
+++ b/Bootstrap.Admin/wwwroot/js/traces.js
@@ -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]
+ }
+ });
+});
\ No newline at end of file
diff --git a/Bootstrap.DataAccess.MongoDB/DbManager.cs b/Bootstrap.DataAccess.MongoDB/DbManager.cs
index ac3aa42d..93a31ba4 100644
--- a/Bootstrap.DataAccess.MongoDB/DbManager.cs
+++ b/Bootstrap.DataAccess.MongoDB/DbManager.cs
@@ -138,6 +138,17 @@ namespace Bootstrap.DataAccess.MongoDB
return DBAccess.GetCollection("ResetUsers");
}
}
+
+ ///
+ ///
+ ///
+ public static IMongoCollection Traces
+ {
+ get
+ {
+ return DBAccess.GetCollection("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(md =>
+ BsonClassMap.RegisterClassMap(md =>
{
md.AutoMap();
md.IdMemberMap.SetSerializer(new StringSerializer(BsonType.ObjectId));
md.IdMemberMap.SetIgnoreIfDefault(true);
});
+ }
+ if (!BsonClassMap.IsClassMapRegistered(typeof(DataAccess.Log)))
+ {
+ BsonClassMap.RegisterClassMap(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);
});
- }
+ }
}
}
}
diff --git a/Bootstrap.DataAccess.MongoDB/Trace.cs b/Bootstrap.DataAccess.MongoDB/Trace.cs
new file mode 100644
index 00000000..2d9f0114
--- /dev/null
+++ b/Bootstrap.DataAccess.MongoDB/Trace.cs
@@ -0,0 +1,79 @@
+using Longbow.Web.Mvc;
+using MongoDB.Driver;
+using PetaPoco;
+using System;
+using System.Linq;
+
+namespace Bootstrap.DataAccess.MongoDB
+{
+ ///
+ ///
+ ///
+ public class Trace : DataAccess.Trace
+ {
+ ///
+ ///
+ ///
+ ///
+ public override Page Retrieves(PaginationOption po, DateTime? startTime, DateTime? endTime)
+ {
+ // filter
+ var filterBuilder = Builders.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.Sort;
+ SortDefinition 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()
+ {
+ 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()
+ };
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public override bool Save(DataAccess.Trace p)
+ {
+ p.Id = null;
+ DbManager.Traces.InsertOne(p);
+ return true;
+ }
+ }
+}
diff --git a/Bootstrap.DataAccess/DbManager.cs b/Bootstrap.DataAccess/DbManager.cs
index d7a1f075..05902e73 100644
--- a/Bootstrap.DataAccess/DbManager.cs
+++ b/Bootstrap.DataAccess/DbManager.cs
@@ -28,6 +28,7 @@ namespace Bootstrap.DataAccess
database.AddMap