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.

384 lines
14 KiB
C#

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;
2 years ago
using Furion.RemoteRequest.Extensions;
using Newtonsoft.Json.Linq;
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 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;
public AuthService(SqlSugarRepository<SysUser> sysUserRep, SqlSugarRepository<SysLogVis> sysLogVisRep, SqlSugarRepository<SysTenant> sysTenantRep,
IHttpContextAccessor httpContextAccessor,
ISysEmpService sysEmpService, ISysRoleService sysRoleService, ISysMenuService sysMenuService,
ISysAppService sysAppService, IClickWordCaptcha captchaHandle, ISysConfigService sysConfigService,
IEventPublisher eventPublisher
)
{
_sysUserRep = sysUserRep;
_sysLogVisRep = sysLogVisRep;
_sysTenantRep = sysTenantRep;
_httpContextAccessor = httpContextAccessor;
_sysEmpService = sysEmpService;
_sysRoleService = sysRoleService;
_sysMenuService = sysMenuService;
_sysAppService = sysAppService;
_captchaHandle = captchaHandle;
_sysConfigService = sysConfigService;
_eventPublisher = eventPublisher;
}
/// <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.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.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));
}
2 years ago
/// <summary>
/// 使用跳转code登录
/// </summary>
/// <param name="code"></param>
/// <returns></returns>
[AllowAnonymous]
[HttpPost("/loginWithCode")]
public async Task<string> LoginWithCode(string code)
{
var djyAuthUrl = App.GetConfig<string>("DjyAuthUrl");
if (!djyAuthUrl.EndsWith("/"))
{
djyAuthUrl += "/";
}
var loginUrl = djyAuthUrl + "user/login";
var infoUrl = djyAuthUrl + "user/info";
//跳转登录
var result = await loginUrl
.SetBody(new { code }, "application/json")
.PostAsStringAsync();
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}");
//获取登录信息
result = await infoUrl
.SetHeaders(headers)
.GetAsStringAsync();
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.FirstOrDefault(x => x.CompId == compId);
if (tenant == null)
{
throw Oops.Bah($"{comname}不存在,请先完成公司认证!");
}
var user = _sysUserRep.FirstOrDefault(u => u.DjyUserId == userId);
if (tenant == null)
2 years ago
{
throw Oops.Bah($"{showname}不存在,请先加入公司{comname}");
2 years ago
}
return await GetLoginToken(user, tenant);
2 years ago
2 years ago
}
else
{
throw Oops.Bah(jRtn.GetStringValue("message"));
}
}
else
{
throw Oops.Bah(jRtn.GetStringValue("message"));
}
}
}