You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

274 lines
9.4 KiB
C#

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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<RoleAggregateRoot> _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<RoleAggregateRoot> repositoryRole)
{
_repository = repository;
_repositoryUserRole = repositoryUserRole;
_repositoryUserPost = repositoryUserPost;
_repositoryUserOrg = repositoryUserOrg;
_userCache = userCache;
_localEventBus = localEventBus;
_repositoryRole = repositoryRole;
}
/// <summary>
/// 创建用户
/// </summary>
/// <param name="aggregateRoot"></param>
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("手机号重复");
}
}
}
/// <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(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<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;
}
}