|  |  | using System.Text.RegularExpressions;
 | 
						
						
						
							|  |  | using Mapster;
 | 
						
						
						
							|  |  | using Microsoft.Extensions.Caching.Distributed;
 | 
						
						
						
							|  |  | using Microsoft.Extensions.DependencyInjection;
 | 
						
						
						
							|  |  | using Microsoft.Extensions.Options;
 | 
						
						
						
							|  |  | using NPin.Framework.SqlSugarCore.Abstractions;
 | 
						
						
						
							|  |  | using NPin.Framework.Upms.Domain.Entities;
 | 
						
						
						
							|  |  | using NPin.Framework.Upms.Domain.Entities.ValueObjects;
 | 
						
						
						
							|  |  | using NPin.Framework.Upms.Domain.Repositories;
 | 
						
						
						
							|  |  | using NPin.Framework.Upms.Domain.Shared.Caches;
 | 
						
						
						
							|  |  | using NPin.Framework.Upms.Domain.Shared.Consts;
 | 
						
						
						
							|  |  | using NPin.Framework.Upms.Domain.Shared.Dtos;
 | 
						
						
						
							|  |  | using NPin.Framework.Upms.Domain.Shared.Etos;
 | 
						
						
						
							|  |  | using NPin.Framework.Upms.Domain.Shared.Options;
 | 
						
						
						
							|  |  | using Volo.Abp.Authorization;
 | 
						
						
						
							|  |  | using Volo.Abp.Caching;
 | 
						
						
						
							|  |  | using Volo.Abp.Domain.Services;
 | 
						
						
						
							|  |  | using Volo.Abp.EventBus.Local;
 | 
						
						
						
							|  |  | using Volo.Abp.Uow;
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | namespace NPin.Framework.Upms.Domain.Managers;
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | /// <summary>
 | 
						
						
						
							|  |  | /// 用户管理器 领域服务
 | 
						
						
						
							|  |  | /// </summary>
 | 
						
						
						
							|  |  | public partial class UserManager : DomainService
 | 
						
						
						
							|  |  | {
 | 
						
						
						
							|  |  |     [GeneratedRegex("^[a-zA-Z0-9]+$")]
 | 
						
						
						
							|  |  |     private static partial Regex UsernameRegex();
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |     private readonly IUserRepository _repository;
 | 
						
						
						
							|  |  |     private readonly ISqlSugarRepository<RoleEntity> _repositoryRole;
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |     private readonly ISqlSugarRepository<UserRoleEntity> _repositoryUserRole;
 | 
						
						
						
							|  |  |     private readonly ISqlSugarRepository<UserPostEntity> _repositoryUserPost;
 | 
						
						
						
							|  |  |     private readonly ISqlSugarRepository<UserOrganizationEntity> _repositoryUserOrg;
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |     private IDistributedCache<UserInfoCacheItem, UserInfoCacheKey> _userCache;
 | 
						
						
						
							|  |  |     private ILocalEventBus _localEventBus;
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |     public UserManager(
 | 
						
						
						
							|  |  |         IUserRepository repository,
 | 
						
						
						
							|  |  |         ISqlSugarRepository<UserRoleEntity> repositoryUserRole,
 | 
						
						
						
							|  |  |         ISqlSugarRepository<UserPostEntity> repositoryUserPost,
 | 
						
						
						
							|  |  |         ISqlSugarRepository<UserOrganizationEntity> repositoryUserOrg,
 | 
						
						
						
							|  |  |         IDistributedCache<UserInfoCacheItem, UserInfoCacheKey> userCache,
 | 
						
						
						
							|  |  |         ILocalEventBus localEventBus, ISqlSugarRepository<RoleEntity> repositoryRole)
 | 
						
						
						
							|  |  |     {
 | 
						
						
						
							|  |  |         _repository = repository;
 | 
						
						
						
							|  |  |         _repositoryUserRole = repositoryUserRole;
 | 
						
						
						
							|  |  |         _repositoryUserPost = repositoryUserPost;
 | 
						
						
						
							|  |  |         _repositoryUserOrg = repositoryUserOrg;
 | 
						
						
						
							|  |  |         _userCache = userCache;
 | 
						
						
						
							|  |  |         _localEventBus = localEventBus;
 | 
						
						
						
							|  |  |         _repositoryRole = repositoryRole;
 | 
						
						
						
							|  |  |     }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |     /// <summary>
 | 
						
						
						
							|  |  |     /// 创建用户
 | 
						
						
						
							|  |  |     /// </summary>
 | 
						
						
						
							|  |  |     /// <param name="entity"></param>
 | 
						
						
						
							|  |  |     public async Task CreateAsync(UserEntity entity)
 | 
						
						
						
							|  |  |     {
 | 
						
						
						
							|  |  |         await ValidateUser(entity);
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |         var returnEntity = await _repository.InsertReturnEntityAsync(entity);
 | 
						
						
						
							|  |  |         entity = returnEntity;
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |         // 触发事件
 | 
						
						
						
							|  |  |         await _localEventBus.PublishAsync(new UserCreatedEventArgs(entity.Id));
 | 
						
						
						
							|  |  |     }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |     private async Task ValidateUser(UserEntity entity)
 | 
						
						
						
							|  |  |     {
 | 
						
						
						
							|  |  |         // TODO 不一定非要用户名,这里需要更自由的逻辑
 | 
						
						
						
							|  |  |         if (entity.Username is UserConst.Admin or UserConst.TenantAdmin)
 | 
						
						
						
							|  |  |         {
 | 
						
						
						
							|  |  |             throw new UserFriendlyException("无效的用户名");
 | 
						
						
						
							|  |  |         }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |         if (entity.Username.Length < 2)
 | 
						
						
						
							|  |  |         {
 | 
						
						
						
							|  |  |             throw new UserFriendlyException("用户名长度错误,需大于2个字符");
 | 
						
						
						
							|  |  |         }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |         // 正则表达式,匹配只包含数字和字母的字符串
 | 
						
						
						
							|  |  |         var isMatch = UsernameRegex().IsMatch(entity.Username);
 | 
						
						
						
							|  |  |         if (!isMatch)
 | 
						
						
						
							|  |  |         {
 | 
						
						
						
							|  |  |             throw new UserFriendlyException("用户名不能包含除【字母】与【数字】的其他字符");
 | 
						
						
						
							|  |  |         }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |         // 密码长度判断
 | 
						
						
						
							|  |  |         // TODO(需要读取配置)
 | 
						
						
						
							|  |  |         if (entity.EncryptPassword.Password.Length < 6)
 | 
						
						
						
							|  |  |         {
 | 
						
						
						
							|  |  |             throw new UserFriendlyException($"密码格式错误,长度需大于等于{6}位");
 | 
						
						
						
							|  |  |         }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |         if (!string.IsNullOrEmpty(entity.PhoneNumber))
 | 
						
						
						
							|  |  |         {
 | 
						
						
						
							|  |  |             if (await _repository.IsAnyAsync(x => x.PhoneNumber == entity.PhoneNumber))
 | 
						
						
						
							|  |  |             {
 | 
						
						
						
							|  |  |                 throw new UserFriendlyException("手机号重复");
 | 
						
						
						
							|  |  |             }
 | 
						
						
						
							|  |  |         }
 | 
						
						
						
							|  |  |     }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |     /// <summary>
 | 
						
						
						
							|  |  |     /// 设置默认角色
 | 
						
						
						
							|  |  |     /// </summary>
 | 
						
						
						
							|  |  |     /// <param name="userId"></param>
 | 
						
						
						
							|  |  |     public async Task SetDefaultRoleAsync(Guid userId)
 | 
						
						
						
							|  |  |     {
 | 
						
						
						
							|  |  |         // 检查默认角色是否存在,不存在不处理
 | 
						
						
						
							|  |  |         var role = await _repositoryRole.GetFirstAsync(x => x.Code == UserConst.DefaultRoleCode);
 | 
						
						
						
							|  |  |         if (role is not null)
 | 
						
						
						
							|  |  |         {
 | 
						
						
						
							|  |  |             await SetRoleAsync([userId], [role.Id]);
 | 
						
						
						
							|  |  |         }
 | 
						
						
						
							|  |  |     }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |     /// <summary>
 | 
						
						
						
							|  |  |     /// 给用户设置角色
 | 
						
						
						
							|  |  |     /// </summary>
 | 
						
						
						
							|  |  |     /// <param name="userIds"></param>
 | 
						
						
						
							|  |  |     /// <param name="roleIds"></param>
 | 
						
						
						
							|  |  |     [UnitOfWork]
 | 
						
						
						
							|  |  |     public async Task SetRoleAsync(List<Guid> userIds, List<Guid> roleIds)
 | 
						
						
						
							|  |  |     {
 | 
						
						
						
							|  |  |         // 删除用户之前的所有关系(物理)
 | 
						
						
						
							|  |  |         await _repositoryUserRole.DeleteAsync(u => userIds.Contains(u.UserId));
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |         var entities = (from userId in userIds
 | 
						
						
						
							|  |  |                 from roleId in roleIds
 | 
						
						
						
							|  |  |                 select new UserRoleEntity { UserId = userId, RoleId = roleId })
 | 
						
						
						
							|  |  |             .ToList();
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |         await _repositoryUserRole.InsertRangeAsync(entities);
 | 
						
						
						
							|  |  |     }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |     /// <summary>
 | 
						
						
						
							|  |  |     /// 设置用户岗位
 | 
						
						
						
							|  |  |     /// </summary>
 | 
						
						
						
							|  |  |     /// <param name="userIds"></param>
 | 
						
						
						
							|  |  |     /// <param name="postIds"></param>
 | 
						
						
						
							|  |  |     [UnitOfWork]
 | 
						
						
						
							|  |  |     public async Task SetPostAsync(List<Guid> userIds, List<Guid> postIds)
 | 
						
						
						
							|  |  |     {
 | 
						
						
						
							|  |  |         // 删除用户之前的所有关系(物理)
 | 
						
						
						
							|  |  |         await _repositoryUserPost.DeleteAsync(u => userIds.Contains(u.UserId));
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |         var entities = (from userId in userIds
 | 
						
						
						
							|  |  |                 from postId in postIds
 | 
						
						
						
							|  |  |                 select new UserPostEntity { UserId = userId, PostId = postId })
 | 
						
						
						
							|  |  |             .ToList();
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |         await _repositoryUserPost.InsertRangeAsync(entities);
 | 
						
						
						
							|  |  |     }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |     /// <summary>
 | 
						
						
						
							|  |  |     /// 设置用户组织机构
 | 
						
						
						
							|  |  |     /// </summary>
 | 
						
						
						
							|  |  |     /// <param name="userIds"></param>
 | 
						
						
						
							|  |  |     /// <param name="orgIds"></param>
 | 
						
						
						
							|  |  |     [UnitOfWork]
 | 
						
						
						
							|  |  |     public async Task SetOrganizationAsync(List<Guid> userIds, List<Guid> orgIds)
 | 
						
						
						
							|  |  |     {
 | 
						
						
						
							|  |  |         // 删除用户之前的所有关系(物理)
 | 
						
						
						
							|  |  |         await _repositoryUserOrg.DeleteAsync(u => userIds.Contains(u.UserId));
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |         var entities = (from userId in userIds
 | 
						
						
						
							|  |  |                 from orgId in orgIds
 | 
						
						
						
							|  |  |                 select new UserOrganizationEntity { UserId = userId, OrganizationId = orgId })
 | 
						
						
						
							|  |  |             .ToList();
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |         await _repositoryUserOrg.InsertRangeAsync(entities);
 | 
						
						
						
							|  |  |     }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |     /// <summary>
 | 
						
						
						
							|  |  |     /// 从缓存中获取用户信息,若缓存不存在则查库
 | 
						
						
						
							|  |  |     /// </summary>
 | 
						
						
						
							|  |  |     /// <param name="userId"></param>
 | 
						
						
						
							|  |  |     /// <returns></returns>
 | 
						
						
						
							|  |  |     /// <exception cref="AbpAuthorizationException">库中无此用户,403</exception>
 | 
						
						
						
							|  |  |     public async Task<UserFullDto> GetInfoByCacheAsync(Guid userId)
 | 
						
						
						
							|  |  |     {
 | 
						
						
						
							|  |  |         // 1. 缓存获取
 | 
						
						
						
							|  |  |         UserFullDto ret = null;
 | 
						
						
						
							|  |  |         var tokenExpires = LazyServiceProvider.GetRequiredService<IOptions<JwtOptions>>().Value.ExpiresMinuteTime;
 | 
						
						
						
							|  |  |         var cached = await _userCache.GetOrAddAsync(new UserInfoCacheKey(userId),
 | 
						
						
						
							|  |  |             async () =>
 | 
						
						
						
							|  |  |             {
 | 
						
						
						
							|  |  |                 // 2. 库查询
 | 
						
						
						
							|  |  |                 var user = await _repository.GetAllInfoAsync(userId);
 | 
						
						
						
							|  |  |                 var dto = EntityMapToDto(user);
 | 
						
						
						
							|  |  |                 ret = dto ?? throw new AbpAuthorizationException();
 | 
						
						
						
							|  |  |                 
 | 
						
						
						
							|  |  |                 return new UserInfoCacheItem(dto);
 | 
						
						
						
							|  |  |             },
 | 
						
						
						
							|  |  |             () => new DistributedCacheEntryOptions
 | 
						
						
						
							|  |  |             {
 | 
						
						
						
							|  |  |                 AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(tokenExpires)
 | 
						
						
						
							|  |  |             });
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |         if (cached is not null)
 | 
						
						
						
							|  |  |         {
 | 
						
						
						
							|  |  |             ret = cached.Info;
 | 
						
						
						
							|  |  |         }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |         return ret!;
 | 
						
						
						
							|  |  |     }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |     private UserFullDto? EntityMapToDto(UserEntity? entity)
 | 
						
						
						
							|  |  |     {
 | 
						
						
						
							|  |  |         if (entity is null)
 | 
						
						
						
							|  |  |         {
 | 
						
						
						
							|  |  |             return null;
 | 
						
						
						
							|  |  |         }
 | 
						
						
						
							|  |  |         var ret = new UserFullDto();
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |         // 密码过滤
 | 
						
						
						
							|  |  |         entity.EncryptPassword = new EncryptPasswordValueObject();
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |         // 超级管理员特殊处理
 | 
						
						
						
							|  |  |         if (UserConst.Admin.Equals(entity.Username))
 | 
						
						
						
							|  |  |         {
 | 
						
						
						
							|  |  |             ret.User = _userCache.Adapt<UserDto>();
 | 
						
						
						
							|  |  |             ret.RoleCodes.Add(UserConst.AdminRoleCode);
 | 
						
						
						
							|  |  |             ret.PermissionCodes.Add(UserConst.AdminPermissionCode);
 | 
						
						
						
							|  |  |             return ret;
 | 
						
						
						
							|  |  |         }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |         // 角色
 | 
						
						
						
							|  |  |         var roleDtoList = new List<RoleDto>();
 | 
						
						
						
							|  |  |         var roleCodes = new HashSet<string>();
 | 
						
						
						
							|  |  |         foreach (var role in entity.Roles)
 | 
						
						
						
							|  |  |         {
 | 
						
						
						
							|  |  |             roleDtoList.Add(role.Adapt<RoleDto>());
 | 
						
						
						
							|  |  |             roleCodes.Add(role.Code);
 | 
						
						
						
							|  |  |         }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |         ret.Roles = roleDtoList;
 | 
						
						
						
							|  |  |         ret.RoleCodes = roleCodes;
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |         // 岗位
 | 
						
						
						
							|  |  |         var postDtoList = new List<PostDto>();
 | 
						
						
						
							|  |  |         var postCodes = new HashSet<string>();
 | 
						
						
						
							|  |  |         foreach (var post in entity.Posts)
 | 
						
						
						
							|  |  |         {
 | 
						
						
						
							|  |  |             postDtoList.Add(post.Adapt<PostDto>());
 | 
						
						
						
							|  |  |             postCodes.Add(post.Code);
 | 
						
						
						
							|  |  |         }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |         ret.Posts = postDtoList;
 | 
						
						
						
							|  |  |         ret.PostCodes = postCodes;
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |         // 组织结构
 | 
						
						
						
							|  |  |         var orgDtoList = new List<OrganizationDto>();
 | 
						
						
						
							|  |  |         var orgCodes = new HashSet<string>();
 | 
						
						
						
							|  |  |         foreach (var org in entity.Organizations)
 | 
						
						
						
							|  |  |         {
 | 
						
						
						
							|  |  |             orgDtoList.Add(org.Adapt<OrganizationDto>());
 | 
						
						
						
							|  |  |             orgCodes.Add(org.Code);
 | 
						
						
						
							|  |  |         }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |         ret.Organizations = orgDtoList;
 | 
						
						
						
							|  |  |         ret.OrganizationCodes = orgCodes;
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |         ret.User = entity.Adapt<UserDto>();
 | 
						
						
						
							|  |  |         // TODO permissionCode
 | 
						
						
						
							|  |  |         return ret;
 | 
						
						
						
							|  |  |     }
 | 
						
						
						
							|  |  | } |