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 =>