IdentityAppService.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. using System.Security.Claims;
  2. using Fw.Utility.UnifyResponse;
  3. using Hotline.Caching.Interfaces;
  4. using Hotline.Caching.Services;
  5. using Hotline.Identity;
  6. using Hotline.Identity.Accounts;
  7. using Hotline.Orders;
  8. using Hotline.Push;
  9. using Hotline.Schedulings;
  10. using Hotline.SeedData;
  11. using Hotline.Settings;
  12. using Hotline.Share.Dtos.FlowEngine;
  13. using Hotline.Share.Dtos.Identity;
  14. using Hotline.Share.Dtos.Snapshot;
  15. using Hotline.Share.Enums.FlowEngine;
  16. using Hotline.Share.Enums.Identity;
  17. using Hotline.Share.Enums.Snapshot;
  18. using Hotline.Share.Enums.User;
  19. using Hotline.Share.Tools;
  20. using Hotline.Snapshot;
  21. using Hotline.Snapshot.Interfaces;
  22. using Hotline.Users;
  23. using IdentityModel;
  24. using Mapster;
  25. using Microsoft.AspNetCore.Identity;
  26. using Microsoft.Extensions.Options;
  27. using SqlSugar;
  28. using XF.Domain.Authentications;
  29. using XF.Domain.Cache;
  30. using XF.Domain.Dependency;
  31. using XF.Domain.Exceptions;
  32. using XF.Domain.Options;
  33. using XF.Domain.Repository;
  34. namespace Hotline.Application.Identity;
  35. public class IdentityAppService : IIdentityAppService, IScopeDependency
  36. {
  37. private readonly IAccountRepository _accountRepository;
  38. private readonly IRepository<Citizen> _citizenRepository;
  39. private readonly ISessionContext _sessionContext;
  40. private readonly IAccountDomainService _accountDomainService;
  41. private readonly IRepository<User> _userRepository;
  42. private readonly IJwtSecurity _jwtSecurity;
  43. private readonly IOptionsSnapshot<IdentityConfiguration> _identityOptionsAccessor;
  44. private readonly ITypedCache<AudienceTicket> _cacheAudience;
  45. private readonly IMessageCodeDomainService _messageCodeDomainService;
  46. private readonly IRepository<Scheduling> _schedulingRepository;
  47. private readonly IOrderDomainService _orderDomainService;
  48. private readonly ISystemSettingCacheManager _systemSettingCacheManager;
  49. private readonly IThirdIdentiyService _thirdIdentiyService;
  50. private readonly IThirdAccountRepository _thirdAccountRepository;
  51. private readonly IGuiderInfoRepository _guiderInfoRepository;
  52. private readonly IVolunteerRepository _volunteerRepository;
  53. private readonly ISystemLogRepository _systemLog;
  54. public IdentityAppService(
  55. IAccountRepository accountRepository,
  56. IAccountDomainService accountDomainService,
  57. IRepository<User> userRepository,
  58. IJwtSecurity jwtSecurity,
  59. IOptionsSnapshot<IdentityConfiguration> identityOptionsAccessor,
  60. ITypedCache<AudienceTicket> cacheAudience,
  61. IMessageCodeDomainService messageCodeDomainService,
  62. IRepository<Scheduling> schedulingRepository,
  63. IOrderDomainService orderDomainService,
  64. ISystemSettingCacheManager systemSettingCacheManager,
  65. IThirdIdentiyService thirdIdentiyService,
  66. IThirdAccountRepository thirdAccountRepository,
  67. ISessionContext sessionContext,
  68. IRepository<Citizen> citizenRepository,
  69. IGuiderInfoRepository guiderInfoRepository,
  70. IVolunteerRepository volunteerRepository,
  71. ISystemLogRepository systemLog)
  72. {
  73. _accountRepository = accountRepository;
  74. _accountDomainService = accountDomainService;
  75. _userRepository = userRepository;
  76. _jwtSecurity = jwtSecurity;
  77. _identityOptionsAccessor = identityOptionsAccessor;
  78. _cacheAudience = cacheAudience;
  79. _messageCodeDomainService = messageCodeDomainService;
  80. _schedulingRepository = schedulingRepository;
  81. _orderDomainService = orderDomainService;
  82. _systemSettingCacheManager = systemSettingCacheManager;
  83. _thirdIdentiyService = thirdIdentiyService;
  84. _thirdAccountRepository = thirdAccountRepository;
  85. _sessionContext = sessionContext;
  86. _citizenRepository = citizenRepository;
  87. _guiderInfoRepository = guiderInfoRepository;
  88. _volunteerRepository = volunteerRepository;
  89. _systemLog = systemLog;
  90. }
  91. public async Task<string> OldToNewLoginAsync(HotlineLoginOldToNewDto dto, CancellationToken cancellationToken)
  92. {
  93. var account = await _accountRepository.GetExtAsync(
  94. d => d.UserName == dto.UserName,
  95. d => d.Includes(x => x.Roles));
  96. if (account is null)
  97. throw UserFriendlyException.SameMessage("未找到帐号,请联系管理员查看");
  98. if (account.Status != EAccountStatus.Normal)
  99. throw UserFriendlyException.SameMessage("帐号已被注销,请联系管理员查看");
  100. if (account.LockoutEnabled && account.LockoutEnd >= DateTime.Now)
  101. throw UserFriendlyException.SameMessage("账号被锁定!请联系管理员查看");
  102. var userInfo = await _userRepository.GetAsync(p => p.Id == account.Id, cancellationToken);
  103. //限制系统类型账户频繁获取token的行为
  104. //todo
  105. if (account.LockoutEnd.HasValue || account.AccessFailedCount > 0)
  106. {
  107. account.LockoutEnd = null;
  108. account.AccessFailedCount = 0;
  109. await _accountRepository.UpdateAsync(account, cancellationToken);
  110. }
  111. var user = await _userRepository.Queryable()
  112. .Includes(d => d.Organization)
  113. .FirstAsync(d => d.Id == account.Id);
  114. //平均派单
  115. var averageSendOrder = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.AverageSendOrder).SettingValue[0]);
  116. if (averageSendOrder)
  117. {
  118. await AverageOrderScheduling(account.Id, cancellationToken);
  119. }
  120. var jwtOptions = _identityOptionsAccessor.Value.Jwt;
  121. var claims = new List<Claim>
  122. {
  123. //new(JwtClaimTypes.Id, account.Id),
  124. new(JwtClaimTypes.Subject, account.Id),
  125. new(JwtClaimTypes.PhoneNumber, account.PhoneNo ?? string.Empty),
  126. new(AppClaimTypes.UserDisplayName, account.Name),
  127. new(JwtClaimTypes.Scope, jwtOptions.Scope),
  128. new(AppClaimTypes.UserPasswordChanged, account.PasswordChanged.ToString()),
  129. new(AppClaimTypes.StaffNo, user.StaffNo ?? string.Empty),
  130. };
  131. if (!string.IsNullOrEmpty(user.OrgId) && user.Organization is not null)
  132. {
  133. claims.AddRange(
  134. new List<Claim>
  135. {
  136. new(AppClaimTypes.DepartmentId, user.OrgId ?? string.Empty),
  137. new(AppClaimTypes.DepartmentIsCenter, user.Organization?.IsCenter.ToString()),
  138. new(AppClaimTypes.DepartmentName, user.Organization?.Name ?? string.Empty),
  139. new(AppClaimTypes.DepartmentAreaCode, user.Organization?.AreaCode ?? string.Empty),
  140. new(AppClaimTypes.DepartmentAreaName, user.Organization?.AreaName ?? string.Empty),
  141. new(AppClaimTypes.DepartmentLevel, user.Organization?.Level.ToString() ?? string.Empty),
  142. new(AppClaimTypes.AreaId, user.OrgId?.GetHigherOrgId() ?? string.Empty),
  143. }
  144. );
  145. }
  146. claims.AddRange(account.Roles.Select(d => new Claim(JwtClaimTypes.Role, d.Name)));
  147. var audience = new AudienceTicket(account.Id);
  148. var expiredSeconds = jwtOptions.Expired <= 0 ? 3600 : jwtOptions.Expired;
  149. await _cacheAudience.SetAsync(audience.Id, audience, TimeSpan.FromSeconds(expiredSeconds), cancellationToken);
  150. var token = _jwtSecurity.EncodeJwtToken(claims, audience.Ticket);
  151. return token;
  152. }
  153. public async Task<(bool,bool, User)> IsCheckAdmin(string userName)
  154. {
  155. var isAdmin = false;
  156. var isCenter = false;
  157. var account = await _accountRepository.GetExtAsync(
  158. d => d.UserName == userName,
  159. d => d.Includes(x => x.Roles)
  160. );
  161. if (account is null)
  162. throw UserFriendlyException.SameMessage("未找到帐号,请联系管理员查看");
  163. if (account.Status != EAccountStatus.Normal)
  164. throw UserFriendlyException.SameMessage("帐号已被注销,请联系管理员查看");
  165. if (account.LockoutEnabled && account.LockoutEnd >= DateTime.Now)
  166. throw UserFriendlyException.SameMessage("账号被锁定!请联系管理员查看");
  167. var user = await _userRepository.Queryable()
  168. .Includes(d => d.Organization)
  169. .Includes(d => d.Roles)
  170. .FirstAsync(d => d.Id == account.Id);
  171. var systemAdministrator = _systemSettingCacheManager.GetSetting(SettingConstants.SystemAdministrator)?.SettingValue[0];
  172. if (!string.IsNullOrEmpty(systemAdministrator) && (user.Roles.Any(x=>x.Name.Contains(systemAdministrator)) || user.Roles.Any(x=>x.Name.Contains(RoleSeedData.AdminRole))))
  173. isAdmin = true;
  174. else
  175. isAdmin = false;
  176. isCenter = user.Organization.IsCenter;
  177. return (isAdmin, isCenter,user);
  178. }
  179. public async Task<string> LoginAsync(LoginDto dto, CancellationToken cancellationToken)
  180. {
  181. var account = await _accountRepository.GetExtAsync(
  182. d => d.UserName == dto.Username,
  183. d => d.Includes(x => x.Roles));
  184. if (account == null)
  185. throw new UserFriendlyException($"用户名或密码错误!{System.Text.Json.JsonSerializer.Serialize(dto)}", "用户名或密码错误!");
  186. var userInfo = await _userRepository.GetAsync(p => p.Id == account.Id, cancellationToken);
  187. //校验验证码
  188. await _messageCodeDomainService.CheckdCode(account.UserName, userInfo.PhoneNo, dto.MsgCode, cancellationToken);
  189. if (account.Status != EAccountStatus.Normal)
  190. throw UserFriendlyException.SameMessage("用户名或密码错误!");
  191. if (account.LockoutEnabled && account.LockoutEnd >= DateTime.Now)
  192. throw UserFriendlyException.SameMessage("账号被锁定!");
  193. var verifyResult = _accountDomainService.VerifyPassword(account, dto.Password);
  194. if (verifyResult == PasswordVerificationResult.Failed)
  195. {
  196. var lockoutOptions = _identityOptionsAccessor.Value.Lockout;
  197. account.AccessFailedCount += 1;
  198. if (account.LockoutEnabled && account.AccessFailedCount >= lockoutOptions.MaxFailedAccessAttempts)
  199. account.LockoutEnd = DateTime.Now.Add(lockoutOptions.DefaultLockoutTimeSpan);
  200. await _accountRepository.UpdateAsync(account, cancellationToken);
  201. throw new UserFriendlyException($"用户名或密码错误!{System.Text.Json.JsonSerializer.Serialize(dto)}", "用户名或密码错误!");
  202. }
  203. //限制系统类型账户频繁获取token的行为
  204. //todo
  205. if (account.LockoutEnd.HasValue || account.AccessFailedCount > 0)
  206. {
  207. account.LockoutEnd = null;
  208. account.AccessFailedCount = 0;
  209. await _accountRepository.UpdateAsync(account, cancellationToken);
  210. }
  211. var user = await _userRepository.Queryable()
  212. .Includes(d => d.Organization)
  213. .FirstAsync(d => d.Id == account.Id);
  214. if (user == null)
  215. throw UserFriendlyException.SameMessage("未查询到用户数据");
  216. //平均派单
  217. var averageSendOrder = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.AverageSendOrder).SettingValue[0]);
  218. if (averageSendOrder)
  219. {
  220. await AverageOrderScheduling(account.Id, cancellationToken);
  221. }
  222. var jwtOptions = _identityOptionsAccessor.Value.Jwt;
  223. var claims = new List<Claim>
  224. {
  225. //new(JwtClaimTypes.Id, account.Id),
  226. new(JwtClaimTypes.Subject, account.Id),
  227. new(JwtClaimTypes.PhoneNumber, account.PhoneNo ?? string.Empty),
  228. new(AppClaimTypes.UserDisplayName, account.Name),
  229. new(JwtClaimTypes.Scope, jwtOptions.Scope),
  230. new(AppClaimTypes.UserPasswordChanged, account.PasswordChanged.ToString()),
  231. new(AppClaimTypes.StaffNo, user.StaffNo ?? string.Empty),
  232. };
  233. if (!string.IsNullOrEmpty(user.OrgId) && user.Organization is not null)
  234. {
  235. claims.AddRange(
  236. new List<Claim>
  237. {
  238. new(AppClaimTypes.DepartmentId, user.OrgId ?? string.Empty),
  239. new(AppClaimTypes.DepartmentIsCenter, user.Organization?.IsCenter.ToString()),
  240. new(AppClaimTypes.DepartmentName, user.Organization?.Name ?? string.Empty),
  241. new(AppClaimTypes.DepartmentAreaCode, user.Organization?.AreaCode ?? string.Empty),
  242. new(AppClaimTypes.DepartmentAreaName, user.Organization?.AreaName ?? string.Empty),
  243. new(AppClaimTypes.DepartmentLevel, user.Organization?.Level.ToString() ?? string.Empty),
  244. new(AppClaimTypes.AreaId, user.OrgId?.GetHigherOrgId() ?? string.Empty),
  245. }
  246. );
  247. }
  248. claims.AddRange(account.Roles.Select(d => new Claim(JwtClaimTypes.Role, d.Name)));
  249. var audience = new AudienceTicket(account.Id);
  250. var expiredSeconds = jwtOptions.Expired <= 0 ? 3600 : jwtOptions.Expired;
  251. await _cacheAudience.SetAsync(audience.Id, audience, TimeSpan.FromSeconds(expiredSeconds), cancellationToken);
  252. var token = _jwtSecurity.EncodeJwtToken(claims, audience.Ticket);
  253. return token;
  254. }
  255. public async Task AverageOrderScheduling(string id, CancellationToken cancellationToken)
  256. {
  257. try
  258. {
  259. DateTime time = DateTime.Parse(DateTime.Now.ToString("yyyy-MM-dd"));
  260. //&& x.AtWork!.Value != true
  261. //根据当前时间获取排班信息
  262. var scheduling = await _schedulingRepository.Queryable()
  263. .Includes(x => x.SchedulingUser)
  264. .Where(x => x.SchedulingTime == time &&
  265. (x.AtWork == true || x.AtWork == null) &&
  266. x.SchedulingUser.UserId == id)
  267. .OrderBy(x => x.SendOrderNum).FirstAsync(cancellationToken);
  268. if (scheduling != null)
  269. {
  270. scheduling.AtWork = true;
  271. //执行登录平均派单
  272. await _orderDomainService.LogAverageOrder(id, scheduling, cancellationToken);
  273. }
  274. }
  275. catch
  276. {
  277. // ignored
  278. }
  279. }
  280. /// <summary>
  281. /// 获取三方令牌
  282. /// </summary>
  283. /// <param name="dto"></param>
  284. /// <returns></returns>
  285. /// <exception cref="UserFriendlyException"></exception>
  286. public async Task<TokenOutDto> GetThredTokenAsync(ThirdTokenInDto dto)
  287. {
  288. var thirdDto = dto.Adapt<ThirdTokenDto>();
  289. if (dto.ThirdType == EThirdType.WeChat)
  290. {
  291. thirdDto.AppId = _systemSettingCacheManager.WxOpenAppId;
  292. thirdDto.Secret = _systemSettingCacheManager.WxOpenAppSecret;
  293. }
  294. var thirdToken = await _thirdIdentiyService.GetTokenAsync(thirdDto);
  295. var phone = await _thirdIdentiyService.GetPhoneNumberAsync(thirdDto);
  296. var thirdAccount = await _thirdAccountRepository.GetByOpenIdAsync(thirdToken.OpenId);
  297. // 新用户注册
  298. if (thirdAccount is null)
  299. {
  300. thirdAccount = thirdToken.Adapt<ThirdAccount>();
  301. thirdAccount.CitizenType = EReadPackUserType.Citizen;
  302. thirdAccount.PhoneNumber = phone.PhoneNumber;
  303. var guider = await _guiderInfoRepository.GetByPhoneNumberAsync(phone.PhoneNumber);
  304. if (guider != null)
  305. {
  306. thirdAccount.CitizenType = EReadPackUserType.Guider;
  307. thirdAccount.UserId = guider.Id;
  308. }
  309. else
  310. {
  311. var citizen = await _citizenRepository.Queryable().Where(m => m.PhoneNumber == phone.PhoneNumber).FirstAsync();
  312. thirdAccount.UserId = citizen?.Id;
  313. }
  314. thirdAccount.Id = await _thirdAccountRepository.AddAsync(thirdAccount);
  315. }
  316. return await GetJwtToken(thirdAccount);
  317. }
  318. public async Task<TokenOutDto> RefreshTokenAsync(string openId)
  319. {
  320. var thirdAccount = await _thirdAccountRepository.GetByOpenIdAsync(openId)
  321. ?? throw UserFriendlyException.SameMessage("未找到用户信息");
  322. return await GetJwtToken(thirdAccount);
  323. }
  324. private async Task<TokenOutDto> GetJwtToken(ThirdAccount thirdAccount)
  325. {
  326. var jwtOptions = _identityOptionsAccessor.Value.Jwt;
  327. var claims = new List<Claim>
  328. {
  329. new(JwtClaimTypes.Subject, thirdAccount.Id),
  330. new(JwtClaimTypes.PhoneNumber, thirdAccount.PhoneNumber ?? string.Empty),
  331. new(JwtClaimTypes.Scope, jwtOptions.Scope),
  332. new(AppClaimTypes.OpenId, thirdAccount.OpenId),
  333. };
  334. var audience = new AudienceTicket(thirdAccount.Id);
  335. var expiredSeconds = jwtOptions.Expired <= 0 ? 3600 : jwtOptions.Expired;
  336. await _cacheAudience.SetAsync(audience.Id, audience, TimeSpan.FromSeconds(expiredSeconds));
  337. var token = _jwtSecurity.EncodeJwtToken(claims, audience.Ticket);
  338. var isVolunteer = await _volunteerRepository.IsVolunteerAsync(thirdAccount.PhoneNumber);
  339. return new TokenOutDto()
  340. {
  341. UserType = thirdAccount.CitizenType,
  342. Token = token,
  343. IsVolunteer = isVolunteer,
  344. OpenId = thirdAccount.OpenId,
  345. PhoneNumber = thirdAccount.PhoneNumber,
  346. InvitationCode = thirdAccount.InvitationCode,
  347. UserName = thirdAccount.UserName
  348. };
  349. }
  350. }