From 1a2d9ba2b2d431f3190e27b9a1ed4a8107c61ac8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A9=99=E5=AD=90?= <454313500@qq.com> Date: Mon, 3 Oct 2022 18:27:37 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BC=BA=E5=88=B6=E9=80=80=E5=87=BA=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Config/SwaggerDoc.xml | 20 ++ .../Controllers/OnlineController.cs | 84 +++++++ .../Controllers/SignalrController.cs | 211 ------------------ .../yi-sqlsugar-dev.db | Bin 204800 -> 204800 bytes .../Yi.Framework.Common/Enum/HubTypeEnum.cs | 7 + .../Yi.Framework.Model/SeedData/MenuSeed.cs | 23 ++ .../SignalRHub/MainHub.cs | 21 +- .../SignalRHub/OnlineUser.cs | 32 +-- Yi.Vue3.X.RuoYi/src/App.vue | 12 + Yi.Vue3.X.RuoYi/src/api/monitor/online.js | 4 +- Yi.Vue3.X.RuoYi/src/utils/signalR.js | 15 +- .../src/views/monitor/online/index.vue | 27 +-- 12 files changed, 208 insertions(+), 248 deletions(-) create mode 100644 Yi.Framework.Net6/Yi.Framework.ApiMicroservice/Controllers/OnlineController.cs delete mode 100644 Yi.Framework.Net6/Yi.Framework.ApiMicroservice/Controllers/SignalrController.cs 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 eb123d18459b61505c34e985a9233ddfa3c7d8e8..f24a8283682fd5f2751f76e8ff0c294a045dd2a5 100644 GIT binary patch literal 204800 zcmeIb4SW;VnKwF;ZCSED#$bdoU@Qy<4A|Ca^bLgg3osbl*v4Q3F|s8afo&N{4w$^z z1SceEnlxlLX_6+JgfwZ=?hW~BJ_@91)0-ygZQu9Z-Mh`N-FCxfBOBh^Y`gE>+r95^ zf9<{J%$XU@hYofUvYVujz>=Ps^Z%c7{?D12Gv}Fe)U~TMo{qVO63Nj>+Ql=)jKRQM z>vA!S@d<`uJmQ}beQrm8`RGr28Kf`tPsCBX^_c>cz{vg{CtJh*JNtWki}fF@4;Q&D zADO4jn@#_x>E42M1;)Hg-fPCadAH}?&M=1mz0~P6+!jkm21a7LQ?VreGd6ZM*LOF& zy6YQSn_ZQ7Q{@_aQDsZC(lwG88+Ns|ce~nlx3;=EI$L(uclNq=H21pJqZDnC(O9MH z`bcu{P$apAb61mO!oK=pv@1TAR%~ia#v*AUTZtHx3aQDi?l|U)qC{!*O@Z8*h{lH| zg_4Oyl96N=71@$%ijBn5F;tgH>$Y_fX9yutpp~Prg^JpYKlHJI;-PP4o>z#$A z<>iK3*9kuCOpL^K#>OV3&jo5iMS94NjVpxJ~tQ_}wx!IIj?&mQGa*D&lSU&#YDuZPji%q4w>uB!i8b^mlYht+H%ApxVy6z- zY2FeND7_(UaL~Oa><4e<>`Y*9+2-mkkKS9tCi~POE6rO%0;RWv4K}*Bg#F+xs<(>f z=q)a!^A_1;ojPQpc#9-Zc#CW(qI!$$2XE25wc2#0ZUpYPk=|S3R8fJ@jkx8Q+%v{| zO={)wCRyw&-`(J(SL#M6uNj0%zT|uc7X^Hk%3kCY`QclM?EMkBp{TCQzf!*!-{KJE zq;CaNEAz;G#S%e1X}k29uU1jo{4tcwaRY%_a%}!Q>$=)_rPjC`-HDejM{f&@?daX; zC}3EFqrhHM*h~Id_M^@40tG+;PyiGF1wet1uYmb!69Vx}qnRl^8QR2@u4b4O3{!4$ zzH_6;W-MmV7>1|D9jf7Y7Z(b<-RQq&lPApawISZmdxBg9zxE8Z9o>5@Ss(0fZED;$ zGCb1T7UA}$dX5ja*X-)^9qH%&!6U=F_O$NU(R^rU_c343=tN83;S+tky4$-vTlNkI zcJbG5?`}LYuw$rYIL!}^dfL;+hK9U71M9XP+p>N1cu#lJ#IANe<*VPcNyw7(@SN9s z?a`yPgNad&4|?3Zn-6(?c)Q2TdF>ZpdFkmWmZ$I)xxyv5!X;Hwh2Q#7`t2!2g*g}J z4|5((g@a*VD9ndy13{h-guFO|zGPEN+jh=9Jk-5)8#gi1+8t=v)wBJ`@QI$S=@adp zso?cTyzV2>z(9RxTm9bT@U|0!4K2wN9qpl5^jODeTVPjeysvvOdTd+k_)x2-r={I{ ztn*lO`?iTb)NS6o`*?3_Tiwo7o&VUTO+t-OrLuJm1j#>P4|>bi)$|h+8hsUH~P)UUE7W~ zHS#Aqw{6|ldt{``mpI(q(Gu?sMUI}>wqwV(@ngH9$@Gb|JI=*?dxo1v2KF|m_~XdQ zCyoyuKjHcCwP!OAzmDAN^9O=n-s@wSf*IMJ^H3EJl+`d?6=!A^{L%NL3E7+88qV$V zc)~uvvTA~ZJzSXg*ZM==Kcc53*%G^R&uHAf4y1EWTo=DxM0|Wnu5C) ziNf&#udJeKS19P`1j#}Ttqs?=q>pxO<0qOYx{^m?yN(?R?LF3Ytp9M2r#aZUbz*W` zAkpawjP-2amD;=AJ6J!OjJsO~I|e&?dUiB6?Fj4)4ejX)c8m-kac_?_H19q%oH)E^ z=ib9xIu0G#HPI5}lbzlCw(Y}6ut6l5R~F0@LPrDovmavmiD30+Mi6WX5^M!$E;FeW zLkB#nMkwr4wSTfH6!v;*g8_HQ%MT8QQeD?KZXerqq<6HZ?POnG8CEcRJ|Ruc!Y~%p z<_ZrZk7OkjTAN38M)Bi3xj@+Cs||2Ij^}*1wRr}byLUIANFDX=2)5J@G#)#i-rjWV zL~8pE&KDbRNOT{qPaQqd+1E4N)YhD6^lc9uZ`hvLHadB@z4Peyy`wcvlMPKhN7}a> zNlqq^&P~I;TO&v6Q|+C;j^V~Fdv^Esa-B!GJ?X}A|1P1iojA@bwF`QJp3uqu{8F)b zh$qV>=y<9?#}lgBLVT`NRSlgxxUiS2b#orS+l|gh;(3&uR>WPwppO??2R}60z#0t8 zOcgj{Y1o861r@QA7}ToER#X`SjM@3#Z;yW!WtDmI>FIAjlzH~_0*Tn;!!6S1N4H9U zi(TBvppq>STQNTKp_lXG`HKU{2Lgt3*SI|{&Kvdx!d|}C@AvRQ-g`VANeo2d9X)$I zyW>YfecfYCe5|##@kr<3VC&$H`u=1*z4cH{Vldj(e7x1YcX+fu-O=oB-!r~F?oV$! zG8l4ij~$Q1x9tfWZ|Uvyb`SV^BEj*=fy3^``ra);|E}TAU5A1lJLAE1^?`bB?=f^z z%RGP2%tNoEl9!QsqsB>&KcU_h&SZAJe{z!S-GJ+*+KW{AV2x$to5cpK%FAE9^xXZ# z6S#)xvW1#cFzof$y4_yfni`LKn))Z*{-&6}#gp)+kM;T!yPJDX^ls}sFe+q&bF=YL`$Q)^Ke&ZYtQkHq_6&1 z_h{?zw!_ztN87v&iMrrGYtKY4I&+coczs@;<9%+oc-o$R^@YqMFLAz*TYO!51a)4EwL_4Esy=TjIZmgfojKxL?D&1R10dMh%B=Y!?&%-V-epdrEI8FQh@O(otGdpxr17)11 z-s^i%NO^YXH43P;aY|8O?Gr^P7A-wK6!iK~f%7!bY=LKk8r~70_*tH}?!Epy3Wc$K zixlI>4{o6c4m@F=4}1KzoCnR7Hr%a&qPmJQ%?jJK)!zjjAul{uoxK+iw2$!#(Yg0KEg5fVbNf0$mnyv zMTU5+$Uw1t!C>&M__fFohGEdO9u&(b1|BFuvHZ8lPzjFzmx%^L;{We`WRIgk6q6L? z#{(Am3d1PJ|Ir?U9REj#5dSx_afbaK8+Qyk23a&`4_=@EC;$q80-yjW01AKtpa3WU z3V;G%P6Zr4VEQYVMzl^1Q(`E!NY5S2oINvh`nJry56TgCVF?L0ANHX~06alI@Al{3 z72>&&e|Tgv8i|g^$It@`e_6&f)}ve65_7r5`PTV=8$`i)GJ9W15DfF^p@vYc&+89) zLZRHd0zoc-R$?LO!4x9Dwu0fsvYRPoM~jsBU3jGxyv&Zwi=Jrkq4j3M9&fEb;0Xl$ zA-T-k4R%u*!=uF#n0Xeo2!iFvL@Mf~KKpv^brKkV=(pO#-c(uNgMSIGQ-acYnp|3@ z{|5FY2K|Q@C;$q80-yjW01AKtpa3WU3V;Hj04M+oe0~(zfmZwH8MIDcJzAK6js4+C z^yt6G&7q&8@YJ|DVVMK8(t-PMWbjCc^LYbkbpow+A!#@A% zd4T~G00lq+PyiGF1wa8%02BZPKmkwy6aWRTjsk5$@V}a28cFc~nKyCp--9Cmy5K*@ zbLc+F&|6d)y01GGp3V;Hj04M+ofC8WZC;$q80-yjW@Oe`}ivQ!W z0}}s#fni_xyp-Gp0Z;%G00lq+PyiGF1wa8%02BZPzF-QpO3VL?D*$|_L0AER z%dz}F?+b=}LARe8{|~|X|6efgLvcX?PyiGF1wa8%02BZPKmkwy6aWQ4fw?Lm#sBfh z0gnGO#dAeMI#2)<00lq+PyiGF1wa8%02BZPKmkwy6!;=4fam|i{r?xyb5Leb02BZP zKmkwy6aWQ40Z;%G00lq+P+;x~6riLA_TMpJ9sBR>0>>{Lk2(e&YwRD{U$h^yZ?qe1 zKem0<)?ss6e`$Tp8nspz{d3VvMaPRa6&WpWTkf*#vMeu-%)GYnw}sy=oGiSi zFwgXk>26b}skGo%1!oF|3fATSF8}5H6ZxC-^Yh-#`&wRC-Xi0#jZYYdjq45nVtB=H zgW+030rL~)9wyXSWHOnU4Gd#uoFA;QY@aG_z@Ho6EY1@@ONH4C99r;@cX5HRCm8nn zYu&z3s6hv<*FYE2p!GWFwHm0C2EA4X-K>Ewpg}k5px0=iB{b+YI_M@1bUqEbNeA7i zfzG2rH|n4pG|*xibb}5W)<9VrG^~S$G*AZ(8qz_78mOHH4eFo)4b(=126Rxr25O~2 z{W_>m11+LKeLARD1GUhgULBOzK+QBLuY-Ct&_Wv2ql0o9sEG#UbWpbjT0n!kb9-FS_fUDfv%@P*XW?DHPCf5=xQBwl?J+&23@6tR%xKsG-#C$x>5sOLxZl=L04#? zt7*^`I%uT^x{3y^)IpbPpj9;JavjvAfv%)MT{>um2D*XAHS`t8W*DTyUqq}A)*BqT%bj!cC5y$_p zvu9e-0`mC zg5x>I6OQ{GcQ|fvj5#8XZpT(f*iqwfIZ7NR`~R~4#{RzjZ|rZ`U$g&}{Zae9_FK_t z!eRRXdxyQz?zdOlm)cpo(f0q?F4_Lx_Frv3w0+n1wCx+VyKOhyQnn#mudUU#+2*lT z*%sNX)<0VR#rmQ3J?l@buUntDe$)DO>sPG*#hS1VSa(~up~Axp6aWQ40Z;%G00lq+ zPyiJ8>=m%(nS>5HicuZ7PGRgnpfL9BR~UW!6h?2K!r0rZF!t{1x*9SWnZU198OQy8r~6~>NMg|U5y!f4s9Ft)WQjIG-g#+I!L zqj`(MXlhm%jZF%pp;2MfHzlMc4YZb;dn-#{UYZS)DO$uYfMuidHpfEyVg%J!X zj6hIf_yY>V=T{hBpTgk13d6%I49=r4+?>Lwb1RJ6I)zbFt1#BrD2#RM6~@|i3Zr_h z!dO$SFjlWo7^_w*jH*=%V`Y`XSg}%JRIX4M%PSRzYq`Rxa4C#s6$+z#nZj6Ft}vD? zRTztxD2%ej3S&{3!YEy&Fcy|74Cg|HvB0S?N){-L`6UWt-h72oJWpY;#R|j0Dh#_r zVc6^n!)j9)MOKAjDN-0_i^3>0D-2Vi!YD8)jQj$Hk(aM9jCl&fU=-&6li)gI&yO$; zor#gy&e+(5^tqt1v$?*z+0|X&(Aw;(lr~qcu@_agL@Qk* ziLqf_D$ z#L!e)R%W>MfFKu1Mc(JDr6L>WC=^ND7QywqK9U?f6iKe({nd){d;!E+?>g9?jK-2} z6Qc)R@v$_rJ7c4fLE)T=jJEio;>baX(YEH%NPI+nIAPd$b`wcgAB`qabIoqXg+VDh z4kgB7*{vjT%UC*@h)xWq<2ZMz_~^H5F=8%9G)mSK8%HNgx$IrBDV`0AIfGpGhq!-Xv={dj~*$<+{g@V@na!1M*u?35+E2=hDnX3wI zrcA-4;kmpnrX{bE{af~LbKJ>0of>Ya1+ zHU@Q1jw|kS)m=8czcn%t8_6jW))uGeiHuArDDcgMVlV?AV?&V%d^4mTb@}o(9@N8Z zji=I$Bj`Rcdy_?Oz#3C2{r2jq^-glKg^{_rjLdzki|15)Il0mC3C9#ZmEpf->f`5Q zoFj2ke5;T>DAY#{-v3$N2z`#vnpG}SX{dzO%fJJF9D1`%EvzUkEh#bFS|YdvZ9@Oe zDuckED&ps#6(R@;lJH!L%iwpY{i!zWubZX;d}`Z={-?s(GhHokb=)(97m7`4xr zZ4#f;o(I3wRJwKf>}^2l|0TEVSyEWKe7WJaNsaHbZDg_P_!6$%q%lEf0iDWoM4IGN zg0|-(of=dP2#+fvvGIvtd(D^55C^XiDIsX#x}kzsuU>Se8w?y?>*w%$*{y7;aGkru zAT$BI4gW1tyB%+;#AjBQOCQujr!%w}C(&KMvb#u!5sh+@O&zi1XgrldO-Xl66ca>} zv21jWY|Ea(iH6Fp>LZuz(H^wQt+~>xT}M)@y@vG0^~^Qohqsf_d1-uH98vE~Or&GU ztb4@GEveS{7#hAu-CM-kl4^_$9ztzEGP@2Xj!D)WTS8-EbQ}%dkEL~a?V5-sCpBBK z)0AU)Yp`SS)ue9WmG1fRO$e?k87;xzoapXyQLX^-RDDSyL_*aKNW8_thy7f z&$0jRIPIuGPwe;E{%AX6Ye3K2k64S*Z2xY{FDy4$D$H-12MhnX@N}Wq^q%RsDX-wE zf|mT>j)Q^rSI0nM%h-!zh+D6dw*JW21@dV`9SGGb7#J=z-NZ1CEx#T9Uzs zZ=9XJ?|YgIV$pbdHv0XmFnt}3zM9ZafBpH)6OU@>Bhlz=?AKLccAmz*Mr42NsY}m2 zp_WLuhpH+)Lf^IoQ_8fCi(8>*NFP4wE zDoU_*#HtfQbO%;k(J!kq&pwbj^_tr8QPd?>yFK?zPvrJ}m6*PUrd$=FSG!%HS2&$Y z-nSf+ucMK#B;;zR3*=dsQ@QuMFgLY9tq{3YE*H3!4yUrGDiFIxmc{8599}0myppib zoINvh`Zm$w0{I-=jnfH#qOs$HFXwkmdY zIi{EEq@{NedbO(sdWEa0 zV#H2&bva>IxmqBfgR5~m{3jjP_`B%pGF&C9tCwEB<(bP5Jfn7X6pe7IU7dTzEq3)H zOiy+75<;(bwLq_MHI=-t6q8e3y_k@zT`iDjT}|cQzYud%U0o(}t6VK`D_u=xPdO1g z-PMZ-yUNu9`5auGPR3$5t>kL_U3B#VTqUZjOE2B?NapVQ)vg{z{XVs;bI%&at}em! zR97z~^lDcN^a@u~$@}JGa;mGHgk0@vfjsMKD);_*n49YA1tPb~)dIKD)l~M$V#JWP#yo9i;TrH5#(bYA=-5f?p4#(d`hqJh1REN*M^z@%!`pS!HhbK|DU+wVR^PjQ9 z9hjc#@Ogw@?Qnr!;czN>pB4~4Tjg+pTj_8r`}J1D zo^|*Vjl&&;UFC3rd=3sDk0r)OVkw+l@;3f1db0lruKI3 zY24V`7EDj|wvEuMy)DoyyiFzVGh=e9x2=R+?QMZP>uoCc{zA-6^>&fSt@5_Ot@JjP zJ!L}dS#N9mXBNV)^0q)e2XEta_)l^*{!UySDOhMKH=`RbVSP5Hw8dgZcLDM}n7KDT zedf05M_E0FnSIzRy&v&ts5L1i=qC1GFCSk@7s}&>XpsH zS!z~gQ8@F``I+;NWnO(MbLv$}Ht9rSqz=Cg+?a<&lJAPj$X$z1>cpBdk32JT?hPW< zXksj$P9(#Lv61*#EUeHe_pE!jm!u@`Q4wB8Od{7VqpB`T%GGr#Aa$8}_}=NSJx!^L zL@Ry{wi{HGWlPkQ`68vzEvHb5z3$W`a$NNqJpaGW{^(~s{~u)nFHis!00lq+PyiGF z1wa8%02BZPK!GbLAWhpw3%PmZ`Txk^$f%a*|083KJpUgVtL6Fs$XF%M|3^lZJpUgVE9LqB$XFrI|3^lp zJpUgV%jNn1$Z*N?|B+E4&;LipGI{<#GRo!o|HxP>&;Lip5_$eVG8W78|B+E9&;Lip zB6e`FNP^Z${-%Jct` z;gIM5Bf~Dw|3`*Rp8t;wt33Z78AbB^e`HwX`Txi;%k%${Q7F&H={HkKAI9Wk ziFAB0HcRO*-~5f4$DUG_Ub*E!VX`Iaof!KTM4;x%l`ag zQ+2Ll`z7)#w-~~u)LgmcU|z9_%f;>^nkPpiBmL2M3NJrLD?bq@bK&O9sh5=EkS+U+ z6mh&bhZLG?(SHa%{6HfYAI4HNc;%KpL$RqUSMhmFjvvqHPsNVYxW(s8Zhks*$6tvB z7oNzO7PjE{e-V3A&iFsh5?-JHC;$q80-yjW01AKtpa3WU3V;Hj04VUO6;M3ju~8ZS z-=K{Dhn4aFkTU)sRL1`U%J{!u8UObw?U-;Zwx6>P zqhBz1#nx{tww||+S}Tg)EgCIaVtLnc6s-dAGxH7R>can{aJbNpfbaqZKmkwy6!?=> zpm3OZwyvy{LF<7uqD5KIP_^^@W0x;96!+orX;s8$`i|3?J0Ba0jHI$-;hc-}g*~3I z+bs`ab1n}b=G|dWu-457IL;l6PCxt2%xedhqUcN+S`bKxEg7oHq^W%Sbvb38eBsh} zpGqeuvKjF%E`TV*USZXdL6U;R45E>-;aGC-5=@UOBGSt<8~5qxb5%smPfGk6;U8Fx z;saHz#LUSB7bKEnR@1MYn)&uQw2sa6-Dg9x8fxDC%0wh|MM}TFOr3{Z`d(amIggq9 z9z%uy@ccJt9zPkR6BC&s;cn`R1Dg6yEE|ZjrH+ z!njAx_}qoesi*uD#{P`3TVz~FVeHW{-hR$UVLXT#Z$Bq8Iw_31RgBYLyJPw{*~@lv_f#ijmBh<|&K=WH(_X-tQ<;^UD6|Qt^_B(jE%$AlWU}eLjV;Q^iPT zM{^X$DA`RI`{v=}E8B+Ty6?h_s^BV$vR!`QSsLpg*{wWwyNlJ_a{W7W+`_5kwd~nO zeSE2o+g27uYqKRTlUdsptTqDXEV#WRB>qIvoJgO|=eiPDi~w9}?0l`GtuB}MVzUJ7YcNs7|#w&FH~ zWYeBecVId>XiQLDyE+cd=ZDmS;Lc^!I? zfXj6)+=?m363U=9DbN)!hPop6q9+NsTo=PFdVX?5sW0nSII24EqXY_>Wi^^HwHz(K z{EgF>9=M09GTBW6?Vxu=$V14TxD#;j8f2j`jPLT zXFX_#{Q!xvlSD1(5rL}g;aVYAxv8-)pDkwCAG3F|ZRiWUKmkwy6aWQ40Z;%G z00lq+PyiGF1wer>m;#1XC1p$_{-HSYI_EnBfA<|@F~fOy;Xip+%N?rWgy$68zOXw~ z>+yIu=G#opD)Dg@=@GYUakzi_p|j|@o6Nlrq8}fiJxdbKvzg{;(VGt?KRkCXbLYdC zUcPnu+-WM-m;-xFDt7tI_hueGJ9GXX^h;YbY|vmcl~ia-y=gF(xODF~KRox<%lE%Q zL*n>6xeRoxbzt-Z-&nMag_?Ds%eo>95>8eb?=<{=eo>1``wj z1wa8%02BZPKmkwy6aWQ40Z;%GxcUkxBKAYd`TsHH{QszO{{NtI{{Mh-{(nR{|No#e z{@<^R|6iw!{~y5de~bNZ81_x}R(1<(cKpO~m!r*5Z2uekeOJG_zz7O}0-yjW01AKt zpa3WU3V;Hj04ShQ;QuuVzioiGnw&rFjZ8HZ=Q&OL{_uQ5F(ZU^@yvC1G)}uh1bD<5hqt742d840%)9201 zg0^3L<)x>i471UsL3bVadIKu?Tj&36P*-oB3wuJfK0k-*P4X_tg*MEJ9{!8Oo2caZ zxzNlkXu0HhT6EhQT=J5C{B^xi>oHVvj;{^)vL!d>LK|j9izPSYLNl|VrIO?LzlFU^ zp8wDOfPIPm7W)u;7kiVYO@avufC8WZC;$q80-yjW01AKtpa3WU3V;HiSOKpzzC+^w zKPAI8B>qnf1Bw3=gCX(%pOUc=68|TLfyDob!I1d>PrHPH2onD%hJnQYiNWCbzs2fRQ5PyiGF1wa8%02BZPKmkwy6!;TTV1vzO zLSgL78{)JAG>^^LH^t<&$jlsI22X)hP_-Z@8LICbATBQcoPj=qyoS9 zk$vP%6jpxkH`7BXtc*WLjKxLL0|6c%ejvc>4|Bd+&gb1=$pJQKz&0A#oC7T6sL0t) z0~e~m@BS)s`&6-E!5@7;nwUbxerI6pqB1J(b$Q%j9tq~I4e;I#rW{~K0q*7ARD{%3 zV17e^3jEHEXWqnd_;+sf*sx&Yj{r!;#%uqhXCOQ{9v||1HlVft6@DgQg95BBwg!xr z{?~y~j&jcGVr#%??SB>6x(&Ciw|Yp1+^58{G{1p2O>~+8(qGTMb43-QqM~GTmg_ zVsz)P%llQ{ufE9j{xX%_=q}x`!EpQ0bYx&8)*TxiZ;B5Mc|4rkET*jPF~6ptlcZSCEzw%x6_9Iq)X zEiX6R-YArzH8C6?LvPY&ojMn3^Q^S3u8z)@o%Nl)t{u(2uJx!80(5sOmaKGLA4v`# ziX_)?Zg({6n`W!HmoQ%+0`8%ja9m$C{Y@HQ|#WEh{lH|b0WzuDzZB^78y%p=V@|mPexSqG%B>tIW&DK-*I$D#*a1MzfqU9G9KvE0DK$D*<0siPzD zbgVxzkxmG9zvSwEPWt9%7{mV%+RAchD_`oiQXN=tD&0{1QLnH+w=;=daD!qdpmS#)RT^M3ND7oJ*zicskGJbA5KYbxDbMr)ysiLs$LYMbP9^)12b!qT!b!)-f+R!O#!_f_haNH)rCP;eQ! zQ0lK}ByC#+pZFyQsoNe&u_HE_O`yH52nFqlj7%s(*T9B8895jzlWm&q?2^rUq9F9hy^Rg0H3R(&tLGzopHx$H}FZ=5m}o#|0S| zA;;$bjN|S<$}Ov|y_PtljqPM z7eUu~`tmvj_3&_wTTu57%T>L4rKxm-lXi+E{=x6dZ*i?CEOj~!x2+I-gSX*7m)a+I z(;N>Iob*cPMZCW;5mlV>)aT9CSOoQOvnNgUUUW@FcQHBq{pEFS_3Fwi`I4Lh@p}bc z|8EWRZH9f0-S7Cdf7MiLIK4BRSvp|j> zt3aR557t;VzFE93bI;WD{m*Cac>MB1Pa0E`sdQ{KJSbc(!^zla;`*4FaJBGs1?R4D zqvtdNVGmv~qt@;AMh~pU8E~`=){+c9eBeU zo_JJ4ABje1W52Env-33eH6r_CPhEQM2@N~yHzp@%FoDsUO%fAe4ZPg+4#HY>See}aw|hex9S*Q zo_XA~1wLm@xSL1Z}PdcvgchS{lxJp!4FTH%r zGnXHDM(ye->ISG?y@=}SMVOxI>Lr9;?P`Hu;c6;*UnwT1x_U7oSG!su&$^n*y?-I* zrnJm&(b@f6*uXeRSuW&V$yl*}xr@Gon$knbE$g{4da_^sqxv8#R zAabi*EpRJcO=X`fM(kNvFV~#FO9;El)dKk(U0ow|{V_svIQ}j=oW&KRI(+`6r~mxY zS6);*Jc(|})DCx09qz#NREN(a^lFC-^a_Vl$@}b>oa*pmLauhWK%R9tm3zMpb5kA8 zirgxP3*1VFQ`xV#BKEAqmuMXBAnYoK3*>Wf_;@TaJ`zjene+h^{3 zX8Mj-)!v>!<05KrTd3Z)V0x;zZG>L!ZGm3lZ7O-68Ix1JZ6)MtZwusEZ&SJV7h-Oz zw~It>mA3_MrMIc5cjD?u!9r8H8Fk9gQi$jt zthB{qMt1@7J(#&SK7Ho4=|^9o_;Ppx#S;&u)8nadUEMGWAH`}16Qgy5gJUt&|4+v1 zCgXiO@=?9ASvX70sw@g;UOGQ>{;|xfPi0QMO35ajNQ~6sw}Bh;ut@SApOU!)Ye*G)d6`2N3=pno0|PRdA+Ys9;_G@A6;HKasyVKR@r?yszbTE7@@`@aVfJE}%g->!87W}m(0MfIMjdp6 z23kymZqPx)8YoMHhIP=82Acbth-3=spg|3kEMcl04nokN4jRxv$%*$NP~KGP)-9i(V(0T>efJW zpR`e3-8yKU2AX@A3PJ01&{`2{6hA$*0@v!GH5w>KgVyMv>org^VJt_JSg(Vw(?IKJ zxvtYe*J_}(H0W9#v|0nLp+T#4&@~$9dKz?%4!T+cT}Oki)=Qg$9~?lpD3-6*_382D*xtYo!jlTm!A5L6_^GE)8@g4eHWCD>Tp* zG-!nmx=aJjJ^YDeTBd`RYoN<%xt8moOEplm+_lG3ljC?@s)H`kKr3j_B|7M04Rjd| zx>yG-(?D|%9U_^^bkIc_=u%p)i*(RZ4Ri?&TB?IC)Ib;0pbK?Srv_R^gF1E41sZ7X zVK*ex0v)tO1EqFqOLWlr8Ys0>J6{K#r-4#Cwexh)VhxnqsV&w)Sq+rhsbzIghXzXR z)H-xfy9P?_)Y^4Wn+8hl)Y^1Vs|HH#)LM1WA`O(gZ8BwRt+I1d%?eom!&~ zYS0vz+Nm|@pwbwJlq;=Mi|_xJSl<4G_x}hCFHis!00lq+PyiGF1weuS33V<% z#LlDCb)}9?9?(JeYoOGz$^AO$J`I#QHn~p+?bAT1W0QS4Xs-rJ9h>acLHBB))UnCE zI_Mq^lsY!KM+fcEK&fMsJv!)a4U{@IxmySA); zx=RD4j!o{;K|3^1>eyt54%)7PQpYCSb8*4!TtXrH)N*)j_vtpwzL+Ejnnk z21*^9Y}P@WG*If;WRnissDaYPCh`5h$$pSwZ)TS{&O6rGe~b)xfdZfaC;$q80-yjW z01AKtpa3WU3Vdt@qP6IOZNp~$4AA$FUB1vz%*{M>KJ&UX`AK>PeFU$n6(1WykE4ro zmr3gEh#2SMd|?mz;rd{$Kj4iH*5F(=(Q6lcd?f%HXuEkjdXj#;dEHt`GStv!N=J03LSW|gg zyK{-4X+Y9+y_kbaQz?Tv2#T7}sF+;VQM8GOcKiEGzV#quueIIan6@6Y-SDxSH~>Ha zPyiGF1wa8%02BZPKmkxdRA6dRp%ZoRo%1S|uReL?q!s^)7Vr{ z$^7L59qnD+gM*wWQ0qqjIJYZv+o_q??``R*i;TzXglX7l-uVrc_*-R|d-dy^iehNGf$Kk&L2k=!e3*K0n$PmlpP|3=c&{Qn4FR zGPE*FOEgs(-gjVqC7#?b8aLihIfz$Y3!_Cs*H@;}k@N&gluk~>)>n?kQmM!=+E*#w zrQC5kbLV4-A1!MfsSJ-zjEvkU&!+IWIB(b&2z&Y3K*-Dag1(dPf(1kxrmk9{%=`b> z|F;Y&GyU>cFFkkvbS`BCJ-X3qtaYu4Vf?wT$mK$XOUB2BD~k@yt;p)a?vvmLvPI@x z9zN_1g*mR)?+f{TKK^8FzEEVQLQ~}b@tsJ6zR0pG|3eFme&vm;4S3z?sU1IBg1HcD zj0+q%Sthr>_b>l-9Q}ZtR^wR;j2aN~F8;kawAT117Fx}s)te86eZg8T$cNm)5GwwF zRQweu3lznFoc}m_PnDXoM`7bhPA0l|{+C}zZS6BZ3O~sahZgV69&y5PsyyT}&)qij z_{k4nJ2&&~bHBZC*YvCBGj~3odGduz-+k)07f#M{5b^IM@_wF|$SeGuiUP#~1?Hx}kgM1z8A)u@jf9jNZw5c(k)&KDS3sL#j! z$p5?=^>O>doVS(_1^j+LH{{Hb&#B8-r9S9v;6{V|d;kse4=vE=Q}@@wsjJYe-JY=5 zSL^rl-hhW6D$(b&W>xId=YxKy2ZiB$wE^@4x9Ep%hvw_^aeeb2-~80|L8oee7{_1z zo&fUD&^&!Ui(aWUUWI%-T-Y0|^?5yBd>Sp*=Tq`;MOULfZVsQWd7m3a)Phk~&S$f9 z^(oHV^6CBKL+4OjA5SR2aYGI{7y0Z~^qU_yd}`Iu6VF_j_tyG6o{%>X7_#f~x%j#Z z2h~5#wG_pvePK6`gT7uL=jK9u%qHg}T@)_9zUY^q(mNy{boB7GKHeSjg@&whHj-=? zFI*b9D%C*`ihEI`_PV{iXQ(JgK9e^XuZC>sn(eMdam!F35Ik96DK?ehKGm??r@DCI z`U8S){9HR#azvAaXfvnpp1$Mjmu@~q!k?G#d+5@0&ndfHL{@j0L)Ti~?Fst4CkxCu z%8~rOAgt#zF9%=icYD!gG8ikAb(fpbg~g{nz3%9-3ZV~!e*N3y_nj;-<*4q$VnO!L zvbsVs_+V{-3kHG#&rpF}-E4Cx z_P;GuH}kzWGOs>bGyUodnMYpwxSe;D6(9C_@xv4$H|OVlzPM4J)nDEI^Q)AV7xfI# z6#-qaeO!f=Xk<#zvB+a4URDchZiUS z3V;Hj04M+ofC8WZC;$q80-(U3x&lJ9S&XH*L_-L8RS<)OrIrzc#E!~ELkRFJ6%8Q@ zvxFEVRXb3}O7SRyKsm!7wj24OU z`wPO5j!E1j46Km2|3xxPAjbU3P)Ht0Mg}X4s7czK7r%4Uc{CV<Mj)>^FIpx$Gu!wLZLfV6N5rNFH@_S*?#G&HW1mxXE zK|qbJ(qei_QK>on8D0-C*J^!Y$!1-xkEEjsI_qkE9eA}sGb61>SW;p(TMX!!6c_tn zKP{{iICJ*Q%<0?2ZMlE=PjDV`QB*vjG0A$x-#YYtt+)?9Kmkwy6aWQ40Z;%G00lq+ RPyiGF1wa8%;0vz6{|{yh7xzluV69GA^DeWef)98kdV< zj9+3H#v}e2(dRDoSBU*8I0_2akdTYzp}rzw^;wy`cR46^1gZ6 zyv_9AP4^aUDl!&i3tltsFSxznc7`$hmvX1ma9cbR861u8O~+IC&)C@2T;J2|>ZxyN zZFW`Sq{|xvNPwVO#w$+LRd2D3Tge@kmA}RwBlvLT<9DCxQ85C{qS~Qy|-tvBc1nP&2Vg zGLmefB3sf;@zHoDj@mMqP*$cVJ{}p*s6V$ zBcqvI2HvmS?%M1++!9TW9~PQ}jP^uy1o^IT~+^k55XUi`2f9lIQ79oIQ^sxPUy#pMH0`s_Pb;${SZ0n8bK2ejc01~}=PbJHSI`L+rgfOMJxj(nC)FD)xBuc$EG$_maPspNg3+7Tpio^BwS z)qPOtK5`%ZEJ(k8p{aa}lcpr@Qv)1+w@gPD6yw9vscJz*oQD6*Y6Wr9JoUp_FQ*>f z-ZM3!J3is`#$-%!NK>oc8A-+S9x^@)?x-&{mG7;ixubgm?HaAg;Q^1^!@1GF0Zx~+ zVEPDKTwYgaxb-H%QMwGegi^H&b=zfs4km_$Q-yT=)ShTi&8~2UAo~nHN=aPu&e9f7 zrz7}mBps_gKA-YfjL&3B59$dNXCK|hE7a@5oSs~+>Uzg~z2(+>OGsj;kJxG65;7>g zAtX5H-V(NhxAG1qu(xdU^_EBPEg{K1eZ)%hmXJZ|Eg`{1_m;37yhZg^$vnNqg>>E` zN!IBj7K*n>28FjsLJ8GdWIK3^=B@Rn%MBy&xQ&e7ilUVMo|l#{*{O|LB= z*A>eI^`vy^vrw&~l>7md%yR*OS@I-*nsr@oyj*YGi>}1WSEIMZrFQgQV6S0VgMHRs zQ`|@XSq`FPc!2_-04M+ofC8Yv2Uo!SvL z&>V)R#vQ8Rco!E6yWQx&Cdm`#_}UQf=RH9#f?xZF+K=r&o~jS_v^F*F9339*YmadI z)4eC69W{IUeIog$0l3)k6z!ur>CRavwiZpIrnr7%TxS{+~6|Y;Ib;I!Eb*r^Mh$cgE<%H4|5((gM(pT zD9ndy13{h-guJ+b{!~*-`!3EsJk+yeCpS6T+7oEl)4OYA`1;-*nd>{c(!uLSyzY@$ zV6eWcy?%dcc<1%ehL+UzogJZg?0Dx`dtgs`qQ55^JHE4ZVyM;A+tT4Z-gP{-Yv*J? z8aD6Wd!nzky{;`?=Rdx6tI#6Us9akELGn-7g5Gj%HT}?pW?u#8R^YZ)Figc7#>_Cz zMTH&B$hk9r{Kf1;4=t1(iki%aIiIe{g4=7kfH&k01~Tz<#_Q|h8wV#dP3fk6HLX2; zV~vx$Cifj3IqFLs9pL+$kHt>x-yRut_qTb*cgOZ0ney%%Iy%|kz4Js}HjN!gp;8qV$Vc)~uvvT1^Y zJzSXg*ZM==Kcc53*%G^RyJ4hf8Hu(a#DF+TrliGUBO+8MB(^=S5{HA zDHQZ`f@GnF)`sg^GRL}i@{`Sz-KmlIp5r5-{l~kH4;=0FGzYtOOit|#B)dF;@!nl~ z())LLqxEB{gu5l$8SU)t-QC!#b{Ji4!K|IzK8M@IHc zwgmZ9R}a5)*Dw++iX`*Of_Xw{Z$N+cBg_C1tlrEBf-OUWt>MfSCbeQ{heyo_g?+02 zPm)4muctN`aEH8nG#W~GU)Q*6e9uVVSa18u{(=guVD5NAx}1e!EU3>FA4ML?NhtI- zkLrlx$7OPXu*X*$;Cvj<`EYOZ3^w=dZN5Hz%)2|-Qa{*u{6uC~)A8%myLNNF_(Vgp z=U9FE*hp7@?{HIlbF$I5D{!J=S90gr)X|QvW4rc`)ig~tH1&>jY#&KYC6Ufe!+kp< zBlYQyE??(x3d-j!5EhlpI#XO~Ig#7kURjG}gcx3@c5maKzHE6@LmU zVka@ES68g5G6op4^QXT#@dZ>=_Q|JbzW!kL*)xkIVvi5^NT2^?0Q-PJy%^POnb?c* zkq^C`7tdcDL_QEOoV&*DadFc5AxmSW3HA4kH}Ub- z*2a;pXtXuDyM7>*$m}>$lZ?i?n@_a5_YaTNXF8kR9s4GBCH$G4Bhiq1SNudIv2$PG zL`z?nw`b7T8wpNK4IXtj*7t1>`u7ZX?Ku+cY)b?;)d%Xi{m0QkE&Iah*$3a4E;m+? zcB9To_CKNB7S3dL{_6Op3k{_P-0qjZF!%iZL~GmzB&aWpc5d;+BsPl=`@C3wk3Yb9 zINXa)9BK7LhCAE)cf^|deA|ci4DH>O3h_sd4(CzJOB1Rv$e1pA3RBTd+^HJ%T8s?GZ|4xL~b6;EhJ>H!-z4r-}T!Ol80sC`?{TFtY{W<$J@!x|&pN2P302BZPKmkwy z6aWQ40Z;%G00lq+PyiJ8#1%N8ETLbD8Wf|{Hm5kFj%TB;k*1vSefGRGVOD4>Fd3vt zsRF?e=Xs3Q0+U6Y{$OzYzmQ$Tuy3<>u^sFp>+_fUhcd$p6aWQ40Z;%G00lq+PyiGF z1^)dMDBNQ*F(_DxBF^vrA~HQ)YH*hQ-7o7=+&a%)2Ivj|9))V%wE;J}p`&oI41MRu z$G>ANHA+zF+CmC=i+3cE+mC!6c9HR$8mPf(I`I1!8cLZfL#H%Q#%b!iuJXMX=(eW@G@%-8xtK~!qF z2Fl2OeftVf<`)X+y9RZs-+dAFD!EILwz|WfV67*F*M&c>fhtSA0(8h^MtxXN_{?|z zz9H9#^`)V^Jls4gjn_bAWhs!*suGh>L1|CHeO2suSJ+cRg$n4!XH#dfEac+3K)P8% zC{=Sm1n>5T3Up9Lgcz>`HHuIy3o7l;-a-{tmzuk|M7l8p-F^`8215o76!jKOsT61c z$Nvl2A4~E7A3J`3xqm1#yg&g^02BZPKmkwy6aWQ40Z;%G00lsS&z=H?0%;ZvFNgmv znvoLb#a1HY&qc$q0vT_K2D2O)FC#;kjY5l5m}f-8uoM{&i3VN{#(YtlKEg5fVbNf0 z$mn;zO{RFP$Uw1t!C>&E_%+B7reV;s9u&(b2A(Kk)*(ZfD#7vp3ejLl{Qsx#+Y@LK z#Y95>WWqv>{|g2q$N%x9f*k+t!61FM)u^qd-|%usHT?mL)$?(x|(w`K2rK#s5rkC1Sq<^Sjw z08h}5;@$Z-g?KLHA0C~GMPg%#add;iA6GDq_2`ne%)G+leEaGWfaIzTLC+(Pf`b%?nz9jJKA1;6&ut2iS-uKMxS8gSln^6r{ z5x#QcDv8gicAB0{@JPX|oDEMp7};48|Nk-jWA;0r≺V0tG+;PyiGF1wa8%02BZP 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 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("删除成功");