diff --git a/Infrastructure/Log.cs b/Infrastructure/Log.cs
new file mode 100644
index 0000000..309b544
--- /dev/null
+++ b/Infrastructure/Log.cs
@@ -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();
+ }
+ }
+}
diff --git a/Infrastructure/OptionsSetting.cs b/Infrastructure/OptionsSetting.cs
index e451fee..7befae4 100644
--- a/Infrastructure/OptionsSetting.cs
+++ b/Infrastructure/OptionsSetting.cs
@@ -1,6 +1,4 @@
-
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
namespace Infrastructure
{
@@ -9,6 +7,10 @@ namespace Infrastructure
///
public class OptionsSetting
{
+ ///
+ /// 是否单点登录
+ ///
+ public bool SingleLogin { get; set; }
///
/// 是否演示模式
///
diff --git a/ZR.Admin.WebApi/Extensions/AppServiceExtensions.cs b/Infrastructure/WebExtensins/AppServiceExtensions.cs
similarity index 94%
rename from ZR.Admin.WebApi/Extensions/AppServiceExtensions.cs
rename to Infrastructure/WebExtensins/AppServiceExtensions.cs
index 466f372..ce694fa 100644
--- a/ZR.Admin.WebApi/Extensions/AppServiceExtensions.cs
+++ b/Infrastructure/WebExtensins/AppServiceExtensions.cs
@@ -1,8 +1,10 @@
-using Infrastructure;
-using Infrastructure.Attribute;
+using Infrastructure.Attribute;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using System.Linq;
using System.Reflection;
-namespace ZR.Admin.WebApi.Extensions
+namespace Infrastructure
{
///
/// App服务注册
diff --git a/Infrastructure/Extensions/HttpContextExtension.cs b/Infrastructure/WebExtensins/HttpContextExtension.cs
similarity index 98%
rename from Infrastructure/Extensions/HttpContextExtension.cs
rename to Infrastructure/WebExtensins/HttpContextExtension.cs
index 28bf657..fabb64b 100644
--- a/Infrastructure/Extensions/HttpContextExtension.cs
+++ b/Infrastructure/WebExtensins/HttpContextExtension.cs
@@ -1,4 +1,5 @@
-using Microsoft.AspNetCore.Http;
+using Infrastructure.Extensions;
+using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.IO;
@@ -8,7 +9,7 @@ using System.Text;
using System.Text.RegularExpressions;
using UAParser;
-namespace Infrastructure.Extensions
+namespace Infrastructure.WebExtensins
{
///
/// HttpContext扩展类
diff --git a/ZR.Admin.WebApi/Controllers/BaseController.cs b/ZR.Admin.WebApi/Controllers/BaseController.cs
index fb72640..bf44474 100644
--- a/ZR.Admin.WebApi/Controllers/BaseController.cs
+++ b/ZR.Admin.WebApi/Controllers/BaseController.cs
@@ -1,5 +1,4 @@
-using Infrastructure.Extensions;
-using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc;
using MiniExcelLibs;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
diff --git a/ZR.Admin.WebApi/Controllers/System/SysLoginController.cs b/ZR.Admin.WebApi/Controllers/System/SysLoginController.cs
index bb52ce6..1cf7d76 100644
--- a/ZR.Admin.WebApi/Controllers/System/SysLoginController.cs
+++ b/ZR.Admin.WebApi/Controllers/System/SysLoginController.cs
@@ -2,10 +2,7 @@
using Lazy.Captcha.Core;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
-using SqlSugar;
-using System.Diagnostics;
using UAParser;
-using ZR.Admin.WebApi.Extensions;
using ZR.Admin.WebApi.Filters;
using ZR.Admin.WebApi.Framework;
using ZR.Model.System;
@@ -31,7 +28,7 @@ namespace ZR.Admin.WebApi.Controllers.System
private readonly ICaptcha SecurityCodeHelper;
private readonly ISysConfigService sysConfigService;
private readonly ISysRoleService roleService;
- private readonly OptionsSetting jwtSettings;
+ private readonly OptionsSetting optionSettings;
public SysLoginController(
IHttpContextAccessor contextAccessor,
@@ -42,7 +39,7 @@ namespace ZR.Admin.WebApi.Controllers.System
ISysConfigService configService,
ISysRoleService sysRoleService,
ICaptcha captcha,
- IOptions jwtSettings)
+ IOptions optionSettings)
{
httpContextAccessor = contextAccessor;
SecurityCodeHelper = captcha;
@@ -52,7 +49,7 @@ namespace ZR.Admin.WebApi.Controllers.System
this.permissionService = permissionService;
this.sysConfigService = configService;
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)}分钟");
}
-
var user = sysLoginService.Login(loginBody, RecordLogInfo(httpContextAccessor.HttpContext));
List roles = roleService.SelectUserRoleListByUserId(user.UserId);
@@ -91,7 +87,7 @@ namespace ZR.Admin.WebApi.Controllers.System
LoginUser loginUser = new(user, roles, 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));
}
///
@@ -131,6 +127,9 @@ namespace ZR.Admin.WebApi.Controllers.System
//权限集合 eg *:*:*,system:user:list
List permissions = permissionService.GetMenuPermission(user);
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 });
}
@@ -300,5 +299,6 @@ namespace ZR.Admin.WebApi.Controllers.System
return ToResponse(ResultCode.FAIL, "二维码已失效");
}
#endregion
+
}
}
diff --git a/ZR.Admin.WebApi/Controllers/System/monitor/SysUserOnlineController.cs b/ZR.Admin.WebApi/Controllers/System/monitor/SysUserOnlineController.cs
index b51eba2..71e3d87 100644
--- a/ZR.Admin.WebApi/Controllers/System/monitor/SysUserOnlineController.cs
+++ b/ZR.Admin.WebApi/Controllers/System/monitor/SysUserOnlineController.cs
@@ -30,11 +30,11 @@ namespace ZR.Admin.WebApi.Controllers.monitor
[HttpGet("list")]
public IActionResult Index([FromQuery] PagerInfo parm)
{
- var result = MessageHub.clientUsers
+ var result = MessageHub.onlineClients
.OrderByDescending(f => f.LoginTime)
.Skip(parm.PageNum - 1).Take(parm.PageSize);
- return SUCCESS(new { result, totalNum = MessageHub.clientUsers.Count });
+ return SUCCESS(new { result, totalNum = MessageHub.onlineClients.Count });
}
///
diff --git a/ZR.Admin.WebApi/Extensions/LogoExtension.cs b/ZR.Admin.WebApi/Extensions/LogoExtension.cs
index ff908ce..71f86e1 100644
--- a/ZR.Admin.WebApi/Extensions/LogoExtension.cs
+++ b/ZR.Admin.WebApi/Extensions/LogoExtension.cs
@@ -1,7 +1,6 @@
using JinianNet.JNTemplate;
-using ZR.Common;
-namespace ZR.Admin.WebApi.Extensions
+namespace Infrastructure
{
public static class LogoExtension
{
diff --git a/ZR.Admin.WebApi/GlobalUsing.cs b/ZR.Admin.WebApi/GlobalUsing.cs
index 37d1b22..2bdf8f8 100644
--- a/ZR.Admin.WebApi/GlobalUsing.cs
+++ b/ZR.Admin.WebApi/GlobalUsing.cs
@@ -5,4 +5,5 @@ global using Infrastructure.Attribute;
global using Infrastructure.Enums;
global using Infrastructure.Model;
global using Mapster;
-global using Infrastructure.Extensions;
\ No newline at end of file
+global using Infrastructure.Extensions;
+global using Infrastructure.WebExtensins;
\ No newline at end of file
diff --git a/ZR.Admin.WebApi/Hubs/MessageHub.cs b/ZR.Admin.WebApi/Hubs/MessageHub.cs
index 6e6069f..a0ba360 100644
--- a/ZR.Admin.WebApi/Hubs/MessageHub.cs
+++ b/ZR.Admin.WebApi/Hubs/MessageHub.cs
@@ -13,8 +13,9 @@ namespace ZR.Admin.WebApi.Hubs
public class MessageHub : Hub
{
//创建用户集合,用于存储所有链接的用户数据
- public static readonly List clientUsers = new();
- private readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
+ public static readonly List onlineClients = new();
+ public static List users = new();
+ //private readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
private readonly ISysNoticeService SysNoticeService;
public MessageHub(ISysNoticeService noticeService)
@@ -43,14 +44,13 @@ namespace ZR.Admin.WebApi.Hubs
ClientInfo clientInfo = HttpContextExtension.GetClientInfo(App.HttpContext);
string device = clientInfo.ToString();
- var qs = HttpContextExtension.GetQueryString(App.HttpContext);
-
+ string qs = HttpContextExtension.GetQueryString(App.HttpContext);
string from = HttpUtility.ParseQueryString(qs).Get("from") ?? "web";
-
+
long userid = HttpContextExtension.GetUId(App.HttpContext);
string uuid = device + userid + ip;
- var user = clientUsers.Any(u => u.ConnnectionId == Context.ConnectionId);
- var user2 = clientUsers.Any(u => u.Uuid == uuid);
+ var user = onlineClients.Any(u => u.ConnnectionId == Context.ConnectionId);
+ var user2 = onlineClients.Any(u => u.Uuid == uuid);
//判断用户是否存在,否则添加集合!user2 && !user &&
if (!user2 && !user && Context.User.Identity.IsAuthenticated)
@@ -61,14 +61,41 @@ namespace ZR.Admin.WebApi.Hubs
Uuid = uuid,
Platform = from
};
- clientUsers.Add(onlineUser);
- Console.WriteLine($"{DateTime.Now}:{name},{Context.ConnectionId}连接服务端success,当前已连接{clientUsers.Count}个");
+ onlineClients.Add(onlineUser);
+ Log.WriteLine(msg: $"{DateTime.Now}:{name},{Context.ConnectionId}连接服务端success,当前已连接{onlineClients.Count}个");
//Clients.All.SendAsync("welcome", $"欢迎您:{name},当前时间:{DateTime.Now}");
Clients.Caller.SendAsync(HubsConstant.MoreNotice, SendNotice());
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();
}
@@ -78,14 +105,25 @@ namespace ZR.Admin.WebApi.Hubs
///
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)
{
- clientUsers.Remove(user);
- Clients.All.SendAsync(HubsConstant.OnlineNum, clientUsers.Count);
+ onlineClients.Remove(user);
+ //给所有用户更新在线人数
+ 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);
}
@@ -95,20 +133,54 @@ namespace ZR.Admin.WebApi.Hubs
///
/// 发送信息
///
- ///
- ///
+ /// 对方链接id
+ ///
///
///
- [HubMethodName("SendMessage")]
- public async Task SendMessage(string connectId, string userName, string message)
+ [HubMethodName("sendMessage")]
+ 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 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();
}
///
- /// 获取链接id
+ /// 移动端使用获取链接id
///
///
[HubMethodName("getConnId")]
@@ -116,5 +188,22 @@ namespace ZR.Admin.WebApi.Hubs
{
return Context.ConnectionId;
}
+
+ ///
+ /// 退出其他设备登录
+ ///
+ ///
+ [HubMethodName("logOut")]
+ public async Task LogOut()
+ {
+ var singleLogin = AppSettings.Get("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");
+ }
+ }
}
}
diff --git a/ZR.Admin.WebApi/Hubs/OnlineUsers.cs b/ZR.Admin.WebApi/Hubs/OnlineUsers.cs
index 81aed2d..2af9fb7 100644
--- a/ZR.Admin.WebApi/Hubs/OnlineUsers.cs
+++ b/ZR.Admin.WebApi/Hubs/OnlineUsers.cs
@@ -9,10 +9,10 @@
///
/// 用户id
///
- public long? Userid { get; set; }
+ public long Userid { get; set; }
public string Name { get; set; }
public DateTime LoginTime { get; set; }
- public string UserIP { get; set; }
+ public string? UserIP { get; set; }
///
/// 登录地点
///
@@ -21,17 +21,41 @@
///
/// 判断用户唯一
///
- public string? Uuid{ get; set; }
+ public string? Uuid { get; set; }
///
/// 浏览器
///
- public string Browser { get; set; }
+ public string? Browser { get; set; }
///
/// 平台
///
- public string Platform { get; set; } = string.Empty;
-
- public OnlineUsers(string clientid, string name, long? userid, string userip, string browser)
+ public string? Platform { get; set; } = string.Empty;
+ ///
+ /// 在线时长
+ ///
+ public double OnlineTime
+ {
+ get
+ {
+ var ts = DateTime.Now - LoginTime;
+ return Math.Round(ts.TotalMinutes, 2);
+ }
+ }
+ ///
+ /// 今日在线时长
+ ///
+ public double TodayOnlineTime { get; set; }
+ ///
+ /// 在线设备数
+ ///
+ public int ClientNum { get; set; }
+ ///
+ ///
+ ///
+ public OnlineUsers()
+ {
+ }
+ public OnlineUsers(string clientid, string name, long userid, string userip, string browser)
{
ConnnectionId = clientid;
Name = name;
diff --git a/ZR.Admin.WebApi/appsettings.json b/ZR.Admin.WebApi/appsettings.json
index 9a3cf89..2e10095 100644
--- a/ZR.Admin.WebApi/appsettings.json
+++ b/ZR.Admin.WebApi/appsettings.json
@@ -32,9 +32,10 @@
"Expire": 1440 //jwt登录过期时间(分)
},
"InjectClass": [ "ZR.Repository", "ZR.Service", "ZR.Tasks" ], //自动注入类
- "ShowDbLog": true,//是否打印db日志
+ "ShowDbLog": true, //是否打印db日志
"InitDb": false, //是否初始化db
"DemoMode": false, //是否演示模式
+ "SingleLogin": false,//是否单点登录
"Upload": {
"uploadUrl": "http://localhost:8888", //本地存储资源访问路径
"localSavePath": "", //本地上传默认文件存储目录 wwwroot
@@ -128,12 +129,7 @@
"Period": "3s",
"Limit": 1
}
- ],
- "IpRateLimitPolicies": {
- //ip规则
- "IpRules": [
- ]
- }
+ ]
},
//验证码配置
"CaptchaOptions": {