|
|
|
@ -0,0 +1,274 @@
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|