using ATS.NonCustodial.Application.Base; using ATS.NonCustodial.Application.Contracts.Interfaces.Admins.Auth; using ATS.NonCustodial.Application.Contracts.Interfaces.Admins.Auth.Input; using ATS.NonCustodial.Application.Contracts.Interfaces.Admins.Auth.Output; using ATS.NonCustodial.Application.Contracts.Interfaces.Admins.User; using ATS.NonCustodial.Application.Contracts.Interfaces.Logs.LoginLog.Input; using ATS.NonCustodial.Domain.Entities.Admins; using ATS.NonCustodial.Domain.Entities.Business; using ATS.NonCustodial.Domain.Entities.Business.EarlyWarning; using ATS.NonCustodial.Domain.Shared.Enums; using ATS.NonCustodial.Domain.Shared.OrmRepositories.Basic.EfCore; using ATS.NonCustodial.DynamicApi; using ATS.NonCustodial.DynamicApi.Attributes; using ATS.NonCustodial.Shared.Captcha.Dto; using ATS.NonCustodial.Shared.Common.Attributes; using ATS.NonCustodial.Shared.Common.Auth; using ATS.NonCustodial.Shared.Common.Constants; using ATS.NonCustodial.Shared.Common.Dtos; using ATS.NonCustodial.Shared.Common.Enums; using ATS.NonCustodial.Shared.Common.UnifiedResults; using ATS.NonCustodial.Shared.Configurations.Options; using ATS.NonCustodial.Shared.Extensions; using ATS.NonCustodial.Shared.Helpers; using ATS.NonCustodial.Shared.Helpers.Core.Hash; using ATS.NonCustodial.Shared.Helpers.Http; using ATS.NonCustodial.Shared.Tools.Captcha; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; using NPOI.SS.Formula.Functions; using NPOI.Util; using StackExchange.Profiling; using System.Diagnostics; using System.Security.Claims; using static System.Runtime.InteropServices.JavaScript.JSType; namespace ATS.NonCustodial.Application.Impl.Admins { /// /// 认证授权服务 /// /// Author:mxg /// CreatedTimed:2022-05-15 11:04 PM [DynamicApi(Area = "admin")] public class AuthService : AdminAppServiceBase, IAuthService, IDynamicApi { #region Identity private readonly VarifyCodeConfiguration _adminUiOptions; private readonly IEfRepository _appUnitRepository; private readonly IEfRepository _appPermissionRepository; private readonly IEfRepository _appUserRepository; private readonly IEfRepository _appUserRoleRepository; private readonly IEfRepository _appRolePermissionRepository; private readonly IEfRepository _appViewRepository; private readonly IEfRepository _appEarlyWarningRepository; private readonly IEfRepository _appEarlyWarningRuleRepository; private readonly ICaptchaTool _captchaTool; private readonly IUserService _userService; private readonly JwtConfiguration _jwtConfiguration; public AuthService( VarifyCodeConfiguration adminUiOptions, IEfRepository appUserRepository, IEfRepository appUnitRepository, IEfRepository appEarlyWarningRepository, IEfRepository appEarlyWarningRuleRepository, IEfRepository appPermissionRepository, IEfRepository appUserRoleRepository, IEfRepository appRolePermissionRepository, IEfRepository appViewRepository, ICaptchaTool captchaTool, IUserService userService, JwtConfiguration jwtConfiguration ) { _appEarlyWarningRepository = appEarlyWarningRepository; _appEarlyWarningRuleRepository = appEarlyWarningRuleRepository; _adminUiOptions = adminUiOptions; _appUserRepository = appUserRepository; _appUnitRepository = appUnitRepository; _appPermissionRepository = appPermissionRepository; _captchaTool = captchaTool; _appUserRoleRepository = appUserRoleRepository; _appRolePermissionRepository = appRolePermissionRepository; _appViewRepository = appViewRepository; _userService = userService; _jwtConfiguration = jwtConfiguration; } #endregion Identity /// /// 查询密钥 /// /// [HttpGet] [AllowAnonymous] [NoOperationLog] public async Task GetPasswordEncryptKeyAsync() { //写入Redis var guid = Guid.NewGuid().ToString("N"); var key = string.Format(CacheKey.passWordEncryptKey, guid); var encyptKey = StringHelper.GenerateRandom(8); await Cache.SetAsync(key, encyptKey, TimeSpan.FromMinutes(5)); var data = new { key = guid, encyptKey }; return ResultOutput.Ok(data); } /// /// 查询单位树 /// /// [HttpGet] [AllowAnonymous] [NoOperationLog] public async Task GetUnitTreeAsync() { var rtnlist = new List();//五条件返回结果 //监管机构 var express = _appUnitRepository.AsQueryable(false, true).Where(q => q.Stat == 0); if (express.Count() == 0) return ResultOutput.Ok(rtnlist); //单位集合 var dwgllist = _appUnitRepository.AsQueryable(false, true).Where(q => q.Stat == 1).ToList(); foreach (var item in await express.Where(q => q.ParentUnitCode == null || q.ParentUnitCode == 0).ToArrayAsync()) { rtnlist.Add(new { Id = item.Id, ParentUnitCode = item.ParentUnitCode, UnitCode = item.UnitCode, NameEntity = item.NameEntity, dw = true, children = pidlist(express.Where(q => q.ParentUnitCode != null || q.ParentUnitCode != 0).ToList(), item.Id, dwgllist) }); } return ResultOutput.Ok(rtnlist); } [HttpGet] [AllowAnonymous] [NoOperationLog] public async Task GetEarlyWarningAsync(int noticeCount) { var rtnlist = new List();//五条件返回结果 //监管机构 //var express = _appEarlyWarningRepository.ExecuteSqlRawAsync(""); //var express = _appEarlyWarningRuleRepository.AsQueryable(false, true).Where(q => q.Stat == 0); return ResultOutput.Ok(rtnlist); } public static List pidlist(List list, long pid, List dwgllist) { var plist = new List(); var dwgllist1 = new List(); //通过监管机构查询查询单位 if (dwgllist != null) dwgllist1 = dwgllist.Where(q => q.mechanismId == pid).ToList(); //监管机构查询下级 foreach (var item in list.Where(q => q.ParentUnitCode == pid).ToList()) { plist.Add(new { Id = item.Id, ParentUnitCode = item.ParentUnitCode, NameEntity = item.NameEntity, UnitCode = item.UnitCode, dw = item.Stat == 0 ? true : false, children = pidlist(list, item.Id, dwgllist) }); } //单位查找下级 if (dwgllist1 != null) { foreach (var item in dwgllist1.Where(q => q.ParentUnitCode == 0 || q.ParentUnitCode == null).ToList()) { plist.Add(new { Id = item.Id, ParentUnitCode = item.ParentUnitCode, NameEntity = item.NameEntity, UnitCode = item.UnitCode, dw = false, children = pidlist(dwgllist.Where(q => q.ParentUnitCode != 0 || q.ParentUnitCode != null).ToList(), item.Id, null) }); } } return plist; } /// /// 查询用户信息 /// /// [Login] public async Task GetUserInfoAsync() { if (!(User?.Id > 0)) return ResultOutput.NotOk("未登录!"); var authUserInfoOutput = new AuthUserInfoOutput { //用户信息 User = await _appUserRepository.FetchAsync(w => new AuthUserProfileDto() { UserName = w.UserName, NickName = w.NickName, Avatar = w.Avatar }, w => w.Id == User.Id) }; //用户菜单 var ptEnums = new[] { PermissionTypeEnum.Group, PermissionTypeEnum.Menu }; authUserInfoOutput.Menus = GetPermissionInfo(0, ptEnums) .Select(w => new AuthUserMenuDto() { ViewPath = w, }).ToList(); //用户权限点 authUserInfoOutput.Permissions = GetPermissionInfo(1, PermissionTypeEnum.Dot); return ResultOutput.Ok(authUserInfoOutput); } /// /// 账号密码登录 /// /// /// [HttpPost] [AllowAnonymous] [NoOperationLog] public async Task LoginAsync(AuthLoginInput input) { var user = await _appUserRepository.FindAsync(a => a.UserName == input.UserName && a.DataStatus == DataStatusEnum.Normal && (a.ChatPersonType == ChatPersonTypeEnum.Admin || a.ChatPersonType == ChatPersonTypeEnum.Supervisor)); if (user == null) return ResultOutput.NotOk($"{input.UserName}:用户不存在!"); return await LoginCommon(user, input.Password); } /// /// 手机号登录 /// /// /// [HttpPost] [AllowAnonymous] [NoOperationLog] public async Task LoginWithPhoneAsync(AuthLoginWithPhoneInput input) { var user = await _appUserRepository.FindAsync(a => (input.UnitId == null || a.UnitId.Equals(input.UnitId)) && (a.UserName == input.UserName || a.Phone == input.UserName) && a.DataStatus == DataStatusEnum.Normal && (a.ChatPersonType == ChatPersonTypeEnum.Admin || a.ChatPersonType == ChatPersonTypeEnum.Supervisor)); if (user == null) return ResultOutput.NotOk($"用户不存在,或者无权限登录!"); user.CId = input.CId; return await LoginCommon(user, input.Password); } /// /// 注销/推出登录 /// /// public async Task LoginOutAsync() { await User.LoginOutAsync(); return ResultOutput.Ok(); } /// /// 刷新Token /// 以旧换新 /// /// /// [HttpGet] [AllowAnonymous] public async Task Refresh([BindRequired] string token) { var userClaims = LazyGetRequiredService().Decode(token); if (userClaims == null || userClaims.Length == 0) return ResultOutput.NotOk(); var refreshExpires = userClaims.FirstOrDefault(a => a.Type == ClaimAttributes.refreshExpires)?.Value; if (refreshExpires.IsNull()) return ResultOutput.NotOk(); if (refreshExpires.ToLong() <= DateTime.Now.ToTimestamp()) return ResultOutput.NotOk("登录信息已过期"); var userId = userClaims.FirstOrDefault(a => a.Type == ClaimAttributes.userId)?.Value; if (userId.IsNull()) return ResultOutput.NotOk("登录信息已失效"); ResultOutput? output = await LazyGetRequiredService().GetLoginUserAsync(userId.ToLong()); string newToken = await GetToken(output?.Data); return ResultOutput.Ok(new { token = newToken }); } /// /// 获取验证数据 /// /// [HttpGet] [AllowAnonymous] [NoOperationLog] [EnableCors(AdminConstant.allowAnyPolicyName)] public async Task GetCaptcha() { using (MiniProfiler.Current.Step("获取滑块验证")) { var data = await _captchaTool.GetAsync(CacheKey.captchaKey); return ResultOutput.Ok(data); } } /// /// 检查验证数据 /// /// [HttpGet] [AllowAnonymous] [NoOperationLog] [EnableCors(AdminConstant.allowAnyPolicyName)] public async Task CheckCaptcha([FromQuery] CaptchaInput input) { input.CaptchaKey = CacheKey.captchaKey; var result = await _captchaTool.CheckAsync(input); return ResultOutput.Result(result); } /// /// 获取登录信息 /// /// /// /// public async Task GetUserValidateInfoAsync(long id) { return await _appUserRepository.FetchAsync(x => new UserValidateDto() { Id = x.Id, UserName = x.UserName, //Status = x.Status, Name = x.Name, //RoleIds = x.RoleIds, ValidationVersion = InfraHelper.Hash.GetHashedString(HashTypeEnum.Md5, x.UserName + x.Password) }, x => x.Id == id); } /// /// 获取权限信息公共逻辑方法 /// /// /// 返回类型(0:ViewPath、1:PermissionCode) /// public async Task> GetPermissionItems(int type = 0, params PermissionTypeEnum[] ptEnums) { var data = (from pr in _appPermissionRepository.Where(w => ptEnums.Contains(w.Type)) join rp in _appRolePermissionRepository.AsQueryable(false, true) on pr.Id equals rp.PermissionId join ur in _appUserRoleRepository.AsQueryable(false, true) on new { a = rp.RoleId, b = User.Id } equals new { a = ur.RoleId, b = ur.UserId } join v in _appViewRepository.AsQueryable(false, true) on pr.ViewId equals v.Id select new { pr, rp, ur, v }).ToList(); List result = new(); if (type == 0) result = data.Select(w => new PermissionItem() { Role = w.ur.RoleId.ToString(), Url = w.v.Path }).ToList(); else if (type == 1) data.Select(w => new PermissionItem() { Role = w.pr.Code, Url = w.v.Path }).ToList(); return result; } /// /// 根据身份证号获取电话号码 /// /// /// [HttpGet] [AllowAnonymous] public async Task GetPhoneByIDCard(string idCard) { var user = await _appUserRepository.FindAsync(a => a.ChatPersonType == ChatPersonTypeEnum.SupervisedPerson && a.IdCard == idCard); return ResultOutput.Ok(user?.Phone); } #region Private /// /// 获得token /// /// 用户信息 /// [NonAction] public async Task GetToken(AuthLoginOutput? user) { if (user == null) return string.Empty; var roles = (await _userService.IsAdmin(user.Id)).Roles.Select(w => w.Id).ToList(); string limits = _appUnitRepository.AsQueryable(false, true).Where(a => a.Id == user.UnitId).Select(a => a.limits).FirstOrDefault(); TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); var timeLogin = Convert.ToInt64(ts.TotalMilliseconds).ToString(); var token = LazyGetRequiredService().Create(new[] { new Claim(ClaimAttributes.userId, user.Id.ToString()), new Claim(ClaimAttributes.userName, user.UserName!), new Claim(ClaimAttributes.userUnitId, user.UnitId.ToString()), new Claim(ClaimAttributes.userDeptcodeId, user.DeptcodeId.ToString()), new Claim(ClaimAttributes.userNickName, user?.NickName??"") , new Claim(ClaimAttributes.avatar,user?.Avatar??"") , new Claim(ClaimAttributes.roles,JsonConvert.SerializeObject(roles)), new Claim(ClaimAttributes.orgs,JsonConvert.SerializeObject(Array.Empty())) , new Claim(ClaimAttributes.phone,user?.Phone??""), new Claim(ClaimAttributes.logtime,timeLogin), new Claim(ClaimAttributes.limits,limits), new Claim(ClaimAttributes.IsAdmin,user.IsAdmin?"true":"false"), new Claim(ClaimAttributes.personType,user?.ChatPersonType.ToString()!) }); return token; } /// /// 获取权限信息公共逻辑方法 /// /// /// 返回类型(0:ViewPath、1:PermissionCode) /// private List GetPermissionInfo(int type = 0, params PermissionTypeEnum[] ptEnums) { var data = (from pr in _appPermissionRepository.Where(w => ptEnums.Contains(w.Type)) join rp in _appRolePermissionRepository.AsQueryable(false, true) on pr.Id equals rp.PermissionId join ur in _appUserRoleRepository.AsQueryable(false, true) on new { a = rp.RoleId, b = User.Id } equals new { a = ur.RoleId, b = ur.UserId } join v in _appViewRepository.AsQueryable(false, true) on pr.ViewId equals v.Id select new { pr, rp, ur, v }).ToList(); List result = new(); if (type == 0) result = data.Select(w => w.v.Path).ToList(); else if (type == 1) result = data.Select(w => w.pr.Code).ToList(); return result; } /// /// 登录公共逻辑方法(一个设备只能登录一次) /// /// /// /// /// /// mac地址 + cookie /// 移动就就设备id /// PC端账号token做踢线处理 ,只能登陆一次,如果其他地方登陆了 更换token /// private async Task LoginCommon(AppUser user, string password) { //var userLoginKey = string.Format(CacheKey.AdminUserLogin, user.Id,); //var redisToken = await Cache.GetAsync(userLoginKey) ?? ""; //if (redisToken != User.GetToken()) ResultOutput.NotOk("同一个设备只能登录一次"); //if (!redisToken.IsNull()) return ResultOutput.Ok(new { redisToken }); //pc端和监管人员手机端只能登录监管人和admin,不能让被监管人登录 var isAdmin = await _userService.IsAdmin(user.Id); if (!isAdmin.IsAdmin && !isAdmin.Roles.Any(w => w.Code is "supervisor" or "admin")) return ResultOutput.NotOk($"被监管人员{user.UserName}:无权限登录!"); var sw = new Stopwatch(); sw.Start(); if (InfraHelper.Hash.GetHashedString(HashTypeEnum.Md5, password) != user.Password) return ResultOutput.NotOk("密码输入有误!"); //更新手机唯一标识码 if (!string.IsNullOrEmpty(user.CId)) { //var userda = _userService.GetLoginUserAsync(user.Id); await _appUserRepository.UpdateAsync(user); } var authLoginOutput = Mapper.Map(user); authLoginOutput.IsAdmin = isAdmin.IsAdmin; var token = await GetToken(authLoginOutput); sw.Stop(); //channel var channelWriter = ChannelHelper.Instance.Writer; { //添加登录日志 var httpContext = HttpContextHelper.GetCurrentHttpContext(); var loginLogAddInput = new LoginLogAddInput { CreatedUserName = user.Name, ElapsedMilliseconds = sw.ElapsedMilliseconds, Status = true, CreatedUserId = authLoginOutput.Id, NickName = authLoginOutput.NickName, TenantId = authLoginOutput.TenantId, Device = httpContext?.Request.Headers["device"].FirstOrDefault() ?? "web", IP = httpContext?.Connection?.RemoteIpAddress?.MapToIPv4().ToString() }; await channelWriter.WriteAsync(loginLogAddInput); //await LazyGetRequiredService().AddAsync(loginLogAddInput); } //将token写入Redis //await Cache.SetAsync(userLoginKey, token, TimeSpan.FromMinutes(_jwtConfiguration.Expires)); return ResultOutput.Ok(new { token }); } #endregion Private } }