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.

407 lines
15 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;
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 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 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
)
{
_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;
}
/// <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 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);
// 生成刷新Token令牌
var refreshToken = JWTEncryption.GenerateRefreshToken(accessToken, 30);
// 设置刷新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>
/// <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);
// 生成刷新Token令牌
var refreshToken = JWTEncryption.GenerateRefreshToken(accessToken, 30);
// 设置刷新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; });
}
// 增加登录日志
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"));
}
}
}