diff --git a/Bootstrap.Admin/Controllers/AccountController.cs b/Bootstrap.Admin/Controllers/AccountController.cs
index 56433c25..c5fa9ed1 100644
--- a/Bootstrap.Admin/Controllers/AccountController.cs
+++ b/Bootstrap.Admin/Controllers/AccountController.cs
@@ -2,9 +2,11 @@
using Bootstrap.DataAccess;
using Longbow;
using Longbow.Configuration;
+using Longbow.Web;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Linq;
@@ -34,13 +36,14 @@ namespace Bootstrap.Admin.Controllers
/// Login the specified userName, password and remember.
///
/// The login.
+ ///
/// User name.
/// Password.
/// Remember.
[HttpPost]
- public async Task Login(string userName, string password, string remember)
+ public async Task Login([FromServices]IOnlineUsers onlineUserSvr, string userName, string password, string remember)
{
- if (UserHelper.Authenticate(userName, password))
+ if (UserHelper.Authenticate(userName, password, loginUser => CreateLoginUser(onlineUserSvr, HttpContext, loginUser)))
{
var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
identity.AddClaim(new Claim(ClaimTypes.Name, userName));
@@ -51,6 +54,21 @@ namespace Bootstrap.Admin.Controllers
return Redirect(originUrl);
}
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ internal static void CreateLoginUser(IOnlineUsers onlineUserSvr, HttpContext context, LoginUser loginUser)
+ {
+ var agent = new UserAgent(context.Request.Headers["User-Agent"]);
+ loginUser.Ip = context.Connection.RemoteIpAddress?.ToString();
+ loginUser.City = onlineUserSvr.RetrieveLocaleByIp(loginUser.Ip);
+ loginUser.Browser = $"{agent.Browser.Name} {agent.Browser.Version}";
+ loginUser.OS = $"{agent.OS.Name} {agent.OS.Version}";
+ }
+
///
/// Logout this instance.
///
diff --git a/Bootstrap.Admin/Controllers/AdminController.cs b/Bootstrap.Admin/Controllers/AdminController.cs
index a4ea5266..3d864ccd 100644
--- a/Bootstrap.Admin/Controllers/AdminController.cs
+++ b/Bootstrap.Admin/Controllers/AdminController.cs
@@ -52,6 +52,12 @@ namespace Bootstrap.Admin.Controllers
///
public ActionResult Logs() => View(new NavigatorBarModel(this));
+ ///
+ ///
+ ///
+ ///
+ public ActionResult Logins() => View(new NavigatorBarModel(this));
+
///
///
///
diff --git a/Bootstrap.Admin/Controllers/Api/LoginController.cs b/Bootstrap.Admin/Controllers/Api/LoginController.cs
index ad81582f..592163b2 100644
--- a/Bootstrap.Admin/Controllers/Api/LoginController.cs
+++ b/Bootstrap.Admin/Controllers/Api/LoginController.cs
@@ -3,6 +3,8 @@ using Bootstrap.Security;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json.Linq;
+using System.Collections.Generic;
+using System.Linq;
namespace Bootstrap.Admin.Controllers.Api
{
@@ -16,20 +18,28 @@ namespace Bootstrap.Admin.Controllers.Api
[ApiController]
public class LoginController : ControllerBase
{
+ ///
+ /// 获得登录历史记录
+ ///
+ ///
+ [HttpGet]
+ public IEnumerable Get() => LoginHelper.Retrieves();
+
///
///
///
+ ///
///
///
[AllowAnonymous]
[HttpPost]
- public string Post([FromBody]JObject value)
+ public string Post([FromServices]IOnlineUsers onlineUserSvr, [FromBody]JObject value)
{
string token = null;
dynamic user = value;
string userName = user.userName;
string password = user.password;
- if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password) && UserHelper.Authenticate(userName, password))
+ if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password) && UserHelper.Authenticate(userName, password, loginUser => AccountController.CreateLoginUser(onlineUserSvr, HttpContext, loginUser)))
{
token = BootstrapAdminJwtTokenHandler.CreateToken(userName);
}
diff --git a/Bootstrap.Admin/Views/Admin/Logins.cshtml b/Bootstrap.Admin/Views/Admin/Logins.cshtml
new file mode 100644
index 00000000..3caa048f
--- /dev/null
+++ b/Bootstrap.Admin/Views/Admin/Logins.cshtml
@@ -0,0 +1,29 @@
+@model NavigatorBarModel
+@{
+ ViewBag.Title = "登录日志";
+}
+@section css {
+
+
+
+
+
+
+}
+@section javascript {
+
+
+
+
+
+
+
+
+
+}
+
\ No newline at end of file
diff --git a/Bootstrap.Admin/Views/Admin/Logs.cshtml b/Bootstrap.Admin/Views/Admin/Logs.cshtml
index a93083ea..ca009ff5 100644
--- a/Bootstrap.Admin/Views/Admin/Logs.cshtml
+++ b/Bootstrap.Admin/Views/Admin/Logs.cshtml
@@ -1,6 +1,6 @@
@model NavigatorBarModel
@{
- ViewBag.Title = "系统日志";
+ ViewBag.Title = "操作日志";
}
@section css {
diff --git a/Bootstrap.Admin/appsettings.json b/Bootstrap.Admin/appsettings.json
index 15dbe2ae..276bcdbd 100644
--- a/Bootstrap.Admin/appsettings.json
+++ b/Bootstrap.Admin/appsettings.json
@@ -65,6 +65,7 @@
"KeepExceptionsPeriod": 12,
"KeepLogsPeriod": 12,
"CookieExpiresDays": 7,
+ "KeepLoginLogsPeriod": 7,
"LongbowCache": {
"Enabled": true,
"CorsItems": [
diff --git a/Bootstrap.Admin/wwwroot/js/logins.js b/Bootstrap.Admin/wwwroot/js/logins.js
new file mode 100644
index 00000000..5bb832eb
--- /dev/null
+++ b/Bootstrap.Admin/wwwroot/js/logins.js
@@ -0,0 +1,31 @@
+// 登录日志
+$(function () {
+ var apiUrl = "api/Login";
+ var $table = $('table').smartTable({
+ url: apiUrl,
+ method: "get",
+ sidePagination: "client",
+ showToggle: false,
+ showRefresh: false,
+ showColumns: false,
+ columns: [
+ {
+ title: "序号", formatter: function (value, row, index) {
+ var options = $table.bootstrapTable('getOptions');
+ return options.pageSize * (options.pageNumber - 1) + index + 1;
+ }
+ },
+ { title: "登陆名称", field: "UserName" },
+ { title: "登录时间", field: "LoginTime" },
+ { title: "主机", field: "Ip" },
+ { title: "登录地点", field: "City" },
+ { title: "浏览器", field: "Browser" },
+ { title: "操作系统", field: "OS" },
+ { title: "登录结果", field: "Result" }
+ ]
+ });
+
+ $('#refreshUsers').tooltip().on('click', function () {
+ $table.bootstrapTable('refresh');
+ });
+});
\ No newline at end of file
diff --git a/Bootstrap.DataAccess.MongoDB/DbManager.cs b/Bootstrap.DataAccess.MongoDB/DbManager.cs
index fb3cc457..75f6b98c 100644
--- a/Bootstrap.DataAccess.MongoDB/DbManager.cs
+++ b/Bootstrap.DataAccess.MongoDB/DbManager.cs
@@ -116,6 +116,17 @@ namespace Bootstrap.DataAccess.MongoDB
return DBAccess.GetCollection("Navigations");
}
}
+
+ ///
+ ///
+ ///
+ public static IMongoCollection LoginUsers
+ {
+ get
+ {
+ return DBAccess.GetCollection("LoginLogs");
+ }
+ }
#endregion
private static void InitDb()
@@ -220,6 +231,15 @@ namespace Bootstrap.DataAccess.MongoDB
md.IdMemberMap.SetIgnoreIfDefault(true);
});
}
+ if (!BsonClassMap.IsClassMapRegistered(typeof(DataAccess.LoginUser)))
+ {
+ BsonClassMap.RegisterClassMap(md =>
+ {
+ md.AutoMap();
+ md.IdMemberMap.SetSerializer(new StringSerializer(BsonType.ObjectId));
+ md.IdMemberMap.SetIgnoreIfDefault(true);
+ });
+ }
}
}
}
diff --git a/Bootstrap.DataAccess.MongoDB/LoginUser.cs b/Bootstrap.DataAccess.MongoDB/LoginUser.cs
new file mode 100644
index 00000000..5d46434f
--- /dev/null
+++ b/Bootstrap.DataAccess.MongoDB/LoginUser.cs
@@ -0,0 +1,28 @@
+using MongoDB.Driver;
+using System.Collections.Generic;
+
+namespace Bootstrap.DataAccess.MongoDB
+{
+ ///
+ ///
+ ///
+ public class LoginUser : DataAccess.LoginUser
+ {
+ ///
+ ///
+ ///
+ ///
+ ///
+ public override bool Log(DataAccess.LoginUser user)
+ {
+ DbManager.LoginUsers.InsertOne(user);
+ return true;
+ }
+
+ ///
+ ///
+ ///
+ ///
+ public override IEnumerable Retrieves() => DbManager.LoginUsers.Find(FilterDefinition.Empty).SortByDescending(user => user.LoginTime).ToList();
+ }
+}
diff --git a/Bootstrap.DataAccess/Helper/LoginHelper.cs b/Bootstrap.DataAccess/Helper/LoginHelper.cs
new file mode 100644
index 00000000..20844f2a
--- /dev/null
+++ b/Bootstrap.DataAccess/Helper/LoginHelper.cs
@@ -0,0 +1,34 @@
+using Longbow.Cache;
+using Longbow.Data;
+using System.Collections.Generic;
+
+namespace Bootstrap.DataAccess
+{
+ ///
+ ///
+ ///
+ public static class LoginHelper
+ {
+ ///
+ ///
+ ///
+ public const string RetrieveLoginLogsDataKey = "LoginHelper-Retrieves";
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static bool Log(LoginUser user)
+ {
+ var ret = DbContextManager.Create().Log(user);
+ if (ret) CacheManager.Clear(RetrieveLoginLogsDataKey);
+ return ret;
+ }
+
+ ///
+ /// 查询一个月内所有登录信息
+ ///
+ public static IEnumerable Retrieves() => CacheManager.GetOrAdd(RetrieveLoginLogsDataKey, key => DbContextManager.Create().Retrieves());
+ }
+}
diff --git a/Bootstrap.DataAccess/Helper/UserHelper.cs b/Bootstrap.DataAccess/Helper/UserHelper.cs
index eaf761e3..e92f5607 100644
--- a/Bootstrap.DataAccess/Helper/UserHelper.cs
+++ b/Bootstrap.DataAccess/Helper/UserHelper.cs
@@ -1,6 +1,7 @@
using Bootstrap.Security;
using Longbow.Cache;
using Longbow.Data;
+using System;
using System.Collections.Generic;
namespace Bootstrap.DataAccess
@@ -29,7 +30,21 @@ namespace Bootstrap.DataAccess
///
///
///
- public static bool Authenticate(string userName, string password) => DbContextManager.Create().Authenticate(userName, password);
+ public static bool Authenticate(string userName, string password, Action config)
+ {
+ var loginUser = new LoginUser()
+ {
+ UserName = userName,
+ LoginTime = DateTime.Now,
+ Result = "登录失败"
+ };
+ config(loginUser);
+ if (string.IsNullOrEmpty(loginUser.Ip)) loginUser.Ip = System.Net.IPAddress.IPv6Loopback.ToString();
+ var ret = DbContextManager.Create().Authenticate(userName, password);
+ if (ret) loginUser.Result = "登录成功";
+ LoginHelper.Log(loginUser);
+ return ret;
+ }
///
/// 查询所有的新注册用户
diff --git a/Bootstrap.DataAccess/LoginUser.cs b/Bootstrap.DataAccess/LoginUser.cs
new file mode 100644
index 00000000..0dc50135
--- /dev/null
+++ b/Bootstrap.DataAccess/LoginUser.cs
@@ -0,0 +1,78 @@
+using Longbow;
+using Longbow.Configuration;
+using PetaPoco;
+using System;
+using System.Collections.Generic;
+
+namespace Bootstrap.DataAccess
+{
+ ///
+ ///
+ ///
+ [TableName("LoginLogs")]
+ public class LoginUser
+ {
+ ///
+ ///
+ ///
+ public string Id { get; set; }
+
+ ///
+ ///
+ ///
+ public string UserName { get; set; }
+
+ ///
+ ///
+ ///
+ public DateTime LoginTime { get; set; }
+
+ ///
+ ///
+ ///
+ public string Ip { get; set; }
+
+ ///
+ ///
+ ///
+ public string Browser { get; set; }
+
+ ///
+ ///
+ ///
+ public string OS { get; set; }
+
+ ///
+ ///
+ ///
+ public string City { get; set; }
+
+ ///
+ ///
+ ///
+ public string Result { get; set; }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public virtual bool Log(LoginUser user)
+ {
+ var db = DbManager.Create();
+ db.Save(user);
+ return true;
+ }
+
+ ///
+ ///
+ ///
+ protected const string KeepLoginLogsPeriodKey = "KeepLoginLogsPeriod";
+
+ ///
+ ///
+ ///
+ ///
+ public virtual IEnumerable Retrieves() => DbManager.Create().Fetch("Where LoginTime > @0 Order by LoginTime desc", DateTime.Today.AddDays(0 - LgbConvert.ReadValue(ConfigurationManager.AppSettings[KeepLoginLogsPeriodKey], 7)));
+ }
+}
diff --git a/DatabaseScripts/InitData.sql b/DatabaseScripts/InitData.sql
index 68719da3..9062d4fe 100644
--- a/DatabaseScripts/InitData.sql
+++ b/DatabaseScripts/InitData.sql
@@ -45,7 +45,9 @@ INSERT [dbo].[Navigations] ([ID], [ParentId], [Name], [Order], [Icon], [Url], [C
INSERT [dbo].[Navigations] ([ID], [ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (10, 0, N'站内消息', 100, N'fa fa-envelope', N'~/Admin/Messages', N'0')
INSERT [dbo].[Navigations] ([ID], [ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (11, 0, N'任务管理', 110, N'fa fa fa-tasks', N'~/Admin/Tasks', N'0')
INSERT [dbo].[Navigations] ([ID], [ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (12, 0, N'通知管理', 120, N'fa fa-bell', N'~/Admin/Notifications', N'0')
-INSERT [dbo].[Navigations] ([ID], [ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (13, 0, N'系统日志', 130, N'fa fa-gears', N'~/Admin/Logs', N'0')
+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 (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 3a60f7cf..2c7a3bdd 100644
--- a/DatabaseScripts/Install.sql
+++ b/DatabaseScripts/Install.sql
@@ -476,4 +476,34 @@ EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'显示名称'
GO
EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'注册时间' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'RejectUsers', @level2type=N'COLUMN',@level2name=N'RegisterTime'
-GO
\ No newline at end of file
+GO
+
+/****** Object: Table [dbo].[LoginLogs] Script Date: 03/03/2019 20:05:42 ******/
+SET ANSI_NULLS ON
+GO
+
+SET QUOTED_IDENTIFIER ON
+GO
+
+SET ANSI_PADDING ON
+GO
+
+CREATE TABLE [dbo].[LoginLogs](
+ [ID] [int] IDENTITY(1,1) NOT NULL,
+ [UserName] [varchar](50) NOT NULL,
+ [LoginTime] [datetime] NOT NULL,
+ [Ip] [varchar](15) NOT NULL,
+ [OS] [varchar](50) NULL,
+ [Browser] [varchar](50) NULL,
+ [City] [nvarchar](50) NULL,
+ [Result] [nvarchar](50) NOT NULL,
+ CONSTRAINT [PK_LoginLogs] 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
diff --git a/DatabaseScripts/MongoDB/BootstrapAdmin.Navigations.json b/DatabaseScripts/MongoDB/BootstrapAdmin.Navigations.json
index e00a5823..0fd52445 100644
--- a/DatabaseScripts/MongoDB/BootstrapAdmin.Navigations.json
+++ b/DatabaseScripts/MongoDB/BootstrapAdmin.Navigations.json
@@ -146,15 +146,39 @@
{
"_id": ObjectId("5bd7b8445fa31256f77e4b9c"),
"ParentId": "0",
- "Name": "系统日志",
+ "Name": "日志管理",
"Order": NumberInt(130),
"Icon": "fa fa-gears",
+ "Url": "#",
+ "Category": "0",
+ "Target": "_self",
+ "IsResource": NumberInt(0),
+ "Application": "0"
+ },
+ {
+ "_id": ObjectId("5bd9b3d868aa001661776f57"),
+ "ParentId": "5bd7b8445fa31256f77e4b9c",
+ "Name": "操作日志",
+ "Order": NumberInt(10),
+ "Icon": "fa fa-edit",
"Url": "~/Admin/Logs",
"Category": "0",
"Target": "_self",
"IsResource": NumberInt(0),
"Application": "0"
},
+ {
+ "_id": ObjectId("5bd9b3d868aa001661776f58"),
+ "ParentId": "5bd7b8445fa31256f77e4b9c",
+ "Name": "登录日志",
+ "Order": NumberInt(20),
+ "Icon": "fa fa-user-circle-o",
+ "Url": "~/Admin/Logins",
+ "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 d02e41e7..f06c3b2d 100644
--- a/DatabaseScripts/MySQL/initData.sql
+++ b/DatabaseScripts/MySQL/initData.sql
@@ -42,7 +42,9 @@ INSERT INTO Navigations (ID, ParentId, Name, `Order`, Icon, Url, Category) VALUE
INSERT INTO Navigations (ID, ParentId, Name, `Order`, Icon, Url, Category) VALUES (10, 0, '站内消息', 100, 'fa fa-envelope', '~/Admin/Messages', '0');
INSERT INTO Navigations (ID, ParentId, Name, `Order`, Icon, Url, Category) VALUES (11, 0, '任务管理', 110, 'fa fa fa-tasks', '~/Admin/Tasks', '0');
INSERT INTO Navigations (ID, ParentId, Name, `Order`, Icon, Url, Category) VALUES (12, 0, '通知管理', 120, 'fa fa-bell', '~/Admin/Notifications', '0');
-INSERT INTO Navigations (ID, ParentId, Name, `Order`, Icon, Url, Category) VALUES (13, 0, '系统日志', 130, 'fa fa-gears', '~/Admin/Logs', '0');
+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 (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 fbb9057c..62b1f67e 100644
--- a/DatabaseScripts/MySQL/install.sql
+++ b/DatabaseScripts/MySQL/install.sql
@@ -146,4 +146,15 @@ CREATE TABLE RejectUsers(
RejectedBy VARCHAR (50) NOT NULL,
RejectedTime DATETIME NOT NULL,
RejectedReason VARCHAR (50) NULL
+);
+
+CREATE TABLE LoginLogs(
+ ID INTEGER PRIMARY KEY Auto_increment,
+ UserName VARCHAR (50) NOT NULL,
+ LoginTime DATETIME NOT NULL,
+ Ip VARCHAR NOT NULL,
+ OS VARCHAR (50) NULL,
+ Browser VARCHAR (50) NULL,
+ City VARCHAR (50) NULL,
+ Result VARCHAR (50) NOT NULL
);
\ No newline at end of file
diff --git a/DatabaseScripts/Postgresql/initData.sql b/DatabaseScripts/Postgresql/initData.sql
index abbcc8ab..e9cd5335 100644
--- a/DatabaseScripts/Postgresql/initData.sql
+++ b/DatabaseScripts/Postgresql/initData.sql
@@ -42,7 +42,9 @@ INSERT INTO Navigations (ParentId, Name, "order", Icon, Url, Category) VALUES (0
INSERT INTO Navigations (ParentId, Name, "order", Icon, Url, Category) VALUES (0, '站内消息', 100, 'fa fa-envelope', '~/Admin/Messages', '0');
INSERT INTO Navigations (ParentId, Name, "order", Icon, Url, Category) VALUES (0, '任务管理', 110, 'fa fa fa-tasks', '~/Admin/Tasks', '0');
INSERT INTO Navigations (ParentId, Name, "order", Icon, Url, Category) VALUES (0, '通知管理', 120, 'fa fa-bell', '~/Admin/Notifications', '0');
-INSERT INTO Navigations (ParentId, Name, "order", Icon, Url, Category) VALUES (0, '系统日志', 130, 'fa fa-gears', '~/Admin/Logs', '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 (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 60e31eab..def463e3 100644
--- a/DatabaseScripts/Postgresql/install.sql
+++ b/DatabaseScripts/Postgresql/install.sql
@@ -146,4 +146,15 @@ CREATE TABLE RejectUsers(
RejectedBy VARCHAR (50) NOT NULL,
RejectedTime DATE NOT NULL,
RejectedReason VARCHAR (50) NULL
+);
+
+CREATE TABLE RejectUsers(
+ ID SERIAL PRIMARY KEY,
+ UserName VARCHAR (50) NOT NULL,
+ LoginTime DATE NOT NULL,
+ Ip VARCHAR NOT NULL,
+ OS VARCHAR (50) NULL,
+ Browser VARCHAR (50) NULL,
+ City VARCHAR (50) NULL,
+ Result VARCHAR (50) NOT NULL
);
\ No newline at end of file
diff --git a/DatabaseScripts/SQLite/InitData.sql b/DatabaseScripts/SQLite/InitData.sql
index fadaf135..b0137ba0 100644
--- a/DatabaseScripts/SQLite/InitData.sql
+++ b/DatabaseScripts/SQLite/InitData.sql
@@ -39,7 +39,9 @@ INSERT INTO [Navigations] ([ID], [ParentId], [Name], [Order], [Icon], [Url], [Ca
INSERT INTO [Navigations] ([ID], [ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (10, 0, '站内消息', 100, 'fa fa-envelope', '~/Admin/Messages', '0');
INSERT INTO [Navigations] ([ID], [ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (11, 0, '任务管理', 110, 'fa fa fa-tasks', '~/Admin/Tasks', '0');
INSERT INTO [Navigations] ([ID], [ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (12, 0, '通知管理', 120, 'fa fa-bell', '~/Admin/Notifications', '0');
-INSERT INTO [Navigations] ([ID], [ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (13, 0, '系统日志', 130, 'fa fa-gears', '~/Admin/Logs', '0');
+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 (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/SQLite/Install.sql b/DatabaseScripts/SQLite/Install.sql
index 861b51f3..13334d96 100644
--- a/DatabaseScripts/SQLite/Install.sql
+++ b/DatabaseScripts/SQLite/Install.sql
@@ -146,4 +146,15 @@ CREATE TABLE RejectUsers(
[RejectedBy] VARCHAR (50) NOT NULL COLLATE NOCASE,
[RejectedTime] DATETIME NOT NULL,
[RejectedReason] VARCHAR (50) NULL
+);
+
+CREATE TABLE LoginLogs(
+ [ID] INTEGER PRIMARY KEY,
+ [UserName] VARCHAR (50) NOT NULL COLLATE NOCASE,
+ [LoginTime] DATETIME NOT NULL,
+ [Ip] VARCHAR (15) NOT NULL,
+ [OS] VARCHAR (50) NULL,
+ [Browser] VARCHAR (50) NULL,
+ [City] VARCHAR (50) NULL,
+ [Result] VARCHAR (50) NOT NULL
);
\ No newline at end of file
diff --git a/UnitTest/Bootstrap.Admin/Api/LoginTest.cs b/UnitTest/Bootstrap.Admin/Api/LoginTest.cs
index 7c275c2a..6e18b793 100644
--- a/UnitTest/Bootstrap.Admin/Api/LoginTest.cs
+++ b/UnitTest/Bootstrap.Admin/Api/LoginTest.cs
@@ -1,4 +1,6 @@
-using System.Net.Http;
+using Bootstrap.DataAccess;
+using System.Collections.Generic;
+using System.Net.Http;
using Xunit;
namespace Bootstrap.Admin.Api
@@ -7,6 +9,13 @@ namespace Bootstrap.Admin.Api
{
public LoginTest(BAWebHost factory) : base(factory, "api/Login") { }
+ [Fact]
+ public async void Login_Get()
+ {
+ var users = await Client.GetAsJsonAsync>();
+ Assert.NotEmpty(users);
+ }
+
[Fact]
public async void Login_Ok()
{
diff --git a/UnitTest/Bootstrap.Admin/Controllers/AdminTest.cs b/UnitTest/Bootstrap.Admin/Controllers/AdminTest.cs
index 80acdc5b..9a58e1cb 100644
--- a/UnitTest/Bootstrap.Admin/Controllers/AdminTest.cs
+++ b/UnitTest/Bootstrap.Admin/Controllers/AdminTest.cs
@@ -14,7 +14,8 @@ namespace Bootstrap.Admin.Controllers
[InlineData("Dicts", "字典表维护")]
[InlineData("Roles", "角色管理")]
[InlineData("Menus", "菜单管理")]
- [InlineData("Logs", "系统日志")]
+ [InlineData("Logs", "操作日志")]
+ [InlineData("Logins", "登录日志")]
[InlineData("FAIcon", "图标集")]
[InlineData("IconView", "图标分类")]
[InlineData("Settings", "网站设置")]
diff --git a/UnitTest/DB/UnitTest.db b/UnitTest/DB/UnitTest.db
index 75955d9b..e5cacefd 100644
Binary files a/UnitTest/DB/UnitTest.db and b/UnitTest/DB/UnitTest.db differ