合并PR:!1 增加在线用户显示功能

Merge pull request !1 from Argo-QQ/dev-Feture
This commit is contained in:
Argo-Windows 2019-03-01 02:48:35 +08:00
commit a27d8bf05f
15 changed files with 361 additions and 5 deletions

View File

@ -108,6 +108,12 @@ namespace Bootstrap.Admin.Controllers
/// <returns></returns>
public ActionResult Mobile() => View(new NavigatorBarModel(this));
/// <summary>
/// 在线用户
/// </summary>
/// <returns></returns>
public ActionResult Online() => View(new NavigatorBarModel(this));
/// <summary>
/// 用于测试ExceptionFilter
/// </summary>

View File

@ -0,0 +1,38 @@
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Bootstrap.Admin.Controllers.Api
{
/// <summary>
/// 在线用户接口
/// </summary>
[Route("api/[controller]")]
[ApiController]
public class OnlineUsersController : ControllerBase
{
/// <summary>
/// 获取所有在线用户数据
/// </summary>
/// <returns></returns>
[HttpPost()]
public IEnumerable<OnlineUser> Post([FromServices]IOnlineUsers onlineUSers)
{
return onlineUSers.OnlineUsers;
}
/// <summary>
/// 获取指定IP地址的在线用户请求地址明细数据
/// </summary>
/// <param name="id"></param>
/// <param name="onlineUSers"></param>
/// <returns></returns>
[HttpGet("{id}")]
public IEnumerable<KeyValuePair<DateTime, string>> Get(string id, [FromServices]IOnlineUsers onlineUSers)
{
var user = onlineUSers.OnlineUsers.FirstOrDefault(u => u.Ip == id);
return user?.RequestUrls ?? new KeyValuePair<DateTime, string>[0];
}
}
}

View File

@ -0,0 +1,32 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
namespace Bootstrap.Admin
{
/// <summary>
///
/// </summary>
internal class DefaultOnlineUsers : IOnlineUsers
{
private ConcurrentDictionary<string, OnlineUser> _onlineUsers = new ConcurrentDictionary<string, OnlineUser>();
/// <summary>
///
/// </summary>
/// <returns></returns>
public IEnumerable<OnlineUser> OnlineUsers
{
get { return _onlineUsers.Values; }
}
/// <summary>
///
/// </summary>
/// <param name="key"></param>
/// <param name="addValueFactory"></param>
/// <param name="updateValueFactory"></param>
/// <returns></returns>
public OnlineUser AddOrUpdate(string key, Func<string, OnlineUser> addValueFactory, Func<string, OnlineUser, OnlineUser> updateValueFactory) => _onlineUsers.AddOrUpdate(key, addValueFactory, updateValueFactory);
}
}

View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
namespace Bootstrap.Admin
{
/// <summary>
///
/// </summary>
public interface IOnlineUsers
{
/// <summary>
///
/// </summary>
IEnumerable<OnlineUser> OnlineUsers { get; }
/// <summary>
///
/// </summary>
/// <param name="key"></param>
/// <param name="addValueFactory"></param>
/// <param name="updateValueFactory"></param>
/// <returns></returns>
OnlineUser AddOrUpdate(string key, Func<string, OnlineUser> addValueFactory, Func<string, OnlineUser, OnlineUser> updateValueFactory);
}
}

View File

@ -0,0 +1,83 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
namespace Bootstrap.Admin
{
/// <summary>
///
/// </summary>
public class OnlineUser
{
private ConcurrentQueue<KeyValuePair<DateTime, string>> _requestUrls;
/// <summary>
///
/// </summary>
/// <param name="ip"></param>
/// <param name="userName"></param>
public OnlineUser(string ip, string userName)
{
Ip = ip;
UserName = userName;
FirstAccessTime = DateTime.Now;
LastAccessTime = DateTime.Now;
_requestUrls = new ConcurrentQueue<KeyValuePair<DateTime, string>>();
}
/// <summary>
///
/// </summary>
public string UserName { get; }
/// <summary>
///
/// </summary>
public DateTime FirstAccessTime { get; }
/// <summary>
///
/// </summary>
public DateTime LastAccessTime { get; set; }
/// <summary>
///
/// </summary>
public string Method { get; set; }
/// <summary>
///
/// </summary>
public string Ip { get; set; }
/// <summary>
///
/// </summary>
public string RequestUrl { get; set; }
/// <summary>
///
/// </summary>
public IEnumerable<KeyValuePair<DateTime, string>> RequestUrls
{
get
{
return _requestUrls.ToArray();
}
}
/// <summary>
///
/// </summary>
/// <param name="url"></param>
public void AddRequestUrl(string url)
{
_requestUrls.Enqueue(new KeyValuePair<DateTime, string>(DateTime.Now, url));
if (_requestUrls.Count > 5)
{
_requestUrls.TryDequeue(out _);
}
}
}
}

View File

@ -0,0 +1,51 @@
using Bootstrap.Admin;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Builder
{
/// <summary>
///
/// </summary>
public static class OnlineUsersMiddlewareExtensions
{
/// <summary>
///
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
public static IApplicationBuilder UseOnlineUsers(this IApplicationBuilder builder) => builder.UseWhen(context => context.Filter(), app => app.Use(async (context, next) =>
{
await Task.Run(() =>
{
var onlineUsers = context.RequestServices.GetService<IOnlineUsers>();
var clientIp = context.Connection.RemoteIpAddress.ToString();
onlineUsers.AddOrUpdate(clientIp, key =>
{
var ou = new OnlineUser(key, context.User.Identity.Name);
ou.Method = context.Request.Method;
ou.RequestUrl = context.Request.Path;
ou.AddRequestUrl(context.Request.Path);
return ou;
}, (key, v) =>
{
v.LastAccessTime = DateTime.Now;
v.Method = context.Request.Method;
v.RequestUrl = context.Request.Path;
v.AddRequestUrl(context.Request.Path);
return v;
});
});
await next();
}));
private static bool Filter(this HttpContext context)
{
var url = context.Request.Path;
return !new string[] { "/api", "/NotiHub", "/swagger" }.Any(r => url.StartsWithSegments(r, StringComparison.OrdinalIgnoreCase));
}
}
}

View File

@ -0,0 +1,22 @@
using Bootstrap.Admin;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
///
/// </summary>
public static class OnlineUsersServicesCollectionExtensions
{
/// <summary>
///
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
public static IServiceCollection AddOnlineUsers(this IServiceCollection services)
{
services.TryAddSingleton<IOnlineUsers, DefaultOnlineUsers>();
return services;
}
}
}

View File

@ -60,6 +60,7 @@ namespace Bootstrap.Admin
services.AddConfigurationManager(Configuration);
services.AddCacheManager(Configuration);
services.AddDbAdapter();
services.AddOnlineUsers();
var dataProtectionBuilder = services.AddDataProtection(op => op.ApplicationDiscriminator = Configuration["ApplicationDiscriminator"])
.SetApplicationName(Configuration["ApplicationName"])
.PersistKeysToFileSystem(new DirectoryInfo(Configuration["KeyPath"]));
@ -125,6 +126,7 @@ namespace Bootstrap.Admin
app.UseStaticFiles();
app.UseAuthentication();
app.UseBootstrapAdminAuthorization(RoleHelper.RetrieveRolesByUserName, RoleHelper.RetrieveRolesByUrl, AppHelper.RetrievesByUserName);
app.UseOnlineUsers();
app.UseCacheManagerCorsHandler();
app.UseSignalR(routes => { routes.MapHub<SignalRHub>("/NotiHub"); });
app.UseMvc(routes =>

View File

@ -0,0 +1,29 @@
@model NavigatorBarModel
@{
ViewBag.Title = "在线用户";
}
@section css {
<environment include="Development">
<link href="~/lib/bootstrap-table/bootstrap-table.css" rel="stylesheet" />
</environment>
<environment exclude="Development">
<link href="~/lib/bootstrap-table/bootstrap-table.min.css" rel="stylesheet" />
</environment>
}
@section javascript {
<environment include="Development">
<script src="~/lib/bootstrap-table/bootstrap-table.js"></script>
<script src="~/lib/bootstrap-table/locale/bootstrap-table-zh-CN.js"></script>
</environment>
<environment exclude="Development">
<script src="~/lib/bootstrap-table/bootstrap-table.min.js"></script>
<script src="~/lib/bootstrap-table/locale/bootstrap-table-zh-CN.min.js"></script>
</environment>
<script src="~/js/online.js" asp-append-version="true"></script>
}
<div class="card">
<div class="card-header">在线用户<span class="pull-right"><a id="refreshUsers" href="javascript:;" class="fa fa-refresh" title="点击刷新" data-toggle="tooltip" data-placement="left"></a></span></div>
<div class="card-body" style="padding-top: 25px;">
<table></table>
</div>
</div>

View File

@ -0,0 +1,52 @@
$(function () {
var apiUrl = "api/OnlineUsers";
var $table = $('table').smartTable({
url: apiUrl,
method: "post",
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: "DisplayName" },
{ title: "登录时间", field: "FirstAccessTime" },
{ title: "最近操作时间", field: "LastAccessTime" },
{ title: "请求方式", field: "Method" },
{ title: "IP地址", field: "Ip" },
{ title: "访问地址", field: "RequestUrl" },
{
title: "历史地址", field: "Ip", formatter: function (value, row, index, field) {
return $.format('<button type="button" class="btn btn-info" data-id="{0}" data-toggle="popover" data-trigger="focus" data-html="true" data-title="访问记录">明细</button >', value);
}
}
]
}).on('click', 'button[data-id]', function () {
var $this = $(this);
if (!$this.data($.fn.popover.Constructor.DATA_KEY)) {
var id = $this.attr('data-id');
$.bc({
id: id, url: apiUrl,
callback: function (result) {
if (!result) return;
var content = result.map(function (item) {
return $.format("<tr><td>{0}</td><td>{1}</td></tr>", item.Key, item.Value);
}).join('');
content = $.format('<div class="fixed-table-container"><table class="table table-hover table-sm mb-0"><thead><tr><th class="p-1"><b>访问时间</b></th><th class="p-1">访问地址</th></tr></thead><tbody>{0}</tbody></table></div>', content);
$this.lgbPopover({ content: content, placement: $(window).width() < 768 ? 'top' : 'left' });
$this.popover('show');
}
});
}
});
$('#refreshUsers').tooltip().on('click', function () {
$table.bootstrapTable('refresh');
});
});

View File

@ -46,7 +46,8 @@ INSERT [dbo].[Navigations] ([ID], [ParentId], [Name], [Order], [Icon], [Url], [C
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 (14, 0, N'程序异常', 140, N'fa fa-cubes', N'~/Admin/Exceptions', 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')
INSERT [dbo].[Navigations] ([ID], [ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (17, 16, N'客户端测试', 10, N'fa fa-wrench', N'~/Admin/Mobile', N'0')
INSERT [dbo].[Navigations] ([ID], [ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (18, 16, N'API文档', 10, N'fa fa-wrench', N'~/swagger', N'0')

View File

@ -155,11 +155,23 @@
"IsResource": NumberInt(0),
"Application": "0"
},
{
"_id": ObjectId("5bd7b8445fa31256f77e4b89"),
"ParentId": "0",
"Name": "在线用户",
"Order": NumberInt(140),
"Icon": "fa fa-users",
"Url": "~/Admin/Online",
"Category": "0",
"Target": "_self",
"IsResource": NumberInt(0),
"Application": "0"
},
{
"_id": ObjectId("5bd7b8445fa31256f77e4b9d"),
"ParentId": "0",
"Name": "程序异常",
"Order": NumberInt(140),
"Order": NumberInt(150),
"Icon": "fa fa-cubes",
"Url": "~/Admin/Exceptions",
"Category": "0",

View File

@ -43,7 +43,8 @@ INSERT INTO Navigations (ID, ParentId, Name, `Order`, Icon, Url, Category) VALUE
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 (14, 0, '程序异常', 140, 'fa fa-cubes', '~/Admin/Exceptions', '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');
INSERT INTO Navigations (ID, ParentId, Name, `Order`, Icon, Url, Category) VALUES (17, 16, '客户端测试', 10, 'fa fa-wrench', '~/Admin/Mobile', '0');
INSERT INTO Navigations (ID, ParentId, Name, `Order`, Icon, Url, Category) VALUES (18, 16, 'API文档', 10, 'fa fa-wrench', '~/swagger', '0');

View File

@ -43,7 +43,8 @@ INSERT INTO Navigations (ParentId, Name, "order", Icon, Url, Category) VALUES (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, '程序异常', 140, 'fa fa-cubes', '~/Admin/Exceptions', '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');
INSERT INTO Navigations (ParentId, Name, "order", Icon, Url, Category) VALUES (currval('navigations_id_seq') - 1, '客户端测试', 10, 'fa fa-wrench', '~/Admin/Mobile', '0');
INSERT INTO Navigations (ParentId, Name, "order", Icon, Url, Category) VALUES (currval('navigations_id_seq') - 2, 'API文档', 10, 'fa fa-wrench', '~/swagger', '0');

View File

@ -40,7 +40,8 @@ INSERT INTO [Navigations] ([ID], [ParentId], [Name], [Order], [Icon], [Url], [Ca
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 (14, 0, '程序异常', 140, 'fa fa-cubes', '~/Admin/Exceptions', '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');
INSERT INTO [Navigations] ([ID], [ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (17, 16, '客户端测试', 10, 'fa fa-wrench', '~/Admin/Mobile', '0');
INSERT INTO [Navigations] ([ID], [ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (18, 16, 'API文档', 10, 'fa fa-wrench', '~/swagger', '0');