From dd2b584c3dd39f93fd4a4396ab23c0d258297915 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A9=99=E5=AD=90?= <454313500@qq.com> Date: Tue, 18 Apr 2023 21:29:44 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E6=B7=BB=E5=8A=A0=E9=95=BF=E8=BF=9E?= =?UTF-8?q?=E6=8E=A5=EF=BC=8C=E5=BC=BA=E5=88=B6=E4=B8=8B=E7=BA=BF=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Rbac/Services/IOnlineService.cs | 12 ++ .../Rbac/Services/Impl/OnlineService.cs | 65 +++++++++ .../Rbac/SignalRHub/Model/OnlineUserModel.cs | 38 +++++ .../Rbac/SignalRHub/OnlineUserHub.cs | 133 ++++++++++++++++++ .../Yi.Furion.Application.xml | 51 +++++++ Yi.Furion.Net6/Yi.Furion.Web.Core/Startup.cs | 4 +- Yi.RuoYi.Vue3/src/api/monitor/online.js | 4 +- Yi.RuoYi.Vue3/src/utils/signalR.js | 2 +- .../src/views/monitor/online/index.vue | 2 +- 9 files changed, 306 insertions(+), 5 deletions(-) create mode 100644 Yi.Furion.Net6/Yi.Furion.Application/Rbac/Services/IOnlineService.cs create mode 100644 Yi.Furion.Net6/Yi.Furion.Application/Rbac/Services/Impl/OnlineService.cs create mode 100644 Yi.Furion.Net6/Yi.Furion.Application/Rbac/SignalRHub/Model/OnlineUserModel.cs create mode 100644 Yi.Furion.Net6/Yi.Furion.Application/Rbac/SignalRHub/OnlineUserHub.cs diff --git a/Yi.Furion.Net6/Yi.Furion.Application/Rbac/Services/IOnlineService.cs b/Yi.Furion.Net6/Yi.Furion.Application/Rbac/Services/IOnlineService.cs new file mode 100644 index 00000000..bc90a185 --- /dev/null +++ b/Yi.Furion.Net6/Yi.Furion.Application/Rbac/Services/IOnlineService.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Yi.Furion.Application.Rbac.Services +{ + public interface IOnlineService + { + } +} diff --git a/Yi.Furion.Net6/Yi.Furion.Application/Rbac/Services/Impl/OnlineService.cs b/Yi.Furion.Net6/Yi.Furion.Application/Rbac/Services/Impl/OnlineService.cs new file mode 100644 index 00000000..6b42cff6 --- /dev/null +++ b/Yi.Furion.Net6/Yi.Furion.Application/Rbac/Services/Impl/OnlineService.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.SignalR; +using Microsoft.Extensions.Logging; +using Yi.Framework.Infrastructure.Ddd.Dtos; +using Yi.Furion.Application.Rbac.SignalRHub; +using Yi.Furion.Application.Rbac.SignalRHub.Model; + +namespace Yi.Furion.Application.Rbac.Services.Impl +{ + public class OnlineService:IOnlineService,IDynamicApiController,ITransient + { + private ILogger _logger; + private IHubContext _hub; + public OnlineService(ILogger logger, IHubContext hub) + { + _logger = logger; + _hub = hub; + } + + /// + /// 动态条件获取当前在线用户 + /// + /// + /// + [HttpGet("")] + public PagedResultDto GetListAsync([FromQuery] OnlineUserModel online) + { + var data = OnlineUserHub.clientUsers; + IEnumerable dataWhere = data.AsEnumerable(); + + if (!string.IsNullOrEmpty(online.Ipaddr)) + { + dataWhere = dataWhere.Where((u) => u.Ipaddr!.Contains(online.Ipaddr)); + } + if (!string.IsNullOrEmpty(online.UserName)) + { + dataWhere = dataWhere.Where((u) => u.UserName!.Contains(online.UserName)); + } + return new PagedResultDto() { Total = data.Count, Items = dataWhere.ToList() }; + } + + + /// + /// 强制退出用户 + /// + /// + /// + [HttpDelete] + [Route("{connnectionId}")] + public async Task ForceOut(string connnectionId) + { + if (OnlineUserHub.clientUsers.Exists(u => u.ConnnectionId == connnectionId)) + { + //前端接受到这个事件后,触发前端自动退出 + await _hub.Clients.Client(connnectionId).SendAsync("forceOut", "你已被强制退出!"); + return true; + } + return false; + } + } +} diff --git a/Yi.Furion.Net6/Yi.Furion.Application/Rbac/SignalRHub/Model/OnlineUserModel.cs b/Yi.Furion.Net6/Yi.Furion.Application/Rbac/SignalRHub/Model/OnlineUserModel.cs new file mode 100644 index 00000000..4557e02f --- /dev/null +++ b/Yi.Furion.Net6/Yi.Furion.Application/Rbac/SignalRHub/Model/OnlineUserModel.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Yi.Furion.Application.Rbac.SignalRHub.Model +{ + public class OnlineUserModel + { + public OnlineUserModel() + { + + } + public OnlineUserModel(string connnectionId) + { + ConnnectionId = connnectionId; + } + + /// + /// 客户端连接Id + /// + public string ConnnectionId { get; } + /// + /// 用户id + /// + public long? UserId { get; set; } + public string UserName { get; set; } + public DateTime? LoginTime { get; set; } + public string Ipaddr { get; set; } + public string LoginLocation { get; set; } + + public string Os { get; set; } + public string Browser { get; set; } + + + } +} diff --git a/Yi.Furion.Net6/Yi.Furion.Application/Rbac/SignalRHub/OnlineUserHub.cs b/Yi.Furion.Net6/Yi.Furion.Application/Rbac/SignalRHub/OnlineUserHub.cs new file mode 100644 index 00000000..60549993 --- /dev/null +++ b/Yi.Furion.Net6/Yi.Furion.Application/Rbac/SignalRHub/OnlineUserHub.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using IPTools.Core; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.SignalR; +using Microsoft.Extensions.Logging; +using UAParser; +using Yi.Framework.Infrastructure.AspNetCore; +using Yi.Framework.Infrastructure.CurrentUsers; +using Yi.Furion.Application.Rbac.SignalRHub.Model; +using Yi.Furion.Core.Rbac.Entities; + +namespace Yi.Furion.Application.Rbac.SignalRHub +{ + public class OnlineUserHub : Hub + { + public static readonly List clientUsers = new(); + + + private HttpContext _httpContext; + private ILogger _logger; + private ICurrentUser _currentUser; + public OnlineUserHub(IHttpContextAccessor httpContextAccessor, ILogger logger, ICurrentUser currentUser) + { + _httpContext = httpContextAccessor.HttpContext; + _logger = logger; + _currentUser = currentUser; + } + + + + /// + /// 成功连接 + /// + /// + public override Task OnConnectedAsync() + { + var name = _currentUser.UserName; + var loginUser = GetLoginLogInfo(_httpContext); + var user = clientUsers.Any(u => u.ConnnectionId == Context.ConnectionId); + //判断用户是否存在,否则添加集合 + if (!user ) + { + OnlineUserModel users = new(Context.ConnectionId) + { + Browser = loginUser?.Browser, + LoginLocation = loginUser?.LoginLocation, + Ipaddr = loginUser?.LoginIp, + LoginTime = DateTime.Now, + Os = loginUser?.Os, + UserName = name ?? "" + }; + clientUsers.Add(users); + _logger.LogInformation($"{DateTime.Now}:{name},{Context.ConnectionId}连接服务端success,当前已连接{clientUsers.Count}个"); + + //Clients.All.SendAsync(HubsConstant.MoreNotice, SendNotice()); + } + //当有人加入,向全部客户端发送当前总数 + Clients.All.SendAsync("onlineNum", clientUsers.Count); + //Clients.All.SendAsync(HubsConstant.OnlineUser, clientUsers); + return base.OnConnectedAsync(); + } + + /// + /// 断开连接 + /// + /// + /// + public override Task OnDisconnectedAsync(Exception exception) + { + var user = clientUsers.Where(p => p.ConnnectionId == Context.ConnectionId).FirstOrDefault(); + //判断用户是否存在,否则添加集合 + if (user != null) + { + clientUsers.Remove(user); + Clients.All.SendAsync("onlineNum", clientUsers.Count); + //Clients.All.SendAsync(HubsConstant.OnlineUser, clientUsers); + _logger.LogInformation($"用户{user?.UserName}离开了,当前已连接{clientUsers.Count}个"); + } + return base.OnDisconnectedAsync(exception); + } + + public async Task SendAllTest(string test) + { + await Clients.All.SendAsync("ReceiveAllInfo", test); + } + + /// + /// 获取客户端信息 + /// + /// + /// + private static ClientInfo GetClientInfo(HttpContext context) + { + var str = context.GetUserAgent(); + var uaParser = Parser.GetDefault(); + ClientInfo c = uaParser.Parse(str); + return c; + } + + /// + /// 记录用户登陆信息 + /// + /// + /// + private static LoginLogEntity GetLoginLogInfo(HttpContext context) + { + var ipAddr = context.GetClientIp(); + IpInfo location; + if (ipAddr == "127.0.0.1") + { + location = new IpInfo() { Province = "本地", City = "本机" }; + } + else + { + location = IpTool.Search(ipAddr); + } + ClientInfo clientInfo = GetClientInfo(context); + LoginLogEntity entity = new() + { + Browser = clientInfo.Device.Family, + Os = clientInfo.OS.ToString(), + LoginIp = ipAddr, + LoginLocation = location.Province + "-" + location.City + }; + + return entity; + } + } +} \ No newline at end of file diff --git a/Yi.Furion.Net6/Yi.Furion.Application/Yi.Furion.Application.xml b/Yi.Furion.Net6/Yi.Furion.Application/Yi.Furion.Application.xml index 8257201d..d5368ce0 100644 --- a/Yi.Furion.Net6/Yi.Furion.Application/Yi.Furion.Application.xml +++ b/Yi.Furion.Net6/Yi.Furion.Application/Yi.Furion.Application.xml @@ -418,6 +418,20 @@ + + + 动态条件获取当前在线用户 + + + + + + + 强制退出用户 + + + + Post服务实现 @@ -516,5 +530,42 @@ User服务抽象 + + + 客户端连接Id + + + + + 用户id + + + + + 成功连接 + + + + + + 断开连接 + + + + + + + 获取客户端信息 + + + + + + + 记录用户登陆信息 + + + + diff --git a/Yi.Furion.Net6/Yi.Furion.Web.Core/Startup.cs b/Yi.Furion.Net6/Yi.Furion.Web.Core/Startup.cs index de9be3c8..5a3c1f92 100644 --- a/Yi.Furion.Net6/Yi.Furion.Web.Core/Startup.cs +++ b/Yi.Furion.Net6/Yi.Furion.Web.Core/Startup.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Yi.Framework.Infrastructure.Data.Json; +using Yi.Furion.Application.Rbac.SignalRHub; using Yi.Furion.Web.Core.Handlers; namespace Yi.Furion.Web.Core; @@ -25,6 +26,7 @@ public class Startup : AppStartup services.AddEventBus(); services.AddHttpContextAccessor(); + services.AddSignalR(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) @@ -44,9 +46,9 @@ public class Startup : AppStartup app.UseAuthorization(); app.UseInject(string.Empty); - app.UseEndpoints(endpoints => { + endpoints.MapHub("/api/hub/main"); endpoints.MapControllers(); }); } diff --git a/Yi.RuoYi.Vue3/src/api/monitor/online.js b/Yi.RuoYi.Vue3/src/api/monitor/online.js index 434aadc4..6a189472 100644 --- a/Yi.RuoYi.Vue3/src/api/monitor/online.js +++ b/Yi.RuoYi.Vue3/src/api/monitor/online.js @@ -3,7 +3,7 @@ import request from '@/utils/request' // 查询在线用户列表 export function list(query) { return request({ - url: '/online/pageList', + url: '/online', method: 'get', params: query }) @@ -12,7 +12,7 @@ export function list(query) { // 强退用户 export function forceLogout(tokenId) { return request({ - url: '/online/ForceOut/' + tokenId, + url: '/online/' + tokenId, method: 'delete' }) } diff --git a/Yi.RuoYi.Vue3/src/utils/signalR.js b/Yi.RuoYi.Vue3/src/utils/signalR.js index 77019867..5674a4a8 100644 --- a/Yi.RuoYi.Vue3/src/utils/signalR.js +++ b/Yi.RuoYi.Vue3/src/utils/signalR.js @@ -31,7 +31,7 @@ export default { }) this.receiveMsg(connection); // 启动 - // this.start(); + this.start(); }, /** * 调用 this.signalR.start().then(async () => { await this.SR.invoke("method")}) diff --git a/Yi.RuoYi.Vue3/src/views/monitor/online/index.vue b/Yi.RuoYi.Vue3/src/views/monitor/online/index.vue index 4a26e625..9c45dfc5 100644 --- a/Yi.RuoYi.Vue3/src/views/monitor/online/index.vue +++ b/Yi.RuoYi.Vue3/src/views/monitor/online/index.vue @@ -81,7 +81,7 @@ const queryParams = ref({ function getList() { loading.value = true; initData(queryParams.value).then(response => { - onlineList.value = response.data.data; + onlineList.value = response.data.items; total.value = response.data.total; loading.value = false; });