diff --git a/Bootstrap.Admin/Controllers/Api/OnlineUsersController.cs b/Bootstrap.Admin/Controllers/Api/OnlineUsersController.cs
new file mode 100644
index 00000000..131969eb
--- /dev/null
+++ b/Bootstrap.Admin/Controllers/Api/OnlineUsersController.cs
@@ -0,0 +1,23 @@
+using Microsoft.AspNetCore.Mvc;
+using System.Collections.Generic;
+
+namespace Bootstrap.Admin.Controllers.Api
+{
+ ///
+ ///
+ ///
+ [Route("api/[controller]")]
+ [ApiController]
+ public class OnlineUsersController : ControllerBase
+ {
+ ///
+ ///
+ ///
+ ///
+ [HttpPost()]
+ public IEnumerable Post([FromServices]IOnlineUsers onlineUSers)
+ {
+ return onlineUSers.OnlineUsers;
+ }
+ }
+}
diff --git a/Bootstrap.Admin/OnlineUsers/DefaultOnlineUsers.cs b/Bootstrap.Admin/OnlineUsers/DefaultOnlineUsers.cs
new file mode 100644
index 00000000..53d4875a
--- /dev/null
+++ b/Bootstrap.Admin/OnlineUsers/DefaultOnlineUsers.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+
+namespace Bootstrap.Admin
+{
+ ///
+ ///
+ ///
+ internal class DefaultOnlineUsers : IOnlineUsers
+ {
+ private ConcurrentDictionary _onlineUsers = new ConcurrentDictionary();
+
+ ///
+ ///
+ ///
+ ///
+ public IEnumerable OnlineUsers
+ {
+ get { return _onlineUsers.Values; }
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public OnlineUser AddOrUpdate(string key, Func addValueFactory, Func updateValueFactory) => _onlineUsers.AddOrUpdate(key, addValueFactory, updateValueFactory);
+ }
+}
diff --git a/Bootstrap.Admin/OnlineUsers/IOnlineUsers.cs b/Bootstrap.Admin/OnlineUsers/IOnlineUsers.cs
new file mode 100644
index 00000000..84123785
--- /dev/null
+++ b/Bootstrap.Admin/OnlineUsers/IOnlineUsers.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+
+namespace Bootstrap.Admin
+{
+ ///
+ ///
+ ///
+ public interface IOnlineUsers
+ {
+ ///
+ ///
+ ///
+ IEnumerable OnlineUsers { get; }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ OnlineUser AddOrUpdate(string key, Func addValueFactory, Func updateValueFactory);
+ }
+}
diff --git a/Bootstrap.Admin/OnlineUsers/OnlineUser.cs b/Bootstrap.Admin/OnlineUsers/OnlineUser.cs
new file mode 100644
index 00000000..39720d73
--- /dev/null
+++ b/Bootstrap.Admin/OnlineUsers/OnlineUser.cs
@@ -0,0 +1,80 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+
+namespace Bootstrap.Admin
+{
+
+ ///
+ ///
+ ///
+ public class OnlineUser
+ {
+ private ConcurrentQueue> _requestUrls;
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public OnlineUser(string ip, string userName, string method)
+ {
+ Ip = ip;
+ UserName = userName;
+ Method = method;
+ FirstAccessTime = DateTime.Now;
+ LastAccessTime = DateTime.Now;
+ _requestUrls = new ConcurrentQueue>();
+ }
+
+ ///
+ ///
+ ///
+ public string UserName { get; }
+
+ ///
+ ///
+ ///
+ public DateTime FirstAccessTime { get; }
+
+ ///
+ ///
+ ///
+ public DateTime LastAccessTime { get; set; }
+
+ ///
+ ///
+ ///
+ public string Method { get; set; }
+
+ ///
+ ///
+ ///
+ public string Ip { get; set; }
+
+ ///
+ ///
+ ///
+ public IEnumerable> RequestUrls
+ {
+ get
+ {
+ return _requestUrls.ToArray();
+ }
+ }
+
+ ///
+ ///
+ ///
+ ///
+ public void AddRequestUrl(string url)
+ {
+ _requestUrls.Enqueue(new KeyValuePair(DateTime.Now, url));
+ if (_requestUrls.Count > 10)
+ {
+ _requestUrls.TryDequeue(out _);
+ }
+ }
+ }
+}
diff --git a/Bootstrap.Admin/OnlineUsers/OnlineUsersMiddlewareExtensions.cs b/Bootstrap.Admin/OnlineUsers/OnlineUsersMiddlewareExtensions.cs
new file mode 100644
index 00000000..9275d165
--- /dev/null
+++ b/Bootstrap.Admin/OnlineUsers/OnlineUsersMiddlewareExtensions.cs
@@ -0,0 +1,48 @@
+using Bootstrap.Admin;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Builder
+{
+ ///
+ ///
+ ///
+ public static class OnlineUsersMiddlewareExtensions
+ {
+ ///
+ ///
+ ///
+ ///
+ ///
+ 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();
+ var clientIp = context.Connection.RemoteIpAddress.ToString();
+ onlineUsers.AddOrUpdate(clientIp, key =>
+ {
+ var ou = new OnlineUser(key, context.User.Identity.Name, context.Request.Method);
+ ou.AddRequestUrl(context.Request.Path);
+ return ou;
+ }, (key, v) =>
+ {
+ v.LastAccessTime = DateTime.Now;
+ v.Method = context.Request.Method;
+ 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));
+ }
+ }
+}
diff --git a/Bootstrap.Admin/OnlineUsers/OnlineUsersServicesCollectionExtensions.cs b/Bootstrap.Admin/OnlineUsers/OnlineUsersServicesCollectionExtensions.cs
new file mode 100644
index 00000000..6cd77909
--- /dev/null
+++ b/Bootstrap.Admin/OnlineUsers/OnlineUsersServicesCollectionExtensions.cs
@@ -0,0 +1,22 @@
+using Bootstrap.Admin;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+ ///
+ ///
+ ///
+ public static class OnlineUsersServicesCollectionExtensions
+ {
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static IServiceCollection AddOnlineUsers(this IServiceCollection services)
+ {
+ services.TryAddSingleton();
+ return services;
+ }
+ }
+}
diff --git a/Bootstrap.Admin/Startup.cs b/Bootstrap.Admin/Startup.cs
index bf51014d..ff603bc7 100644
--- a/Bootstrap.Admin/Startup.cs
+++ b/Bootstrap.Admin/Startup.cs
@@ -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("/NotiHub"); });
app.UseMvc(routes =>