From 45adc853e26bc5bfa9d964e289fb84750426d982 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Sat, 16 Mar 2019 20:24:33 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=8A=9F=E8=83=BD=EF=BC=9A?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E6=97=A5=E5=BF=97=E5=A2=9E=E5=8A=A0=E8=AE=BF?= =?UTF-8?q?=E9=97=AE=E6=97=A5=E5=BF=97=20closed=20#ITO8H?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #Issue https://gitee.com/LongbowEnterprise/BootstrapAdmin/issues/ITO8H --- Bootstrap.Admin/BootstrapAdmin.db | Bin 90112 -> 90112 bytes .../Controllers/AdminController.cs | 10 +- .../Controllers/Api/TracesController.cs | 26 +++++ .../OnlineUsersMiddlewareExtensions.cs | 67 ++++++------ Bootstrap.Admin/Query/QueryTraceOptions.cs | 36 ++++++ Bootstrap.Admin/Views/Admin/Traces.cshtml | 103 ++++++++++++++++++ Bootstrap.Admin/wwwroot/js/traces.js | 25 +++++ Bootstrap.DataAccess.MongoDB/DbManager.cs | 27 ++++- Bootstrap.DataAccess.MongoDB/Trace.cs | 79 ++++++++++++++ Bootstrap.DataAccess/DbManager.cs | 1 + Bootstrap.DataAccess/Helper/LogHelper.cs | 1 - Bootstrap.DataAccess/Helper/TraceHelper.cs | 26 +++++ Bootstrap.DataAccess/Log.cs | 42 +------ Bootstrap.DataAccess/Trace.cs | 79 ++++++++++++++ DatabaseScripts/InitData.sql | 1 + DatabaseScripts/Install.sql | 30 +++++ .../MongoDB/BootstrapAdmin.Navigations.json | 12 ++ DatabaseScripts/MySQL/initData.sql | 1 + DatabaseScripts/MySQL/install.sql | 11 ++ DatabaseScripts/Postgresql/initData.sql | 1 + DatabaseScripts/Postgresql/install.sql | 11 ++ DatabaseScripts/SQLite/InitData.sql | 1 + DatabaseScripts/SQLite/Install.sql | 11 ++ .../Bootstrap.Admin/Api/MySql/TracesTest.cs | 10 ++ .../Bootstrap.Admin/Api/SQLite/TracesTest.cs | 10 ++ UnitTest/Bootstrap.Admin/Api/TracesTest.cs | 27 +++++ .../Bootstrap.Admin/Controllers/AdminTest.cs | 1 + .../Bootstrap.DataAccess/MySql/TracesTest.cs | 10 ++ .../Bootstrap.DataAccess/SQLite/TracesTest.cs | 10 ++ UnitTest/Bootstrap.DataAccess/TracesTest.cs | 43 ++++++++ UnitTest/DB/UnitTest.db | Bin 90112 -> 90112 bytes 31 files changed, 631 insertions(+), 81 deletions(-) create mode 100644 Bootstrap.Admin/Controllers/Api/TracesController.cs create mode 100644 Bootstrap.Admin/Query/QueryTraceOptions.cs create mode 100644 Bootstrap.Admin/Views/Admin/Traces.cshtml create mode 100644 Bootstrap.Admin/wwwroot/js/traces.js create mode 100644 Bootstrap.DataAccess.MongoDB/Trace.cs create mode 100644 Bootstrap.DataAccess/Helper/TraceHelper.cs create mode 100644 Bootstrap.DataAccess/Trace.cs create mode 100644 UnitTest/Bootstrap.Admin/Api/MySql/TracesTest.cs create mode 100644 UnitTest/Bootstrap.Admin/Api/SQLite/TracesTest.cs create mode 100644 UnitTest/Bootstrap.Admin/Api/TracesTest.cs create mode 100644 UnitTest/Bootstrap.DataAccess/MySql/TracesTest.cs create mode 100644 UnitTest/Bootstrap.DataAccess/SQLite/TracesTest.cs create mode 100644 UnitTest/Bootstrap.DataAccess/TracesTest.cs diff --git a/Bootstrap.Admin/BootstrapAdmin.db b/Bootstrap.Admin/BootstrapAdmin.db index 7e0fbc805af99edd1a1a9fcea03eb1962d332132..796d982eb4fd1c0649aff1f555d3a53aded82d6b 100644 GIT binary patch delta 809 zcmYL`TS(JU9LN9X%>6h2x3f)m`KxpPKe5`_#ceikWHhJcWq4~w1fesx80nNv5FZi= zddTfzbsk0!T@Z|Vv1EeLi@qsBn2;fEFF|F!_!Lxc{r_8+ADr_!=llKgJMcS;Tgt^P zWwutkP7s7aT7PTrzOzxePPpq{txd!%W^a?=^R)Sz+s-!oJl;i#@Q@T_njmSBX@q3G%qmElW$GYVCsPZ_TA3P1*2ttF$;(tg(j*f?rLlsj z1W;*^u9b#-;tBSU(pQPpK32VR>$jPQ%q^yq5gCGhPcPFGG*9hIqg|kAXFW+++qLTB ziz0qjxC8`zQs@O1+*)i0^?0u61!nxB=m2&2plE?&Ew&XzXsy9NkF3ziGV(mQi)JI^ zlhN`l%ex^s+!Y9S3E@CTNZLzNwo!sqJ)QV5Yg3P;qRIZqR1|ACKDT2BT1GZQ(>KHM zsi=T+oTayCP?kz{B`_T94h#!6uiq{R{R3g4er{|<*a_(Qu@3EK7fy=Uv jKXOJg7@wUlA9KFbK!lWi!*2yGmB6r(aDxZp@{Hy;Pd>x1 delta 837 zcmaKpUrbU_9LMiD!UgZY11ek;k;~l87>EdxQ)f*H9c9uKrca_yY-}nDIM<7s>m^{* zI`-6BTiJ3xWQ{eKPd?gK+2(3ZcxY`z53Q$GuiZv-Q{fUXm+}r|619i*)-l}pmqp! zh;E_FsET{f-RCAbEBl?@WfQEMRWboaLw}|p(J|W1ykSzbf;ypgvRX}?;v5E=0JXp* zB3_MnyqszTJbsw60zH16aY6do|oNBNt-NIIt9MSy8@ur(tt>oB%c)DjP?E)IS zlWqnGe@veT9Og3}ut$e8nO;znJ<510Dg@~z)rO-wj7CrZ^`i@@9krk)BwZ?Xb;OO3 zf`S0R07St-Hd0Y)Mmo3@;2C4kB?9Kfe{;Xmp~n!&UJ%Yyp?MO2K*ss6L^vkMWin5 zZx%PVi`(hK{`!{}>94zY3oo;U^=Gc^kG#fPM=3o7nr5FeVS0mdseh>+lXF~B=}~&5 zhD626jji=oKEqBKA#K2x$ND|eO9@;V*3xlkHqAhvc+rz#nN 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("Navigations", new string[] { "ParentName", "CategoryName", "Active", "Menus" }); database.AddMap("Roles", new string[] { "Checked" }); database.AddMap("Tasks"); + database.AddMap("Traces"); return database; } } diff --git a/Bootstrap.DataAccess/Helper/LogHelper.cs b/Bootstrap.DataAccess/Helper/LogHelper.cs index 937a929d..6d0d772d 100644 --- a/Bootstrap.DataAccess/Helper/LogHelper.cs +++ b/Bootstrap.DataAccess/Helper/LogHelper.cs @@ -17,7 +17,6 @@ namespace Bootstrap.DataAccess /// /// 查询所有日志信息 /// - /// /// public static IEnumerable Retrieves() => CacheManager.GetOrAdd(RetrieveLogsDataKey, key => DbContextManager.Create().Retrieves()); diff --git a/Bootstrap.DataAccess/Helper/TraceHelper.cs b/Bootstrap.DataAccess/Helper/TraceHelper.cs new file mode 100644 index 00000000..d4c31808 --- /dev/null +++ b/Bootstrap.DataAccess/Helper/TraceHelper.cs @@ -0,0 +1,26 @@ +using Longbow.Data; +using Longbow.Web.Mvc; +using PetaPoco; +using System; + +namespace Bootstrap.DataAccess +{ + public static class TraceHelper + { + + /// + /// 保存访问历史记录 + /// + /// + public static void Save(Trace p) => DbContextManager.Create().Save(p); + + /// + /// 获得指定IP历史访问记录 + /// + /// + /// + /// + /// + public static Page Retrieves(PaginationOption po, DateTime? startTime, DateTime? endTime) => DbContextManager.Create().Retrieves(po, startTime, endTime); + } +} diff --git a/Bootstrap.DataAccess/Log.cs b/Bootstrap.DataAccess/Log.cs index 3a71d51f..3490f127 100644 --- a/Bootstrap.DataAccess/Log.cs +++ b/Bootstrap.DataAccess/Log.cs @@ -6,53 +6,13 @@ namespace Bootstrap.DataAccess /// /// /// - public class Log + public class Log : Trace { - /// - /// 获得/设置 操作日志主键ID - /// - public string Id { get; set; } - /// /// 获得/设置 操作类型 /// public string CRUD { get; set; } - /// - /// 获得/设置 用户名称 - /// - public string UserName { get; set; } - - /// - /// 获得/设置 操作时间 - /// - public DateTime LogTime { get; set; } - - /// - /// 获得/设置 客户端IP - /// - public string Ip { get; set; } - - /// - /// - /// - public string City { get; set; } - - /// - /// - /// - public string Browser { get; set; } - - /// - /// - /// - public string OS { get; set; } - - /// - /// 获取/设置 请求网址 - /// - public string RequestUrl { get; set; } - /// /// 获得/设置 请求数据 /// diff --git a/Bootstrap.DataAccess/Trace.cs b/Bootstrap.DataAccess/Trace.cs new file mode 100644 index 00000000..8b8bea68 --- /dev/null +++ b/Bootstrap.DataAccess/Trace.cs @@ -0,0 +1,79 @@ +using Longbow.Web.Mvc; +using PetaPoco; +using System; + +namespace Bootstrap.DataAccess +{ + /// + /// + /// + public class Trace + { + /// + /// 获得/设置 操作日志主键ID + /// + public string Id { get; set; } + + /// + /// 获得/设置 用户名称 + /// + public string UserName { get; set; } + + /// + /// 获得/设置 操作时间 + /// + public DateTime LogTime { get; set; } + + /// + /// 获得/设置 客户端IP + /// + public string Ip { get; set; } + + /// + /// + /// + public string City { get; set; } + + /// + /// + /// + public string Browser { get; set; } + + /// + /// + /// + public string OS { get; set; } + + /// + /// 获取/设置 请求网址 + /// + public string RequestUrl { get; set; } + + /// + /// 保存访问历史记录 + /// + /// + public virtual bool Save(Trace p) + { + if (p == null) throw new ArgumentNullException(nameof(p)); + DbManager.Create().Save(p); + return true; + } + + /// + /// + /// + /// + /// + public virtual Page 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(po.PageIndex, po.Limit, sql); + } + } +} diff --git a/DatabaseScripts/InitData.sql b/DatabaseScripts/InitData.sql index 5ab8b813..a681531c 100644 --- a/DatabaseScripts/InitData.sql +++ b/DatabaseScripts/InitData.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') diff --git a/DatabaseScripts/Install.sql b/DatabaseScripts/Install.sql index 2f72cf8e..767c65ef 100644 --- a/DatabaseScripts/Install.sql +++ b/DatabaseScripts/Install.sql @@ -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 \ No newline at end of file diff --git a/DatabaseScripts/MongoDB/BootstrapAdmin.Navigations.json b/DatabaseScripts/MongoDB/BootstrapAdmin.Navigations.json index 0fd52445..c986301f 100644 --- a/DatabaseScripts/MongoDB/BootstrapAdmin.Navigations.json +++ b/DatabaseScripts/MongoDB/BootstrapAdmin.Navigations.json @@ -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", diff --git a/DatabaseScripts/MySQL/initData.sql b/DatabaseScripts/MySQL/initData.sql index 1e53f089..521a138b 100644 --- a/DatabaseScripts/MySQL/initData.sql +++ b/DatabaseScripts/MySQL/initData.sql @@ -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'); diff --git a/DatabaseScripts/MySQL/install.sql b/DatabaseScripts/MySQL/install.sql index 72051da2..1db335c5 100644 --- a/DatabaseScripts/MySQL/install.sql +++ b/DatabaseScripts/MySQL/install.sql @@ -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 ); \ No newline at end of file diff --git a/DatabaseScripts/Postgresql/initData.sql b/DatabaseScripts/Postgresql/initData.sql index 7f91f076..8f9775f3 100644 --- a/DatabaseScripts/Postgresql/initData.sql +++ b/DatabaseScripts/Postgresql/initData.sql @@ -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'); diff --git a/DatabaseScripts/Postgresql/install.sql b/DatabaseScripts/Postgresql/install.sql index 462e85ee..e7e75097 100644 --- a/DatabaseScripts/Postgresql/install.sql +++ b/DatabaseScripts/Postgresql/install.sql @@ -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 +); \ No newline at end of file diff --git a/DatabaseScripts/SQLite/InitData.sql b/DatabaseScripts/SQLite/InitData.sql index 31a26c4e..30f6633b 100644 --- a/DatabaseScripts/SQLite/InitData.sql +++ b/DatabaseScripts/SQLite/InitData.sql @@ -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'); diff --git a/DatabaseScripts/SQLite/Install.sql b/DatabaseScripts/SQLite/Install.sql index 9654a9d8..b636b0ce 100644 --- a/DatabaseScripts/SQLite/Install.sql +++ b/DatabaseScripts/SQLite/Install.sql @@ -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 ); \ No newline at end of file diff --git a/UnitTest/Bootstrap.Admin/Api/MySql/TracesTest.cs b/UnitTest/Bootstrap.Admin/Api/MySql/TracesTest.cs new file mode 100644 index 00000000..8e123db1 --- /dev/null +++ b/UnitTest/Bootstrap.Admin/Api/MySql/TracesTest.cs @@ -0,0 +1,10 @@ +using Xunit; + +namespace Bootstrap.Admin.Api.MySql +{ + [Collection("MySqlContext")] + public class TracesTest : Api.TracesTest + { + public TracesTest(MySqlBAWebHost factory) : base(factory) { } + } +} diff --git a/UnitTest/Bootstrap.Admin/Api/SQLite/TracesTest.cs b/UnitTest/Bootstrap.Admin/Api/SQLite/TracesTest.cs new file mode 100644 index 00000000..26509c74 --- /dev/null +++ b/UnitTest/Bootstrap.Admin/Api/SQLite/TracesTest.cs @@ -0,0 +1,10 @@ +using Xunit; + +namespace Bootstrap.Admin.Api.SQLite +{ + [Collection("SQLiteContext")] + public class TracesTest : Api.TracesTest + { + public TracesTest(SQLiteBAWebHost factory) : base(factory) { } + } +} diff --git a/UnitTest/Bootstrap.Admin/Api/TracesTest.cs b/UnitTest/Bootstrap.Admin/Api/TracesTest.cs new file mode 100644 index 00000000..ecb8a527 --- /dev/null +++ b/UnitTest/Bootstrap.Admin/Api/TracesTest.cs @@ -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>(query); + Assert.NotEmpty(qd.rows); + + // clean + DbManager.Create().Execute("Delete from Traces where LogTime = @0", trac.LogTime); + } + } +} diff --git a/UnitTest/Bootstrap.Admin/Controllers/AdminTest.cs b/UnitTest/Bootstrap.Admin/Controllers/AdminTest.cs index 9a58e1cb..eb5101a4 100644 --- a/UnitTest/Bootstrap.Admin/Controllers/AdminTest.cs +++ b/UnitTest/Bootstrap.Admin/Controllers/AdminTest.cs @@ -15,6 +15,7 @@ namespace Bootstrap.Admin.Controllers [InlineData("Roles", "角色管理")] [InlineData("Menus", "菜单管理")] [InlineData("Logs", "操作日志")] + [InlineData("Traces", "访问日志")] [InlineData("Logins", "登录日志")] [InlineData("FAIcon", "图标集")] [InlineData("IconView", "图标分类")] diff --git a/UnitTest/Bootstrap.DataAccess/MySql/TracesTest.cs b/UnitTest/Bootstrap.DataAccess/MySql/TracesTest.cs new file mode 100644 index 00000000..f3c2e716 --- /dev/null +++ b/UnitTest/Bootstrap.DataAccess/MySql/TracesTest.cs @@ -0,0 +1,10 @@ +using Xunit; + +namespace Bootstrap.DataAccess.MySql +{ + [Collection("MySqlContext")] + public class TracesTest : DataAccess.TracesTest + { + + } +} diff --git a/UnitTest/Bootstrap.DataAccess/SQLite/TracesTest.cs b/UnitTest/Bootstrap.DataAccess/SQLite/TracesTest.cs new file mode 100644 index 00000000..99c1581d --- /dev/null +++ b/UnitTest/Bootstrap.DataAccess/SQLite/TracesTest.cs @@ -0,0 +1,10 @@ +using Xunit; + +namespace Bootstrap.DataAccess.SQLite +{ + [Collection("SQLiteContext")] + public class TracesTest : DataAccess.TracesTest + { + + } +} diff --git a/UnitTest/Bootstrap.DataAccess/TracesTest.cs b/UnitTest/Bootstrap.DataAccess/TracesTest.cs new file mode 100644 index 00000000..4823cd9a --- /dev/null +++ b/UnitTest/Bootstrap.DataAccess/TracesTest.cs @@ -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); + } + } +} diff --git a/UnitTest/DB/UnitTest.db b/UnitTest/DB/UnitTest.db index 7e0fbc805af99edd1a1a9fcea03eb1962d332132..74b9c49ade273f370c80bb048888dc3f0d35c5e2 100644 GIT binary patch delta 852 zcmZvZOH30%7{_;}bX)c{ZK02aLU&`TZCc7wENO`tOF?-wJd5#Sph#jXiVsrhWcD>Ve_q{g~`q}-9sI?}{b zGX#2r?xJpF;10M)+$iT{kJ*=Ol5J$w%v)xO>0^xa2l_cZPWz}|)Gqaun#t*PO%&(h zX#((|#*WNdWWleOP5}!pEIEN0lPM2tl3g2cITg182UGQ|v8dQ>CFNG?3|lEHE@1zY zSxb3A1^$$(0|snJp9gx}pAMY!>*SCfh{X_lw7T^zyZR-&@_8#;8IKC_Xx(TukzA+` zj!lkF)khN1Yq6v>oQzGxr69uTbSvO;dug9aB_bWxWIVskMmu?h69BbP>bgZ4WI_UD7b(X9~5j*tj3PK2<=t)=b;_CdEA|ML$?L* z9=f5+jOX*N-1mYmR7I;c2^42nnPECZd9^<@E94Y6tM;ijP}!=;|q#Ppne wtCBs5`6Oq_>>X5e{E;)0q4C-I(r?y#8iD1jJHyfLCZZ1)=m~+a?TnUeu(Q zwt?2Mr--18#D^?KNIv;!P#H)OZVwSGeTbf-Ub}b6=%pSGpWpZUJ%4^b&dR=WWnZ~& z)Q%Dap+8$d;=4OGmvWS_gOL}LQ>J=HtI*=^a5*|$ZB9o^dy~V}^p}-AHPw!_8mMgo z9iZFj3aa2fa1Xc%&cc3Yci05$VwFsQ(a>M$$8?N#F>jd^t)Pyn?W|T4r#QQTCO|c) z6A`aQJYGsQ03JU|S%4nDN!i&-DK!DBMNTzXm2PG$ijHXd<9O4JtX6WYKs?*Cn05jU z-cC0Ggg>P(0uJ+;cIeUJOr{%DWe+p%vNAz>NwsdT45MKbKz-;EYD3Ma5lNRyT^(}6 zq@W-GFhJ5IlZRxzOg$v)WL845R%QhxjWV^6tdXgKWVK8Rk_MS&kmO}TsMITnasacl z53z%!>j*P-fK~79x+CT(v%s{`-{?(xkvgFL)KB`H)WQ-4ZT0wm-UdYh_vS@f`j&FB z3#m`cX55fBLz@ZDNU60Bzs_4>Z!JDL?gK__%v}aGcq-Qgs_|y71sL$hoK@gSLKKm@ zu(xrtv30VQF6^y+eVIPpxmS3VEv!9vW`E>0-Wp2jCeRf7j0w~0lvDjn^@N<|l1jJI zEj1)6mT!($srb%LusW0+pO}s$=BJ~{NFuo~8I6ULk(s&h|7^W?(B}#IgrKLp-zWUz z?0HHM{5^tyAZU{+?_j7$2>M2fB9vH;#0SP_A|5V3Dl*C|UenA=_6rV0yl!d!SXnCCu+?p?r(jQyBpi>o%KyrN7BLZ7FsM?*i PgS+OeF!mW?K_mPIPleb<