diff --git a/Yi.Framework.Net6/Yi.Framework.ApiMicroservice/Config/SwaggerDoc.xml b/Yi.Framework.Net6/Yi.Framework.ApiMicroservice/Config/SwaggerDoc.xml index 4555c42b..b9c3e71f 100644 --- a/Yi.Framework.Net6/Yi.Framework.ApiMicroservice/Config/SwaggerDoc.xml +++ b/Yi.Framework.Net6/Yi.Framework.ApiMicroservice/Config/SwaggerDoc.xml @@ -344,6 +344,26 @@ + + + 在线管理 + + + + + 动态条件获取当前在线用户 + + + + + + + + 强制退出用户 + + + + 动态条件分页查询 diff --git a/Yi.Framework.Net6/Yi.Framework.ApiMicroservice/Controllers/OnlineController.cs b/Yi.Framework.Net6/Yi.Framework.ApiMicroservice/Controllers/OnlineController.cs new file mode 100644 index 00000000..654d5249 --- /dev/null +++ b/Yi.Framework.Net6/Yi.Framework.ApiMicroservice/Controllers/OnlineController.cs @@ -0,0 +1,84 @@ +using Hei.Captcha; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.SignalR; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Yi.Framework.Common.Const; +using Yi.Framework.Common.Enum; +using Yi.Framework.Common.Helper; +using Yi.Framework.Common.Models; +using Yi.Framework.Core; +using Yi.Framework.DTOModel; +using Yi.Framework.Interface; +using Yi.Framework.Model.Models; +using Yi.Framework.Repository; +using Yi.Framework.WebCore; +using Yi.Framework.WebCore.AttributeExtend; +using Yi.Framework.WebCore.AuthorizationPolicy; +using Yi.Framework.WebCore.SignalRHub; + +namespace Yi.Framework.ApiMicroservice.Controllers +{ + /// + /// 在线管理 + /// + [ApiController] + [Authorize] + [Route("api/[controller]/[action]")] + public class OnlineController : ControllerBase + { + private ILogger _logger; + private IHubContext _hub; + public OnlineController(ILogger logger, IHubContext hub) + { + _logger = logger; + _hub = hub; + } + + /// + /// 动态条件获取当前在线用户 + /// + /// + /// + /// + [HttpGet] + public Result PageList([FromQuery] OnlineUser online, [FromQuery] PageParModel page) + { + var data = MainHub.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 Result.Success().SetData(new PageModel>() { Total = data.Count, Data = dataWhere.ToList() }); + } + + + /// + /// 强制退出用户 + /// + /// + /// + [HttpDelete] + [Route("{connnectionId}")] + public async Task ForceOut(string connnectionId) + { + if (MainHub.clientUsers.Exists(u => u.ConnnectionId == connnectionId)) + { + //前端接受到这个事件后,触发前端自动退出 + await _hub.Clients.Client(connnectionId).SendAsync(HubTypeEnum.forceOut.ToString(),"你已被强制退出!"); + return Result.Success(); + } + return Result.Error("操作失败!未发现该连接!"); + } + } +} diff --git a/Yi.Framework.Net6/Yi.Framework.ApiMicroservice/Controllers/SignalrController.cs b/Yi.Framework.Net6/Yi.Framework.ApiMicroservice/Controllers/SignalrController.cs deleted file mode 100644 index 17235691..00000000 --- a/Yi.Framework.Net6/Yi.Framework.ApiMicroservice/Controllers/SignalrController.cs +++ /dev/null @@ -1,211 +0,0 @@ -using Hei.Captcha; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Yi.Framework.Common.Const; -using Yi.Framework.Common.Enum; -using Yi.Framework.Common.Helper; -using Yi.Framework.Common.Models; -using Yi.Framework.Core; -using Yi.Framework.DTOModel; -using Yi.Framework.Interface; -using Yi.Framework.Model.Models; -using Yi.Framework.Repository; -using Yi.Framework.WebCore; -using Yi.Framework.WebCore.AttributeExtend; -using Yi.Framework.WebCore.AuthorizationPolicy; - -namespace Yi.Framework.ApiMicroservice.Controllers -{ - /// - /// 账户管理 - /// - [ApiController] - [Authorize] - [Route("api/[controller]/[action]")] - public class AccountController : ControllerBase - { - private IUserService _iUserService; - private JwtInvoker _jwtInvoker; - private ILogger _logger; - private SecurityCodeHelper _securityCode; - private IRepository _repository; - public AccountController(ILogger logger, IUserService iUserService, JwtInvoker jwtInvoker, SecurityCodeHelper securityCode) - { - _iUserService = iUserService; - _jwtInvoker = jwtInvoker; - _logger = logger; - _securityCode = securityCode; - _repository = iUserService._repository; - } - - /// - /// 重置管理员CC的密码 - /// - /// - [HttpGet] - [AllowAnonymous] - public async Task RestCC() - { - var user = await _iUserService._repository.GetFirstAsync(u => u.UserName == "cc"); - user.Password = "123456"; - user.BuildPassword(); - await _iUserService._repository.UpdateIgnoreNullAsync(user); - return Result.Success(); - } - - /// - /// 没啥说,登录 - /// - /// - /// - [AllowAnonymous] - [HttpPost] - public async Task Login(LoginDto loginDto) - { - //跳过,需要redis缓存获取uuid与code的关系,进行比较即可 - //先效验验证码和UUID - //登录还需要进行登录日志的落库 - - var loginInfo = HttpContext.GetLoginLogInfo(); - loginInfo.LoginUser = loginDto.UserName; - loginInfo.LogMsg = "登录成功!"; - var loginLogRepository = _repository.ChangeRepository>(); - UserEntity user = new(); - if (await _iUserService.Login(loginDto.UserName, loginDto.Password, o => user = o)) - { - var userRoleMenu = await _iUserService.GetUserAllInfo(user.Id); - await loginLogRepository.InsertReturnSnowflakeIdAsync(loginInfo); - return Result.Success(loginInfo.LogMsg).SetData(new { token = _jwtInvoker.GetAccessToken(userRoleMenu.User, userRoleMenu.Menus) }); - } - loginInfo.LogMsg = "登录失败!用户名或者密码错误!"; - await loginLogRepository.InsertReturnSnowflakeIdAsync(loginInfo); - return Result.Error(loginInfo.LogMsg); - } - - - - /// - /// 没啥说,注册 - /// - /// - /// - [AllowAnonymous] - [HttpPost] - public async Task Register(RegisterDto registerDto) - { - UserEntity user = new(); - if (await _iUserService.Register(WebCore.Mapper.MapperHelper.Map(registerDto), o => user = o)) - { - return Result.Success("注册成功!").SetData(user); - } - return Result.SuccessError("注册失败!用户名已存在!"); - } - - /// - /// 没啥说,登出 - /// - /// - [HttpPost] - [AllowAnonymous] - public Result Logout() - { - return Result.Success("安全登出成功!"); - } - - /// - /// 通过已登录的用户获取用户信息 - /// - /// - [HttpGet] - //[Authorize] - public async Task GetUserAllInfo() - { - //通过鉴权jwt获取到用户的id - var userId = HttpContext.GetUserIdInfo(); - var data = await _iUserService.GetUserAllInfo(userId); - //系统用户数据被重置,老前端访问重新授权 - if (data is null) - { - return Result.UnAuthorize(); - } - - data.Menus.Clear(); - return Result.Success().SetData(data); - } - - /// - /// 获取当前登录用户的前端路由 - /// - /// - [HttpGet] - public async Task GetRouterInfo() - { - var userId = HttpContext.GetUserIdInfo(); - var data = await _iUserService.GetUserAllInfo(userId); - var menus = data.Menus.ToList(); - - //为超级管理员直接给全部路由 - if (SystemConst.Admin.Equals(data.User.UserName)) - { - menus = await _iUserService._repository.ChangeRepository>().GetListAsync(); - } - //将后端菜单转换成前端路由,组件级别需要过滤 - List routers = MenuEntity.RouterBuild(menus); - return Result.Success().SetData(routers); - } - - /// - /// 更新已登录用户的用户信息 - /// - /// - /// - [HttpPut] - public async Task UpdateUserByHttp(UserEntity user) - { - //当然,密码是不能给他修改的 - user.Password = null; - user.Salt = null; - - //修改需要赋值上主键哦 - user.Id = HttpContext.GetUserIdInfo(); - return Result.Success().SetStatus(await _iUserService._repository.UpdateIgnoreNullAsync(user)); - } - - /// - /// 自己更新密码 - /// - /// - /// - [HttpPut] - public async Task UpdatePassword(UpdatePasswordDto dto) - { - long userId = HttpContext.GetUserIdInfo(); - - if (await _iUserService.UpdatePassword(dto, userId)) - { - return Result.Success(); - } - return Result.Error("更新失败!"); - } - - /// - /// 验证码 - /// - /// - [AllowAnonymous] - [HttpGet] - public Result CaptchaImage() - { - var uuid = Guid.NewGuid(); - var code = _securityCode.GetRandomEnDigitalText(4); - //将uuid与code,Redis缓存中心化保存起来,登录根据uuid比对即可 - var imgbyte = _securityCode.GetEnDigitalCodeByte(code); - return Result.Success().SetData(new { uuid = uuid, img = imgbyte }); - } - } -} diff --git a/Yi.Framework.Net6/Yi.Framework.ApiMicroservice/yi-sqlsugar-dev.db b/Yi.Framework.Net6/Yi.Framework.ApiMicroservice/yi-sqlsugar-dev.db index eb123d18..f24a8283 100644 Binary files a/Yi.Framework.Net6/Yi.Framework.ApiMicroservice/yi-sqlsugar-dev.db and b/Yi.Framework.Net6/Yi.Framework.ApiMicroservice/yi-sqlsugar-dev.db differ diff --git a/Yi.Framework.Net6/Yi.Framework.Common/Enum/HubTypeEnum.cs b/Yi.Framework.Net6/Yi.Framework.Common/Enum/HubTypeEnum.cs index d78a7844..e31558f2 100644 --- a/Yi.Framework.Net6/Yi.Framework.Common/Enum/HubTypeEnum.cs +++ b/Yi.Framework.Net6/Yi.Framework.Common/Enum/HubTypeEnum.cs @@ -8,7 +8,14 @@ namespace Yi.Framework.Common.Enum { public enum HubTypeEnum { + /// + /// 在线总数 + /// onlineNum, + /// + /// 强制退出 + /// + forceOut } } diff --git a/Yi.Framework.Net6/Yi.Framework.Model/SeedData/MenuSeed.cs b/Yi.Framework.Net6/Yi.Framework.Model/SeedData/MenuSeed.cs index 19c53633..dae1310f 100644 --- a/Yi.Framework.Net6/Yi.Framework.Model/SeedData/MenuSeed.cs +++ b/Yi.Framework.Net6/Yi.Framework.Model/SeedData/MenuSeed.cs @@ -45,6 +45,29 @@ namespace Yi.Framework.Model.SeedData }; Entitys.Add(monitoring); + + //在线用户 + MenuEntity online = new MenuEntity() + { + Id = SnowFlakeSingle.Instance.NextId(), + MenuName = "在线用户", + PermissionCode = "monitor:online:list", + MenuType = MenuTypeEnum.Menu.GetHashCode(), + Router = "online", + IsShow = true, + IsLink = false, + IsCache = true, + Component = "monitor/online/index", + MenuIcon = "online", + OrderNum = 100, + ParentId = monitoring.Id, + IsDeleted = false + }; + Entitys.Add(online); + + + + //系统工具 MenuEntity tool = new MenuEntity() { diff --git a/Yi.Framework.Net6/Yi.Framework.WebCore/SignalRHub/MainHub.cs b/Yi.Framework.Net6/Yi.Framework.WebCore/SignalRHub/MainHub.cs index 0fac1551..d1baf01f 100644 --- a/Yi.Framework.Net6/Yi.Framework.WebCore/SignalRHub/MainHub.cs +++ b/Yi.Framework.Net6/Yi.Framework.WebCore/SignalRHub/MainHub.cs @@ -13,6 +13,9 @@ namespace Yi.Framework.WebCore.SignalRHub { public class MainHub : Hub { + public static readonly List clientUsers = new(); + + private HttpContext _httpContext; private ILogger _logger; public MainHub(IHttpContextAccessor httpContextAccessor,ILogger logger) @@ -21,7 +24,7 @@ namespace Yi.Framework.WebCore.SignalRHub _logger = logger; } - private static readonly List clientUsers = new(); + /// /// 成功连接 @@ -30,17 +33,19 @@ namespace Yi.Framework.WebCore.SignalRHub public override Task OnConnectedAsync() { var name = _httpContext.GetUserNameInfo(); - var ip = _httpContext.GetClientIp(); - var ip_info = IpTool.Search(ip); - - var loginUser = _httpContext.GetUserEntityInfo(out _); + var loginUser = _httpContext.GetLoginLogInfo(); var user = clientUsers.Any(u => u.ConnnectionId == Context.ConnectionId); //判断用户是否存在,否则添加集合 if (!user && Context.User.Identity.IsAuthenticated) { - OnlineUser users = new(Context.ConnectionId, name, loginUser.Id, ip) + OnlineUser users = new(Context.ConnectionId) { - Location = ip_info.City + 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}个"); @@ -67,7 +72,7 @@ namespace Yi.Framework.WebCore.SignalRHub clientUsers.Remove(user); Clients.All.SendAsync(HubTypeEnum.onlineNum.ToString(), clientUsers.Count); //Clients.All.SendAsync(HubsConstant.OnlineUser, clientUsers); - _logger.LogInformation($"用户{user?.Name}离开了,当前已连接{clientUsers.Count}个"); + _logger.LogInformation($"用户{user?.UserName}离开了,当前已连接{clientUsers.Count}个"); } return base.OnDisconnectedAsync(exception); } diff --git a/Yi.Framework.Net6/Yi.Framework.WebCore/SignalRHub/OnlineUser.cs b/Yi.Framework.Net6/Yi.Framework.WebCore/SignalRHub/OnlineUser.cs index d9ce0cc1..8b3259d2 100644 --- a/Yi.Framework.Net6/Yi.Framework.WebCore/SignalRHub/OnlineUser.cs +++ b/Yi.Framework.Net6/Yi.Framework.WebCore/SignalRHub/OnlineUser.cs @@ -6,26 +6,32 @@ namespace Yi.Framework.WebCore.SignalRHub { public class OnlineUser { + + public OnlineUser() + { + + } + public OnlineUser(string connnectionId) + { + this.ConnnectionId = connnectionId; + } + /// /// 客户端连接Id /// - public string ConnnectionId { get; set; } + public string ConnnectionId { get; } /// /// 用户id /// - public long? Userid { get; set; } - public string Name { get; set; } + public long? UserId { get; set; } + public string UserName { get; set; } public DateTime LoginTime { get; set; } - public string UserIP { get; set; } - public string Location { get; set; } + public string Ipaddr { get; set; } + public string LoginLocation { get; set; } + + public string Os { get; set; } + public string Browser { get; set; } + - public OnlineUser(string clientid, string name, long? userid, string userip) - { - ConnnectionId = clientid; - Name = name; - LoginTime = DateTime.Now; - Userid = userid; - UserIP = userip; - } } } diff --git a/Yi.Vue3.X.RuoYi/src/App.vue b/Yi.Vue3.X.RuoYi/src/App.vue index 70ffc223..584591d6 100644 --- a/Yi.Vue3.X.RuoYi/src/App.vue +++ b/Yi.Vue3.X.RuoYi/src/App.vue @@ -5,6 +5,11 @@ diff --git a/Yi.Vue3.X.RuoYi/src/api/monitor/online.js b/Yi.Vue3.X.RuoYi/src/api/monitor/online.js index bd221378..434aadc4 100644 --- a/Yi.Vue3.X.RuoYi/src/api/monitor/online.js +++ b/Yi.Vue3.X.RuoYi/src/api/monitor/online.js @@ -3,7 +3,7 @@ import request from '@/utils/request' // 查询在线用户列表 export function list(query) { return request({ - url: '/monitor/online/list', + url: '/online/pageList', method: 'get', params: query }) @@ -12,7 +12,7 @@ export function list(query) { // 强退用户 export function forceLogout(tokenId) { return request({ - url: '/monitor/online/' + tokenId, + url: '/online/ForceOut/' + tokenId, method: 'delete' }) } diff --git a/Yi.Vue3.X.RuoYi/src/utils/signalR.js b/Yi.Vue3.X.RuoYi/src/utils/signalR.js index 38d100bc..77019867 100644 --- a/Yi.Vue3.X.RuoYi/src/utils/signalR.js +++ b/Yi.Vue3.X.RuoYi/src/utils/signalR.js @@ -2,7 +2,8 @@ import * as signalR from '@microsoft/signalr' import useSocketStore from '@/store/modules/socket' import { getToken } from '@/utils/auth' - +import useUserStore from '@/store/modules/user' +import { ElMessage } from 'element-plus' export default { // signalR对象 @@ -36,6 +37,12 @@ export default { * 调用 this.signalR.start().then(async () => { await this.SR.invoke("method")}) * @returns */ +async close(){ + var that = this; + await this.SR.stop(); +}, + + async start() { var that = this; @@ -62,6 +69,12 @@ export default { const socketStore = useSocketStore(); socketStore.setOnlineNum(data) }); + connection.on("forceOut", (msg) => { + useUserStore().logOut().then(() => { + ElMessage.error(msg); + location.href = '/index'; + }) + }); // connection.on("onlineNum", (data) => { // store.dispatch("socket/changeOnlineNum", data); // }); diff --git a/Yi.Vue3.X.RuoYi/src/views/monitor/online/index.vue b/Yi.Vue3.X.RuoYi/src/views/monitor/online/index.vue index 2ee7b7e5..a031b24e 100644 --- a/Yi.Vue3.X.RuoYi/src/views/monitor/online/index.vue +++ b/Yi.Vue3.X.RuoYi/src/views/monitor/online/index.vue @@ -1,10 +1,10 @@ @@ -68,10 +67,12 @@ const { proxy } = getCurrentInstance(); const onlineList = ref([]); const loading = ref(true); const total = ref(0); -const pageNum = ref(1); -const pageSize = ref(10); +// const pageNum = ref(1); +// const pageSize = ref(10); const queryParams = ref({ + pageNum: 1, + pageSize: 10, ipaddr: undefined, userName: undefined }); @@ -80,14 +81,14 @@ const queryParams = ref({ function getList() { loading.value = true; initData(queryParams.value).then(response => { - onlineList.value = response.rows; - total.value = response.total; + onlineList.value = response.data.data; + total.value = response.data.total; loading.value = false; }); } /** 搜索按钮操作 */ function handleQuery() { - pageNum.value = 1; + queryParams.value.pageNum = 1; getList(); } /** 重置按钮操作 */ @@ -98,7 +99,7 @@ function resetQuery() { /** 强退按钮操作 */ function handleForceLogout(row) { proxy.$modal.confirm('是否确认强退名称为"' + row.userName + '"的用户?').then(function () { - return forceLogout(row.tokenId); + return forceLogout(row.connnectionId); }).then(() => { getList(); proxy.$modal.msgSuccess("删除成功");