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; /// /// 登录授权相关服务 /// [ApiDescriptionSettings(Name = "Auth", Order = 160)] public class AuthService : IAuthService, IDynamicApiController, ITransient { private readonly SqlSugarRepository _sysUserRep; // 用户表仓储 private readonly SqlSugarRepository _sysLogVisRep; private readonly SqlSugarRepository _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 IDjyTenantParamService _djyTenantParamService; // 租户参数服务 private readonly IEventPublisher _eventPublisher; private readonly ILogger _logger; public AuthService(SqlSugarRepository sysUserRep, SqlSugarRepository sysLogVisRep, SqlSugarRepository sysTenantRep, IHttpContextAccessor httpContextAccessor, ISysCacheService cache, ISysEmpService sysEmpService, ISysRoleService sysRoleService, ISysMenuService sysMenuService, ISysAppService sysAppService, IClickWordCaptcha captchaHandle, ISysConfigService sysConfigService, IEventPublisher eventPublisher, ILogger logger, IDjyTenantParamService djyTenantParamService) { _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; } /// /// 用户登录 /// /// /// 默认用户名/密码:admin/admin /// [HttpPost("/login")] [AllowAnonymous] public async Task LoginAsync([Required] LoginInput input) { var keyDES = App.GetOptions().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 GetLoginToken(SysUser user, SysTenant tenant) { // 生成Token令牌 //var accessToken = await _jwtBearerManager.CreateTokenAdmin(user); var accessToken = JWTEncryption.Encrypt(new Dictionary { {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("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; } /// /// 模拟租户登录 /// /// /// 默认用户名/密码:admin/admin /// [HttpPost("/simulationTenantLogin")] public async Task 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 { { 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"); // 生成刷新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; } /// /// 获取当前登录用户信息 /// /// [HttpGet("getLoginUser")] public async Task GetLoginUserAsync() { var user = _sysUserRep.Single(UserManager.UserId); var userId = user.Id; var httpContext = App.GetService().HttpContext; var loginOutput = user.Adapt(); 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 }; loginOutput.TenantParams = await _djyTenantParamService.GetParaCodeWithValue(paraCodeArr); // 增加登录日志 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; } /// /// 退出 /// /// [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; } /// /// 获取验证码开关 /// /// [HttpGet("getCaptchaOpen")] [AllowAnonymous] public async Task GetCaptchaOpen() { return await _sysConfigService.GetCaptchaOpenFlag(); } /// /// 获取验证码(默认点选模式) /// /// [HttpPost("captcha/get")] [AllowAnonymous] [NonUnify] public async Task GetCaptcha() { // 图片大小要与前端保持一致(坐标范围) return await Task.FromResult(_captchaHandle.CreateCaptchaImage(_captchaHandle.RandomCode(6), 310, 155)); } /// /// 校验验证码 /// /// /// [HttpPost("captcha/check")] [AllowAnonymous] [NonUnify] public async Task VerificationCode(ClickWordCaptchaInput input) { return await Task.FromResult(_captchaHandle.CheckCode(input)); } /// /// 使用跳转code登录 /// /// /// [AllowAnonymous] [HttpPost("/loginWithCode")] public async Task 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(); 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")); } } }