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.
BookingHeChuan/Myshipping.Core/Service/Auth/AuthService.cs

471 lines
18 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 Furion;
using Furion.DataEncryption;
using Furion.DependencyInjection;
using Furion.DynamicApiController;
using Furion.EventBus;
using Furion.FriendlyException;
using Myshipping.Core.Entity;
using Mapster;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using UAParser;
using Furion.RemoteRequest.Extensions;
using Newtonsoft.Json.Linq;
using Microsoft.Extensions.Logging;
using Myshipping.Core.Const;
namespace Myshipping.Core.Service;
/// <summary>
/// 登录授权相关服务
/// </summary>
[ApiDescriptionSettings(Name = "Auth", Order = 160)]
public class AuthService : IAuthService, IDynamicApiController, ITransient
{
private readonly SqlSugarRepository<SysUser> _sysUserRep; // 用户表仓储
private readonly SqlSugarRepository<SysLogVis> _sysLogVisRep;
private readonly SqlSugarRepository<SysTenant> _sysTenantRep;
private readonly SqlSugarRepository<SysUserAccountRelation> _sysUserAccountRelation;
private readonly ISysCacheService _cache;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ISysEmpService _sysEmpService; // 系统员工服务
private readonly ISysRoleService _sysRoleService; // 系统角色服务
private readonly ISysMenuService _sysMenuService; // 系统菜单服务
private readonly ISysAppService _sysAppService; // 系统应用服务
private readonly IClickWordCaptcha _captchaHandle; // 验证码服务
private readonly ISysConfigService _sysConfigService; // 验证码服务
private readonly IDjyTenantParamService _djyTenantParamService; // 租户参数服务
private readonly IEventPublisher _eventPublisher;
private readonly ILogger<AuthService> _logger;
public AuthService(SqlSugarRepository<SysUser> sysUserRep, SqlSugarRepository<SysLogVis> sysLogVisRep, SqlSugarRepository<SysTenant> sysTenantRep,
IHttpContextAccessor httpContextAccessor, ISysCacheService cache,
ISysEmpService sysEmpService, ISysRoleService sysRoleService, ISysMenuService sysMenuService,
ISysAppService sysAppService, IClickWordCaptcha captchaHandle, ISysConfigService sysConfigService,
IEventPublisher eventPublisher,
ILogger<AuthService> logger, IDjyTenantParamService djyTenantParamService, SqlSugarRepository<SysUserAccountRelation> sysUserAccountRelation)
{
_sysUserRep = sysUserRep;
_sysLogVisRep = sysLogVisRep;
_sysTenantRep = sysTenantRep;
_httpContextAccessor = httpContextAccessor;
_sysEmpService = sysEmpService;
_sysRoleService = sysRoleService;
_sysMenuService = sysMenuService;
_sysAppService = sysAppService;
_captchaHandle = captchaHandle;
_sysConfigService = sysConfigService;
_eventPublisher = eventPublisher;
_logger = logger;
_cache = cache;
_djyTenantParamService = djyTenantParamService;
_sysUserAccountRelation = sysUserAccountRelation;
}
/// <summary>
/// 用户登录
/// </summary>
/// <param name="input"></param>
/// <remarks>默认用户名/密码admin/admin</remarks>
/// <returns></returns>
[HttpPost("/login")]
[AllowAnonymous]
public async Task<string> LoginAsync([Required] LoginInput input)
{
var keyDES = App.GetOptions<EncryptKeyOptions>().DES;
// 获取加密后的密码
var encryptPassword = DESCEncryption.Encrypt(input.Password, keyDES);
// 判断用户名和密码是否正确(排除全局多租户过滤器)Filter(null,true)
var user = _sysUserRep.AsQueryable().Filter(null, true)
.WhereIF(input.TenantId.HasValue && input.TenantId.Value > 0, m => m.TenantId == input.TenantId).First(u =>
u.Account.Equals(input.Account) && u.Password.Equals(encryptPassword) && !u.IsDeleted);
_ = user ?? throw Oops.Oh(ErrorCode.D1000);
// 验证账号是否被冻结
if (user.Status == CommonStatus.DISABLE)
throw Oops.Oh(ErrorCode.D1017);
//是否需要修改密码
var needModifyPassword = true;
if (user.LastModifyPassword.HasValue && (DateTime.Now - user.LastModifyPassword.Value).TotalDays < 90)
{
needModifyPassword = false;
}
if (needModifyPassword)
{
_httpContextAccessor.HttpContext.Response.Headers["need-modify-password"] = "1";
}
//获取对应租户
var tenant = _sysTenantRep.Single(user.TenantId);
// 生成Token令牌
return await GetLoginToken(user, tenant);
}
private async Task<string> GetLoginToken(SysUser user, SysTenant tenant)
{
// 生成Token令牌
//var accessToken = await _jwtBearerManager.CreateTokenAdmin(user);
var accessToken = JWTEncryption.Encrypt(new Dictionary<string, object>
{
{ClaimConst.CLAINM_USERID, user.Id},
{ClaimConst.TENANT_ID, user.TenantId},
{ClaimConst.CLAINM_ACCOUNT, user.Account},
{ClaimConst.CLAINM_NAME, user.Name},
{ClaimConst.CLAINM_SUPERADMIN, user.AdminType},
{ ClaimConst.CLAINM_TENANT_TYPE, tenant.TenantType },
{ ClaimConst.TENANT_NAME, tenant.Name },
{ ClaimConst.DjyCompanyId, tenant.CompId == null ? string.Empty : tenant.CompId },
{ ClaimConst.DjyUserId, user.DjyUserId },
{ ClaimConst.Tel, user.Tel },
{ ClaimConst.Phone, user.Phone },
{ ClaimConst.Email, user.Email },
});
// 设置Swagger自动登录
_httpContextAccessor.HttpContext.SigninToSwagger(accessToken);
var jwtSettinng = App.GetConfig<JWTSettingsOptions>("JWTSettings");
// 生成刷新Token令牌
var refreshToken = JWTEncryption.GenerateRefreshToken(accessToken, jwtSettinng.RefreshTokenExpired);
// 设置刷新Token令牌
_httpContextAccessor.HttpContext.Response.Headers["x-access-token"] = refreshToken;
_logger.LogInformation($"{user.Account} 登录颁发刷新token有效期{jwtSettinng.RefreshTokenExpired} 分钟");
var httpContext = App.HttpContext;
await _eventPublisher.PublishAsync(new ChannelEventSource("Update:UserLoginInfo",
new SysUser { Id = user.Id, LastLoginIp = httpContext.GetLocalIpAddressToIPv4(), LastLoginTime = DateTime.Now }));
return accessToken;
}
/// <summary>
/// 模拟租户登录
/// </summary>
/// <param name="input"></param>
/// <remarks>默认用户名/密码admin/admin</remarks>
/// <returns></returns>
[HttpPost("/simulationTenantLogin")]
public async Task<string> SimulationLoginAsync([Required] LoginInput input)
{
SysTenant tenant = null;
if (input.TenantId.HasValue && input.TenantId.Value > 0)
{
tenant = _sysTenantRep.Single(input.TenantId.Value);
if (tenant == null || (tenant.IsDeleted && tenant.TenantType != TenantTypeEnum.SYSTEM))
throw Oops.Oh("租户不存在");
}
// 判断用户名和密码是否正确(排除全局多租户过滤器)Filter(null,true)
var user = _sysUserRep.AsQueryable().Filter(null, true)
.Where(m => m.TenantId == input.TenantId)
.First(u => u.Account.Equals(input.Account) && !u.IsDeleted);
_ = user ?? throw Oops.Oh(ErrorCode.D1000);
// 验证账号是否被冻结
if (user.Status == CommonStatus.DISABLE)
throw Oops.Oh(ErrorCode.D1017);
// 生成Token令牌
//var accessToken = await _jwtBearerManager.CreateTokenAdmin(user);
var accessToken = JWTEncryption.Encrypt(new Dictionary<string, object>
{
{ ClaimConst.CLAINM_USERID, user.Id },
{ ClaimConst.TENANT_ID, user.TenantId },
{ ClaimConst.CLAINM_ACCOUNT, user.Account },
{ ClaimConst.CLAINM_NAME, user.Name },
{ ClaimConst.CLAINM_SUPERADMIN, user.AdminType },
{ ClaimConst.CLAINM_TENANT_TYPE, tenant.TenantType },
{ ClaimConst.TENANT_NAME, tenant.Name },
{ ClaimConst.DjyCompanyId, tenant.CompId == null ? string.Empty : tenant.CompId },
{ ClaimConst.DjyUserId, user.DjyUserId },
{ ClaimConst.Tel, user.Tel },
{ ClaimConst.Phone, user.Phone },
{ ClaimConst.Email, user.Email },
});
// 设置Swagger自动登录
_httpContextAccessor.HttpContext.SigninToSwagger(accessToken);
var jwtSettinng = App.GetConfig<JWTSettingsOptions>("JWTSettingsOptions");
// 生成刷新Token令牌
var refreshToken = JWTEncryption.GenerateRefreshToken(accessToken, jwtSettinng.RefreshTokenExpired);
// 设置刷新Token令牌
_httpContextAccessor.HttpContext.Response.Headers["x-access-token"] = refreshToken;
var httpContext = App.HttpContext;
await _eventPublisher.PublishAsync(new ChannelEventSource("Update:UserLoginInfo",
new SysUser { Id = user.Id, LastLoginIp = httpContext.GetLocalIpAddressToIPv4(), LastLoginTime = DateTime.Now }));
return accessToken;
}
/// <summary>
/// 获取当前登录用户信息
/// </summary>
/// <returns></returns>
[HttpGet("getLoginUser")]
public async Task<LoginOutput> GetLoginUserAsync()
{
var user = _sysUserRep.Single(UserManager.UserId);
var userId = user.Id;
var httpContext = App.GetService<IHttpContextAccessor>().HttpContext;
var loginOutput = user.Adapt<LoginOutput>();
loginOutput.LastLoginTime = user.LastLoginTime = DateTime.Now;
var ip = HttpNewUtil.Ip;
loginOutput.LastLoginIp = user.LastLoginIp =
string.IsNullOrEmpty(user.LastLoginIp) ? HttpNewUtil.Ip : ip;
var clent = Parser.GetDefault().Parse(httpContext.Request.Headers["User-Agent"]);
loginOutput.LastLoginBrowser = clent.UA.Family + clent.UA.Major;
loginOutput.LastLoginOs = clent.OS.Family + clent.OS.Major;
// 员工信息
loginOutput.LoginEmpInfo = await _sysEmpService.GetEmpInfo(userId);
// 角色信息
loginOutput.Roles = await _sysRoleService.GetUserRoleList(userId);
// 权限信息
loginOutput.Permissions = await _sysMenuService.GetLoginPermissionList(userId);
// 数据范围信息(机构Id集合)
loginOutput.DataScopes = await DataFilterExtensions.GetDataScopeIdList();
// 具备应用信息(多系统,默认激活一个,可根据系统切换菜单),返回的结果中第一个为激活的系统
loginOutput.Apps = await _sysAppService.GetLoginApps(userId);
// 菜单信息
if (loginOutput.Apps.Count > 0)
{
var defaultActiveAppCode = loginOutput.Apps.FirstOrDefault().Code;
loginOutput.Menus = await _sysMenuService.GetLoginMenusAntDesign(userId, "");
loginOutput.Menus.ForEach(item => { item.Hidden = item.Application != defaultActiveAppCode; });
}
// 返回前端需要使用的租户参数
var paraCodeArr = new string[] { TenantParamCode.ENABLE_SLOT_ABILITY, TenantParamCode.ENABLE_FEE_ABILITY, TenantParamCode.VESSEL_FROM_CONFIG_ONLY };
loginOutput.TenantParams = await _djyTenantParamService.GetParaCodeWithValue(paraCodeArr);
//多账号关联
var accRela = await _sysUserAccountRelation.AsQueryable().Filter(null, true).Where(x => x.UserId == userId && x.IsDeleted == false).FirstAsync();
if (accRela != null)
{
var accRelaList = await _sysUserAccountRelation.AsQueryable().Filter(null, true).Where(x => x.GroupId == accRela.GroupId && x.IsDeleted == false && x.UserId != userId).ToListAsync();
loginOutput.UserAccountRelation = accRelaList.Adapt<List<UserAccountRelationDto>>();
}
// 增加登录日志
await _eventPublisher.PublishAsync(new ChannelEventSource("Create:VisLog",
new SysLogVis
{
Name = user.Name,
Success = YesOrNot.Y,
Message = "登录成功",
Ip = loginOutput.LastLoginIp,
Browser = loginOutput.LastLoginBrowser,
Os = loginOutput.LastLoginOs,
VisType = LoginType.LOGIN,
VisTime = loginOutput.LastLoginTime,
Account = user.Name
}));
return loginOutput;
}
/// <summary>
/// 退出
/// </summary>
/// <returns></returns>
[HttpGet("logout")]
public async Task LogoutAsync()
{
_httpContextAccessor.HttpContext.SignoutToSwagger();
var ip = HttpNewUtil.Ip;
var user = _sysUserRep.Single(UserManager.UserId);
// 增加退出日志
await _eventPublisher.PublishAsync(new ChannelEventSource("Create:VisLog",
new SysLogVis
{
Name = user.Name,
Success = YesOrNot.Y,
Message = "退出成功",
VisType = LoginType.LOGOUT,
VisTime = DateTime.Now,
Account = user.Account,
Ip = ip
}));
await Task.CompletedTask;
}
/// <summary>
/// 获取验证码开关
/// </summary>
/// <returns></returns>
[HttpGet("getCaptchaOpen")]
[AllowAnonymous]
public async Task<bool> GetCaptchaOpen()
{
return await _sysConfigService.GetCaptchaOpenFlag();
}
/// <summary>
/// 获取验证码(默认点选模式)
/// </summary>
/// <returns></returns>
[HttpPost("captcha/get")]
[AllowAnonymous]
[NonUnify]
public async Task<dynamic> GetCaptcha()
{
// 图片大小要与前端保持一致(坐标范围)
return await Task.FromResult(_captchaHandle.CreateCaptchaImage(_captchaHandle.RandomCode(6), 310, 155));
}
/// <summary>
/// 校验验证码
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("captcha/check")]
[AllowAnonymous]
[NonUnify]
public async Task<dynamic> VerificationCode(ClickWordCaptchaInput input)
{
return await Task.FromResult(_captchaHandle.CheckCode(input));
}
/// <summary>
/// 使用跳转code登录
/// </summary>
/// <param name="code"></param>
/// <returns></returns>
[AllowAnonymous]
[HttpPost("/loginWithCode")]
public async Task<string> LoginWithCode(string code)
{
var cfg = _cache.GetAllSysConfig().Result.FirstOrDefault(x => x.Code == "DjyAuthUrl");
if (cfg == null || string.IsNullOrEmpty(cfg.Value))
{
throw Oops.Bah("未找到大简云授权登录URL配置请联系管理员");
}
var djyAuthUrl = cfg.Value;
if (!djyAuthUrl.EndsWith("/"))
{
djyAuthUrl += "/";
}
var loginUrl = djyAuthUrl + "user/login";
var infoUrl = djyAuthUrl + "user/info";
_logger.LogInformation($"统一登录:{loginUrl}");
//跳转登录
var result = await loginUrl
.SetBody(new { code }, "application/json")
.PostAsStringAsync();
_logger.LogInformation($"登录返回:{result}");
var jRtn = JObject.Parse(result);
if (jRtn.GetIntValue("code") == 200)
{
var jData = jRtn.GetJObjectValue("data");
var token = jData.GetStringValue("token");
var headers = new Dictionary<string, object>();
headers.Add("Authorization", $"Bearer {token}");
_logger.LogInformation($"获取登录信息:{infoUrl}");
//获取登录信息
result = await infoUrl
.SetHeaders(headers)
.GetAsStringAsync();
_logger.LogInformation($"登录信息返回:{result}");
jRtn = JObject.Parse(result);
if (jRtn.GetIntValue("code") == 200)
{
jData = jRtn.GetJObjectValue("data");
var compId = jData.GetStringValue("compId");
var comname = jData.GetStringValue("comname");
var userId = jData.GetStringValue("gid");
var showname = jData.GetStringValue("showname");
var tenant = _sysTenantRep.AsQueryable().Filter(null, true).First(x => x.CompId == compId);
if (tenant == null)
{
throw Oops.Bah($"{comname}不存在,请先完成公司认证!");
}
var user = _sysUserRep.AsQueryable().Filter(null, true).First(u => u.DjyUserId == userId);
if (user == null)
{
throw Oops.Bah($"{showname}不存在,请先加入公司{comname}");
}
return await GetLoginToken(user, tenant);
}
else
{
throw Oops.Bah(jRtn.GetStringValue("message"));
}
}
else
{
throw Oops.Bah(jRtn.GetStringValue("message"));
}
}
/// <summary>
/// 切换登录账号
/// </summary>
/// <param name="changeTo"></param>
/// <returns></returns>
[HttpPost("/ChangeLogin")]
public async Task<string> ChangeLogin(long changeTo)
{
var accRela = await _sysUserAccountRelation.AsQueryable().Filter(null, true).FirstAsync(x => x.Id == changeTo && x.IsDeleted == false);
if (accRela == null)
{
throw Oops.Bah("未找到账号关联数据");
}
//判断当前用户的账号和要切换到的账号是否在一个关联配置中
var cc = await _sysUserAccountRelation.AsQueryable().Filter(null, true).CountAsync(x => x.GroupId == accRela.GroupId && x.UserId == UserManager.UserId && x.IsDeleted == false);
if (cc == 0)
{
throw Oops.Bah("无权切换到此账号");
}
var user = await _sysUserRep.AsQueryable().Filter(null, true).FirstAsync(u => u.Id == accRela.UserId);
//获取对应租户
var tenant = _sysTenantRep.Single(user.TenantId);
// 生成Token令牌
return await GetLoginToken(user, tenant);
}
}