diff --git a/IRaCIS.Core.API/Controllers/UploadDownLoadController.cs b/IRaCIS.Core.API/Controllers/UploadDownLoadController.cs index f8b4d72bd..a4f2cc0d0 100644 --- a/IRaCIS.Core.API/Controllers/UploadDownLoadController.cs +++ b/IRaCIS.Core.API/Controllers/UploadDownLoadController.cs @@ -27,6 +27,7 @@ using Microsoft.AspNetCore.WebUtilities; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Microsoft.Net.Http.Headers; using MiniExcelLibs; using Newtonsoft.Json; @@ -38,6 +39,7 @@ using System.Data; using System.IO; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using System.Threading.Tasks; using Path = System.IO.Path; @@ -527,7 +529,7 @@ namespace IRaCIS.Core.API.Controllers } var uploadFinishedTime = DateTime.Now; - var noneDicomStudy = await _noneDicomStudyRepository.FirstOrDefaultAsync(t => t.Id == noneDicomStudyId,true); + var noneDicomStudy = await _noneDicomStudyRepository.FirstOrDefaultAsync(t => t.Id == noneDicomStudyId, true); noneDicomStudy.FileCount = noneDicomStudy.FileCount + (incommand.VisitTaskId != null ? 0 : incommand.UploadedFileList.Count); @@ -799,12 +801,13 @@ namespace IRaCIS.Core.API.Controllers [HttpPost, Route("TrialSiteSurvey/UploadTrialSiteSurveyUser")] [DisableFormValueModelBinding] - [UnitOfWork] public async Task UploadTrialSiteSurveyUser(Guid trialId, string baseUrl, string routeUrl, [FromServices] IRepository _trialSiteRepository, [FromServices] IRepository _usertypeRepository, [FromServices] ITrialSiteSurveyService _trialSiteSurveyService, [FromServices] IOSSService oSSService, + [FromServices] IOptionsMonitor _systemEmailConfig, + [FromServices] IRepository _inspectionFileRepository) { var templateFileStream = new MemoryStream(); @@ -823,7 +826,7 @@ namespace IRaCIS.Core.API.Controllers var ossRelativePath = await oSSService.UploadToOSSAsync(fileStream, "InspectionUpload/SiteSurvey", realFileName); - await _inspectionFileRepository.AddAsync(new InspectionFile() { FileName = realFileName, RelativePath = ossRelativePath, TrialId = trialId },true); + await _inspectionFileRepository.AddAsync(new InspectionFile() { FileName = realFileName, RelativePath = ossRelativePath, TrialId = trialId }, true); @@ -836,6 +839,26 @@ namespace IRaCIS.Core.API.Controllers .Where(t => !(string.IsNullOrWhiteSpace(t.TrialSiteCode) && string.IsNullOrWhiteSpace(t.FirstName) && string.IsNullOrWhiteSpace(t.LastName) && string.IsNullOrWhiteSpace(t.Email) && string.IsNullOrWhiteSpace(t.Phone) && string.IsNullOrWhiteSpace(t.UserTypeStr) && string.IsNullOrWhiteSpace(t.OrganizationName))).ToList(); + //处理前后空格 + foreach (var excel in excelList) + { + excel.Email = excel.Email.Trim(); + excel.Phone = excel.Phone.Trim(); + excel.OrganizationName = excel.OrganizationName.Trim(); + excel.UserTypeStr = excel.UserTypeStr?.Trim(); + excel.TrialSiteCode = excel.TrialSiteCode.Trim(); + excel.FirstName = excel.FirstName.Trim(); + excel.LastName = excel.LastName.Trim(); + } + + var emailRegexStr = _systemEmailConfig.CurrentValue.EmailRegexStr; + if (excelList.Any(t => !Regex.IsMatch(t.Email, emailRegexStr))) + { + var errorList = excelList.Where(t => !Regex.IsMatch(t.Email, emailRegexStr)).Select(t => t.Email).ToList(); + //有邮箱不符合邮箱格式,请核查Excel数据 + throw new BusinessValidationFailedException(_localizer["UploadDownLoad_InvalidEmail"] + string.Join(" | ", errorList)); + } + if (excelList.Any(t => string.IsNullOrWhiteSpace(t.TrialSiteCode) || string.IsNullOrWhiteSpace(t.FirstName) || string.IsNullOrWhiteSpace(t.LastName) || string.IsNullOrWhiteSpace(t.Email) || string.IsNullOrWhiteSpace(t.UserTypeStr))) { //请确保Excel中 每一行的 中心编号,姓名,邮箱,用户类型数据记录完整再进行上传 @@ -864,11 +887,6 @@ namespace IRaCIS.Core.API.Controllers } } - if (excelList.Any(t => !t.Email.Contains("@"))) - { - //有邮箱不符合邮箱格式,请核查Excel数据 - throw new BusinessValidationFailedException(_localizer["UploadDownLoad_InvalidEmail"]); - } var generateUserTypeList = new List() { "CRC", "CRA" }; //if (excelList.Any(t => !generateUserTypeList.Contains(t.UserTypeStr.ToUpper()))) diff --git a/IRaCIS.Core.API/IRaCIS.Core.API.csproj b/IRaCIS.Core.API/IRaCIS.Core.API.csproj index 3ed9ec7e5..9361510e9 100644 --- a/IRaCIS.Core.API/IRaCIS.Core.API.csproj +++ b/IRaCIS.Core.API/IRaCIS.Core.API.csproj @@ -76,16 +76,15 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + - + - - - - + + + diff --git a/IRaCIS.Core.API/appsettings.Test_IRC.json b/IRaCIS.Core.API/appsettings.Test_IRC.json index 16ad99cb2..9d5ac14ff 100644 --- a/IRaCIS.Core.API/appsettings.Test_IRC.json +++ b/IRaCIS.Core.API/appsettings.Test_IRC.json @@ -94,6 +94,7 @@ "CompanyShortNameCN": "展影医疗", "IsEnv_US": false, "IsOpenErrorNoticeEmail": false, + "EmailRegexStr": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", "ErrorNoticeEmailList": [ "872297557@qq.com" ] }, diff --git a/IRaCIS.Core.Application/BusinessFilter/LegacyController/UnifiedApiResultFilter.cs b/IRaCIS.Core.Application/BusinessFilter/LegacyController/UnifiedApiResultFilter.cs index bba090181..36771bd63 100644 --- a/IRaCIS.Core.Application/BusinessFilter/LegacyController/UnifiedApiResultFilter.cs +++ b/IRaCIS.Core.Application/BusinessFilter/LegacyController/UnifiedApiResultFilter.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Logging; +using ILogger = Microsoft.Extensions.Logging.ILogger; namespace IRaCIS.Core.Application.Service.BusinessFilter; diff --git a/IRaCIS.Core.Application/BusinessFilter/_Config/_AppSettings.cs b/IRaCIS.Core.Application/BusinessFilter/_Config/_AppSettings.cs index 406b2ddda..348638218 100644 --- a/IRaCIS.Core.Application/BusinessFilter/_Config/_AppSettings.cs +++ b/IRaCIS.Core.Application/BusinessFilter/_Config/_AppSettings.cs @@ -70,6 +70,8 @@ public class SystemEmailSendConfig public bool IsOpenErrorNoticeEmail { get; set; } + public string EmailRegexStr { get; set; } + public List ErrorNoticeEmailList { get; set; } =new List(); } diff --git a/IRaCIS.Core.Application/GlobalUsings.cs b/IRaCIS.Core.Application/GlobalUsings.cs index 7712c2f49..3a78de5d9 100644 --- a/IRaCIS.Core.Application/GlobalUsings.cs +++ b/IRaCIS.Core.Application/GlobalUsings.cs @@ -11,5 +11,6 @@ global using AutoMapper; global using IRaCIS.Core.Domain.Share; global using IRaCIS.Core.Application.BusinessFilter; global using IdentityUser = IRaCIS.Core.Domain.Models.IdentityUser; +global using Serilog; diff --git a/IRaCIS.Core.Application/Helper/Email/IRCEmailPasswordHelper.cs b/IRaCIS.Core.Application/Helper/Email/IRCEmailPasswordHelper.cs index efb9ad4e2..2ba054b00 100644 --- a/IRaCIS.Core.Application/Helper/Email/IRCEmailPasswordHelper.cs +++ b/IRaCIS.Core.Application/Helper/Email/IRCEmailPasswordHelper.cs @@ -1,4 +1,7 @@ -namespace IRaCIS.Core.Application.Helper; +using System.Globalization; +using System.Text.RegularExpressions; + +namespace IRaCIS.Core.Application.Helper; public static class IRCEmailPasswordHelper { @@ -74,4 +77,56 @@ public static class IRCEmailPasswordHelper // 随机打乱密码字符顺序 return new string(password.OrderBy(_ => Random.Next()).ToArray()); } + + + + // https://learn.microsoft.com/zh-cn/dotnet/standard/base-types/how-to-verify-that-strings-are-in-valid-email-format + /// + /// 微软官方邮件验证 很宽松 + /// + /// + /// + public static bool IsValidEmail(string email) + { + if (string.IsNullOrWhiteSpace(email)) + return false; + + try + { + // Normalize the domain + email = Regex.Replace(email, @"(@)(.+)$", DomainMapper, + RegexOptions.None, TimeSpan.FromMilliseconds(200)); + + // Examines the domain part of the email and normalizes it. + string DomainMapper(Match match) + { + // Use IdnMapping class to convert Unicode domain names. + var idn = new IdnMapping(); + + // Pull out and process domain name (throws ArgumentException on invalid) + string domainName = idn.GetAscii(match.Groups[2].Value); + + return match.Groups[1].Value + domainName; + } + } + catch (RegexMatchTimeoutException e) + { + return false; + } + catch (ArgumentException e) + { + return false; + } + + try + { + return Regex.IsMatch(email, + @"^[^@\s]+@[^@\s]+\.[^@\s]+$", + RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(250)); + } + catch (RegexMatchTimeoutException) + { + return false; + } + } } diff --git a/IRaCIS.Core.Application/Helper/OSSService.cs b/IRaCIS.Core.Application/Helper/OSSService.cs index 2145b8ef9..8882d7dd7 100644 --- a/IRaCIS.Core.Application/Helper/OSSService.cs +++ b/IRaCIS.Core.Application/Helper/OSSService.cs @@ -123,7 +123,7 @@ public class AWSTempToken public string SecretAccessKey { get; set; } public string BucketName { get; set; } public string ViewEndpoint { get; set; } - public DateTime Expiration { get; set; } + public DateTime? Expiration { get; set; } } public enum ObjectStoreUse diff --git a/IRaCIS.Core.Application/IRaCIS.Core.Application.csproj b/IRaCIS.Core.Application/IRaCIS.Core.Application.csproj index 216bb3771..2ef636bc0 100644 --- a/IRaCIS.Core.Application/IRaCIS.Core.Application.csproj +++ b/IRaCIS.Core.Application/IRaCIS.Core.Application.csproj @@ -33,38 +33,39 @@ - - + + - + - + - - - + + + - + - + + true - + - + - + diff --git a/IRaCIS.Core.Application/IRaCIS.Core.Application.xml b/IRaCIS.Core.Application/IRaCIS.Core.Application.xml index 74965fa7a..0a4f75eff 100644 --- a/IRaCIS.Core.Application/IRaCIS.Core.Application.xml +++ b/IRaCIS.Core.Application/IRaCIS.Core.Application.xml @@ -14182,6 +14182,13 @@ + + + 微软官方邮件验证 很宽松 + + + + 镜像里面打入libreoffice 的方案 diff --git a/IRaCIS.Core.Application/Service/Common/MailService.cs b/IRaCIS.Core.Application/Service/Common/MailService.cs index c6de2ecb1..f49527200 100644 --- a/IRaCIS.Core.Application/Service/Common/MailService.cs +++ b/IRaCIS.Core.Application/Service/Common/MailService.cs @@ -10,9 +10,41 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; using MimeKit; +using System.Runtime.CompilerServices; namespace IRaCIS.Core.Application.Service { + + public static class SafeMailHelper + { + public static async Task Run(Func func, [CallerMemberName] string caller = "") + { + try + { + await func(); + } + catch (Exception ex) + { + Log.Logger.Error($"【邮件失败 - {caller}】: {ex.Message}"); + + } + } + + public static async Task Run(Func> func, [CallerMemberName] string caller = "") + { + try + { + return await func(); + } + catch (Exception ex) + { + Log.Logger.Error($"【邮件失败 - {caller}】: {ex.Message}"); + } + return default; + } + } + + public interface IMailVerificationService { @@ -68,6 +100,8 @@ namespace IRaCIS.Core.Application.Service if (configInfo == null) { + Log.Logger.Error($"系统未找到当前场景:{scenario}邮件配置信息"); + throw new BusinessValidationFailedException("系统未找到当前场景邮件配置信息,请联系运维人员核查"); } @@ -81,6 +115,7 @@ namespace IRaCIS.Core.Application.Service } catch (Exception ex) { + Log.Logger.Error($"邮件模板内容有误,填充内容出现问题,需要核查{scenario}场景邮件配置信息"); throw new BusinessValidationFailedException("邮件模板内容有误,填充内容出现问题,请联系运维人员核查"); } @@ -103,6 +138,8 @@ namespace IRaCIS.Core.Application.Service if (configInfo == null) { + Log.Logger.Error($"系统未找到当前场景:{scenario}邮件配置信息"); + throw new BusinessValidationFailedException("系统未找到当前场景邮件配置信息,请联系运维人员核查"); } @@ -292,7 +329,7 @@ namespace IRaCIS.Core.Application.Service var domain = baseUrl.Substring(0, baseUrl.IndexOf("/login")); var redirectUrl = $"{domain}/api/User/UserRedirect?url={System.Web.HttpUtility.UrlEncode(routeUrl)}"; - + var companyName = _userInfo.IsEn_Us ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN; diff --git a/IRaCIS.Core.Application/Service/ImageAndDoc/DownloadAndUploadService.cs b/IRaCIS.Core.Application/Service/ImageAndDoc/DownloadAndUploadService.cs index 77a140e77..e6168c157 100644 --- a/IRaCIS.Core.Application/Service/ImageAndDoc/DownloadAndUploadService.cs +++ b/IRaCIS.Core.Application/Service/ImageAndDoc/DownloadAndUploadService.cs @@ -65,7 +65,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc //subject 随机阅片 或者无序 有上传 才处理任务编号 if (_visitTaskRepository.Any(t => t.SubjectId == subjectId && t.TrialReadingCriterionId == trialReadingCriterionId && (t.TrialReadingCriterion.IsReadingTaskViewInOrder == ReadingOrder.SubjectRandom || - (t.TrialReadingCriterion.IsReadingTaskViewInOrder == ReadingOrder.Random && t.TrialReadingCriterion.ImageUploadEnum != ReadingImageUpload.None)))) + (t.TrialReadingCriterion.IsReadingTaskViewInOrder == ReadingOrder.Random && t.TrialReadingCriterion.ImageDownloadEnum != ReadingImageDownload.None)))) { //找到 非一致性分析,未签名,状态正常的 并且任务名称是TimePoint的 任务 var needDealTaskList = await _visitTaskRepository.Where(t => t.SubjectId == subjectId && t.TrialReadingCriterionId == trialReadingCriterionId && t.IsAnalysisCreate == false && t.DoctorUserId == _userInfo.UserRoleId diff --git a/IRaCIS.Core.Application/Service/SiteSurvey/TrialSiteSurveyService.cs b/IRaCIS.Core.Application/Service/SiteSurvey/TrialSiteSurveyService.cs index 51279e187..a12ff3ffe 100644 --- a/IRaCIS.Core.Application/Service/SiteSurvey/TrialSiteSurveyService.cs +++ b/IRaCIS.Core.Application/Service/SiteSurvey/TrialSiteSurveyService.cs @@ -70,8 +70,9 @@ namespace IRaCIS.Core.Application.Contracts [AllowAnonymous] public async Task SendEmialVerifyCode(SendEmialVerifyCodeInDto userInfo) { + var emailRegexStr = _systemEmailConfig.EmailRegexStr; //检查手机或者邮箱是否有效 - if (!Regex.IsMatch(userInfo.Email, @"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")) + if (!Regex.IsMatch(userInfo.Email, emailRegexStr)) { //---请输入正确的邮箱地址。 throw new BusinessValidationFailedException(_localizer["TrialSiteSurvey_InvalidEmail"]); @@ -271,8 +272,10 @@ namespace IRaCIS.Core.Application.Contracts [AllowAnonymous] public async Task SendVerifyCode(SiteSurveySendVerifyCode userInfo) { + var emailRegexStr = _systemEmailConfig.EmailRegexStr; + //检查手机或者邮箱是否有效 - if (!Regex.IsMatch(userInfo.Email, @"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")) + if (!Regex.IsMatch(userInfo.Email, emailRegexStr)) { //---请输入正确的邮箱地址。 throw new BusinessValidationFailedException(_localizer["TrialSiteSurvey_InvalidEmail"]); diff --git a/IRaCIS.Core.Application/Service/WorkLoad/EnrollService.cs b/IRaCIS.Core.Application/Service/WorkLoad/EnrollService.cs index 4d3381fdc..a84d4da04 100644 --- a/IRaCIS.Core.Application/Service/WorkLoad/EnrollService.cs +++ b/IRaCIS.Core.Application/Service/WorkLoad/EnrollService.cs @@ -486,7 +486,7 @@ namespace IRaCIS.Core.Application.Service } catch (Exception ex) { - Console.WriteLine(ex.Message); + Log.Logger.Error($"【邮件失败 - ConfirmReviewer接口】: {ex.Message}"); intoGroupItem.EnrollStatus = EnrollStatus.ConfirmIntoGroupFailed; } diff --git a/IRaCIS.Core.Application/TestService.cs b/IRaCIS.Core.Application/TestService.cs index a0909f0e2..c9fe6896b 100644 --- a/IRaCIS.Core.Application/TestService.cs +++ b/IRaCIS.Core.Application/TestService.cs @@ -93,6 +93,7 @@ namespace IRaCIS.Core.Application.Service await _readingConsistentClinicalDataRepository.BatchDeleteNoTrackingAsync(t => consistentSubjectIdList.Contains(t.SubjectId)); await _readingConsistentClinicalDataRepository.SaveChangesAsync(); + return ResponseOutput.Ok(); }