diff --git a/IRaCIS.Core.API/Progranm.cs b/IRaCIS.Core.API/Progranm.cs index 2b7576d98..eb197ff4b 100644 --- a/IRaCIS.Core.API/Progranm.cs +++ b/IRaCIS.Core.API/Progranm.cs @@ -4,30 +4,16 @@ using IRaCIS.Core.API.HostService; using IRaCIS.Core.Application.BusinessFilter; using IRaCIS.Core.Application.BusinessFilter.LegacyController.Database.Api; using IRaCIS.Core.Application.Filter; -using IRaCIS.Core.Application.MassTransit.Consumer; using IRaCIS.Core.Application.Service; -using IRaCIS.Core.Application.Service.BusinessFilter; using IRaCIS.Core.Infra.EFCore; using IRaCIS.Core.Infrastructure.Extention; using MassTransit; using MassTransit.NewIdProviders; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.SignalR; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Localization; using Newtonsoft.Json; using Serilog; -using System; -using System.Collections.Generic; using System.Globalization; -using System.IO; -using System.Reflection; using System.Runtime.InteropServices; +using UAParser; AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true); AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", true); @@ -95,6 +81,8 @@ builder.Services.AddSingleton(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); +builder.Services.AddSingleton(_ => Parser.GetDefault()); + //minimal api 异常处理 builder.Services.AddExceptionHandler(); //builder.Services.AddProblemDetails(); diff --git a/IRaCIS.Core.API/_PipelineExtensions/Serilog/SerilogConfig.cs b/IRaCIS.Core.API/_PipelineExtensions/Serilog/SerilogConfig.cs index 7b35b71df..e48cfea7f 100644 --- a/IRaCIS.Core.API/_PipelineExtensions/Serilog/SerilogConfig.cs +++ b/IRaCIS.Core.API/_PipelineExtensions/Serilog/SerilogConfig.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Serilog; using System.Linq; +using UAParser; namespace IRaCIS.Core.API { @@ -17,11 +18,14 @@ namespace IRaCIS.Core.API { app.UseMiddleware(); + // 从 DI 容器中获取 Parser 实例 + var uaParser = app.ApplicationServices.GetRequiredService(); + app.UseSerilogRequestLogging(opts => { - opts.MessageTemplate = "{FullName} {UserType} {UserIp} {Host} {RequestMethod} {RequestPath} {RequestBody} responded {StatusCode} in {Elapsed:0.0000} ms"; + opts.MessageTemplate = "{FullName} {UserType} {UserIp} {Host} {RequestMethod} {RequestPath}{QueryString} {RequestBody} {OS} {Browser} {DeviceType} responded {StatusCode} in {Elapsed:0.0000} ms"; opts.EnrichDiagnosticContext = (diagnosticContext, httpContext) => { @@ -33,10 +37,9 @@ namespace IRaCIS.Core.API // Only set it if available. You're not sending sensitive data in a querystring right?! - if (request.QueryString.HasValue) - { - diagnosticContext.Set("QueryString", request.QueryString.Value); - } + + diagnosticContext.Set("QueryString", request.QueryString.Value ?? ""); + diagnosticContext.Set("FullName", httpContext?.User?.FindFirst(JwtIRaCISClaimType.FullName)?.Value); @@ -52,6 +55,38 @@ namespace IRaCIS.Core.API diagnosticContext.Set("UserIp", clientIp); + + // ==================== 新增:解析 User-Agent ==================== + try + { + var userAgent = httpContext.Request.Headers["User-Agent"].ToString(); + + if (!string.IsNullOrEmpty(userAgent)) + { + var clientInfo = uaParser.Parse(userAgent); + + diagnosticContext.Set("OS", clientInfo.OS.ToString()); + diagnosticContext.Set("Browser", clientInfo.UA.ToString()); + //diagnosticContext.Set("DeviceType", clientInfo.Device.ToString()); + diagnosticContext.Set("DeviceType", userAgent.Contains("Mobile") ? "Mobile": "Desktop"); + + } + else + { + diagnosticContext.Set("OS", "Unknown"); + diagnosticContext.Set("Browser", "Unknown"); + diagnosticContext.Set("DeviceType", "Unknown"); + } + } + catch (Exception ex) + { + // 解析失败时记录异常,但不要影响主流程 + diagnosticContext.Set("OS", "ParseError"); + diagnosticContext.Set("Browser", "ParseError"); + diagnosticContext.Set("DeviceType", "ParseError"); + } + // ============================================================== + #region 非必要不记录 //diagnosticContext.Set("Protocol", request.Protocol); //diagnosticContext.Set("Scheme", request.Scheme); diff --git a/IRaCIS.Core.Application/Service/SiteSurvey/TrialSiteUserSurveyService.cs b/IRaCIS.Core.Application/Service/SiteSurvey/TrialSiteUserSurveyService.cs index 4e3593cce..958ced908 100644 --- a/IRaCIS.Core.Application/Service/SiteSurvey/TrialSiteUserSurveyService.cs +++ b/IRaCIS.Core.Application/Service/SiteSurvey/TrialSiteUserSurveyService.cs @@ -56,6 +56,12 @@ namespace IRaCIS.Core.Application.Contracts var item = addOrEditTrialSiteUserSurvey; + if (item.UserTypeId == null) + { + throw new BusinessValidationFailedException(_localizer["SystemCommon_WebErrorArgument"]); + } + + //找下系统中是否存在该用户类型的 并且邮箱 或者手机的账户 var sysUserInfo = await _identityUserRepository.Where(t => t.EMail == item.Email,true).Include(t => t.UserRoleList).FirstOrDefaultAsync(); diff --git a/IRaCIS.Core.Application/TestService.cs b/IRaCIS.Core.Application/TestService.cs index d1c8d8653..70f40183a 100644 --- a/IRaCIS.Core.Application/TestService.cs +++ b/IRaCIS.Core.Application/TestService.cs @@ -1728,10 +1728,10 @@ namespace IRaCIS.Core.Application.Service //var d = _dicRepository.UpdateFromDTOAsync(new AddOrEditBasicDic() { Id = Guid.Parse("60d86683-c33b-4349-b672-08da1e91b622"), ParentId = null, ChildGroup = null, Code = null }, true, true).Result; //var a = 123; - throw new BusinessValidationFailedException(_localizer["TrialIsDoubleCheck{0}", "测试"]); + //throw new BusinessValidationFailedException(_localizer["TrialIsDoubleCheck{0}", "测试"]); - var b = _localizer["test{0}", "测试"]; - return _localizer["test{0}", "测试"]; + //var b = _localizer["test{0}", "测试"]; + //return _localizer["test{0}", "测试"]; //var list = _subjectVisitRepository.Where(t => t.Id == Guid.NewGuid()).SelectMany(t => t.VisitTaskList).ToList(); @@ -1741,6 +1741,8 @@ namespace IRaCIS.Core.Application.Service //var list3 = _visitTaskRepository.Where(t => t.Id == Guid.NewGuid()).SelectMany(t => t.SourceSubjectVisit.VisitTaskList).ToList(); //var list2 = _subjectRepository.Where(t => t.Id == Guid.NewGuid()).SelectMany(t => t.SubjectVisitTaskList).ToList(); //return _userInfo.LocalIp; + + return _userInfo.ClientInfo; } diff --git a/IRaCIS.Core.Infra.EFCore/AuthUser/IUserInfo.cs b/IRaCIS.Core.Infra.EFCore/AuthUser/IUserInfo.cs index 7ff1467e5..377b52eb0 100644 --- a/IRaCIS.Core.Infra.EFCore/AuthUser/IUserInfo.cs +++ b/IRaCIS.Core.Infra.EFCore/AuthUser/IUserInfo.cs @@ -63,5 +63,17 @@ namespace IRaCIS.Core.Domain.Share string BrowserFingerprint { get; } UserWorkLanguage UserWorkLanguage { get; } + + + ClientInfo ClientInfo { get; } } + + public class ClientInfo + { + public string Browser { get; set; } + public string OS { get; set; } + public string DeviceType { get; set; } + } + } + diff --git a/IRaCIS.Core.Infra.EFCore/AuthUser/UserInfo.cs b/IRaCIS.Core.Infra.EFCore/AuthUser/UserInfo.cs index 8ccac279e..cc49c56a7 100644 --- a/IRaCIS.Core.Infra.EFCore/AuthUser/UserInfo.cs +++ b/IRaCIS.Core.Infra.EFCore/AuthUser/UserInfo.cs @@ -1,19 +1,15 @@ using IRaCIS.Core.Domain.Models; using Microsoft.AspNetCore.Http; +using UAParser; namespace IRaCIS.Core.Domain.Share { /// /// 用户信息 /// - public class UserInfo : IUserInfo + public class UserInfo(IHttpContextAccessor _accessor, Parser parser) : IUserInfo { - private readonly IHttpContextAccessor _accessor; - public UserInfo(IHttpContextAccessor accessor) - { - _accessor = accessor; - } /// /// 用户角色Id @@ -348,6 +344,55 @@ namespace IRaCIS.Core.Domain.Share } + public ClientInfo ClientInfo + { + get + { + try + { + var userAgent = _accessor?.HttpContext.Request.Headers["User-Agent"].ToString(); + + if (!string.IsNullOrEmpty(userAgent)) + { + var clientInfo = parser.Parse(userAgent); + + return new ClientInfo() + { + OS = clientInfo.OS.ToString(), + Browser = clientInfo.UA.ToString(), + DeviceType = userAgent.Contains("Mobile") ? "Mobile" : "Desktop" + }; + + + + } + else + { + + return new ClientInfo() + { + OS = "Unknown", + Browser = "Unknown", + DeviceType = "Unknown" + }; + + + } + } + catch (Exception ex) + { + + return new ClientInfo() + { + OS = "ParseError", + Browser = "ParseError", + DeviceType = "ParseError" + }; + + } + } + } + public Guid? SignId { @@ -417,7 +462,7 @@ namespace IRaCIS.Core.Domain.Share public const string PermissionStr = "permissionStr"; - public const string UserWorkLanguage = "userWorkLanguage"; + public const string UserWorkLanguage = "userWorkLanguage"; } diff --git a/IRaCIS.Core.Infra.EFCore/IRaCIS.Core.Infra.EFCore.csproj b/IRaCIS.Core.Infra.EFCore/IRaCIS.Core.Infra.EFCore.csproj index 9d8f20752..b2e4ef85c 100644 --- a/IRaCIS.Core.Infra.EFCore/IRaCIS.Core.Infra.EFCore.csproj +++ b/IRaCIS.Core.Infra.EFCore/IRaCIS.Core.Infra.EFCore.csproj @@ -15,6 +15,7 @@ +