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("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 7e0fbc80..74b9c49a 100644 Binary files a/UnitTest/DB/UnitTest.db and b/UnitTest/DB/UnitTest.db differ