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;
///
/// 用户管理器 领域服务
///
public partial class UserManager : DomainService
{
[GeneratedRegex("^[a-zA-Z0-9]+$")]
private static partial Regex UsernameRegex();
private readonly IUserRepository _repository;
private readonly ISqlSugarRepository _repositoryRole;
private readonly ISqlSugarRepository _repositoryUserRole;
private readonly ISqlSugarRepository _repositoryUserPost;
private readonly ISqlSugarRepository _repositoryUserOrg;
private IDistributedCache _userCache;
private ILocalEventBus _localEventBus;
public UserManager(
IUserRepository repository,
ISqlSugarRepository repositoryUserRole,
ISqlSugarRepository repositoryUserPost,
ISqlSugarRepository repositoryUserOrg,
IDistributedCache userCache,
ILocalEventBus localEventBus, ISqlSugarRepository repositoryRole)
{
_repository = repository;
_repositoryUserRole = repositoryUserRole;
_repositoryUserPost = repositoryUserPost;
_repositoryUserOrg = repositoryUserOrg;
_userCache = userCache;
_localEventBus = localEventBus;
_repositoryRole = repositoryRole;
}
///
/// 创建用户
///
///
public async Task CreateAsync(UserAggregateRoot aggregateRoot)
{
await ValidateUser(aggregateRoot);
var returnEntity = await _repository.InsertReturnEntityAsync(aggregateRoot);
aggregateRoot = returnEntity;
// 触发事件
await _localEventBus.PublishAsync(new UserCreatedEventArgs(aggregateRoot.Id));
}
private async Task ValidateUser(UserAggregateRoot aggregateRoot)
{
// TODO 不一定非要用户名,这里需要更自由的逻辑
if (aggregateRoot.Username is UserConst.Admin or UserConst.TenantAdmin)
{
throw new UserFriendlyException("无效的用户名");
}
if (aggregateRoot.Username.Length < 2)
{
throw new UserFriendlyException("用户名长度错误,需大于2个字符");
}
// 正则表达式,匹配只包含数字和字母的字符串
var isMatch = UsernameRegex().IsMatch(aggregateRoot.Username);
if (!isMatch)
{
throw new UserFriendlyException("用户名不能包含除【字母】与【数字】的其他字符");
}
// 密码长度判断
// TODO(需要读取配置)
if (aggregateRoot.EncryptPassword.Password.Length < 6)
{
throw new UserFriendlyException($"密码格式错误,长度需大于等于{6}位");
}
if (!string.IsNullOrEmpty(aggregateRoot.PhoneNumber))
{
if (await _repository.IsAnyAsync(x => x.PhoneNumber == aggregateRoot.PhoneNumber))
{
throw new UserFriendlyException("手机号重复");
}
}
}
///
/// 设置默认角色
///
///
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]);
}
}
///
/// 给用户设置角色
///
///
///
[UnitOfWork]
public async Task SetRoleAsync(List userIds, List 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);
}
///
/// 设置用户岗位
///
///
///
[UnitOfWork]
public async Task SetPostAsync(List userIds, List 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);
}
///
/// 设置用户组织机构
///
///
///
[UnitOfWork]
public async Task SetOrganizationAsync(List userIds, List 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);
}
///
/// 从缓存中获取用户信息,若缓存不存在则查库
///
///
///
/// 库中无此用户,403
public async Task GetInfoByCacheAsync(Guid userId)
{
// 1. 缓存获取
UserFullDto ret = null;
var tokenExpires = LazyServiceProvider.GetRequiredService>().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(UserAggregateRoot? 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();
ret.RoleCodes.Add(UserConst.AdminRoleCode);
ret.PermissionCodes.Add(UserConst.AdminPermissionCode);
return ret;
}
// 角色
var roleDtoList = new List();
var roleCodes = new HashSet();
foreach (var role in entity.Roles)
{
roleDtoList.Add(role.Adapt());
roleCodes.Add(role.Code);
}
ret.Roles = roleDtoList;
ret.RoleCodes = roleCodes;
// 岗位
var postDtoList = new List();
var postCodes = new HashSet();
foreach (var post in entity.Posts)
{
postDtoList.Add(post.Adapt());
postCodes.Add(post.Code);
}
ret.Posts = postDtoList;
ret.PostCodes = postCodes;
// 组织结构
var orgDtoList = new List();
var orgCodes = new HashSet();
foreach (var org in entity.Organizations)
{
orgDtoList.Add(org.Adapt());
orgCodes.Add(org.Code);
}
ret.Organizations = orgDtoList;
ret.OrganizationCodes = orgCodes;
ret.User = entity.Adapt();
// TODO permissionCode
return ret;
}
}