新增单点登录功能、在线时长

This commit is contained in:
不做码农 2023-08-27 20:51:47 +08:00
parent 55733f07e8
commit 839e400ed1
12 changed files with 186 additions and 59 deletions

14
Infrastructure/Log.cs Normal file
View File

@ -0,0 +1,14 @@
using System;
namespace Infrastructure
{
public class Log
{
public static void WriteLine(ConsoleColor color = ConsoleColor.Black, string msg = "")
{
Console.ForegroundColor = color;
Console.WriteLine($"{DateTime.Now} {msg}");
Console.ResetColor();
}
}
}

View File

@ -1,6 +1,4 @@
 using System.Collections.Generic;
using System;
using System.Collections.Generic;
namespace Infrastructure namespace Infrastructure
{ {
@ -9,6 +7,10 @@ namespace Infrastructure
/// </summary> /// </summary>
public class OptionsSetting public class OptionsSetting
{ {
/// <summary>
/// 是否单点登录
/// </summary>
public bool SingleLogin { get; set; }
/// <summary> /// <summary>
/// 是否演示模式 /// 是否演示模式
/// </summary> /// </summary>

View File

@ -1,8 +1,10 @@
using Infrastructure; using Infrastructure.Attribute;
using Infrastructure.Attribute; using Microsoft.Extensions.DependencyInjection;
using System;
using System.Linq;
using System.Reflection; using System.Reflection;
namespace ZR.Admin.WebApi.Extensions namespace Infrastructure
{ {
/// <summary> /// <summary>
/// App服务注册 /// App服务注册

View File

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Http; using Infrastructure.Extensions;
using Microsoft.AspNetCore.Http;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -8,7 +9,7 @@ using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using UAParser; using UAParser;
namespace Infrastructure.Extensions namespace Infrastructure.WebExtensins
{ {
/// <summary> /// <summary>
/// HttpContext扩展类 /// HttpContext扩展类

View File

@ -1,5 +1,4 @@
using Infrastructure.Extensions; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using MiniExcelLibs; using MiniExcelLibs;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Serialization; using Newtonsoft.Json.Serialization;

View File

@ -2,10 +2,7 @@
using Lazy.Captcha.Core; using Lazy.Captcha.Core;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using SqlSugar;
using System.Diagnostics;
using UAParser; using UAParser;
using ZR.Admin.WebApi.Extensions;
using ZR.Admin.WebApi.Filters; using ZR.Admin.WebApi.Filters;
using ZR.Admin.WebApi.Framework; using ZR.Admin.WebApi.Framework;
using ZR.Model.System; using ZR.Model.System;
@ -31,7 +28,7 @@ namespace ZR.Admin.WebApi.Controllers.System
private readonly ICaptcha SecurityCodeHelper; private readonly ICaptcha SecurityCodeHelper;
private readonly ISysConfigService sysConfigService; private readonly ISysConfigService sysConfigService;
private readonly ISysRoleService roleService; private readonly ISysRoleService roleService;
private readonly OptionsSetting jwtSettings; private readonly OptionsSetting optionSettings;
public SysLoginController( public SysLoginController(
IHttpContextAccessor contextAccessor, IHttpContextAccessor contextAccessor,
@ -42,7 +39,7 @@ namespace ZR.Admin.WebApi.Controllers.System
ISysConfigService configService, ISysConfigService configService,
ISysRoleService sysRoleService, ISysRoleService sysRoleService,
ICaptcha captcha, ICaptcha captcha,
IOptions<OptionsSetting> jwtSettings) IOptions<OptionsSetting> optionSettings)
{ {
httpContextAccessor = contextAccessor; httpContextAccessor = contextAccessor;
SecurityCodeHelper = captcha; SecurityCodeHelper = captcha;
@ -52,7 +49,7 @@ namespace ZR.Admin.WebApi.Controllers.System
this.permissionService = permissionService; this.permissionService = permissionService;
this.sysConfigService = configService; this.sysConfigService = configService;
roleService = sysRoleService; roleService = sysRoleService;
this.jwtSettings = jwtSettings.Value; this.optionSettings = optionSettings.Value;
} }
@ -82,7 +79,6 @@ namespace ZR.Admin.WebApi.Controllers.System
{ {
return ToResponse(ResultCode.LOGIN_ERROR, $"你的账号已被锁,剩余{Math.Round(ts.TotalMinutes, 0)}分钟"); return ToResponse(ResultCode.LOGIN_ERROR, $"你的账号已被锁,剩余{Math.Round(ts.TotalMinutes, 0)}分钟");
} }
var user = sysLoginService.Login(loginBody, RecordLogInfo(httpContextAccessor.HttpContext)); var user = sysLoginService.Login(loginBody, RecordLogInfo(httpContextAccessor.HttpContext));
List<SysRole> roles = roleService.SelectUserRoleListByUserId(user.UserId); List<SysRole> roles = roleService.SelectUserRoleListByUserId(user.UserId);
@ -91,7 +87,7 @@ namespace ZR.Admin.WebApi.Controllers.System
LoginUser loginUser = new(user, roles, permissions); LoginUser loginUser = new(user, roles, permissions);
CacheService.SetUserPerms(GlobalConstant.UserPermKEY + user.UserId, permissions); CacheService.SetUserPerms(GlobalConstant.UserPermKEY + user.UserId, permissions);
return SUCCESS(JwtUtil.GenerateJwtToken(JwtUtil.AddClaims(loginUser), jwtSettings.JwtSettings)); return SUCCESS(JwtUtil.GenerateJwtToken(JwtUtil.AddClaims(loginUser), optionSettings.JwtSettings));
} }
/// <summary> /// <summary>
@ -131,6 +127,9 @@ namespace ZR.Admin.WebApi.Controllers.System
//权限集合 eg *:*:*,system:user:list //权限集合 eg *:*:*,system:user:list
List<string> permissions = permissionService.GetMenuPermission(user); List<string> permissions = permissionService.GetMenuPermission(user);
user.WelcomeContent = GlobalConstant.WelcomeMessages[new Random().Next(0, GlobalConstant.WelcomeMessages.Length)]; user.WelcomeContent = GlobalConstant.WelcomeMessages[new Random().Next(0, GlobalConstant.WelcomeMessages.Length)];
//LoginUser loginUser = new(user, roleService.SelectUserRoleListByUserId(user.UserId), permissions);
//var token = JwtUtil.GenerateJwtToken(JwtUtil.AddClaims(loginUser), optionSettings.JwtSettings);
return SUCCESS(new { user, roles, permissions }); return SUCCESS(new { user, roles, permissions });
} }
@ -300,5 +299,6 @@ namespace ZR.Admin.WebApi.Controllers.System
return ToResponse(ResultCode.FAIL, "二维码已失效"); return ToResponse(ResultCode.FAIL, "二维码已失效");
} }
#endregion #endregion
} }
} }

View File

@ -30,11 +30,11 @@ namespace ZR.Admin.WebApi.Controllers.monitor
[HttpGet("list")] [HttpGet("list")]
public IActionResult Index([FromQuery] PagerInfo parm) public IActionResult Index([FromQuery] PagerInfo parm)
{ {
var result = MessageHub.clientUsers var result = MessageHub.onlineClients
.OrderByDescending(f => f.LoginTime) .OrderByDescending(f => f.LoginTime)
.Skip(parm.PageNum - 1).Take(parm.PageSize); .Skip(parm.PageNum - 1).Take(parm.PageSize);
return SUCCESS(new { result, totalNum = MessageHub.clientUsers.Count }); return SUCCESS(new { result, totalNum = MessageHub.onlineClients.Count });
} }
/// <summary> /// <summary>

View File

@ -1,7 +1,6 @@
using JinianNet.JNTemplate; using JinianNet.JNTemplate;
using ZR.Common;
namespace ZR.Admin.WebApi.Extensions namespace Infrastructure
{ {
public static class LogoExtension public static class LogoExtension
{ {

View File

@ -6,3 +6,4 @@ global using Infrastructure.Enums;
global using Infrastructure.Model; global using Infrastructure.Model;
global using Mapster; global using Mapster;
global using Infrastructure.Extensions; global using Infrastructure.Extensions;
global using Infrastructure.WebExtensins;

View File

@ -13,8 +13,9 @@ namespace ZR.Admin.WebApi.Hubs
public class MessageHub : Hub public class MessageHub : Hub
{ {
//创建用户集合,用于存储所有链接的用户数据 //创建用户集合,用于存储所有链接的用户数据
public static readonly List<OnlineUsers> clientUsers = new(); public static readonly List<OnlineUsers> onlineClients = new();
private readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); public static List<OnlineUsers> users = new();
//private readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
private readonly ISysNoticeService SysNoticeService; private readonly ISysNoticeService SysNoticeService;
public MessageHub(ISysNoticeService noticeService) public MessageHub(ISysNoticeService noticeService)
@ -43,14 +44,13 @@ namespace ZR.Admin.WebApi.Hubs
ClientInfo clientInfo = HttpContextExtension.GetClientInfo(App.HttpContext); ClientInfo clientInfo = HttpContextExtension.GetClientInfo(App.HttpContext);
string device = clientInfo.ToString(); string device = clientInfo.ToString();
var qs = HttpContextExtension.GetQueryString(App.HttpContext); string qs = HttpContextExtension.GetQueryString(App.HttpContext);
string from = HttpUtility.ParseQueryString(qs).Get("from") ?? "web"; string from = HttpUtility.ParseQueryString(qs).Get("from") ?? "web";
long userid = HttpContextExtension.GetUId(App.HttpContext); long userid = HttpContextExtension.GetUId(App.HttpContext);
string uuid = device + userid + ip; string uuid = device + userid + ip;
var user = clientUsers.Any(u => u.ConnnectionId == Context.ConnectionId); var user = onlineClients.Any(u => u.ConnnectionId == Context.ConnectionId);
var user2 = clientUsers.Any(u => u.Uuid == uuid); var user2 = onlineClients.Any(u => u.Uuid == uuid);
//判断用户是否存在,否则添加集合!user2 && !user && //判断用户是否存在,否则添加集合!user2 && !user &&
if (!user2 && !user && Context.User.Identity.IsAuthenticated) if (!user2 && !user && Context.User.Identity.IsAuthenticated)
@ -61,14 +61,41 @@ namespace ZR.Admin.WebApi.Hubs
Uuid = uuid, Uuid = uuid,
Platform = from Platform = from
}; };
clientUsers.Add(onlineUser); onlineClients.Add(onlineUser);
Console.WriteLine($"{DateTime.Now}{name},{Context.ConnectionId}连接服务端success当前已连接{clientUsers.Count}个"); Log.WriteLine(msg: $"{DateTime.Now}{name},{Context.ConnectionId}连接服务端success当前已连接{onlineClients.Count}个");
//Clients.All.SendAsync("welcome", $"欢迎您:{name},当前时间:{DateTime.Now}"); //Clients.All.SendAsync("welcome", $"欢迎您:{name},当前时间:{DateTime.Now}");
Clients.Caller.SendAsync(HubsConstant.MoreNotice, SendNotice()); Clients.Caller.SendAsync(HubsConstant.MoreNotice, SendNotice());
Clients.Caller.SendAsync(HubsConstant.ConnId, onlineUser.ConnnectionId); Clients.Caller.SendAsync(HubsConstant.ConnId, onlineUser.ConnnectionId);
} }
OnlineUsers? userInfo = GetUserById(userid);
if (userInfo == null)
{
userInfo = new OnlineUsers() { Userid = userid, Name = name, LoginTime = DateTime.Now };
users.Add(userInfo);
}
else
{
if (userInfo.LoginTime <= Convert.ToDateTime(DateTime.Now.ToShortDateString()))
{
userInfo.LoginTime = DateTime.Now;
userInfo.TodayOnlineTime = 0;
}
var clientUser = onlineClients.Find(x => x.Userid == userid);
userInfo.TodayOnlineTime += clientUser?.OnlineTime ?? 0;
}
//给当前所有登录当前账号的用户下发登录时长
var connIds = onlineClients.Where(f => f.Userid == userid).ToList();
userInfo.ClientNum = connIds.Count;
Clients.Caller.SendAsync(HubsConstant.OnlineNum, clientUsers.Count); Clients.Clients(connIds.Select(f => f.ConnnectionId)).SendAsync("onlineInfo", userInfo);
Log.WriteLine(ConsoleColor.Blue, msg: $"用户{name}已连接,今日已在线{userInfo?.TodayOnlineTime}分钟,当前已连接{onlineClients.Count}个");
//给所有用户更新在线人数
Clients.All.SendAsync(HubsConstant.OnlineNum, new
{
num = onlineClients.Count,
onlineClients
});
return base.OnConnectedAsync(); return base.OnConnectedAsync();
} }
@ -78,14 +105,25 @@ namespace ZR.Admin.WebApi.Hubs
/// <returns></returns> /// <returns></returns>
public override Task OnDisconnectedAsync(Exception? exception) public override Task OnDisconnectedAsync(Exception? exception)
{ {
var user = clientUsers.Where(p => p.ConnnectionId == Context.ConnectionId).FirstOrDefault(); var user = onlineClients.Where(p => p.ConnnectionId == Context.ConnectionId).FirstOrDefault();
//判断用户是否存在,否则添加集合
if (user != null) if (user != null)
{ {
clientUsers.Remove(user); onlineClients.Remove(user);
Clients.All.SendAsync(HubsConstant.OnlineNum, clientUsers.Count); //给所有用户更新在线人数
Clients.All.SendAsync(HubsConstant.OnlineNum, new
{
num = onlineClients.Count,
onlineClients,
leaveUser = user
});
Console.WriteLine($"用户{user?.Name}离开了,当前已连接{clientUsers.Count}个"); //累计用户时长
OnlineUsers? userInfo = GetUserById(user.Userid);
if (userInfo != null)
{
userInfo.TodayOnlineTime += user?.OnlineTime ?? 0;
}
Log.WriteLine(ConsoleColor.Green, msg: $"用户{user?.Name}离开了,已在线{userInfo?.TodayOnlineTime}分,当前已连接{onlineClients.Count}个");
} }
return base.OnDisconnectedAsync(exception); return base.OnDisconnectedAsync(exception);
} }
@ -95,20 +133,54 @@ namespace ZR.Admin.WebApi.Hubs
/// <summary> /// <summary>
/// 发送信息 /// 发送信息
/// </summary> /// </summary>
/// <param name="connectId"></param> /// <param name="toConnectId">对方链接id</param>
/// <param name="userName"></param> /// <param name="toUserId"></param>
/// <param name="message"></param> /// <param name="message"></param>
/// <returns></returns> /// <returns></returns>
[HubMethodName("SendMessage")] [HubMethodName("sendMessage")]
public async Task SendMessage(string connectId, string userName, string message) public async Task SendMessage(string toConnectId, long toUserId, string message)
{ {
Console.WriteLine($"{connectId},message={message}"); var userName = HttpContextExtension.GetName(App.HttpContext);
long userid = HttpContextExtension.GetUId(App.HttpContext);
var toUserList = onlineClients.Where(p => p.Userid == toUserId);
var toUserInfo = toUserList.FirstOrDefault();
IList<string> sendToUser = toUserList.Select(x => x.ConnnectionId).ToList();
sendToUser.Add(GetConnectId());
if (toUserInfo != null)
{
await Clients.Clients(sendToUser)
.SendAsync("receiveChat", new
{
msgType = 0,//文本
chatid = Guid.NewGuid().ToString(),
userName,
userid,
toUserName = toUserInfo.Name,
toUserid = toUserInfo.Userid,
message,
chatTime = DateTime.Now
});
}
else
{
//TODO 存储离线消息
Console.WriteLine($"{toUserId}不在线");
}
await Clients.Client(connectId).SendAsync("receiveChat", new { userName, message }); Console.WriteLine($"用户{userName}对{toConnectId}-{toUserId}说:{message}");
}
private OnlineUsers GetUserByConnId(string connId)
{
return onlineClients.Where(p => p.ConnnectionId == connId).FirstOrDefault();
}
private static OnlineUsers? GetUserById(long userid)
{
return users.Where(f => f.Userid == userid).FirstOrDefault();
} }
/// <summary> /// <summary>
/// 获取链接id /// 移动端使用获取链接id
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
[HubMethodName("getConnId")] [HubMethodName("getConnId")]
@ -116,5 +188,22 @@ namespace ZR.Admin.WebApi.Hubs
{ {
return Context.ConnectionId; return Context.ConnectionId;
} }
/// <summary>
/// 退出其他设备登录
/// </summary>
/// <returns></returns>
[HubMethodName("logOut")]
public async Task LogOut()
{
var singleLogin = AppSettings.Get<bool>("singleLogin");
long userid = HttpContextExtension.GetUId(App.HttpContext);
if (singleLogin)
{
var onlineUsers = onlineClients.Where(p => p.ConnnectionId != Context.ConnectionId && p.Userid == userid);
await Clients.Clients(onlineUsers.Select(x => x.ConnnectionId))
.SendAsync("logOut");
}
}
} }
} }

View File

@ -9,10 +9,10 @@
/// <summary> /// <summary>
/// 用户id /// 用户id
/// </summary> /// </summary>
public long? Userid { get; set; } public long Userid { get; set; }
public string Name { get; set; } public string Name { get; set; }
public DateTime LoginTime { get; set; } public DateTime LoginTime { get; set; }
public string UserIP { get; set; } public string? UserIP { get; set; }
/// <summary> /// <summary>
/// 登录地点 /// 登录地点
/// </summary> /// </summary>
@ -25,13 +25,37 @@
/// <summary> /// <summary>
/// 浏览器 /// 浏览器
/// </summary> /// </summary>
public string Browser { get; set; } public string? Browser { get; set; }
/// <summary> /// <summary>
/// 平台 /// 平台
/// </summary> /// </summary>
public string Platform { get; set; } = string.Empty; public string? Platform { get; set; } = string.Empty;
/// <summary>
public OnlineUsers(string clientid, string name, long? userid, string userip, string browser) /// 在线时长
/// </summary>
public double OnlineTime
{
get
{
var ts = DateTime.Now - LoginTime;
return Math.Round(ts.TotalMinutes, 2);
}
}
/// <summary>
/// 今日在线时长
/// </summary>
public double TodayOnlineTime { get; set; }
/// <summary>
/// 在线设备数
/// </summary>
public int ClientNum { get; set; }
/// <summary>
///
/// </summary>
public OnlineUsers()
{
}
public OnlineUsers(string clientid, string name, long userid, string userip, string browser)
{ {
ConnnectionId = clientid; ConnnectionId = clientid;
Name = name; Name = name;

View File

@ -35,6 +35,7 @@
"ShowDbLog": true, //db "ShowDbLog": true, //db
"InitDb": false, //db "InitDb": false, //db
"DemoMode": false, // "DemoMode": false, //
"SingleLogin": false,//
"Upload": { "Upload": {
"uploadUrl": "http://localhost:8888", //访 "uploadUrl": "http://localhost:8888", //访
"localSavePath": "", // wwwroot "localSavePath": "", // wwwroot
@ -128,12 +129,7 @@
"Period": "3s", "Period": "3s",
"Limit": 1 "Limit": 1
} }
],
"IpRateLimitPolicies": {
//ip
"IpRules": [
] ]
}
}, },
// //
"CaptchaOptions": { "CaptchaOptions": {