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; using Newtonsoft.Json; using NPOI.POIFS.Crypt.Dsig; 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 SqlSugarRepository _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 _logger; private readonly SqlSugarRepository _pingtaiUser; // 平台用户 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, SqlSugarRepository 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; } /// /// 用户登录 /// /// /// 默认用户名/密码: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, TenantParamCode.ENABLE_FEE_ABILITY, TenantParamCode.VESSEL_FROM_CONFIG_ONLY, TenantParamCode.BOOKING_CHANNEL_SELECT_SHOW, TenantParamCode.BC_TASK_CREATE_ORDER,TenantParamCode.BOOKING_SUB_TENANT,TenantParamCode.DESCRIP_CAN_CHINESE, TenantParamCode.BOOKING_LIST_SHOW_CANCEL,TenantParamCode.ERP_CODE_CAN_EDIT}; 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>(); } // 增加登录日志 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")); } } /// /// 切换登录账号 /// /// /// [HttpPost("/ChangeLogin")] public async Task 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); } /// /// 获取跨站点登录授权key /// /// /// 默认用户名/密码:admin/admin /// [HttpPost("/GetAuthorizationKey")] [AllowAnonymous, ApiUser(ApiCode = "GetAuthorizationKey")] public async Task GetAuthorizationKey([Required] GetAuthorizationKeyInput input) { //验证用户ID有效性 var user = _sysUserRep.AsQueryable().Filter(null, true).Where(u => u.DjyUserId == input.UserId).First(); if (user is null) { throw Oops.Bah("用户Id不存在"); } //将key写入到redis 并指定五秒过期 string key = Guid.NewGuid().ToString(); await _cache.SetTimeoutAsync(key, JsonConvert.SerializeObject(input), TimeSpan.FromSeconds(50)); //将key返回 return key; } /// /// 授权key换取token /// /// /// 默认用户名/密码:admin/admin /// [HttpPost("/GetTokenByAuthorizationKey")] [AllowAnonymous] public async Task GetTokenByAuthorizationKey([Required] GetTokenByAuthorizationKeyInput input) { var _repPingTaiUser = App.GetService>(); //判断key有效性 if (!_cache.Exists(input.Key)) { throw Oops.Bah("授权码无效"); } //获取缓存数据 GetAuthorizationKeyInput data = JsonConvert.DeserializeObject(_cache.Get(input.Key)); var datainfo = _sysUserRep.AsQueryable().Filter(null, true).First(u => u.DjyUserId == data.UserId); //如果类型为公司,将公司下所有用户关联出 if (data.AuthorityType == 2) { data.Authority = _repPingTaiUser.Where(t => data.Authority.Contains(t.CompId)).Select(t => t.GID).ToList(); } await _cache.SetAsync(datainfo.DjyUserId + "Authorization", data.Authority); //获取对应租户 var tenant = _sysTenantRep.Single(datainfo.TenantId); //颁发token return await GetLoginToken(datainfo, tenant); } }