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

665 lines
21 KiB

using Mapster;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using SqlSugar;
using System.Text;
using Wood.Cache;
using Wood.Data.Repository;
using Wood.Entity;
using Wood.Entity.SystemManage;
using Wood.Service.SystemManage.Dto;
using Wood.Service.SystemManage.Manager;
using Wood.Service.SystemManage.Param;
using Wood.Util;
using Wood.Util.JwtAuthorization;
namespace Wood.Service.SystemManage
{
/// <summary>
/// 用户管理
/// </summary>
public class UserService : ApiService
{
private readonly UserManager _userManager;
private readonly SqlSugarRepository<TenantEntity> _tenantRepository;
private readonly SqlSugarRepository<RoleEntity> _roleRepository;
private readonly SqlSugarRepository<PositionEntity> _positionRepository;
private readonly SqlSugarRepository<MenuAuthorizeEntity> _menuAuthorizeRepository;
private readonly SqlSugarRepository<MenuEntity> _menuRepository;
private readonly SqlSugarRepository<RefreshTokenEntity> _refreshTokenRepository;
private readonly SqlSugarRepository<LogLoginEntity> _logLoginRepository;
private readonly FileManager _fileManager;
private readonly OrgManager _orgManager;
private readonly ICache _cache;
private SqlSugarRepository<UserEntity> _userRepository => _userManager.AsRepository();
public UserService(UserManager userManager, SqlSugarRepository<RoleEntity> roleRepository, SqlSugarRepository<TenantEntity> tenantRepository, ICache cache, SqlSugarRepository<RefreshTokenEntity> refreshTokenRepository, SqlSugarRepository<MenuAuthorizeEntity> menuAuthorizeRepository, SqlSugarRepository<MenuEntity> menuRepository, FileManager fileManager, SqlSugarRepository<LogLoginEntity> logLoginRepository, OrgManager orgManager, SqlSugarRepository<PositionEntity> positionRepository)
{
_userManager = userManager;
_roleRepository = roleRepository;
_tenantRepository = tenantRepository;
_cache = cache;
_refreshTokenRepository = refreshTokenRepository;
_menuAuthorizeRepository = menuAuthorizeRepository;
_menuRepository = menuRepository;
_fileManager = fileManager;
_logLoginRepository = logLoginRepository;
_orgManager = orgManager;
_positionRepository = positionRepository;
}
/// <summary>
/// 分页获取用户数据
/// </summary>
/// <param name="param"></param>
/// <returns></returns>
public async Task<TDataPaged<UserPagedDto>> Paged(UserPagedParam param)
{
return await BuildQuery(param)
.Select(it => new UserPagedDto
{
Id = it.Id.SelectAll(),
OrgName = it.Org!.OrgName,
Roles = SqlFunc.Subqueryable<UserBelongRoleEntity>()
.LeftJoin<RoleEntity>((ur, ro) => ur.RoleId == ro.Id)
.Where((ur, ro) => ur.UserId == it.Id)
.SelectStringJoin((ur, ro) => ro.RoleName, ","),
})
.ToPagedListAsync(param);
}
/// <summary>
/// 获取用户信息列表用于选择
/// </summary>
/// <returns></returns>
public async Task<List<ElSelectDto>> SelectList(UserSelectListParam param)
{
return await _userRepository.AsQueryable()
.WhereIF(!string.IsNullOrEmpty(param.Name), it => it.RealName!.Contains(param.Name!) || it.UserName.Contains(param.Name!))
.WhereIF(param.Ids.Any(), it => param.Ids.Contains(it.Id))
.Where(it => it.AccountType != AccountTypeEnum.SuperAdmin)
.Select(it => new ElSelectDto()
{
Label = it.RealName + "(" + it.UserName + ")",
Disabled = it.Status == 0,
Value = it.Id.ToString()
}).ToListAsync();
}
/// <summary>
/// 是否存在用户
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
public async Task<bool> GetExist(string userName)
{
return await _userRepository.IsAnyAsync(it => it.UserName == userName);
}
/// <summary>
/// 获取验证码
/// </summary>
/// <returns></returns>
[AllowAnonymous]
public UserCaptchaDto GetCaptcha()
{
var tuple = CaptchaHelper.GetCaptchaCode();
var image = CaptchaHelper.CreateCaptchaImage(tuple.Item1);
UserCaptchaDto dto = new UserCaptchaDto()
{
Guid = IdGeneratorHelper.Instance.GetGuid(),
Img = Convert.ToBase64String(image)
};
_cache.SetCache(dto.Guid, tuple.Item2, DateTime.Now.AddMinutes(2));
return dto;
}
/// <summary>
/// 用户登录
/// </summary>
/// <returns>可用租户信息</returns>
[AllowAnonymous]
public async Task<dynamic> Login(UserLoginParam param)
{
if (!(int.TryParse(param.CaptchaCode, out int val) && _cache.TryGetCache<int>(param.Captcha, out int cacheVal) && val == cacheVal))
{
_cache.RemoveCache(param.Captcha);
throw Oops.Oh("验证码错误!");
}
_cache.SetCache(param.Captcha, param.UserName, DateTime.Now.AddMinutes(5));
var users = await _userRepository.AsQueryable()
.Where(it => it.UserName == param.UserName)
.Where(it => it.Status == 1)
.ToListAsync();
if (users == null || users.Count < 1)
throw Oops.Oh($"不存在用户【{param.UserName}】!");
List<UserEntity> passOk = new List<UserEntity>(); //密码验证通过的账户信息
foreach (var item in users)
{
if (param!.Password == CryptogramHelper.GMSM4Decrypt(item.Password))
passOk.Add(item);
}
if (passOk.Any())
{
var tenantIds = passOk.Select(it => it.TenantId).ToList();
var tenants = await _tenantRepository.AsQueryable()
.Where(it => tenantIds.Contains(it.Id))
.Select(it => new { it.Id, it.TenantName, it.Status })
.ToListAsync();
if (tenants.All(it => it.Status != 1))
throw Oops.Oh("登录失败,账号已经冻结!");
return tenants;
}
throw Oops.Oh("登录失败,没有相关用户信息!");
}
/// <summary>
/// 用户登录
/// </summary>
/// <returns>可用租户信息</returns>
[AllowAnonymous]
public async Task<dynamic> LoginExtned(UserLoginParam param)
{
if (!(int.TryParse(param.CaptchaCode, out int val) && _cache.TryGetCache<int>(param.Captcha, out int cacheVal) && val == cacheVal))
{
_cache.RemoveCache(param.Captcha);
throw Oops.Oh("验证码错误!");
}
_cache.SetCache(param.Captcha, param.UserName, DateTime.Now.AddMinutes(5));
var users = await _userRepository.AsQueryable()
.Where(it => it.UserName == param.UserName)
.Where(it => it.Status == 1)
.ToListAsync();
if (users == null || users.Count < 1)
throw Oops.Oh($"不存在用户【{param.UserName}】!");
List<UserEntity> passOk = new List<UserEntity>(); //密码验证通过的账户信息
foreach (var item in users)
{
if (param!.Password == CryptogramHelper.GMSM4Decrypt(item.Password))
passOk.Add(item);
}
if (passOk.Any())
{
var tenantIds = passOk.Select(it => it.TenantId).ToList();
var tenants = await _tenantRepository.AsQueryable()
.Where(it => tenantIds.Contains(it.Id))
.Select(it => new { it.Id, it.TenantName, it.Status })
.ToListAsync();
if (tenants.All(it => it.Status != 1))
throw Oops.Oh("登录失败,账号已经冻结!");
return tenants;
}
throw Oops.Oh("登录失败,没有相关用户信息!");
}
/// <summary>
/// 用户租户登录
/// </summary>
[AllowAnonymous]
public async Task<JwtToken> TenantLogin(UserTenantLoginParam param)
{
if (_cache.TryGetCache(param.Captcha, out string? cacheVal) && cacheVal == param.UserName)
{
LogLoginEntity logLoginEntity = new LogLoginEntity()
{
Account = param.UserName,
Browser = NetHelper.Browser,
IpAddress = NetHelper.Ip,
LogStatus = LoginStatusEnum.Failed,
Os = NetHelper.GetOSVersion()
};
_cache.RemoveCache(param.Captcha);
var user = await _userRepository.GetFirstAsync(it => it.UserName == param.UserName);
if (user == null)
{
logLoginEntity.LogStatus = LoginStatusEnum.NoAccount;
logLoginEntity.Message = $"不存在用户【{param.UserName}】!";
await _logLoginRepository.InsertAsync(logLoginEntity);
throw Oops.Oh($"不存在用户【{param.UserName}】!");
}
if (user!.Status != 1)
{
logLoginEntity.LogStatus = LoginStatusEnum.Frozen;
logLoginEntity.Message = $"登录失败,账号已经冻结!";
await _logLoginRepository.InsertAsync(logLoginEntity);
throw Oops.Oh("登录失败,账号已经冻结!");
}
if (param!.Password != CryptogramHelper.GMSM4Decrypt(user.Password))
{
logLoginEntity.LogStatus = LoginStatusEnum.PasswordError;
logLoginEntity.Message = $"密码不正确!";
await _logLoginRepository.InsertAsync(logLoginEntity);
throw Oops.Oh("密码不正确!");
}
var tenant = await _tenantRepository.GetByIdAsync(param.TenantId);
if (tenant.Status == 1)
{
var token = await DoLogin(user.Id);
logLoginEntity.LogStatus = LoginStatusEnum.Success;
await _logLoginRepository.InsertAsync(logLoginEntity);
return token;
}
throw Oops.Oh("无效租户!");
}
else
{
_cache.RemoveCache(param.Captcha);
throw Oops.Oh("登录失败!验证码无效。");
}
}
/// <summary>
/// 用户登录
/// </summary>
/// <returns>可用租户信息</returns>
[AllowAnonymous]
public async Task<JwtToken> RefreshLogin(UserRefreshLoginParam param)
{
var refreshToken = await _refreshTokenRepository.GetFirstAsync(it => it.RefreshToken == param.RefreshToken);
if (refreshToken == null)
throw Oops.Oh("登录过期,请重新登录!");
//refreshToken已经撤销
if (refreshToken.RevokedAt != null && refreshToken.RevokedAt.Value < DateTime.Now)
throw Oops.Oh("登录过期,请重新登录!");
//refreshToken已经过期
if (refreshToken.ExpiresAt < DateTime.Now)
throw Oops.Oh("登录过期,请重新登录!");
var token = await DoLogin(refreshToken.UserId);
var userInfo = await _userRepository.GetByIdAsync(refreshToken.UserId);
LogLoginEntity logLoginEntity = new LogLoginEntity()
{
Account = userInfo!.UserName,
Browser = NetHelper.Browser,
IpAddress = NetHelper.Ip,
LogStatus = LoginStatusEnum.RefreshSuccess,
Os = NetHelper.GetOSVersion(),
Message = param.RefreshToken
};
await _logLoginRepository.InsertAsync(logLoginEntity);
//重置refreshToken有效期
token.RefreshToken = param.RefreshToken;
refreshToken.ExpiresAt = token.RefreshTokenExpiresTime;
await _refreshTokenRepository.UpdateAsync(refreshToken);
return token;
}
/// <summary>
/// 修改密码
/// </summary>
/// <param name="param"></param>
/// <returns></returns>
public async Task ResetPassword(UserChangePasswordParam param)
{
var user = await _userRepository.GetByIdAsync(param.Id);
if (user != null)
{
if (param!.Password == CryptogramHelper.GMSM4Decrypt(user.Password))
{
if (param.NewPassword == param.ConfirmPassword)
{
if (param.Password != param.NewPassword)
{
user.Password = CryptogramHelper.GMSM4Encrypt(param.Password);
await _userRepository.UpdateAsync(user);
}
else
throw Oops.Oh("旧密码不能和新密码一样!");
}
else
throw Oops.Oh("两次输入密码不一致!");
}
else
throw Oops.Oh("密码错误!");
}
else
throw Oops.Oh("用户不存在!");
}
/// <summary>
/// 获取当前用户的基本信息
/// </summary>
/// <returns></returns>
public async Task<dynamic?> GetCurrentUserInfo()
{
var user = this.UserInfo();
var userInfo = await _userRepository.AsQueryable()
.Includes(it => it.Roles)
.Includes(it => it.Org)
.Includes(it => it.Position)
.Where(it => it.Id == user!.UserId)
.FirstAsync();
string roleName = userInfo!.AccountType.GetDescription();
if (userInfo!.Roles != null && userInfo!.Roles.Any())
roleName = string.Join(',', userInfo.Roles.Select(it => it.RoleName));
var avatorInfo = await _fileManager.GetFilePathsByCode(userInfo.Avatar!);
return new
{
userInfo.Id,
userInfo.UserName,
userInfo.TenantId,
userInfo.OrgId,
userInfo.RealName,
userInfo.NickName,
userInfo.AccountType,
Avatar = avatorInfo.FirstOrDefault(),
roleName,
userInfo.Position?.PositionName,
userInfo.PositionId,
userInfo.Remark,
userInfo.LastVisit,
userInfo.Org?.OrgName,
};
}
/// <summary>
/// 获取当前用户的菜单权限信息
/// </summary>
/// <returns></returns>
public async Task<List<MenuEntity>> GetCurrentRoleAuthorizeInfo()
{
var user = this.UserInfo();
//超级管理员
if (user!.IsSuperAdmin)
{
return await _menuRepository.AsQueryable().Where(it => it.Status == 1).OrderBy(it => it.Sort).ToListAsync();
}
else
{
var usercache = _cache.GetCache<UserCache>(user!.CacheKey);
var menuIds = await _menuAuthorizeRepository.AsQueryable()
.Where(it => usercache!.Roles.Contains(it.RoleId))
.Select(it => it.MenuId)
.Distinct().ToListAsync();
return await _menuRepository.AsQueryable()
.Where(it => it.Status == 1)
.Where(it => menuIds.Contains(it.Id))
.OrderBy(it => it.Sort)
.ToListAsync();
}
}
/// <summary>
/// 删除用户
/// </summary>
/// <param name="param"></param>
/// <returns></returns>
[UnitOfWork]
public async Task Delete(BaseIdListParam param)
{
if (param.Ids.Any())
{
//用户标记为删除
await _userRepository.FakeDeleteAsync(it => param.Ids.Contains(it.Id));
//清空 refrshtoken
await _refreshTokenRepository.DeleteAsync(it => param.Ids.Contains(it.UserId));
}
}
/// <summary>
/// 新增
/// </summary>
/// <returns></returns>
[UnitOfWork]
public async Task Add(UserAddParam param)
{
var entity = param.Adapt<UserEntity>();
entity.AccountType = AccountTypeEnum.User;
entity.LoginCount = 0;
entity.Roles = await _roleRepository.GetListAsync(it => param.Roles.Contains(it.Id));
entity.Salt = CryptogramHelper.GenerateRandomString(16);
entity.Password = CryptogramHelper.GMSM4Encrypt("123456");
entity.Avatar = await _fileManager.AddFile(param.AvatarImgId);
await _userRepository.AsSugarClient().InsertNav(entity).Include(it => it.Roles).ExecuteCommandAsync();
}
/// <summary>
/// 更新
/// </summary>
/// <returns></returns>
[UnitOfWork]
public async Task Update([FromBody] UserUpdateParam param)
{
var entity = await _userRepository.GetByIdAsync(param.Id);
param.Adapt(entity);
entity.Roles = await _roleRepository.GetListAsync(it => param.Roles.Contains(it.Id));
entity.Avatar = await _fileManager.UpdateFile(entity.Avatar, param.AvatarImgId);
//更新A表和 关系表
await _userRepository.AsSugarClient().UpdateNav(entity)
.Include(it => it.Roles, new UpdateNavOptions()
{
ManyToManyIsUpdateA = true
}).ExecuteCommandAsync();
}
/// <summary>
/// 获取明细
/// </summary>
/// <param name="param"></param>
/// <returns></returns>
public async Task<UserDetailDto> GetDetail(BaseIdParam param)
{
var entity = await _userRepository.AsQueryable()
.Includes(it => it.Roles)
.Where(it => it.Id == param.Id).FirstAsync();
var dto = entity.Adapt<UserDetailDto>();
dto.Roles = entity.Roles!.Select(it => it.Id).ToList();
return dto;
}
/// <summary>
/// 导出
/// </summary>
/// <returns></returns>
public async Task<IActionResult> Export(UserPagedParam param)
{
var dtos = await BuildQuery(param)
.Select(it => new UserExportDto
{
Id = it.Id.SelectAll(),
Roles = SqlFunc.Subqueryable<UserBelongRoleEntity>()
.LeftJoin<RoleEntity>((ur, ro) => ur.RoleId == ro.Id)
.Where((ur, ro) => ur.UserId == it.Id)
.SelectStringJoin((ur, ro) => ro.RoleName, ","),
Position = it.Position!.PositionName,
Org = it.Org!.OrgName
})
.ToListAsync();
return await ExportFile(dtos, "用户信息.xlsx");
}
/// <summary>
/// 导入
/// </summary>
/// <returns></returns>
[UnitOfWork]
public async Task<List<ImportErrorDto>> ImportAsync(IFormCollection fileList)
{
if (fileList.Files.Count == 0)
throw Oops.Oh("没有可导入的文件!");
List<ImportErrorDto> errors = new List<ImportErrorDto>();
var imports = await ImportFile<UserImportParam>(fileList.Files[0], errors);
if (errors.Count > 0)
return errors;
var positionDict = (await _positionRepository.GetListAsync()).ToDictionary(it => it.PositionName, it => it);
var roleDict = (await _roleRepository.GetListAsync()).ToDictionary(it => it.RoleName, it => it);
Dictionary<string, OrgEntity> orgDict = new Dictionary<string, OrgEntity>();
List<UserEntity> users = new List<UserEntity>();
int headerIndex = 2 + 1;
StringBuilder msgBuilder = new StringBuilder();
for (int i = 0; i < imports.Data.Count; i++)
{
msgBuilder.Clear();
var item = imports.Data.ElementAt(i);
UserEntity userEntity = new UserEntity
{
UserName = item.UserName,
RealName = item.RealName,
NickName = item.RealName,
Mobile = item.Mobile,
AccountType = AccountTypeEnum.User,
Email = item.Email,
Gender = item.Gender == "男" ? 1 : 0,
};
if (!string.IsNullOrEmpty(item.Position))
{
if (positionDict.ContainsKey(item.Position!))
userEntity.Position = positionDict[item.Position];
else
msgBuilder.Append($";找不到职位:【{item.Position}】!");
}
if (!string.IsNullOrEmpty(item.Birthday))
{
if (DateTime.TryParse(item.Birthday, out DateTime r))
userEntity.Birthday = r;
else if (double.TryParse(item.Birthday, out double d))
userEntity.Birthday = DateTime.FromOADate(d);
else
msgBuilder.Append(";出生日期格式错误!");
}
string[] roles = item.Roles!.Split(',');
userEntity.Roles = new List<RoleEntity>();
foreach (var r in roles)
{
if (roleDict.ContainsKey(r))
userEntity.Roles.Add(roleDict[r]);
else
msgBuilder.Append($";没有找到角色【{r}】!");
}
if (orgDict.ContainsKey(item.Org!))
userEntity.Org = orgDict[item.Org!];
else
{
var org = await _orgManager.GetOrgsByPath(item.Org!);
if (org.Count == 1)
{
userEntity.Org = org.First();
orgDict.Add(item.Org!, org.First());
}
else if (org.Count == 0)
msgBuilder.Append($";没有找到机构【{item.Org}】!");
else
msgBuilder.Append($";找到多个机构【{item.Org}】!");
}
if (msgBuilder.Length > 0)
errors.Add(new ImportErrorDto { Index = headerIndex + i, Errors = msgBuilder.ToString().Substring(1) });
else
users.Add(userEntity);
}
//没有错误信息再进行入库
if (!errors.Any())
await _userRepository.AsSugarClient().InsertNav(users).Include(it => it.Org).Include(it => it.Position).Include(it => it.Roles).ExecuteCommandAsync();
return errors;
}
#region 私有方法
private ISugarQueryable<UserEntity> BuildQuery(UserPagedParam param)
{
return _userRepository.AsQueryable()
.WhereIF(!string.IsNullOrEmpty(param.Mobile), it => it.Mobile!.Contains(param.Mobile!))
.WhereIF(!string.IsNullOrEmpty(param.UserName), it => it.UserName.Contains(param.UserName!))
.WhereIF(param.Status > 0, it => it.Status == param.Status)
.WhereIF(param.OrgId > 0, it => it.OrgId == param.OrgId)
.Where(it => it.AccountType != AccountTypeEnum.SuperAdmin);
}
/// <summary>
/// 生成Token信息
/// </summary>
/// <param name="userId">用户id</param>
/// <returns>JwtToken</returns>
private async Task<JwtToken> DoLogin(long userId)
{
UserEntity? userInfo = await _userRepository.AsQueryable()
.Includes(it => it.Roles)
.Includes(it => it.Org)
.Includes(it => it.Position)
.Where(it => it.Id == userId)
.FirstAsync();
userInfo.LoginCount += 1;
if (userInfo.FirstVisit == null)
userInfo.FirstVisit = DateTime.Now;
userInfo.PreviousVisit = userInfo.LastVisit;
userInfo.LastVisit = DateTime.Now;
await _userRepository.UpdateAsync(userInfo);
//生成 jwtToken
JwtHelper jwtHelper = new JwtHelper();
JwtToken? token = jwtHelper.CreateToken(new JwtUserInfo()
{
UserName = userInfo!.UserName,
AccountType = (int)userInfo.AccountType,
NickName = userInfo.NickName,
RealName = userInfo.RealName,
TenantId = userInfo.TenantId,
OrgId = userInfo.OrgId,
UserId = userInfo.Id,
});
//增加对应用户的refreshtoken
await _refreshTokenRepository.InsertAsync(new RefreshTokenEntity()
{
ExpiresAt = token.RefreshTokenExpiresTime,
IssuedAt = token.IssuedAt,
RevokedAt = null,
RefreshToken = token.RefreshToken!,
UserId = userInfo.Id
});
//缓存用户信息
await _userManager.InitCache(userInfo, token.TokenExpiresTime);
return token;
}
#endregion
}
}