From 8c9c5b5d6c9f3f9a056ebfc03bf70cbcc1ae8931 Mon Sep 17 00:00:00 2001 From: Argo-MacBookPro Date: Sat, 2 Mar 2019 15:15:47 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=8A=9F=E8=83=BD=EF=BC=9A?= =?UTF-8?q?=E5=9C=A8=E7=BA=BF=E7=94=A8=E6=88=B7=E5=A2=9E=E5=8A=A0=E5=AE=A2?= =?UTF-8?q?=E6=88=B7=E7=AB=AF=E6=B5=8F=E8=A7=88=E5=99=A8=E4=B8=8E=E6=93=8D?= =?UTF-8?q?=E4=BD=9C=E7=B3=BB=E7=BB=9F=E4=BF=A1=E6=81=AF=EF=BC=8C1?= =?UTF-8?q?=E5=88=86=E9=92=9F=E5=90=8E=E6=B8=85=E7=90=86=E4=BC=9A=E8=AF=9D?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=20#IS60Z?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Bootstrap.Admin/Bootstrap.Admin.csproj | 1 - .../Controllers/Api/OnlineUsersController.cs | 6 +- .../OnlineUsers/DefaultOnlineUsers.cs | 61 ++++++++++++++++-- Bootstrap.Admin/OnlineUsers/IOnlineUsers.cs | 17 ++++- Bootstrap.Admin/OnlineUsers/OnlineUser.cs | 25 ++++++++ .../OnlineUsers/OnlineUserCache.cs | 63 +++++++++++++++++++ .../OnlineUsersMiddlewareExtensions.cs | 38 ++++++----- ...OnlineUsersServicesCollectionExtensions.cs | 9 +++ Bootstrap.Admin/appsettings.json | 1 + Bootstrap.Admin/wwwroot/js/online.js | 20 ++++-- .../Bootstrap.Client.DataAccess.csproj | 2 +- Bootstrap.Client/Bootstrap.Client.csproj | 1 - .../Bootstrap.DataAccess.csproj | 2 +- Bootstrap.DataAccess/Helper/UserHelper.cs | 1 - UnitTest/BAWebHost.cs | 7 ++- UnitTest/Bootstrap.Admin/Api/OnlineTest.cs | 8 +-- 16 files changed, 221 insertions(+), 41 deletions(-) create mode 100644 Bootstrap.Admin/OnlineUsers/OnlineUserCache.cs diff --git a/Bootstrap.Admin/Bootstrap.Admin.csproj b/Bootstrap.Admin/Bootstrap.Admin.csproj index 67408f820..7062be8be 100644 --- a/Bootstrap.Admin/Bootstrap.Admin.csproj +++ b/Bootstrap.Admin/Bootstrap.Admin.csproj @@ -15,7 +15,6 @@ - diff --git a/Bootstrap.Admin/Controllers/Api/OnlineUsersController.cs b/Bootstrap.Admin/Controllers/Api/OnlineUsersController.cs index 55409a444..6b3b86a77 100644 --- a/Bootstrap.Admin/Controllers/Api/OnlineUsersController.cs +++ b/Bootstrap.Admin/Controllers/Api/OnlineUsersController.cs @@ -19,11 +19,11 @@ namespace Bootstrap.Admin.Controllers.Api [HttpPost()] public IEnumerable Post([FromServices]IOnlineUsers onlineUSers) { - return onlineUSers.OnlineUsers; + return onlineUSers.OnlineUsers.OrderByDescending(u => u.LastAccessTime); } /// - /// 获取指定IP地址的在线用户请求地址明细数据 + /// 获取指定会话的在线用户请求地址明细数据 /// /// /// @@ -31,7 +31,7 @@ namespace Bootstrap.Admin.Controllers.Api [HttpGet("{id}")] public IEnumerable> Get(string id, [FromServices]IOnlineUsers onlineUSers) { - var user = onlineUSers.OnlineUsers.FirstOrDefault(u => u.Ip == id); + var user = onlineUSers.OnlineUsers.FirstOrDefault(u => u.ConnectionId == id); return user?.RequestUrls ?? new KeyValuePair[0]; } } diff --git a/Bootstrap.Admin/OnlineUsers/DefaultOnlineUsers.cs b/Bootstrap.Admin/OnlineUsers/DefaultOnlineUsers.cs index 53d4875a2..84c12f58a 100644 --- a/Bootstrap.Admin/OnlineUsers/DefaultOnlineUsers.cs +++ b/Bootstrap.Admin/OnlineUsers/DefaultOnlineUsers.cs @@ -1,6 +1,10 @@ -using System; +using Longbow.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Linq; +using System.Net.Http; namespace Bootstrap.Admin { @@ -9,7 +13,17 @@ namespace Bootstrap.Admin /// internal class DefaultOnlineUsers : IOnlineUsers { - private ConcurrentDictionary _onlineUsers = new ConcurrentDictionary(); + private ConcurrentDictionary _onlineUsers = new ConcurrentDictionary(); + private HttpClient _client; + private IEnumerable _local = new string[] { "::1", "127.0.0.1" }; + /// + /// + /// + /// + public DefaultOnlineUsers(IHttpClientFactory factory) + { + _client = factory.CreateClient(OnlineUsersServicesCollectionExtensions.IPSvrHttpClientName); + } /// /// @@ -17,7 +31,7 @@ namespace Bootstrap.Admin /// public IEnumerable OnlineUsers { - get { return _onlineUsers.Values; } + get { return _onlineUsers.Values.Select(v => v.User); } } /// @@ -27,6 +41,45 @@ namespace Bootstrap.Admin /// /// /// - public OnlineUser AddOrUpdate(string key, Func addValueFactory, Func updateValueFactory) => _onlineUsers.AddOrUpdate(key, addValueFactory, updateValueFactory); + public OnlineUserCache AddOrUpdate(string key, Func addValueFactory, Func updateValueFactory) => _onlineUsers.AddOrUpdate(key, addValueFactory, updateValueFactory); + + /// + /// + /// + /// + /// + /// + public bool TryRemove(string key, out OnlineUserCache onlineUserCache) => _onlineUsers.TryRemove(key, out onlineUserCache); + + /// + /// + /// + /// + /// + public string RetrieveLocaleByIp(string ip = null) + { + if (ip.IsNullOrEmpty() || _local.Any(p => p == ip)) return "本地连接"; + + var url = ConfigurationManager.AppSettings["IPSvrUrl"]; + var task = _client.GetAsJsonAsync($"{url}{ip}"); + task.Wait(); + return task.Result.status == "0" ? string.Join(" ", task.Result.address.SpanSplit("|").Skip(1).Take(2)) : "XX XX"; + } + + /// + /// + /// + private class IPLocator + { + /// + /// 详细地址信息 + /// + public string address { get; set; } + + /// + /// 结果状态返回码 + /// + public string status { get; set; } + } } } diff --git a/Bootstrap.Admin/OnlineUsers/IOnlineUsers.cs b/Bootstrap.Admin/OnlineUsers/IOnlineUsers.cs index 841237858..73ce30cd9 100644 --- a/Bootstrap.Admin/OnlineUsers/IOnlineUsers.cs +++ b/Bootstrap.Admin/OnlineUsers/IOnlineUsers.cs @@ -20,6 +20,21 @@ namespace Bootstrap.Admin /// /// /// - OnlineUser AddOrUpdate(string key, Func addValueFactory, Func updateValueFactory); + OnlineUserCache AddOrUpdate(string key, Func addValueFactory, Func updateValueFactory); + + /// + /// + /// + /// + /// + /// + bool TryRemove(string key, out OnlineUserCache onlineUserCache); + + /// + /// + /// + /// + /// + string RetrieveLocaleByIp(string ip = null); } } diff --git a/Bootstrap.Admin/OnlineUsers/OnlineUser.cs b/Bootstrap.Admin/OnlineUsers/OnlineUser.cs index 690810baf..35d3f8f46 100644 --- a/Bootstrap.Admin/OnlineUsers/OnlineUser.cs +++ b/Bootstrap.Admin/OnlineUsers/OnlineUser.cs @@ -12,11 +12,21 @@ namespace Bootstrap.Admin { private ConcurrentQueue> _requestUrls = new ConcurrentQueue>(); + /// + /// + /// + public string ConnectionId { get; set; } + /// /// /// public string UserName { get; set; } + /// + /// + /// + public string DisplayName { get; set; } + /// /// /// @@ -27,6 +37,11 @@ namespace Bootstrap.Admin /// public DateTime LastAccessTime { get; set; } + /// + /// + /// + public string Location { get; set; } + /// /// /// @@ -37,6 +52,16 @@ namespace Bootstrap.Admin /// public string Ip { get; set; } + /// + /// + /// + public string Browser { get; set; } + + /// + /// + /// + public string OS { get; set; } + /// /// /// diff --git a/Bootstrap.Admin/OnlineUsers/OnlineUserCache.cs b/Bootstrap.Admin/OnlineUsers/OnlineUserCache.cs new file mode 100644 index 000000000..e6d52d853 --- /dev/null +++ b/Bootstrap.Admin/OnlineUsers/OnlineUserCache.cs @@ -0,0 +1,63 @@ +using System; +using System.Threading; + +namespace Bootstrap.Admin +{ + /// + /// + /// + public class OnlineUserCache : IDisposable + { + private Timer dispatcher; + + /// + /// + /// + /// + /// + public OnlineUserCache(OnlineUser user, Action action) + { + User = user; + dispatcher = new Timer(_ => action(), null, TimeSpan.FromMinutes(1), Timeout.InfiniteTimeSpan); + } + + /// + /// + /// + public OnlineUser User { get; set; } + + /// + /// + /// + public void Reset() + { + if (dispatcher != null) dispatcher.Change(TimeSpan.FromMinutes(1), Timeout.InfiniteTimeSpan); + } + + #region Impletement IDispose + /// + /// + /// + /// + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + if (dispatcher != null) + { + dispatcher.Dispose(); + dispatcher = null; + } + } + } + /// + /// + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + } +} diff --git a/Bootstrap.Admin/OnlineUsers/OnlineUsersMiddlewareExtensions.cs b/Bootstrap.Admin/OnlineUsers/OnlineUsersMiddlewareExtensions.cs index 102c8f835..a8a8792f2 100644 --- a/Bootstrap.Admin/OnlineUsers/OnlineUsersMiddlewareExtensions.cs +++ b/Bootstrap.Admin/OnlineUsers/OnlineUsersMiddlewareExtensions.cs @@ -1,9 +1,10 @@ using Bootstrap.Admin; +using Bootstrap.DataAccess; +using Longbow.Web; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using System; using System.Linq; -using System.Threading.Tasks; namespace Microsoft.AspNetCore.Builder { @@ -19,30 +20,33 @@ namespace Microsoft.AspNetCore.Builder /// public static IApplicationBuilder UseOnlineUsers(this IApplicationBuilder builder) => builder.UseWhen(context => context.Filter(), app => app.Use(async (context, next) => { - await Task.Run(() => + await System.Threading.Tasks.Task.Run(() => { - var onlineUsers = context.RequestServices.GetService(); - var clientIp = context.Connection.RemoteIpAddress?.ToString() ?? "::1"; - onlineUsers.AddOrUpdate(clientIp, key => - { - var ou = new OnlineUser(); - ou.Ip = clientIp; - ou.UserName = context.User.Identity.Name; - ou.FirstAccessTime = DateTime.Now; - ou.LastAccessTime = DateTime.Now; - ou.Method = context.Request.Method; - ou.RequestUrl = context.Request.Path; - ou.AddRequestUrl(context.Request.Path); - return ou; - }, (key, v) => + var onlineUserSvr = context.RequestServices.GetRequiredService(); + var proxy = new Func((c, action) => { + var v = c.User; v.UserName = context.User.Identity.Name; + if (!v.UserName.IsNullOrEmpty()) v.DisplayName = UserHelper.RetrieveUserByUserName(v.UserName).DisplayName; v.LastAccessTime = DateTime.Now; v.Method = context.Request.Method; v.RequestUrl = context.Request.Path; v.AddRequestUrl(context.Request.Path); - return v; + 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?.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(); })); diff --git a/Bootstrap.Admin/OnlineUsers/OnlineUsersServicesCollectionExtensions.cs b/Bootstrap.Admin/OnlineUsers/OnlineUsersServicesCollectionExtensions.cs index 6cd779095..6bbfa3f54 100644 --- a/Bootstrap.Admin/OnlineUsers/OnlineUsersServicesCollectionExtensions.cs +++ b/Bootstrap.Admin/OnlineUsers/OnlineUsersServicesCollectionExtensions.cs @@ -8,6 +8,11 @@ namespace Microsoft.Extensions.DependencyInjection /// public static class OnlineUsersServicesCollectionExtensions { + /// + /// + /// + internal const string IPSvrHttpClientName = "IPSvr"; + /// /// /// @@ -16,6 +21,10 @@ namespace Microsoft.Extensions.DependencyInjection public static IServiceCollection AddOnlineUsers(this IServiceCollection services) { services.TryAddSingleton(); + services.AddHttpClient(IPSvrHttpClientName, client => + { + client.DefaultRequestHeaders.Connection.Add("keep-alive"); + }); return services; } } diff --git a/Bootstrap.Admin/appsettings.json b/Bootstrap.Admin/appsettings.json index a63467e21..15dbe2aef 100644 --- a/Bootstrap.Admin/appsettings.json +++ b/Bootstrap.Admin/appsettings.json @@ -49,6 +49,7 @@ } } ], + "IPSvrUrl": "http://api.map.baidu.com/location/ip?ak=6lvVPMDlm2gjLpU0aiqPsHXi2OiwGQRj&ip=", "SwaggerPathBase": "/BA", "AllowOrigins": "http://localhost,http://10.15.63.218", "KeyPath": "..\\keys", diff --git a/Bootstrap.Admin/wwwroot/js/online.js b/Bootstrap.Admin/wwwroot/js/online.js index 040a100ac..e21a2b826 100644 --- a/Bootstrap.Admin/wwwroot/js/online.js +++ b/Bootstrap.Admin/wwwroot/js/online.js @@ -14,16 +14,22 @@ return options.pageSize * (options.pageNumber - 1) + index + 1; } }, + { + title: "会话Id", field: "ConnectionId" + }, { title: "登陆名称", field: "UserName" }, { title: "显示名称", field: "DisplayName" }, { title: "登录时间", field: "FirstAccessTime" }, { title: "最近操作时间", field: "LastAccessTime" }, { title: "请求方式", field: "Method" }, - { title: "IP地址", field: "Ip" }, + { title: "主机", field: "Ip" }, + { title: "登录地点", field: "Location" }, + { title: "浏览器", field: "Browser" }, + { title: "操作系统", field: "OS" }, { title: "访问地址", field: "RequestUrl" }, { - title: "历史地址", field: "Ip", formatter: function (value, row, index, field) { - return $.format('', value); + title: "历史地址", field: "ConnectionId", formatter: function (value, row, index, field) { + return $.format('', value); } } ] @@ -38,9 +44,11 @@ var content = result.map(function (item) { return $.format("{0}{1}", item.Key, item.Value); }).join(''); - content = $.format('
{0}
访问时间访问地址
', content); - $this.lgbPopover({ content: content, placement: $(window).width() < 768 ? 'top' : 'left' }); - $this.popover('show'); + content = content === '' ? + '已断开' : + $.format('
{0}
访问时间访问地址
', content); + $this.popover({ content: content, placement: $(window).width() < 768 ? 'top' : 'left' }); + $this.trigger('focus'); } }); } diff --git a/Bootstrap.Client.DataAccess/Bootstrap.Client.DataAccess.csproj b/Bootstrap.Client.DataAccess/Bootstrap.Client.DataAccess.csproj index 58c1c04e1..db0c8adfa 100644 --- a/Bootstrap.Client.DataAccess/Bootstrap.Client.DataAccess.csproj +++ b/Bootstrap.Client.DataAccess/Bootstrap.Client.DataAccess.csproj @@ -14,7 +14,7 @@ - +
diff --git a/Bootstrap.Client/Bootstrap.Client.csproj b/Bootstrap.Client/Bootstrap.Client.csproj index 2016b9e87..a5b2d5ae8 100644 --- a/Bootstrap.Client/Bootstrap.Client.csproj +++ b/Bootstrap.Client/Bootstrap.Client.csproj @@ -12,7 +12,6 @@ - diff --git a/Bootstrap.DataAccess/Bootstrap.DataAccess.csproj b/Bootstrap.DataAccess/Bootstrap.DataAccess.csproj index 655b9c0e8..d8ef91fb4 100644 --- a/Bootstrap.DataAccess/Bootstrap.DataAccess.csproj +++ b/Bootstrap.DataAccess/Bootstrap.DataAccess.csproj @@ -14,7 +14,7 @@ - + diff --git a/Bootstrap.DataAccess/Helper/UserHelper.cs b/Bootstrap.DataAccess/Helper/UserHelper.cs index 2b4a54030..eaf761e3d 100644 --- a/Bootstrap.DataAccess/Helper/UserHelper.cs +++ b/Bootstrap.DataAccess/Helper/UserHelper.cs @@ -1,7 +1,6 @@ using Bootstrap.Security; using Longbow.Cache; using Longbow.Data; -using System; using System.Collections.Generic; namespace Bootstrap.DataAccess diff --git a/UnitTest/BAWebHost.cs b/UnitTest/BAWebHost.cs index b87df918f..6b1489fa3 100644 --- a/UnitTest/BAWebHost.cs +++ b/UnitTest/BAWebHost.cs @@ -73,7 +73,12 @@ namespace Bootstrap.Admin /// /// /// - public HttpClient CreateClient(string baseAddress) => CreateDefaultClient(new Uri($"http://localhost/{baseAddress}/"), new RedirectHandler(7), new CookieContainerHandler(_cookie)); + public HttpClient CreateClient(string baseAddress) + { + var client = CreateDefaultClient(new Uri($"http://localhost/{baseAddress}/"), new RedirectHandler(7), new CookieContainerHandler(_cookie)); + client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0.1 Safari/605.1.15"); + return client; + } private readonly CookieContainer _cookie = new CookieContainer(); diff --git a/UnitTest/Bootstrap.Admin/Api/OnlineTest.cs b/UnitTest/Bootstrap.Admin/Api/OnlineTest.cs index 7e77fd8fa..391ed2bcc 100644 --- a/UnitTest/Bootstrap.Admin/Api/OnlineTest.cs +++ b/UnitTest/Bootstrap.Admin/Api/OnlineTest.cs @@ -11,15 +11,15 @@ namespace Bootstrap.Admin.Api [Fact] public async void Post_Ok() { - var usres = await Client.PostAsJsonAsync>(string.Empty); - Assert.Single(usres); + var users = await Client.PostAsJsonAsync>(string.Empty); + Assert.Single(users); } [Fact] public async void Get_Ok() { - var urls = await Client.GetAsJsonAsync>>("::1"); - Assert.NotEmpty(urls); + var urls = await Client.GetAsJsonAsync>>("UnitTest"); + Assert.Empty(urls); } } } -- Gitee