Merge pull request '开启单设备登陆情况下,单用户可以在PC、APP端同时登陆连接signlar,不被强制下线并可以接收消息' (#89) from zhao-fuck-sky/Admin.NET.Pro:main into main

Reviewed-on: http://101.43.53.74:3000/Admin.NET/Admin.NET.Pro/pulls/89
This commit is contained in:
zuohuaijun 2024-08-09 23:15:36 +08:00
commit d621527805
8 changed files with 69 additions and 21 deletions

View File

@ -99,7 +99,7 @@ public class AppAuthService : IDynamicApiController, ITransient
// 登录成功则清空密码错误次数 // 登录成功则清空密码错误次数
_sysCacheService.Remove(keyPasswordErrorTimes); _sysCacheService.Remove(keyPasswordErrorTimes);
return await CreateToken(user); return await CreateToken(user, input.LoginMode);
} }
/// <summary> /// <summary>
@ -148,19 +148,23 @@ public class AppAuthService : IDynamicApiController, ITransient
var user = await _sysUserRep.AsQueryable().Includes(t => t.SysOrg).ClearFilter().FirstAsync(u => u.Phone.Equals(input.Phone)); var user = await _sysUserRep.AsQueryable().Includes(t => t.SysOrg).ClearFilter().FirstAsync(u => u.Phone.Equals(input.Phone));
_ = user ?? throw Oops.Oh(ErrorCodeEnum.D0009); _ = user ?? throw Oops.Oh(ErrorCodeEnum.D0009);
return await CreateToken(user); return await CreateToken(user, input.LoginMode);
} }
/// <summary> /// <summary>
/// 生成Token令牌 🔖 /// 生成Token令牌 🔖
/// </summary> /// </summary>
/// <param name="user"></param> /// <param name="user"></param>
/// <param name="loginMode"></param>
/// <returns></returns> /// <returns></returns>
[NonAction] [NonAction]
public virtual async Task<LoginOutput> CreateToken(SysUser user) public virtual async Task<LoginOutput> CreateToken(SysUser user, LoginModeEnum loginMode)
{ {
// 兼容处理前端未传递loginMode,导致收不到消息
if (loginMode == 0) loginMode = LoginModeEnum.PC;
// 单用户登录 // 单用户登录
await _sysOnlineUserService.SingleLogin(user.Id); await _sysOnlineUserService.SingleLogin(user.Id, loginMode);
// 生成Token令牌 // 生成Token令牌
var tokenExpire = await _sysConfigService.GetTokenExpire(); var tokenExpire = await _sysConfigService.GetTokenExpire();
@ -174,6 +178,7 @@ public class AppAuthService : IDynamicApiController, ITransient
{ AppClaimConst.OrgId, user.OrgId }, { AppClaimConst.OrgId, user.OrgId },
{ AppClaimConst.OrgName, user.SysOrg?.Name }, { AppClaimConst.OrgName, user.SysOrg?.Name },
{ AppClaimConst.OrgType, user.SysOrg?.Type }, { AppClaimConst.OrgType, user.SysOrg?.Type },
{ ClaimConst.LoginMode, loginMode },
}, tokenExpire); }, tokenExpire);
// 生成刷新Token令牌 // 生成刷新Token令牌

View File

@ -65,4 +65,10 @@ public partial class SysOnlineUser : EntityTenantId
[SugarColumn(ColumnDescription = "操作系统", Length = 128)] [SugarColumn(ColumnDescription = "操作系统", Length = 128)]
[MaxLength(128)] [MaxLength(128)]
public string? Os { get; set; } public string? Os { get; set; }
/// <summary>
/// 登陆模式
/// </summary>
[SugarColumn(ColumnDescription = "登陆模式")]
public LoginModeEnum LoginMode { get; set; }
} }

View File

@ -47,6 +47,7 @@ public class OnlineUserHub : Hub<IOnlineUserHub>
var account = httpContext.User.FindFirst(ClaimConst.Account)?.Value; var account = httpContext.User.FindFirst(ClaimConst.Account)?.Value;
var realName = httpContext.User.FindFirst(ClaimConst.RealName)?.Value; var realName = httpContext.User.FindFirst(ClaimConst.RealName)?.Value;
var tenantId = (httpContext.User.FindFirst(ClaimConst.TenantId)?.Value).ToLong(); var tenantId = (httpContext.User.FindFirst(ClaimConst.TenantId)?.Value).ToLong();
var loginMode = (LoginModeEnum)(httpContext.User.FindFirst(ClaimConst.LoginMode)?.Value).ToInt();
var user = new SysOnlineUser var user = new SysOnlineUser
{ {
@ -59,13 +60,14 @@ public class OnlineUserHub : Hub<IOnlineUserHub>
Browser = httpContext.GetClientBrowser(), Browser = httpContext.GetClientBrowser(),
Os = httpContext.GetClientOs(), Os = httpContext.GetClientOs(),
TenantId = tenantId, TenantId = tenantId,
LoginMode = loginMode,
}; };
await _sysOnlineUerRep.InsertAsync(user); await _sysOnlineUerRep.InsertAsync(user);
// 是否开启单用户登录 // 是否开启单用户登录
if (await _sysConfigService.GetConfigValue<bool>(ConfigConst.SysSingleLogin)) if (await _sysConfigService.GetConfigValue<bool>(ConfigConst.SysSingleLogin))
{ {
_sysCacheService.Set(CacheConst.KeyUserOnline + user.UserId, user); _sysCacheService.Set(CacheConst.KeyUserOnline + user.UserId + loginMode, user);
} }
else else
{ {

View File

@ -34,6 +34,11 @@ public class LoginInput
/// 验证码 /// 验证码
/// </summary> /// </summary>
public string Code { get; set; } public string Code { get; set; }
/// <summary>
/// 登陆模式
/// </summary>
public LoginModeEnum LoginMode { get; set; }
} }
public class LoginPhoneInput public class LoginPhoneInput
@ -52,4 +57,9 @@ public class LoginPhoneInput
/// <example>123456</example> /// <example>123456</example>
[Required(ErrorMessage = "验证码不能为空"), MinLength(4, ErrorMessage = "验证码不能少于4个字符")] [Required(ErrorMessage = "验证码不能为空"), MinLength(4, ErrorMessage = "验证码不能少于4个字符")]
public string Code { get; set; } public string Code { get; set; }
/// <summary>
/// 登陆模式
/// </summary>
public LoginModeEnum LoginMode { get; set; }
} }

View File

@ -123,7 +123,7 @@ public class SysAuthService : IDynamicApiController, ITransient
// 登录成功则清空密码错误次数 // 登录成功则清空密码错误次数
_sysCacheService.Remove(keyPasswordErrorTimes); _sysCacheService.Remove(keyPasswordErrorTimes);
return await CreateToken(user); return await CreateToken(user, input.LoginMode);
} }
/// <summary> /// <summary>
@ -202,19 +202,23 @@ public class SysAuthService : IDynamicApiController, ITransient
var user = await _sysUserRep.AsQueryable().Includes(t => t.SysOrg).ClearFilter().FirstAsync(u => u.Phone.Equals(input.Phone)); var user = await _sysUserRep.AsQueryable().Includes(t => t.SysOrg).ClearFilter().FirstAsync(u => u.Phone.Equals(input.Phone));
_ = user ?? throw Oops.Oh(ErrorCodeEnum.D0009); _ = user ?? throw Oops.Oh(ErrorCodeEnum.D0009);
return await CreateToken(user); return await CreateToken(user, input.LoginMode);
} }
/// <summary> /// <summary>
/// 生成Token令牌 🔖 /// 生成Token令牌 🔖
/// </summary> /// </summary>
/// <param name="user"></param> /// <param name="user"></param>
/// <param name="loginMode"></param>
/// <returns></returns> /// <returns></returns>
[NonAction] [NonAction]
internal async Task<LoginOutput> CreateToken(SysUser user) internal async Task<LoginOutput> CreateToken(SysUser user, LoginModeEnum loginMode)
{ {
// 兼容处理前端未传递loginMode,导致收不到消息
if (loginMode == 0) loginMode = LoginModeEnum.PC;
// 单用户登录 // 单用户登录
await _sysOnlineUserService.SingleLogin(user.Id); await _sysOnlineUserService.SingleLogin(user.Id, loginMode);
// 生成Token令牌 // 生成Token令牌
var tokenExpire = await _sysConfigService.GetTokenExpire(); var tokenExpire = await _sysConfigService.GetTokenExpire();
@ -228,6 +232,7 @@ public class SysAuthService : IDynamicApiController, ITransient
{ ClaimConst.OrgId, user.OrgId }, { ClaimConst.OrgId, user.OrgId },
{ ClaimConst.OrgName, user.SysOrg?.Name }, { ClaimConst.OrgName, user.SysOrg?.Name },
{ ClaimConst.OrgType, user.SysOrg?.Type }, { ClaimConst.OrgType, user.SysOrg?.Type },
{ ClaimConst.LoginMode, loginMode },
}, tokenExpire); }, tokenExpire);
// 生成刷新Token令牌 // 生成刷新Token令牌

View File

@ -50,9 +50,14 @@ public class SysMessageService : IDynamicApiController, ITransient
// 是否开启单用户登录 // 是否开启单用户登录
if (await _sysConfigService.GetConfigValue<bool>(ConfigConst.SysSingleLogin)) if (await _sysConfigService.GetConfigValue<bool>(ConfigConst.SysSingleLogin))
{ {
var user = _sysCacheService.Get<SysOnlineUser>(cacheKey); var values = Enum.GetValues(typeof(LoginModeEnum)).Cast<LoginModeEnum>().ToList();
if (user == null) return;
await _chatHubContext.Clients.AllExcept(user.ConnectionId).ReceiveMessage(input); foreach (var value in values)
{
var user = _sysCacheService.Get<SysOnlineUser>(cacheKey + value);
if (user == null) continue;
await _chatHubContext.Clients.AllExcept(user.ConnectionId).ReceiveMessage(input);
}
} }
else else
{ {
@ -78,9 +83,14 @@ public class SysMessageService : IDynamicApiController, ITransient
// 是否开启单用户登录 // 是否开启单用户登录
if (await _sysConfigService.GetConfigValue<bool>(ConfigConst.SysSingleLogin)) if (await _sysConfigService.GetConfigValue<bool>(ConfigConst.SysSingleLogin))
{ {
var user = _sysCacheService.Get<SysOnlineUser>(cacheKey); var values = Enum.GetValues(typeof(LoginModeEnum)).Cast<LoginModeEnum>().ToList();
if (user == null) return;
await _chatHubContext.Clients.Client(user.ConnectionId).ReceiveMessage(input); foreach (var value in values)
{
var user = _sysCacheService.Get<SysOnlineUser>(cacheKey + value);
if (user == null) continue;
await _chatHubContext.Clients.Client(user.ConnectionId).ReceiveMessage(input);
}
} }
else else
{ {
@ -109,8 +119,13 @@ public class SysMessageService : IDynamicApiController, ITransient
// 是否开启单用户登录 // 是否开启单用户登录
if (await _sysConfigService.GetConfigValue<bool>(ConfigConst.SysSingleLogin)) if (await _sysConfigService.GetConfigValue<bool>(ConfigConst.SysSingleLogin))
{ {
var user = _sysCacheService.Get<SysOnlineUser>(cacheKey); var values = Enum.GetValues(typeof(LoginModeEnum)).Cast<LoginModeEnum>().ToList();
if (user != null) userList.Add(user.ConnectionId);
foreach (var value in values)
{
var user = _sysCacheService.Get<SysOnlineUser>(cacheKey + value);
if (user != null) userList.Add(user.ConnectionId);
}
} }
else else
{ {

View File

@ -109,8 +109,8 @@ public class SysOAuthService : IDynamicApiController, ITransient
wechatUser = await _sysOAuthUserRep.AsQueryable().Includes(u => u.SysUser).ClearFilter().FirstAsync(u => u.OpenId == openIdClaim.Value); wechatUser = await _sysOAuthUserRep.AsQueryable().Includes(u => u.SysUser).ClearFilter().FirstAsync(u => u.OpenId == openIdClaim.Value);
} }
// 构建Token令牌 // 构建Token令牌 TODO 这里先默认所有回调登陆的都是PC
var token = await App.GetRequiredService<SysAuthService>().CreateToken(wechatUser.SysUser); var token = await App.GetRequiredService<SysAuthService>().CreateToken(wechatUser.SysUser, LoginModeEnum.PC);
return new RedirectResult($"{redirectUrl}/#/login?token={token.AccessToken}"); return new RedirectResult($"{redirectUrl}/#/login?token={token.AccessToken}");
} }

View File

@ -74,16 +74,21 @@ public class SysOnlineUserService : IDynamicApiController, ITransient
/// <summary> /// <summary>
/// 单用户登录 /// 单用户登录
/// </summary> /// </summary>
/// <param name="userId"></param>
/// <param name="loginMode"></param>
/// <returns></returns> /// <returns></returns>
[NonAction] [NonAction]
public async Task SingleLogin(long userId) public async Task SingleLogin(long userId, LoginModeEnum loginMode)
{ {
if (await _sysConfigService.GetConfigValue<bool>(ConfigConst.SysSingleLogin)) if (await _sysConfigService.GetConfigValue<bool>(ConfigConst.SysSingleLogin))
{ {
var users = await _sysOnlineUerRep.GetListAsync(u => u.UserId == userId); var users = await _sysOnlineUerRep.GetListAsync(u => u.UserId == userId);
foreach (var user in users) foreach (var user in users)
{ {
await ForceOffline(user); if (loginMode == user.LoginMode)
{
await ForceOffline(user);
}
} }
} }
} }