添加项目文件。
This commit is contained in:
@@ -0,0 +1,502 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System.Services.Auth;
|
||||
|
||||
/// <inheritdoc cref="IAuthService"/>
|
||||
public class AuthService : IAuthService
|
||||
{
|
||||
private readonly ISimpleCacheService _simpleCacheService;
|
||||
private readonly IEventPublisher _eventPublisher;
|
||||
private readonly IConfigService _configService;
|
||||
private readonly ISysUserService _userService;
|
||||
private readonly ISysOrgService _sysOrgService;
|
||||
|
||||
public AuthService(ISimpleCacheService simpleCacheService, IEventPublisher eventPublisher, IConfigService configService,
|
||||
ISysUserService userService, ISysOrgService sysOrgService)
|
||||
{
|
||||
_simpleCacheService = simpleCacheService;
|
||||
_eventPublisher = eventPublisher;
|
||||
_configService = configService;
|
||||
_userService = userService;
|
||||
_sysOrgService = sysOrgService;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<PicValidCodeOutPut> GetCaptchaInfo()
|
||||
{
|
||||
var config = await _configService.GetByConfigKey(CateGoryConst.CONFIG_LOGIN_POLICY, SysConfigConst.LOGIN_CAPTCHA_TYPE);
|
||||
var captchaType = (CaptchaType)Enum.Parse(typeof(CaptchaType), config.ConfigValue);
|
||||
//生成验证码
|
||||
var captchaInfo = CaptchaUtil.CreateCaptcha(captchaType, 4, 100, 38);
|
||||
//生成请求号,并将验证码放入缓存
|
||||
var reqNo = AddValidCodeToRedis(captchaInfo.Code);
|
||||
//返回验证码和请求号
|
||||
return new PicValidCodeOutPut
|
||||
{
|
||||
ValidCodeBase64 = captchaInfo.Base64Str,
|
||||
ValidCodeReqNo = reqNo
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<string> GetPhoneValidCode(GetPhoneValidCodeInput input, LoginClientTypeEnum loginClientType)
|
||||
{
|
||||
await ValidPhoneValidCode(input, loginClientType);//校验手机号验证码
|
||||
var phoneValidCode = RandomHelper.CreateNum(6);//生产随机数字;
|
||||
|
||||
|
||||
#region 发送短信和记录数据库等操作
|
||||
|
||||
//这里为什么不封装阿里云或者腾讯云短信接口,是因为短信发送是一个通用功能,一般每个公司都有自己封装好的短信组件来适配各种项目,这里再封装一次显得多余了。
|
||||
//这里不建议在该系统中封装短信发送接口,当然后续如果确实有需要也会增加
|
||||
//这里执行发送短信验证码代码,这里先默认就是年月日
|
||||
phoneValidCode = DateTime.Now.ToString("yyMMdd");
|
||||
|
||||
#endregion 发送短信和记录数据库等操作
|
||||
|
||||
//生成请求号,并将验证码放入缓存
|
||||
var reqNo = AddValidCodeToRedis(phoneValidCode);
|
||||
return reqNo;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<LoginOutPut> Login(LoginInput input, LoginClientTypeEnum loginClientType)
|
||||
{
|
||||
await CheckCaptcha(input);//检查验证码
|
||||
await CheckWebOpen(input);//检查网站是否开启
|
||||
var password = CryptogramUtil.Sm2Decrypt(input.Password);//SM2解密
|
||||
//获取多租户配置
|
||||
var isTenant = await _configService.IsTenant();
|
||||
//获取登录策略
|
||||
var loginPolicy = await _configService.GetConfigsByCategory(CateGoryConst.CONFIG_LOGIN_POLICY);
|
||||
await BeforeLogin(loginPolicy, input, isTenant);//登录前校验
|
||||
// 根据账号获取用户信息,根据B端或C端判断
|
||||
if (loginClientType == LoginClientTypeEnum.B)//如果是B端
|
||||
{
|
||||
var userInfo = await _userService.GetUserByAccount(input.Account, input.TenantId);//获取用户信息
|
||||
if (userInfo == null) throw Oops.Bah("用户不存在");//用户不存在
|
||||
if (userInfo.Password != password)
|
||||
{
|
||||
LoginError(loginPolicy, input, isTenant);//登录错误操作
|
||||
throw Oops.Bah("账号密码错误");//账号密码错误
|
||||
}
|
||||
var result = await ExecLoginB(userInfo, input.Device, loginClientType, input.TenantId);// 执行B端登录
|
||||
return result;
|
||||
}
|
||||
//执行c端登录
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<LoginOutPut> LoginByPhone(LoginByPhoneInput input, LoginClientTypeEnum loginClientType)
|
||||
{
|
||||
await ValidPhoneValidCode(input, loginClientType, false);//校验手机号和验证码,这里不删除Redis,防止输入错误又要重新输入验证码
|
||||
// 根据手机号获取用户信息,根据B端或C端判断
|
||||
if (loginClientType == LoginClientTypeEnum.B)//如果是B端
|
||||
{
|
||||
var userInfo = await _userService.GetUserByPhone(input.Phone);//获取信息
|
||||
if (userInfo == null) throw Oops.Bah("用户不存在");//用户不存在
|
||||
var result = await ExecLoginB(userInfo, input.Device, loginClientType, input.TenantId);// 执行B端登录
|
||||
RemoveValidCodeFromRedis(input.ValidCodeReqNo);//删除验证码
|
||||
return result;
|
||||
}
|
||||
//执行c端登录
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task LoginOut(string token, LoginClientTypeEnum loginClientType)
|
||||
{
|
||||
//获取用户信息
|
||||
var userinfo = await _userService.GetUserByAccount(UserManager.UserAccount, UserManager.TenantId);
|
||||
if (userinfo != null)
|
||||
{
|
||||
var loginEvent = new LoginEvent
|
||||
{
|
||||
Ip = App.HttpContext.GetRemoteIpAddressToIPv4(),
|
||||
SysUser = userinfo,
|
||||
Token = token
|
||||
};
|
||||
RemoveTokenFromRedis(loginEvent, loginClientType);//移除token
|
||||
//发布登出事件总线
|
||||
await _eventPublisher.PublishAsync(EventSubscriberConst.LOGIN_OUT_B, loginEvent);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<LoginUserOutput> GetLoginUser()
|
||||
{
|
||||
var userInfo = await _userService.GetUserByAccount(UserManager.UserAccount, UserManager.TenantId);//根据账号获取用户信息
|
||||
if (userInfo != null)
|
||||
{
|
||||
userInfo.Avatar = await _userService.GetUserAvatar(userInfo.Id);//获取头像
|
||||
return userInfo.Adapt<LoginUserOutput>();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
#region 方法
|
||||
|
||||
/// <summary>
|
||||
/// 检查验证码
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <exception cref="AppFriendlyException"></exception>
|
||||
public async Task CheckCaptcha(LoginInput input)
|
||||
{
|
||||
//判断是否有验证码
|
||||
var sysBase = await _configService.GetByConfigKey(CateGoryConst.CONFIG_LOGIN_POLICY, SysConfigConst.LOGIN_CAPTCHA_OPEN);
|
||||
if (sysBase != null)//如果有这个配置项
|
||||
{
|
||||
if (sysBase.ConfigValue.ToBoolean())//如果需要验证码
|
||||
{
|
||||
//如果没填验证码,提示验证码不能为空
|
||||
if (string.IsNullOrEmpty(input.ValidCode) || string.IsNullOrEmpty(input.ValidCodeReqNo))
|
||||
throw Oops.Bah("验证码不能为空").StatusCode(410);
|
||||
ValidValidCode(input.ValidCode, input.ValidCodeReqNo);//校验验证码
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查网站是否开启
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <exception cref="AppFriendlyException"></exception>
|
||||
public async Task CheckWebOpen(LoginInput input)
|
||||
{
|
||||
//判断是否开启web访问
|
||||
var webStatus = await _configService.GetByConfigKey(CateGoryConst.CONFIG_SYS_BASE, SysConfigConst.SYS_WEB_STATUS);
|
||||
if (webStatus != null && webStatus.ConfigValue == CommonStatusConst.DISABLED
|
||||
&& input.Account.ToLower() != SysRoleConst.SUPER_ADMIN.ToLower())//如果禁用了网站并且不是超级管理员
|
||||
{
|
||||
var closePrompt = await _configService.GetByConfigKey(CateGoryConst.CONFIG_SYS_BASE, SysConfigConst.SYS_WEB_CLOSE_PROMPT);
|
||||
throw Oops.Bah(closePrompt.ConfigValue);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 登录之前执行的方法
|
||||
/// </summary>
|
||||
/// <param name="loginPolicy"></param>
|
||||
/// <param name="input"></param>
|
||||
/// <param name="isTenant"></param>
|
||||
public async Task BeforeLogin(List<SysConfig> loginPolicy, LoginInput input, bool isTenant)
|
||||
{
|
||||
var lockTime = loginPolicy.First(x => x.ConfigKey == SysConfigConst.LOGIN_ERROR_LOCK).ConfigValue.ToInt();//获取锁定时间
|
||||
var errorCount = loginPolicy.First(x => x.ConfigKey == SysConfigConst.LOGIN_ERROR_COUNT).ConfigValue.ToInt();//获取错误次数
|
||||
var key = SystemConst.CACHE_LOGIN_ERROR_COUNT;//获取登录错误次数Key值
|
||||
//如果是关闭多租户则直接用账号
|
||||
if (!isTenant)
|
||||
key += input.Account;
|
||||
else
|
||||
{
|
||||
//如果租户ID为空表示用域名登录
|
||||
if (input.TenantId == null)
|
||||
{
|
||||
//获取域名
|
||||
var origin = App.HttpContext.Request.Headers["Origin"].ToString();
|
||||
// 如果Origin头不存在,可以尝试使用Referer头作为备选
|
||||
if (string.IsNullOrEmpty(origin))
|
||||
origin = App.HttpContext.Request.Headers["Referer"].ToString();
|
||||
//根据域名获取二级域名
|
||||
var domain = origin.Split("//")[1].Split(".")[0];
|
||||
//根据二级域名获取租户
|
||||
var tenantList = await _sysOrgService.GetTenantList();
|
||||
var tenant = tenantList.FirstOrDefault(x => x.Code.ToLower() == domain);//获取租户默认是机构编码
|
||||
if (tenant != null)
|
||||
input.TenantId = tenant.Id;
|
||||
else
|
||||
throw Oops.Bah("租户不存在");
|
||||
}
|
||||
//如果是手动选择多租户则用账号+租户
|
||||
key += $"{input.TenantId}:{input.Account}";
|
||||
}
|
||||
var errorCountCache = _simpleCacheService.Get<int>(key);//获取登录错误次数
|
||||
if (errorCountCache >= errorCount)
|
||||
{
|
||||
_simpleCacheService.SetExpire(key, TimeSpan.FromMinutes(lockTime));//设置缓存
|
||||
throw Oops.Bah($"密码错误次数过多,请{lockTime}分钟后再试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 登录错误操作
|
||||
/// </summary>
|
||||
/// <param name="loginPolicy"></param>
|
||||
/// <param name="input"></param>
|
||||
/// <param name="isTenant"></param>
|
||||
public void LoginError(List<SysConfig> loginPolicy, LoginInput input, bool isTenant)
|
||||
{
|
||||
var resetTime = loginPolicy.First(x => x.ConfigKey == SysConfigConst.LOGIN_ERROR_RESET_TIME).ConfigValue.ToInt();//获取重置时间
|
||||
var key = SystemConst.CACHE_LOGIN_ERROR_COUNT;//获取登录错误次数Key值
|
||||
//如果是关闭多租户则直接用账号
|
||||
if (!isTenant)
|
||||
key += input.Account;
|
||||
else
|
||||
{
|
||||
//如果是手动选择多租户则用账号+租户
|
||||
key += $"{input.TenantId}:{input.Account}";
|
||||
}
|
||||
_simpleCacheService.Increment(key, 1);// 登录错误次数+1
|
||||
_simpleCacheService.SetExpire(key, TimeSpan.FromMinutes(resetTime));//设置过期时间
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 校验验证码方法
|
||||
/// </summary>
|
||||
/// <param name="validCode">验证码</param>
|
||||
/// <param name="validCodeReqNo">请求号</param>
|
||||
/// <param name="isDelete">是否从Redis删除</param>
|
||||
public void ValidValidCode(string validCode, string validCodeReqNo, bool isDelete = true)
|
||||
{
|
||||
var key = SystemConst.CACHE_CAPTCHA + validCodeReqNo;//获取验证码Key值
|
||||
var code = _simpleCacheService.Get<string>(key);//从缓存拿数据
|
||||
if (isDelete) RemoveValidCodeFromRedis(validCodeReqNo);//如果需要删除验证码
|
||||
if (code != null && validCode != null)//如果有
|
||||
{
|
||||
//验证码如果不匹配直接抛错误,这里忽略大小写
|
||||
if (validCode.ToLower() != code.ToLower()) throw Oops.Bah("验证码错误");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw Oops.Bah("验证码不能为空");//抛出验证码不能为空
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从Redis中删除验证码
|
||||
/// </summary>
|
||||
/// <param name="validCodeReqNo"></param>
|
||||
public void RemoveValidCodeFromRedis(string validCodeReqNo)
|
||||
{
|
||||
var key = SystemConst.CACHE_CAPTCHA + validCodeReqNo;//获取验证码Key值
|
||||
_simpleCacheService.Remove(key);//删除验证码
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 校验手机验证码
|
||||
/// </summary>
|
||||
/// <param name="input">输入参数</param>
|
||||
/// <param name="loginClientType">登录端类型</param>
|
||||
/// <param name="isDelete">是否删除</param>
|
||||
/// <returns></returns>
|
||||
public async Task ValidPhoneValidCode(GetPhoneValidCodeInput input, LoginClientTypeEnum loginClientType, bool isDelete = true)
|
||||
{
|
||||
ValidValidCode(input.ValidCode, input.ValidCodeReqNo, isDelete);//校验验证码
|
||||
if (loginClientType == LoginClientTypeEnum.B)//B端登录
|
||||
{
|
||||
var userId = await _userService.GetIdByPhone(input.Phone);//获取用户名
|
||||
if (userId == 0)
|
||||
{
|
||||
throw Oops.Bah("手机号不存在");//手机号不存在
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//校验C段用户手机号
|
||||
throw Oops.Bah("手机号不存在");//手机号不存在
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加验证码到缓存
|
||||
/// </summary>
|
||||
/// <param name="code">验证码</param>
|
||||
/// <param name="expire">过期时间</param>
|
||||
/// <returns>验证码请求号</returns>
|
||||
public string AddValidCodeToRedis(string code, int expire = 5)
|
||||
{
|
||||
//生成请求号
|
||||
var reqNo = CommonUtils.GetSingleId().ToString();
|
||||
//插入缓存
|
||||
_simpleCacheService.Set(SystemConst.CACHE_CAPTCHA + reqNo, code, TimeSpan.FromMinutes(expire));
|
||||
return reqNo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行B端登录
|
||||
/// </summary>
|
||||
/// <param name="sysUser">用户信息</param>
|
||||
/// <param name="device">登录设备</param>
|
||||
/// <param name="loginClientType">登录类型</param>
|
||||
/// <param name="tenantId">租户id</param>
|
||||
/// <returns></returns>
|
||||
public async Task<LoginOutPut> ExecLoginB(SysUser sysUser, AuthDeviceTypeEnum device, LoginClientTypeEnum loginClientType,
|
||||
long? tenantId)
|
||||
{
|
||||
if (sysUser.Status == CommonStatusConst.DISABLED)
|
||||
throw Oops.Bah("账号已停用");//账号冻结
|
||||
if (sysUser.ModuleList.Count == 0) throw Oops.Bah("该账号未分配模块,请联系管理员");//没有分配菜单权限
|
||||
var org = await _sysOrgService.GetSysOrgById(sysUser.OrgId);//获取机构
|
||||
if (org.Status == CommonStatusConst.DISABLED) throw Oops.Bah("所属公司/部门已停用,请联系管理员");//机构冻结
|
||||
//生成Token
|
||||
var accessToken = JWTEncryption.Encrypt(new Dictionary<string, object>
|
||||
{
|
||||
{
|
||||
ClaimConst.USER_ID, sysUser.Id
|
||||
},
|
||||
{
|
||||
ClaimConst.ACCOUNT, sysUser.Account
|
||||
},
|
||||
{
|
||||
ClaimConst.NAME, sysUser.Name
|
||||
},
|
||||
{
|
||||
ClaimConst.IS_SUPER_ADMIN, sysUser.RoleCodeList.Contains(SysRoleConst.SUPER_ADMIN)
|
||||
},
|
||||
{
|
||||
ClaimConst.ORG_ID, sysUser.OrgId
|
||||
},
|
||||
{
|
||||
ClaimConst.TENANT_ID, tenantId
|
||||
}
|
||||
});
|
||||
var expire = App.GetConfig<int>("JWTSettings:ExpiredTime");//获取过期时间(分钟)
|
||||
// 生成刷新Token令牌
|
||||
var refreshToken = JWTEncryption.GenerateRefreshToken(accessToken, expire * 2);
|
||||
// 设置Swagger自动登录
|
||||
App.HttpContext.SigninToSwagger(accessToken);
|
||||
// 设置响应报文头
|
||||
App.HttpContext.SetTokensOfResponseHeaders(accessToken, refreshToken);
|
||||
//登录事件参数
|
||||
var loginEvent = new LoginEvent
|
||||
{
|
||||
Ip = App.HttpContext.GetRemoteIpAddressToIPv4(),
|
||||
Device = device,
|
||||
Expire = expire,
|
||||
SysUser = sysUser,
|
||||
Token = accessToken
|
||||
};
|
||||
await WriteTokenToRedis(loginEvent, loginClientType);//写入token到缓存
|
||||
await _eventPublisher.PublishAsync(EventSubscriberConst.LOGIN_B, loginEvent);//发布登录事件总线
|
||||
//返回结果
|
||||
return new LoginOutPut
|
||||
{
|
||||
Token = accessToken,
|
||||
Account = sysUser.Account,
|
||||
Name = sysUser.Name,
|
||||
DefaultModule = sysUser.DefaultModule,
|
||||
ModuleList = sysUser.ModuleList
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入用户token到缓存
|
||||
/// </summary>
|
||||
/// <param name="loginEvent">登录事件参数</param>
|
||||
/// <param name="loginClientType">登录类型</param>
|
||||
private async Task WriteTokenToRedis(LoginEvent loginEvent, LoginClientTypeEnum loginClientType)
|
||||
{
|
||||
//获取token列表
|
||||
var tokenInfos = GetTokenInfos(loginEvent.SysUser.Id);
|
||||
var tokenTimeout = loginEvent.DateTime.AddMinutes(loginEvent.Expire);
|
||||
//生成token信息
|
||||
var tokenInfo = new TokenInfo
|
||||
{
|
||||
Device = loginEvent.Device.ToString(),
|
||||
Expire = loginEvent.Expire,
|
||||
TokenTimeout = tokenTimeout,
|
||||
LoginClientType = loginClientType,
|
||||
Token = loginEvent.Token
|
||||
};
|
||||
//如果缓存有数据
|
||||
if (tokenInfos != null)
|
||||
{
|
||||
var isSingle = false;//默认不开启单用户登录
|
||||
var singleConfig = await _configService.GetByConfigKey(CateGoryConst.CONFIG_LOGIN_POLICY, SysConfigConst.LOGIN_SINGLE_OPEN);//获取系统单用户登录选项
|
||||
if (singleConfig != null)
|
||||
isSingle = singleConfig.ConfigValue.ToBoolean();//如果配置不为空则设置单用户登录选项为系统配置的值
|
||||
//判断是否单用户登录
|
||||
if (isSingle)
|
||||
{
|
||||
await SingleLogin(loginEvent.SysUser.Id.ToString(), tokenInfos.Where(it => it.LoginClientType == loginClientType).ToList());//单用户登录方法
|
||||
tokenInfos = tokenInfos.Where(it => it.LoginClientType != loginClientType).ToList();//去掉当前登录类型的token
|
||||
tokenInfos.Add(tokenInfo);//添加到列表
|
||||
}
|
||||
else
|
||||
{
|
||||
tokenInfos.Add(tokenInfo);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tokenInfos = new List<TokenInfo>
|
||||
{
|
||||
tokenInfo
|
||||
};//直接就一个
|
||||
}
|
||||
|
||||
//添加到token列表
|
||||
_simpleCacheService.HashAdd(CacheConst.CACHE_USER_TOKEN, loginEvent.SysUser.Id.ToString(), tokenInfos);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 缓存删除用户token
|
||||
/// </summary>
|
||||
/// <param name="loginEvent">登录事件参数</param>
|
||||
/// <param name="loginClientType">登录类型</param>
|
||||
private void RemoveTokenFromRedis(LoginEvent loginEvent, LoginClientTypeEnum loginClientType)
|
||||
{
|
||||
//获取token列表
|
||||
var tokenInfos = GetTokenInfos(loginEvent.SysUser.Id);
|
||||
if (tokenInfos != null)
|
||||
{
|
||||
//获取当前用户的token
|
||||
var token = tokenInfos.Where(it => it.Token == loginEvent.Token && it.LoginClientType == loginClientType).FirstOrDefault();
|
||||
if (token != null)
|
||||
tokenInfos.Remove(token);
|
||||
if (tokenInfos.Count > 0)
|
||||
{
|
||||
//更新token列表
|
||||
_simpleCacheService.HashAdd(CacheConst.CACHE_USER_TOKEN, loginEvent.SysUser.Id.ToString(), tokenInfos);
|
||||
}
|
||||
else
|
||||
{
|
||||
//从列表中删除
|
||||
_simpleCacheService.HashDel<List<TokenInfo>>(CacheConst.CACHE_USER_TOKEN, loginEvent.SysUser.Id.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户token列表
|
||||
/// </summary>
|
||||
/// <param name="userId">用户ID</param>
|
||||
/// <returns>token列表</returns>
|
||||
private List<TokenInfo> GetTokenInfos(long userId)
|
||||
{
|
||||
//缓存获取用户token列表
|
||||
var tokenInfos = _simpleCacheService.HashGetOne<List<TokenInfo>>(CacheConst.CACHE_USER_TOKEN, userId.ToString());
|
||||
if (tokenInfos != null)
|
||||
{
|
||||
tokenInfos = tokenInfos.Where(it => it.TokenTimeout > DateTime.Now).ToList();//去掉登录超时的
|
||||
}
|
||||
return tokenInfos;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单用户登录通知用户下线
|
||||
/// </summary>
|
||||
/// <param name="userId">用户ID</param>
|
||||
/// <param name="tokenInfos">Token列表</param>
|
||||
private async Task SingleLogin(string userId, List<TokenInfo> tokenInfos)
|
||||
{
|
||||
await _eventPublisher.PublishAsync(EventSubscriberConst.USER_LOGIN_OUT, new UserLoginOutEvent
|
||||
{
|
||||
Message = "您的账号已在别处登录!",
|
||||
TokenInfos = tokenInfos,
|
||||
UserId = userId
|
||||
});//通知用户下线
|
||||
}
|
||||
|
||||
#endregion 方法
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 权限认证输入
|
||||
/// </summary>
|
||||
public class AuthInput
|
||||
{
|
||||
}
|
||||
|
||||
public class ValidCodeInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 验证码
|
||||
/// </summary>
|
||||
public string ValidCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 请求号
|
||||
/// </summary>
|
||||
public string ValidCodeReqNo { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 登录输入参数
|
||||
/// </summary>
|
||||
public class LoginInput : ValidCodeInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 账号
|
||||
///</summary>
|
||||
/// <example>superAdmin</example>
|
||||
[Required(ErrorMessage = "账号不能为空")]
|
||||
public string Account { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 密码
|
||||
///</summary>
|
||||
///<example>04fc514b346f14b23d7cf5e6f64663b030512aa380a9e7d311288ed1e8be7b863ae5ee0bb570df2405fc9daff2b2d1ac627a0fbbd49ef2c6b8fac4fd5e4b9a1b7120999bdc0a8e425aa37abab3aec6f9f3570775ff901f2845e957b0c2d0542e21fbf1bbb65c04</example>
|
||||
[Required(ErrorMessage = "密码不能为空")]
|
||||
public string Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备类型,默认PC
|
||||
/// </summary>
|
||||
/// <example>0</example>
|
||||
public AuthDeviceTypeEnum Device { get; set; } = AuthDeviceTypeEnum.PC;
|
||||
|
||||
/// <summary>
|
||||
/// 租户ID
|
||||
/// </summary>
|
||||
public long? TenantId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 登出输入参数
|
||||
/// </summary>
|
||||
public class LoginOutInput
|
||||
{
|
||||
/// <summary>
|
||||
/// token
|
||||
/// </summary>
|
||||
public string Token { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取短信验证码输入
|
||||
/// </summary>
|
||||
public class GetPhoneValidCodeInput : ValidCodeInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 手机号
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "手机号不能为空")]
|
||||
[Phone(ErrorMessage = "手机号格式不对")]
|
||||
public string Phone { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 手机号登录输入
|
||||
/// </summary>
|
||||
public class LoginByPhoneInput : GetPhoneValidCodeInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 设备类型,默认PC
|
||||
/// </summary>
|
||||
/// <example>0</example>
|
||||
public AuthDeviceTypeEnum Device { get; set; } = AuthDeviceTypeEnum.PC;
|
||||
|
||||
/// <summary>
|
||||
/// 租户ID
|
||||
/// </summary>
|
||||
public long? TenantId { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
public class AuthOutPut
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证码返回
|
||||
/// </summary>
|
||||
public class PicValidCodeOutPut
|
||||
{
|
||||
/// <summary>
|
||||
/// 验证码图片,Base64
|
||||
/// </summary>
|
||||
public string ValidCodeBase64 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 验证码请求号
|
||||
/// </summary>
|
||||
public string ValidCodeReqNo { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 登录返回参数
|
||||
/// </summary>
|
||||
public class LoginOutPut
|
||||
{
|
||||
/// <summary>
|
||||
/// 令牌Token
|
||||
/// </summary>
|
||||
public string Token { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 账号
|
||||
/// </summary>
|
||||
public string Account { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 姓名
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 默认模块
|
||||
/// </summary>
|
||||
public long? DefaultModule { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模块列表
|
||||
/// </summary>
|
||||
public List<SysResource> ModuleList { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 登录用互信息
|
||||
/// </summary>
|
||||
public class LoginUserOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 头像
|
||||
///</summary>
|
||||
public string Avatar { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 签名
|
||||
///</summary>
|
||||
public string Signature { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 账号
|
||||
///</summary>
|
||||
public string Account { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 姓名
|
||||
///</summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 昵称
|
||||
///</summary>
|
||||
public string Nickname { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 手机号
|
||||
/// </summary>
|
||||
public string Phone { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 生日
|
||||
/// </summary>
|
||||
public string Birthday { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 邮箱
|
||||
/// </summary>
|
||||
|
||||
public string Email { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 性别
|
||||
///</summary>
|
||||
public string Gender { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 家庭住址
|
||||
///</summary>
|
||||
public string HomeAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 机构信息
|
||||
/// </summary>
|
||||
public string OrgName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 机构信息全称
|
||||
/// </summary>
|
||||
public string OrgNames { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 职位信息
|
||||
/// </summary>
|
||||
public string PositionName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色码集合
|
||||
/// </summary>
|
||||
public List<string> RoleCodeList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 按钮码集合
|
||||
/// </summary>
|
||||
public List<string> ButtonCodeList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 机构及以下机构ID集合
|
||||
/// </summary>
|
||||
public List<SysResource> ModuleList { get; set; } = new List<SysResource>();
|
||||
|
||||
/// <summary>
|
||||
/// 默认模块
|
||||
///</summary>
|
||||
public long? DefaultModule { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 权限校验服务
|
||||
/// </summary>
|
||||
public interface IAuthService : ITransient
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取图形验证码
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<PicValidCodeOutPut> GetCaptchaInfo();
|
||||
|
||||
/// <summary>
|
||||
/// 获取登录用户信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<LoginUserOutput> GetLoginUser();
|
||||
|
||||
/// <summary>
|
||||
/// 获取手机短信验证码
|
||||
/// </summary>
|
||||
/// <param name="input">验证码参数</param>
|
||||
/// <param name="loginClientType">登录类型</param>
|
||||
/// <returns></returns>
|
||||
Task<string> GetPhoneValidCode(GetPhoneValidCodeInput input, LoginClientTypeEnum loginClientType);
|
||||
|
||||
/// <summary>
|
||||
/// 登录
|
||||
/// </summary>
|
||||
/// <param name="input">登录参数</param>
|
||||
/// <param name="loginClientType">登录类型</param>
|
||||
/// <returns>Token信息</returns>
|
||||
Task<LoginOutPut> Login(LoginInput input, LoginClientTypeEnum loginClientType);
|
||||
|
||||
/// <summary>
|
||||
/// 手机号登录
|
||||
/// </summary>
|
||||
/// <param name="input">登录参数</param>
|
||||
/// <param name="loginClientType">登录端类型</param>
|
||||
/// <returns>Token信息</returns>
|
||||
Task<LoginOutPut> LoginByPhone(LoginByPhoneInput input, LoginClientTypeEnum loginClientType);
|
||||
|
||||
/// <summary>
|
||||
/// 退出登录
|
||||
/// </summary>
|
||||
/// <param name="token">token</param>
|
||||
/// <param name="loginClientType">登出类型</param>
|
||||
/// <returns></returns>
|
||||
Task LoginOut(string token, LoginClientTypeEnum loginClientType);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 会话分页查询
|
||||
/// </summary>
|
||||
public class SessionPageInput : BasePageInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 账号
|
||||
///</summary>
|
||||
public virtual string Account { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 姓名
|
||||
///</summary>
|
||||
|
||||
public virtual string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最新登录ip
|
||||
///</summary>
|
||||
public string LatestLoginIp { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Token退出参数
|
||||
/// </summary>
|
||||
public class ExitTokenInput : BaseIdInput
|
||||
{
|
||||
/// <summary>
|
||||
/// token
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Tokens不能为空")]
|
||||
public List<string> Tokens { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 会话输出
|
||||
/// </summary>
|
||||
public class SessionOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 主键Id
|
||||
/// </summary>
|
||||
public virtual long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 头像
|
||||
///</summary>
|
||||
public string Avatar { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 账号
|
||||
///</summary>
|
||||
public virtual string Account { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 姓名
|
||||
///</summary>
|
||||
|
||||
public virtual string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 在线状态
|
||||
/// </summary>
|
||||
public string OnlineStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最新登录ip
|
||||
///</summary>
|
||||
public string LatestLoginIp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最新登录地点
|
||||
///</summary>
|
||||
public string LatestLoginAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最新登录时间
|
||||
///</summary>
|
||||
public DateTime? LatestLoginTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 令牌数量
|
||||
/// </summary>
|
||||
public int TokenCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 令牌信息集合
|
||||
/// </summary>
|
||||
public List<TokenInfo> TokenSignList { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 会话统计结果
|
||||
/// </summary>
|
||||
public class SessionAnalysisOutPut
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前会话总数量
|
||||
/// </summary>
|
||||
public int CurrentSessionTotalCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最大签发令牌数
|
||||
/// </summary>
|
||||
public int MaxTokenCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 在线用户数
|
||||
/// </summary>
|
||||
public int OnLineCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// BC端会话比例
|
||||
/// </summary>
|
||||
public string ProportionOfBAndC { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 会话管理服务
|
||||
/// </summary>
|
||||
public interface ISessionService : ITransient
|
||||
{
|
||||
/// <summary>
|
||||
/// 会话统计
|
||||
/// </summary>
|
||||
/// <returns>统计结果</returns>
|
||||
SessionAnalysisOutPut Analysis();
|
||||
|
||||
/// <summary>
|
||||
/// 强退会话
|
||||
/// </summary>
|
||||
/// <param name="input">用户ID</param>
|
||||
Task ExitSession(BaseIdInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 强退token
|
||||
/// </summary>
|
||||
/// <param name="input">token列表</param>
|
||||
Task ExitToken(ExitTokenInput input);
|
||||
|
||||
/// <summary>
|
||||
/// B端会话分页查询
|
||||
/// </summary>
|
||||
/// <param name="input">查询参数</param>
|
||||
/// <returns>B端会话列表</returns>
|
||||
Task<SqlSugarPagedList<SessionOutput>> PageB(SessionPageInput input);
|
||||
|
||||
/// <summary>
|
||||
/// C端会话分页查询
|
||||
/// </summary>
|
||||
/// <param name="input">查询参数</param>
|
||||
/// <returns>C端会话列表</returns>
|
||||
Task<SqlSugarPagedList<SessionOutput>> PageC(SessionPageInput input);
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
using Masuit.Tools.DateTimeExt;
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="ISessionService"/>
|
||||
/// </summary>
|
||||
public class SessionService : DbRepository<SysUser>, ISessionService
|
||||
{
|
||||
private readonly ISimpleCacheService _simpleCacheService;
|
||||
private readonly IEventPublisher _eventPublisher;
|
||||
|
||||
public SessionService(ISimpleCacheService simpleCacheService, IEventPublisher eventPublisher)
|
||||
{
|
||||
_simpleCacheService = simpleCacheService;
|
||||
_eventPublisher = eventPublisher;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<SqlSugarPagedList<SessionOutput>> PageB(SessionPageInput input)
|
||||
{
|
||||
//获取b端token列表
|
||||
var bTokenInfoDic = GetTokenDicFromRedis();
|
||||
|
||||
//获取用户ID列表
|
||||
var userIds = bTokenInfoDic.Keys.Select(it => it.ToLong()).ToList();
|
||||
var query = Context.Queryable<SysUser>().Where(it => userIds.Contains(it.Id))//根据ID查询
|
||||
.WhereIF(!string.IsNullOrEmpty(input.Name), it => it.Name.Contains(input.Name))//根据姓名查询
|
||||
.WhereIF(!string.IsNullOrEmpty(input.Account), it => it.Account.Contains(input.Account))//根据账号查询
|
||||
.WhereIF(!string.IsNullOrEmpty(input.LatestLoginIp), it => it.LatestLoginIp.Contains(input.LatestLoginIp))//根据IP查询
|
||||
.OrderBy(it => it.LatestLoginTime, OrderByType.Desc).Select<SessionOutput>().Mapper(it =>
|
||||
{
|
||||
var tokenInfos = bTokenInfoDic[it.Id.ToString()];//获取用户token信息
|
||||
GetTokenInfos(ref tokenInfos, LoginClientTypeEnum.B);//获取剩余时间
|
||||
it.TokenCount = tokenInfos.Count;//令牌数量
|
||||
it.TokenSignList = tokenInfos;//令牌列表
|
||||
//如果有mqtt客户端ID就是在线
|
||||
it.OnlineStatus = tokenInfos.Any(it => it.ClientIds.Count > 0)
|
||||
? SysDictConst.ONLINE_STATUS_ONLINE
|
||||
: SysDictConst.ONLINE_STATUS_OFFLINE;
|
||||
});
|
||||
|
||||
var pageInfo = await query.ToPagedListAsync(input.PageNum, input.PageSize);//分页
|
||||
pageInfo.List.OrderByDescending(it => it.TokenCount);
|
||||
return pageInfo;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<SqlSugarPagedList<SessionOutput>> PageC(SessionPageInput input)
|
||||
{
|
||||
return new SqlSugarPagedList<SessionOutput> { PageNum = 1, PageSize = 20, Total = 0, Pages = 1, HasNextPages = false };
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SessionAnalysisOutPut Analysis()
|
||||
{
|
||||
var tokenDic = GetTokenDicFromRedis();//redisToken会话字典信息
|
||||
var tokenInfosList = tokenDic.Values.ToList();//端token列表
|
||||
var dicB = new Dictionary<string, List<TokenInfo>>();
|
||||
var dicC = new Dictionary<string, List<TokenInfo>>();
|
||||
var onLineCount = 0;
|
||||
foreach (var token in tokenDic)
|
||||
{
|
||||
var b = token.Value.Where(it => it.LoginClientType == LoginClientTypeEnum.B).ToList();//获取该用户B端token
|
||||
var c = token.Value.Where(it => it.LoginClientType == LoginClientTypeEnum.C).ToList();//获取该用户C端token
|
||||
if (b.Count > 0)
|
||||
dicB.Add(token.Key, b);
|
||||
if (c.Count > 0)
|
||||
dicC.Add(token.Key, c);
|
||||
var count = token.Value.Count(it => it.ClientIds.Count > 0);//计算在线用户
|
||||
onLineCount += count;
|
||||
}
|
||||
var tokenB = dicB.Values.ToList();//b端token列表
|
||||
var tokenC = dicC.Values.ToList();//c端token列表
|
||||
int maxCountB = 0, maxCountC = 0;
|
||||
if (tokenB.Count > 0)
|
||||
maxCountB = tokenB.OrderByDescending(it => it.Count).Take(1).First().Count;//b端最大会话数
|
||||
|
||||
if (tokenC.Count > 0)
|
||||
maxCountC = tokenC.OrderByDescending(it => it.Count).Take(1).First().Count;//C端最大会话数
|
||||
return new SessionAnalysisOutPut
|
||||
{
|
||||
OnLineCount = onLineCount,
|
||||
CurrentSessionTotalCount = tokenB.Count + tokenC.Count,
|
||||
MaxTokenCount = maxCountB > maxCountC ? maxCountB : maxCountC,
|
||||
ProportionOfBAndC = $"{tokenB.Count}/{tokenC.Count}"
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task ExitSession(BaseIdInput input)
|
||||
{
|
||||
var userId = input.Id.ToString();
|
||||
//token列表
|
||||
var tokenInfos = _simpleCacheService.HashGetOne<List<TokenInfo>>(CacheConst.CACHE_USER_TOKEN, userId);
|
||||
//从列表中删除
|
||||
_simpleCacheService.HashDel<List<TokenInfo>>(CacheConst.CACHE_USER_TOKEN, userId);
|
||||
await NoticeUserLoginOut(userId, tokenInfos);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task ExitToken(ExitTokenInput input)
|
||||
{
|
||||
var userId = input.Id.ToString();
|
||||
//获取该用户的token信息
|
||||
var tokenInfos = _simpleCacheService.HashGetOne<List<TokenInfo>>(CacheConst.CACHE_USER_TOKEN, userId);
|
||||
//当前需要踢掉用户的token
|
||||
var deleteTokens = tokenInfos.Where(it => input.Tokens.Contains(it.Token)).ToList();
|
||||
//踢掉包含token列表的token信息
|
||||
tokenInfos = tokenInfos.Where(it => !input.Tokens.Contains(it.Token)).ToList();
|
||||
if (tokenInfos.Count > 0)
|
||||
_simpleCacheService.HashAdd(CacheConst.CACHE_USER_TOKEN, userId, tokenInfos);//如果还有token则更新token
|
||||
else
|
||||
_simpleCacheService.HashDel<List<TokenInfo>>(CacheConst.CACHE_USER_TOKEN, userId);//否则直接删除key
|
||||
await NoticeUserLoginOut(userId, deleteTokens);
|
||||
}
|
||||
|
||||
#region 方法
|
||||
|
||||
/// <summary>
|
||||
/// 获取redis中token信息列表
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Dictionary<string, List<TokenInfo>> GetTokenDicFromRedis()
|
||||
{
|
||||
var clockSkew = App.GetConfig<int>("JWTSettings:ClockSkew");//获取过期时间容错值(秒)
|
||||
//redis获取token信息hash集合,并转成字典
|
||||
var bTokenDic = _simpleCacheService.HashGetAll<List<TokenInfo>>(CacheConst.CACHE_USER_TOKEN).ToDictionary(u => u.Key, u => u.Value);
|
||||
if (bTokenDic != null)
|
||||
{
|
||||
bTokenDic.ForEach(it =>
|
||||
{
|
||||
var tokens = it.Value.Where(it => it.TokenTimeout.AddSeconds(clockSkew) > DateTime.Now).ToList();//去掉登录超时的
|
||||
if (tokens.Count == 0)
|
||||
{
|
||||
//表示都过期了
|
||||
bTokenDic.Remove(it.Key);
|
||||
}
|
||||
else
|
||||
{
|
||||
bTokenDic[it.Key] = tokens;//重新赋值token
|
||||
}
|
||||
});
|
||||
if (bTokenDic.Count > 0)
|
||||
{
|
||||
_simpleCacheService.HashSet(CacheConst.CACHE_USER_TOKEN, bTokenDic);//如果还有token则更新token
|
||||
}
|
||||
else
|
||||
{
|
||||
_simpleCacheService.Remove(CacheConst.CACHE_USER_TOKEN);//否则直接删除key
|
||||
}
|
||||
return bTokenDic;
|
||||
}
|
||||
return new Dictionary<string, List<TokenInfo>>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取token剩余时间信息
|
||||
/// </summary>
|
||||
/// <param name="tokenInfos">token列表</param>
|
||||
/// <param name="loginClientType">登录类型</param>
|
||||
public void GetTokenInfos(ref List<TokenInfo> tokenInfos, LoginClientTypeEnum loginClientType)
|
||||
{
|
||||
tokenInfos = tokenInfos.Where(it => it.LoginClientType == loginClientType).ToList();
|
||||
tokenInfos.ForEach(it =>
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
it.TokenRemain = now.GetDiffTime(it.TokenTimeout);//获取时间差
|
||||
var tokenSecond = it.TokenTimeout.AddMinutes(-it.Expire).ConvertDateTimeToLong();//颁发时间转为时间戳
|
||||
var timeoutSecond = it.TokenTimeout.ConvertDateTimeToLong();//过期时间转为时间戳
|
||||
var tokenRemainPercent =
|
||||
1 - (now.ConvertDateTimeToLong() - tokenSecond) * 1.0 / (timeoutSecond - tokenSecond);//求百分比,用现在时间-token颁布时间除以超时时间-token颁布时间
|
||||
it.TokenRemainPercent = tokenRemainPercent;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通知用户下线
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private async Task NoticeUserLoginOut(string userId, List<TokenInfo> tokenInfos)
|
||||
{
|
||||
await _eventPublisher.PublishAsync(EventSubscriberConst.USER_LOGIN_OUT, new UserLoginOutEvent
|
||||
{
|
||||
Message = "您已被强制下线!",
|
||||
TokenInfos = tokenInfos,
|
||||
UserId = userId
|
||||
});//通知用户下线
|
||||
}
|
||||
|
||||
#endregion 方法
|
||||
}
|
||||
Reference in New Issue
Block a user