using System.Security.Claims; using Hotline.Identity; using Hotline.Identity.Accounts; using Hotline.Orders; using Hotline.Push; using Hotline.Schedulings; using Hotline.Settings; using Hotline.Share.Dtos.Identity; using Hotline.Share.Enums.Identity; using Hotline.Users; using IdentityModel; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; using XF.Domain.Authentications; using XF.Domain.Cache; using XF.Domain.Dependency; using XF.Domain.Exceptions; using XF.Domain.Options; using XF.Domain.Repository; namespace Hotline.Application.Identity; public class IdentityAppService : IIdentityAppService, IScopeDependency { private readonly IAccountRepository _accountRepository; private readonly IAccountDomainService _accountDomainService; private readonly IRepository _userRepository; private readonly IJwtSecurity _jwtSecurity; private readonly IOptionsSnapshot _identityOptionsAccessor; private readonly ITypedCache _cacheAudience; private readonly IMessageCodeDomainService _messageCodeDomainService; private readonly IRepository _schedulingRepository; private readonly IOrderDomainService _orderDomainService; public IdentityAppService( IAccountRepository accountRepository, IAccountDomainService accountDomainService, IRepository userRepository, IJwtSecurity jwtSecurity, IOptionsSnapshot identityOptionsAccessor, ITypedCache cacheAudience, IMessageCodeDomainService messageCodeDomainService, IRepository schedulingRepository, IOrderDomainService orderDomainService) { _accountRepository = accountRepository; _accountDomainService = accountDomainService; _userRepository = userRepository; _jwtSecurity = jwtSecurity; _identityOptionsAccessor = identityOptionsAccessor; _cacheAudience = cacheAudience; _messageCodeDomainService = messageCodeDomainService; _schedulingRepository = schedulingRepository; _orderDomainService = orderDomainService; } public async Task LoginAsync(LoginDto dto, CancellationToken cancellationToken) { var account = await _accountRepository.GetExtAsync( d => d.UserName == dto.Username, d => d.Includes(x => x.Roles)); if (account == null) throw new UserFriendlyException($"用户名或密码错误!{System.Text.Json.JsonSerializer.Serialize(dto)}", "用户名或密码错误!"); var userInfo = await _userRepository.GetAsync(p => p.Id == account.Id, cancellationToken); //校验验证码 await _messageCodeDomainService.CheckdCode(account.UserName, userInfo.PhoneNo, dto.MsgCode, cancellationToken); if (account.Status != EAccountStatus.Normal) throw UserFriendlyException.SameMessage("用户名或密码错误!"); if (account.LockoutEnabled && account.LockoutEnd >= DateTime.Now) throw UserFriendlyException.SameMessage("账号被锁定!"); var verifyResult = _accountDomainService.VerifyPassword(account, dto.Password); if (verifyResult == PasswordVerificationResult.Failed) { var lockoutOptions = _identityOptionsAccessor.Value.Lockout; account.AccessFailedCount += 1; if (account.LockoutEnabled && account.AccessFailedCount >= lockoutOptions.MaxFailedAccessAttempts) account.LockoutEnd = DateTime.Now.Add(lockoutOptions.DefaultLockoutTimeSpan); await _accountRepository.UpdateAsync(account, cancellationToken); throw new UserFriendlyException($"用户名或密码错误!{System.Text.Json.JsonSerializer.Serialize(dto)}", "用户名或密码错误!"); } //限制系统类型账户频繁获取token的行为 //todo if (account.LockoutEnd.HasValue || account.AccessFailedCount > 0) { account.LockoutEnd = null; account.AccessFailedCount = 0; await _accountRepository.UpdateAsync(account, cancellationToken); } var user = await _userRepository.Queryable() .Includes(d => d.Organization) .FirstAsync(d => d.Id == account.Id); if (user == null) throw UserFriendlyException.SameMessage("未查询到用户数据"); //平均派单 await AverageOrderScheduling(account.Id, cancellationToken); var jwtOptions = _identityOptionsAccessor.Value.Jwt; var claims = new List { //new(JwtClaimTypes.Id, account.Id), new(JwtClaimTypes.Subject, account.Id), new(JwtClaimTypes.PhoneNumber, account.PhoneNo ?? string.Empty), new(AppClaimTypes.UserDisplayName, account.Name), new(JwtClaimTypes.Scope, jwtOptions.Scope), new(AppClaimTypes.UserPasswordChanged, account.PasswordChanged.ToString()), new(AppClaimTypes.StaffNo, user.StaffNo ?? string.Empty), }; if (!string.IsNullOrEmpty(user.OrgId) && user.Organization is not null) { claims.AddRange( new List { new(AppClaimTypes.DepartmentId, user.OrgId ?? string.Empty), new(AppClaimTypes.DepartmentIsCenter, user.Organization?.IsCenter.ToString()), new(AppClaimTypes.DepartmentName, user.Organization?.Name ?? string.Empty), new(AppClaimTypes.DepartmentAreaCode, user.Organization?.AreaCode ?? string.Empty), new(AppClaimTypes.DepartmentAreaName, user.Organization?.AreaName ?? string.Empty), new(AppClaimTypes.DepartmentLevel, user.Organization?.Level.ToString() ?? string.Empty), new(AppClaimTypes.AreaId, user.OrgId?.GetHigherOrgId() ?? string.Empty), } ); } claims.AddRange(account.Roles.Select(d => new Claim(JwtClaimTypes.Role, d.Name))); var audience = new AudienceTicket(account.Id); var expiredSeconds = jwtOptions.Expired <= 0 ? 3600 : jwtOptions.Expired; await _cacheAudience.SetAsync(audience.Id, audience, TimeSpan.FromSeconds(expiredSeconds), cancellationToken); var token = _jwtSecurity.EncodeJwtToken(claims, audience.Ticket); return token; } public async Task AverageOrderScheduling(string id, CancellationToken cancellationToken) { try { DateTime time = DateTime.Parse(DateTime.Now.ToString("yyyy-MM-dd")); //&& x.AtWork!.Value != true //根据当前时间获取排班信息 var scheduling = await _schedulingRepository.Queryable() .Includes(x => x.SchedulingUser) .Where(x => x.SchedulingTime == time && x.WorkingTime <= DateTime.Now.TimeOfDay && x.OffDutyTime >= DateTime.Now.TimeOfDay && (x.AtWork == true || x.AtWork == null) && x.SchedulingUser.UserId == id) .OrderBy(x => x.SendOrderNum).FirstAsync(cancellationToken); if (scheduling != null) { scheduling.AtWork = true; await _schedulingRepository.UpdateAsync(scheduling, cancellationToken); //执行登录平均派单 await _orderDomainService.LogAverageOrder(id, cancellationToken); } } catch { // ignored } } }