From 6dce301ff7165d04436ce24ffcaa45e60c6d5c0b Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Fri, 16 Jan 2026 16:50:48 +0800 Subject: [PATCH 01/14] =?UTF-8?q?=E5=A4=96=E9=83=A8=E4=BA=BA=E5=91=98?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E7=AB=AF=E6=9F=A5=E7=9C=8B=E5=9F=B9=E8=AE=AD?= =?UTF-8?q?=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Document/DTO/SystemDocumentViewModel.cs | 4 ++++ .../Document/Interface/ITrialDocumentService.cs | 2 ++ .../Service/Document/TrialDocumentService.cs | 15 +++++++++++---- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/IRaCIS.Core.Application/Service/Document/DTO/SystemDocumentViewModel.cs b/IRaCIS.Core.Application/Service/Document/DTO/SystemDocumentViewModel.cs index a0b89b912..e7ce1734a 100644 --- a/IRaCIS.Core.Application/Service/Document/DTO/SystemDocumentViewModel.cs +++ b/IRaCIS.Core.Application/Service/Document/DTO/SystemDocumentViewModel.cs @@ -92,6 +92,10 @@ namespace IRaCIS.Core.Application.Contracts public class UnionDocumentWithConfirmInfoView : UnionDocumentView { + public DocUserSignType SysDocUserSignType { get; set; } + + public bool IsConfirmIdentityUserInner { get; set; } + public Guid TrialId { get; set; } public bool IsNeedSendEmial { get; set; } diff --git a/IRaCIS.Core.Application/Service/Document/Interface/ITrialDocumentService.cs b/IRaCIS.Core.Application/Service/Document/Interface/ITrialDocumentService.cs index e2ed0597b..d83a287ac 100644 --- a/IRaCIS.Core.Application/Service/Document/Interface/ITrialDocumentService.cs +++ b/IRaCIS.Core.Application/Service/Document/Interface/ITrialDocumentService.cs @@ -19,6 +19,8 @@ namespace IRaCIS.Core.Application.Contracts Task UserConfirm(UserConfirmCommand userConfirmCommand); Task> GetTrialUserSelect(Guid trialId); + Task>> GetSysDocumentConfirmList(SystemDocQuery inQuery); + //Task> GetTrialSystemDocumentList(DocumentTrialUnionQuery querySystemDocument); //List GetTrialUserDocumentList(Guid trialId); diff --git a/IRaCIS.Core.Application/Service/Document/TrialDocumentService.cs b/IRaCIS.Core.Application/Service/Document/TrialDocumentService.cs index ac4d047ed..e9850bc6f 100644 --- a/IRaCIS.Core.Application/Service/Document/TrialDocumentService.cs +++ b/IRaCIS.Core.Application/Service/Document/TrialDocumentService.cs @@ -951,10 +951,13 @@ namespace IRaCIS.Core.Application.Services return ResponseOutput.Ok(new PageOutput()); } + var systemDocQuery = - from sysDoc in _systemDocumentRepository.AsQueryable(false) + from sysDoc in _systemDocumentRepository.Where(t => t.IsPublish) .Where(t => inQuery.UserTypeId != null ? t.NeedConfirmedUserTypeList.Any(t => t.NeedConfirmUserTypeId == inQuery.UserTypeId) : true) - from identityUser in _identityUserRepository.AsQueryable(false).Where(t => t.Status == UserStateEnum.Enable && t.UserRoleList.Where(t => t.IsUserRoleDisabled == false).Any(t => sysDoc.NeedConfirmedUserTypeList.AsQueryable().Any(c => c.NeedConfirmUserTypeId == t.UserTypeId))) + from identityUser in _identityUserRepository.AsQueryable(false) + + .Where(t => t.Status == UserStateEnum.Enable && t.UserRoleList.Where(t => t.IsUserRoleDisabled == false).Any(t => sysDoc.NeedConfirmedUserTypeList.AsQueryable().Any(c => c.NeedConfirmUserTypeId == t.UserTypeId))) .Where(t => inQuery.UserId != null ? t.Id == inQuery.UserId : true) .Where(t => inQuery.UserTypeId != null ? t.UserRoleList.Any(t => t.UserTypeId == inQuery.UserTypeId && t.IsUserRoleDisabled == false) : true) .Where(t => isEA ? t.IsZhiZhun == true : true) //EA 只能查看内部人员文档 @@ -963,6 +966,8 @@ namespace IRaCIS.Core.Application.Services select new UnionDocumentWithConfirmInfoView() { IsSystemDoc = true, + SysDocUserSignType = sysDoc.DocUserSignType, + IsConfirmIdentityUserInner = identityUser.IsZhiZhun, Id = sysDoc.Id, CreateTime = sysDoc.CreateTime, IsDeleted = sysDoc.IsDeleted, @@ -994,14 +999,16 @@ namespace IRaCIS.Core.Application.Services }; var unionQuery = systemDocQuery.IgnoreQueryFilters().Where(t => !(t.IsDeleted == true && t.ConfirmTime == null)) + //外部人员 只签署 外部需要签署的 + .Where(t => t.IsConfirmIdentityUserInner == false ? t.SysDocUserSignType == DocUserSignType.InnerAndOuter : true) .WhereIf(!string.IsNullOrEmpty(inQuery.Name), t => t.Name.Contains(inQuery.Name)) .WhereIf(inQuery.FileTypeId != null, t => t.FileTypeId == inQuery.FileTypeId) .WhereIf(inQuery.IsConfirmed == true, t => t.ConfirmTime != null) .WhereIf(inQuery.IsConfirmed == false, t => t.ConfirmTime == null) .WhereIf(inQuery.StartConfirmTime != null, t => t.ConfirmTime >= inQuery.StartConfirmTime.Value) .WhereIf(inQuery.EndConfirmTime != null, t => t.ConfirmTime <= inQuery.EndConfirmTime.Value) - .WhereIf(inQuery.BeginCreateTime != null, t => t.CreateTime >= inQuery.BeginCreateTime) - .WhereIf(inQuery.EndCreateTime != null, t => t.CreateTime <= inQuery.EndCreateTime) + .WhereIf(inQuery.BeginCreateTime != null, t => t.CreateTime >= inQuery.BeginCreateTime) + .WhereIf(inQuery.EndCreateTime != null, t => t.CreateTime <= inQuery.EndCreateTime) .WhereIf(!string.IsNullOrEmpty(inQuery.UserName), t => t.UserName.Contains(inQuery.UserName)) .WhereIf(inQuery.IsDeleted != null, t => t.IsDeleted == inQuery.IsDeleted) .WhereIf(isInternal == false, t => t.ConfirmTime != null); //不是内部的人,看有签名时间的 From 05859662150352f1c77e2056557aff8a2796c3c0 Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Fri, 16 Jan 2026 16:53:09 +0800 Subject: [PATCH 02/14] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=AF=BC=E8=A1=A8bug?= =?UTF-8?q?=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Service/Common/Export/TumorExportService.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/IRaCIS.Core.Application/Service/Common/Export/TumorExportService.cs b/IRaCIS.Core.Application/Service/Common/Export/TumorExportService.cs index f08d03e6c..214fae7bb 100644 --- a/IRaCIS.Core.Application/Service/Common/Export/TumorExportService.cs +++ b/IRaCIS.Core.Application/Service/Common/Export/TumorExportService.cs @@ -601,9 +601,11 @@ public class Tumor_CDISC_ExportService(IRepository t.SubjectCode == task.SubjectCode && t.ARM_TumorNo == $"{task.ArmEnumStr}_{lesion.LessionCode}")) { - var tu = CreatNewTUExport(task, lesion, visitIndexNoDic, translateDataList, isEn_Us); if (lesion.OrganInfoId.HasValue) { From 7fed9cdceb84afcd00f586e411700f3721dff8a7 Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Fri, 16 Jan 2026 17:23:09 +0800 Subject: [PATCH 03/14] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=AD=BE=E7=BD=B2?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Service/Document/SystemDocumentService.cs | 2 ++ .../Service/Document/TrialDocumentService.cs | 1 + 2 files changed, 3 insertions(+) diff --git a/IRaCIS.Core.Application/Service/Document/SystemDocumentService.cs b/IRaCIS.Core.Application/Service/Document/SystemDocumentService.cs index e934d2864..e279c8f91 100644 --- a/IRaCIS.Core.Application/Service/Document/SystemDocumentService.cs +++ b/IRaCIS.Core.Application/Service/Document/SystemDocumentService.cs @@ -302,6 +302,7 @@ namespace IRaCIS.Core.Application.Services { AttachmentCount=sysDoc.SystemDocumentAttachmentList.Where(z=>!z.OffLine).Count(), IsSystemDoc = true, + IsPublish=sysDoc.IsPublish, CurrentStaffTrainDays=sysDoc.CurrentStaffTrainDays, NewStaffTrainDays = sysDoc.NewStaffTrainDays, Id = sysDoc.Id, @@ -327,6 +328,7 @@ namespace IRaCIS.Core.Application.Services //UserTypeShortName = user.UserTypeRole.UserTypeShortName }; + var list = await query //过滤掉删除的,并且没有签名的 .Where(t => !(t.IsDeleted == true && t.ConfirmTime == null)) diff --git a/IRaCIS.Core.Application/Service/Document/TrialDocumentService.cs b/IRaCIS.Core.Application/Service/Document/TrialDocumentService.cs index 6fcb47220..ef14f4cfb 100644 --- a/IRaCIS.Core.Application/Service/Document/TrialDocumentService.cs +++ b/IRaCIS.Core.Application/Service/Document/TrialDocumentService.cs @@ -969,6 +969,7 @@ namespace IRaCIS.Core.Application.Services IsSystemDoc = true, SysDocUserSignType = sysDoc.DocUserSignType, IsConfirmIdentityUserInner = identityUser.IsZhiZhun, + IsPublish=sysDoc.IsPublish, Id = sysDoc.Id, CreateTime = sysDoc.CreateTime, IsDeleted = sysDoc.IsDeleted, From cf2b3f59544a6ed582167a6475577c88376e7ede Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Mon, 19 Jan 2026 11:03:36 +0800 Subject: [PATCH 04/14] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=9C=89=E5=BA=8F?= =?UTF-8?q?=E9=80=80=E5=9B=9E=EF=BC=8C=E5=BD=B1=E5=93=8D=E6=97=A0=E5=BA=8F?= =?UTF-8?q?=E9=80=80=E5=9B=9E=E4=BB=BB=E5=8A=A1bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Service/Allocation/VisitTaskService.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/IRaCIS.Core.Application/Service/Allocation/VisitTaskService.cs b/IRaCIS.Core.Application/Service/Allocation/VisitTaskService.cs index 2537cbd5e..e6e02aeb1 100644 --- a/IRaCIS.Core.Application/Service/Allocation/VisitTaskService.cs +++ b/IRaCIS.Core.Application/Service/Allocation/VisitTaskService.cs @@ -1173,7 +1173,7 @@ public class VisitTaskService(IRepository _visitTaskRepository, var visitQuery = _visitTaskRepository.Where(x => x.TrialId == trialId && x.DoctorUserId == _userInfo.UserRoleId && x.TaskState == TaskState.Effect) .WhereIf(inQuery.SubjectId != null, x => x.SubjectId == inQuery.SubjectId) .WhereIf(!string.IsNullOrEmpty(subjectCode), t => (t.Subject.Code.Contains(subjectCode!) && t.IsAnalysisCreate == false) || (t.BlindSubjectCode.Contains(subjectCode!) && t.IsAnalysisCreate)) - .WhereIf(critrion.CriterionType == CriterionType.OCT, t => t.ReadingCategory == ReadingCategory.Visit ? t.SourceSubjectVisit.NoneDicomStudyList.Where(t=>t.Modality=="OCT").SelectMany(t => t.ImageLabelNoneDicomFileList).Any() : true) + .WhereIf(critrion.CriterionType == CriterionType.OCT, t => t.ReadingCategory == ReadingCategory.Visit ? t.SourceSubjectVisit.NoneDicomStudyList.Where(t => t.Modality == "OCT").SelectMany(t => t.ImageLabelNoneDicomFileList).Any() : true) .WhereIf(critrion.CriterionType == CriterionType.IVUS, t => t.ReadingCategory == ReadingCategory.Visit ? t.SourceSubjectVisit.NoneDicomStudyList.Where(t => t.Modality == "IVUS").SelectMany(t => t.ImageLabelNoneDicomFileList).Any() : true); var visitGroupQuery = visitQuery.GroupBy(x => new { x.SubjectId, x.Subject.Code, x.BlindSubjectCode }); @@ -2293,12 +2293,12 @@ public class VisitTaskService(IRepository _visitTaskRepository, foreach (var item in readingTableAnswerRowInfoList) { - if (item.SplitRowId!=null&&lesionRelationship.ContainsKey(item.SplitRowId.Value)) + if (item.SplitRowId != null && lesionRelationship.ContainsKey(item.SplitRowId.Value)) { item.SplitRowId = lesionRelationship[item.SplitRowId.Value]; } - if (item.MergeRowId!=null&&lesionRelationship.ContainsKey(item.MergeRowId.Value)) + if (item.MergeRowId != null && lesionRelationship.ContainsKey(item.MergeRowId.Value)) { item.MergeRowId = lesionRelationship[item.MergeRowId.Value]; } @@ -2403,6 +2403,10 @@ public class VisitTaskService(IRepository _visitTaskRepository, //另一个阅片人的任务根据任务进度自动进入PM退回或PM申请重阅 filterExpression = filterExpression.And(t => t.VisitTaskNum >= task.VisitTaskNum); + //退回只影响有序的后续所有的,无序的当前访视 + filterExpression = filterExpression.And(t => (t.TrialReadingCriterion.IsReadingTaskViewInOrder == ReadingOrder.InOrder) || + (t.TrialReadingCriterion.IsReadingTaskViewInOrder != ReadingOrder.InOrder && t.SourceSubjectVisitId == task.SourceSubjectVisitId)); + var influenceTaskList = await _visitTaskRepository.Where(filterExpression, true).ToListAsync(); @@ -2564,7 +2568,7 @@ public class VisitTaskService(IRepository _visitTaskRepository, otherVisitIdList = otherVisitIdList.Where(t => t != task.SourceSubjectVisitId.Value).ToList(); } - + //BM后续访视 ,筛选状态不变,任务生成状态重置(实际该访视任务状态 可能是重阅重置了或者失效了,需要后续生成,或者取消分配了,需要后续重新分配) await _subjectCriteriaEvaluationVisitFilterRepository.UpdatePartialFromQueryAsync(t => t.TrialReadingCriterion.CriterionType == CriterionType.RECIST1Pointt1_MB && t.SubjectVisit.SubjectId == task.SubjectId && otherVisitIdList.Contains(t.SubjectVisitId), @@ -2727,6 +2731,13 @@ public class VisitTaskService(IRepository _visitTaskRepository, //默认影响的都是该标准的任务 filterExpression = filterExpression.And(t => t.TrialReadingCriterionId == filterObj.TrialReadingCriterionId); } + + //退回只影响有序的后续所有的,无序的当前访视 + if (isReReading == false) + { + filterExpression = filterExpression.And(t => (t.TrialReadingCriterion.IsReadingTaskViewInOrder == ReadingOrder.InOrder) || + (t.TrialReadingCriterion.IsReadingTaskViewInOrder != ReadingOrder.InOrder && t.SourceSubjectVisitId == filterObj.SourceSubjectVisitId)); + } } From 59ba1a61935aecd3a12be4357ca73ac6191db3f4 Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Mon, 19 Jan 2026 13:52:06 +0800 Subject: [PATCH 05/14] =?UTF-8?q?=E4=BF=AE=E6=94=B9=20=E5=8F=AF=E7=A9=BA?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E4=BC=A0=E5=8F=82=E5=A4=84=E7=90=86=EF=BC=8C?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=A2=84=E5=A4=84=E7=90=86=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E7=BB=91=E5=AE=9A=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IRaCIS.Core.API/Progranm.cs | 3 + .../NullToEmptyStringResolver.cs | 69 ++++++++++++++----- .../LegacyController/ModelActionFilter .cs | 64 +++++++++++++++++ IRaCIS.Core.Application/TestService.cs | 40 ++++++++++- 4 files changed, 158 insertions(+), 18 deletions(-) diff --git a/IRaCIS.Core.API/Progranm.cs b/IRaCIS.Core.API/Progranm.cs index 69be6d74f..c80a2171a 100644 --- a/IRaCIS.Core.API/Progranm.cs +++ b/IRaCIS.Core.API/Progranm.cs @@ -96,6 +96,9 @@ builder.Services.AddJsonLocalization(options => options.ResourcesPath = "Resourc // 异常、参数统一验证过滤器、Json序列化配置、字符串参数绑型统一Trim() builder.Services.AddControllers(options => { + // 插到最前,抢在默认绑定器之前 + options.ModelBinderProviders.Insert(0, new NullableStructModelBinderProvider()); + options.Filters.Add(); options.Filters.Add(); options.Filters.Add(); diff --git a/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/NullToEmptyStringResolver.cs b/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/NullToEmptyStringResolver.cs index 04bb3a2c0..4a636d653 100644 --- a/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/NullToEmptyStringResolver.cs +++ b/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/NullToEmptyStringResolver.cs @@ -3,8 +3,10 @@ using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; +using System.Reflection.Metadata; namespace IRaCIS.Core.API { @@ -51,34 +53,69 @@ namespace IRaCIS.Core.API } public class NullToEmptyStringValueProvider : IValueProvider { - PropertyInfo _MemberInfo; + PropertyInfo _memberInfo; + private readonly bool _isNullable; public NullToEmptyStringValueProvider(PropertyInfo memberInfo) { - _MemberInfo = memberInfo; + _memberInfo = memberInfo; + } + + + + // DTO → JSON 返回前端的时候 处理null 变为"" 方便前端判断 public object GetValue(object target) { - object result = _MemberInfo.GetValue(target); - if (_MemberInfo.PropertyType == typeof(string) && result == null) result = ""; - else if (_MemberInfo.PropertyType == typeof(String[]) && result == null) result = new string[] { }; - //else if (_MemberInfo.PropertyType == typeof(Nullable) && result == null) result = 0; - //else if (_MemberInfo.PropertyType == typeof(Nullable) && result == null) result = 0.00M; + var result = _memberInfo.GetValue(target); + // 检查类型是否为string或string? + if (_memberInfo.PropertyType == typeof(string) && result == null) + { + #region 返回的时候处理 string string? null 为"" 不区分处理 + + + //// 如果是string?类型,返回null 可以做到,但是得反射,因为 string string? 是编译层区分的 + + //var isNullable1 = _memberInfo.CustomAttributes + // .Any(a => a.AttributeType.Name == "NullableAttribute"); + + ////var isNullable2 = _memberInfo.CustomAttributes + //// .Any(a => a.AttributeType.FullName == "System.Runtime.CompilerServices.NullableAttribute"); + + //if (isNullable1) + //{ + // return result; + //} + + #endregion + + + // 如果是string类型,返回空字符串 + return string.Empty; + } return result; } + + + //影响 模型绑定时接收前端的值,同时影响 正常 JSON 序列化 public void SetValue(object target, object value) { - if (_MemberInfo.PropertyType == typeof(string)) - { - //去掉前后空格 - _MemberInfo.SetValue(target, value == null ? string.Empty : value.ToString() == string.Empty ? value : value/*.ToString().Trim()*/); + _memberInfo.SetValue(target, value); + + + //// 会影响 string? 传递null 变为"" + + //if (_memberInfo.PropertyType == typeof(string)) + //{ + // //去掉前后空格 + // _memberInfo.SetValue(target, value == null ? string.Empty : value.ToString() == string.Empty ? value : value/*.ToString().Trim()*/); + //} + //else + //{ + // _memberInfo.SetValue(target, value); + //} - } - else - { - _MemberInfo.SetValue(target, value); - } } } diff --git a/IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelActionFilter .cs b/IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelActionFilter .cs index 63255f4fa..abe4800de 100644 --- a/IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelActionFilter .cs +++ b/IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelActionFilter .cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.Localization; using Newtonsoft.Json; @@ -28,3 +29,66 @@ public class ModelActionFilter(IStringLocalizer _localizer) : ActionFilterAttrib } } } + + +public class NullableStructModelBinderProvider : IModelBinderProvider +{ + public IModelBinder? GetBinder(ModelBinderProviderContext context) + { + // 获取要绑定的模型类型,比如 Guid?, int?, DateTime? 等 + var type = context.Metadata.ModelType; + + // 检查是否是 Nullable 类型 + // 1. 必须是泛型类型 + // 2. 泛型定义必须是 Nullable<> + if (!type.IsGenericType || + type.GetGenericTypeDefinition() != typeof(Nullable<>)) + return null; // 返回 null 表示"我不处理这个类型" + + + // 获取可空类型内部的实际类型,如 Guid?, int? 中的 Guid, int + var innerType = Nullable.GetUnderlyingType(type)!; + + // 创建默认的模型绑定器(系统的原逻辑) + var fallback = context.CreateBinder(context.Metadata); + + // 创建泛型绑定器类型:NullableEmptyStringToNullBinder + // 比如对于 Guid?,会创建 NullableEmptyStringToNullBinder + var binderType = typeof(NullableEmptyStringToNullBinder<>).MakeGenericType(innerType); + + // 实例化绑定器,传入默认绑定器作为备用 + return (IModelBinder)Activator.CreateInstance(binderType, fallback)!; + } +} + +// 泛型约束:T 必须是值类型(struct),这确保了只处理可空值类型 +public class NullableEmptyStringToNullBinder : IModelBinder where T : struct +{ + private readonly IModelBinder _fallbackBinder; + + // 构造函数接收一个备用的绑定器(系统的默认绑定器) + public NullableEmptyStringToNullBinder(IModelBinder fallbackBinder) + { + _fallbackBinder = fallbackBinder; + } + + public async Task BindModelAsync(ModelBindingContext context) + { + // 获取前端传递的值 + var value = context.ValueProvider.GetValue(context.ModelName).FirstValue; + + // 关键逻辑:如果值是空字符串或空白,直接返回 null + if (string.IsNullOrWhiteSpace(value)) + { + // 设置绑定结果为 null(表示空值) + context.Result = ModelBindingResult.Success(null); + return; + } + + // 非空 → 完全走系统原逻辑 + + // 如果不是空字符串,则使用系统的默认绑定逻辑 + // 比如将 "123" 转换为 int? 123,或将 GUID 字符串转换为 Guid? + await _fallbackBinder.BindModelAsync(context); + } +} \ No newline at end of file diff --git a/IRaCIS.Core.Application/TestService.cs b/IRaCIS.Core.Application/TestService.cs index fd751a860..f7c4723e2 100644 --- a/IRaCIS.Core.Application/TestService.cs +++ b/IRaCIS.Core.Application/TestService.cs @@ -39,6 +39,7 @@ using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Processing; using System.Collections.Concurrent; +using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Globalization; using System.IO; @@ -77,6 +78,41 @@ namespace IRaCIS.Core.Application.Service public static int IntValue = 100; + public class ModelVerifyCommand + { + public int? IntNUllValue { get; set; } + public int IntValue { get; set; } + + public string StringValue { get; set; } + + public string? StringNUllValue { get; set; } + + public Guid GuidValue { get; set; } = NewId.NextSequentialGuid(); + + public Guid? GuidNUllValue { get; set; } + + [NotDefault] + public Guid GuidValueNotDefault { get; set; } + + + public bool BoolValue { get; set; } + + public bool? BoolNUllValue { get; set; } + + public DateTime DateTimeValue { get; set; } + + public DateTime? DateTimeNUllValue { get; set; } + } + + //创建一个模型验证的方法 + [AllowAnonymous] + [HttpPost] + public async Task PostModelVerify(ModelVerifyCommand modelVerify) + { + return ResponseOutput.Ok(modelVerify); + } + + [AllowAnonymous] public async Task CreatNewDBStruct() @@ -99,10 +135,10 @@ namespace IRaCIS.Core.Application.Service public async Task DeleteCacheDIR() { - var list= _dicomStudyRepository.Where(t => t.StudyDIRPath!="").Select(t => t.StudyDIRPath).ToList(); + var list = _dicomStudyRepository.Where(t => t.StudyDIRPath != "").Select(t => t.StudyDIRPath).ToList(); - await _IOSSService.DeleteObjects(list.Select(t => t.TrimStart('/')).ToList(),true); + await _IOSSService.DeleteObjects(list.Select(t => t.TrimStart('/')).ToList(), true); return ResponseOutput.Ok(); } From 473d10533a76e136344f4366d275fb5424bd0bd4 Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Mon, 19 Jan 2026 14:25:59 +0800 Subject: [PATCH 06/14] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=BB=91=E5=AE=9A?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IRaCIS.Core.API/Progranm.cs | 2 +- .../LegacyController/ModelActionFilter .cs | 61 ++++++++++++++----- 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/IRaCIS.Core.API/Progranm.cs b/IRaCIS.Core.API/Progranm.cs index c80a2171a..394625ba5 100644 --- a/IRaCIS.Core.API/Progranm.cs +++ b/IRaCIS.Core.API/Progranm.cs @@ -97,7 +97,7 @@ builder.Services.AddJsonLocalization(options => options.ResourcesPath = "Resourc builder.Services.AddControllers(options => { // 插到最前,抢在默认绑定器之前 - options.ModelBinderProviders.Insert(0, new NullableStructModelBinderProvider()); + //options.ModelBinderProviders.Insert(0, new NullableStructModelBinderProvider()); options.Filters.Add(); options.Filters.Add(); diff --git a/IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelActionFilter .cs b/IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelActionFilter .cs index abe4800de..8095e55b8 100644 --- a/IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelActionFilter .cs +++ b/IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelActionFilter .cs @@ -38,30 +38,38 @@ public class NullableStructModelBinderProvider : IModelBinderProvider // 获取要绑定的模型类型,比如 Guid?, int?, DateTime? 等 var type = context.Metadata.ModelType; + //创建默认的模型绑定器(系统的原逻辑) + var fallback = context.CreateBinder(context.Metadata); + + + // 1. 处理 string 类型 + if (type == typeof(string)) + { + return new StringNotNullableModelBinder(fallback); + } + // 检查是否是 Nullable 类型 // 1. 必须是泛型类型 // 2. 泛型定义必须是 Nullable<> - if (!type.IsGenericType || - type.GetGenericTypeDefinition() != typeof(Nullable<>)) - return null; // 返回 null 表示"我不处理这个类型" + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + // 获取可空类型内部的实际类型,如 Guid?, int? 中的 Guid, int + var innerType = Nullable.GetUnderlyingType(type)!; - // 获取可空类型内部的实际类型,如 Guid?, int? 中的 Guid, int - var innerType = Nullable.GetUnderlyingType(type)!; + var binderType = typeof(NullableEmptyStringToNullBinder<>).MakeGenericType(innerType); - // 创建默认的模型绑定器(系统的原逻辑) - var fallback = context.CreateBinder(context.Metadata); + // 实例化绑定器,传入默认绑定器作为备用 + return (IModelBinder)Activator.CreateInstance(binderType, fallback)!; + } - // 创建泛型绑定器类型:NullableEmptyStringToNullBinder - // 比如对于 Guid?,会创建 NullableEmptyStringToNullBinder - var binderType = typeof(NullableEmptyStringToNullBinder<>).MakeGenericType(innerType); - // 实例化绑定器,传入默认绑定器作为备用 - return (IModelBinder)Activator.CreateInstance(binderType, fallback)!; + return null; // 返回 null 表示"我不处理这个类型" + } } -// 泛型约束:T 必须是值类型(struct),这确保了只处理可空值类型 +// 泛型约束:T 必须是值类型(struct),这确保了只处理可空值类型 比如处理guid? 传递"" 为null public class NullableEmptyStringToNullBinder : IModelBinder where T : struct { private readonly IModelBinder _fallbackBinder; @@ -91,4 +99,29 @@ public class NullableEmptyStringToNullBinder : IModelBinder where T : struct // 比如将 "123" 转换为 int? 123,或将 GUID 字符串转换为 Guid? await _fallbackBinder.BindModelAsync(context); } -} \ No newline at end of file +} + +// 处理 string 的绑定器 如果前端不传递 或者null 时 处理为 "" +public class StringNotNullableModelBinder : IModelBinder +{ + private readonly IModelBinder _fallbackBinder; + + public StringNotNullableModelBinder(IModelBinder fallbackBinder) + { + _fallbackBinder = fallbackBinder; + } + + public async Task BindModelAsync(ModelBindingContext context) + { + var value = context.ValueProvider.GetValue(context.ModelName).FirstValue; + + // 前端不传或传空字符串,都设为 string.Empty + if (string.IsNullOrWhiteSpace(value)) + { + context.Result = ModelBindingResult.Success(string.Empty); + return; + } + + await _fallbackBinder.BindModelAsync(context); + } +} From 296ecfa9d218c3c78b21fc0d53f8b3b03ad0d64b Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Mon, 19 Jan 2026 15:00:35 +0800 Subject: [PATCH 07/14] =?UTF-8?q?=E4=BF=AE=E6=94=B9json=20=E5=BA=8F?= =?UTF-8?q?=E5=88=97=E5=8C=96=E7=BB=91=E5=AE=9A=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NewtonsoftJson/NullToEmptyStringResolver.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/NullToEmptyStringResolver.cs b/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/NullToEmptyStringResolver.cs index 4a636d653..f6330817f 100644 --- a/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/NullToEmptyStringResolver.cs +++ b/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/NullToEmptyStringResolver.cs @@ -101,12 +101,25 @@ namespace IRaCIS.Core.API public void SetValue(object target, object value) { - _memberInfo.SetValue(target, value); //// 会影响 string? 传递null 变为"" - //if (_memberInfo.PropertyType == typeof(string)) + var isNullable1 = _memberInfo.CustomAttributes.Any(a => a.AttributeType.Name == "NullableAttribute"); + + + if (_memberInfo.PropertyType == typeof(string) && isNullable1 == false) + { + //如果不处理 前段传递null string不会接收,说前段没传递会验证报错 + _memberInfo.SetValue(target, value == null ? string.Empty : value); + } + else + { + _memberInfo.SetValue(target, value); + + } + + //if () //{ // //去掉前后空格 // _memberInfo.SetValue(target, value == null ? string.Empty : value.ToString() == string.Empty ? value : value/*.ToString().Trim()*/); From 2bf47515e783427ce6288e0760e33a4ea7421de2 Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Mon, 19 Jan 2026 15:33:57 +0800 Subject: [PATCH 08/14] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E7=BB=91=E5=AE=9A=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NullToEmptyStringResolver.cs | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/NullToEmptyStringResolver.cs b/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/NullToEmptyStringResolver.cs index f6330817f..15814a6cc 100644 --- a/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/NullToEmptyStringResolver.cs +++ b/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/NullToEmptyStringResolver.cs @@ -102,33 +102,39 @@ namespace IRaCIS.Core.API { + #region 前端针对 string 类型的变量,如果传递null 会报错必传 - //// 会影响 string? 传递null 变为"" - - var isNullable1 = _memberInfo.CustomAttributes.Any(a => a.AttributeType.Name == "NullableAttribute"); - - - if (_memberInfo.PropertyType == typeof(string) && isNullable1 == false) + if (_memberInfo.PropertyType == typeof(string)) { - //如果不处理 前段传递null string不会接收,说前段没传递会验证报错 _memberInfo.SetValue(target, value == null ? string.Empty : value); } else { _memberInfo.SetValue(target, value); - } - //if () + #endregion + + #region 处理模型验证区分 string string? + + //var isNullable1 = _memberInfo.CustomAttributes.Any(a => a.AttributeType.Name == "NullableAttribute"); + + ////不影响 string? 传递null 变为"" + //if (_memberInfo.PropertyType == typeof(string) && isNullable1 == false) //{ - // //去掉前后空格 - // _memberInfo.SetValue(target, value == null ? string.Empty : value.ToString() == string.Empty ? value : value/*.ToString().Trim()*/); + // //如果不处理 前段传递null string不会接收,说前段没传递会验证报错 + // _memberInfo.SetValue(target, value == null ? string.Empty : value); //} //else //{ + // _memberInfo.SetValue(target, value); + //} + #endregion + + } } From 0ef7fd85d4c75c4389b5a0afd322c65e0b65af3c Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Mon, 19 Jan 2026 17:58:47 +0800 Subject: [PATCH 09/14] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=94=9F=E6=88=90dir?= =?UTF-8?q?=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IRaCIS.Core.API/Progranm.cs | 5 +- .../NullToEmptyStringResolver.cs | 18 ++-- .../LegacyController/ModelActionFilter .cs | 88 ++++--------------- .../Helper/DicomDIRHelper.cs | 6 +- .../ImageAndDoc/DownloadAndUploadService.cs | 14 ++- IRaCIS.Core.Application/TestService.cs | 5 ++ 6 files changed, 50 insertions(+), 86 deletions(-) diff --git a/IRaCIS.Core.API/Progranm.cs b/IRaCIS.Core.API/Progranm.cs index 394625ba5..a3b23aa44 100644 --- a/IRaCIS.Core.API/Progranm.cs +++ b/IRaCIS.Core.API/Progranm.cs @@ -96,8 +96,11 @@ builder.Services.AddJsonLocalization(options => options.ResourcesPath = "Resourc // 异常、参数统一验证过滤器、Json序列化配置、字符串参数绑型统一Trim() builder.Services.AddControllers(options => { + // 关键配置:禁用不可空引用类型的自动 Required 验证 + options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true; + // 插到最前,抢在默认绑定器之前 - //options.ModelBinderProviders.Insert(0, new NullableStructModelBinderProvider()); + //options.ModelBinderProviders.Insert(0, new SpecificNullableBinderProvider()); options.Filters.Add(); options.Filters.Add(); diff --git a/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/NullToEmptyStringResolver.cs b/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/NullToEmptyStringResolver.cs index 15814a6cc..146bdf25d 100644 --- a/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/NullToEmptyStringResolver.cs +++ b/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/NullToEmptyStringResolver.cs @@ -101,22 +101,24 @@ namespace IRaCIS.Core.API public void SetValue(object target, object value) { + _memberInfo.SetValue(target, value); #region 前端针对 string 类型的变量,如果传递null 会报错必传 - if (_memberInfo.PropertyType == typeof(string)) - { - _memberInfo.SetValue(target, value == null ? string.Empty : value); - } - else - { - _memberInfo.SetValue(target, value); - } + //if (_memberInfo.PropertyType == typeof(string)) + //{ + // _memberInfo.SetValue(target, value == null ? string.Empty : value); + //} + //else + //{ + // _memberInfo.SetValue(target, value); + //} #endregion #region 处理模型验证区分 string string? + //////接收模型的时候 定义的明明是string 但是上面也有该属性,判断不准的 比如阅片跟踪列表查询 //var isNullable1 = _memberInfo.CustomAttributes.Any(a => a.AttributeType.Name == "NullableAttribute"); ////不影响 string? 传递null 变为"" diff --git a/IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelActionFilter .cs b/IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelActionFilter .cs index 8095e55b8..7d7c885a3 100644 --- a/IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelActionFilter .cs +++ b/IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelActionFilter .cs @@ -1,8 +1,11 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Binders; +using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; using Microsoft.Extensions.Localization; using Newtonsoft.Json; +using System.Reflection; namespace IRaCIS.Core.Application.Filter; @@ -31,97 +34,40 @@ public class ModelActionFilter(IStringLocalizer _localizer) : ActionFilterAttrib } -public class NullableStructModelBinderProvider : IModelBinderProvider + + + +public class SpecificNullableBinderProvider : IModelBinderProvider { public IModelBinder? GetBinder(ModelBinderProviderContext context) { - // 获取要绑定的模型类型,比如 Guid?, int?, DateTime? 等 var type = context.Metadata.ModelType; - //创建默认的模型绑定器(系统的原逻辑) - var fallback = context.CreateBinder(context.Metadata); - - - // 1. 处理 string 类型 - if (type == typeof(string)) + // 只处理 Guid? 和 int? + if (type == typeof(Guid?) || type == typeof(int?)) { - return new StringNotNullableModelBinder(fallback); + return new SpecificNullableBinder(); } - // 检查是否是 Nullable 类型 - // 1. 必须是泛型类型 - // 2. 泛型定义必须是 Nullable<> - if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) - { - // 获取可空类型内部的实际类型,如 Guid?, int? 中的 Guid, int - var innerType = Nullable.GetUnderlyingType(type)!; - - - var binderType = typeof(NullableEmptyStringToNullBinder<>).MakeGenericType(innerType); - - // 实例化绑定器,传入默认绑定器作为备用 - return (IModelBinder)Activator.CreateInstance(binderType, fallback)!; - } - - - return null; // 返回 null 表示"我不处理这个类型" - + return null; } } -// 泛型约束:T 必须是值类型(struct),这确保了只处理可空值类型 比如处理guid? 传递"" 为null -public class NullableEmptyStringToNullBinder : IModelBinder where T : struct +public class SpecificNullableBinder : IModelBinder { - private readonly IModelBinder _fallbackBinder; - - // 构造函数接收一个备用的绑定器(系统的默认绑定器) - public NullableEmptyStringToNullBinder(IModelBinder fallbackBinder) + public Task BindModelAsync(ModelBindingContext context) { - _fallbackBinder = fallbackBinder; - } - - public async Task BindModelAsync(ModelBindingContext context) - { - // 获取前端传递的值 var value = context.ValueProvider.GetValue(context.ModelName).FirstValue; - // 关键逻辑:如果值是空字符串或空白,直接返回 null if (string.IsNullOrWhiteSpace(value)) { - // 设置绑定结果为 null(表示空值) context.Result = ModelBindingResult.Success(null); - return; } - - // 非空 → 完全走系统原逻辑 - - // 如果不是空字符串,则使用系统的默认绑定逻辑 - // 比如将 "123" 转换为 int? 123,或将 GUID 字符串转换为 Guid? - await _fallbackBinder.BindModelAsync(context); - } -} - -// 处理 string 的绑定器 如果前端不传递 或者null 时 处理为 "" -public class StringNotNullableModelBinder : IModelBinder -{ - private readonly IModelBinder _fallbackBinder; - - public StringNotNullableModelBinder(IModelBinder fallbackBinder) - { - _fallbackBinder = fallbackBinder; - } - - public async Task BindModelAsync(ModelBindingContext context) - { - var value = context.ValueProvider.GetValue(context.ModelName).FirstValue; - - // 前端不传或传空字符串,都设为 string.Empty - if (string.IsNullOrWhiteSpace(value)) + else { - context.Result = ModelBindingResult.Success(string.Empty); - return; + context.Result = ModelBindingResult.Success(value); } - await _fallbackBinder.BindModelAsync(context); + return Task.CompletedTask; } -} +} \ No newline at end of file diff --git a/IRaCIS.Core.Application/Helper/DicomDIRHelper.cs b/IRaCIS.Core.Application/Helper/DicomDIRHelper.cs index 44f71203b..12c387440 100644 --- a/IRaCIS.Core.Application/Helper/DicomDIRHelper.cs +++ b/IRaCIS.Core.Application/Helper/DicomDIRHelper.cs @@ -1,4 +1,5 @@ -using FellowOakDicom; +using DocumentFormat.OpenXml.Office.CustomUI; +using FellowOakDicom; using FellowOakDicom.Media; using System; using System.Collections.Generic; @@ -57,6 +58,7 @@ namespace IRaCIS.Core.Application.Helper var mappings = new List(); int index = 1; + var studyUid=list.FirstOrDefault()?.StudyInstanceUid; var dicomDir = new DicomDirectory(); @@ -130,7 +132,7 @@ namespace IRaCIS.Core.Application.Helper var relativePath= await _oSSService.UploadToOSSAsync(memoryStream, ossFolder, "DICOMDIR", true); - dic.Add("DICOMDIR" , relativePath.Split('/').Last()); + dic.Add($"{studyUid}_DICOMDIR" , relativePath.Split('/').Last()); } //清理临时文件 diff --git a/IRaCIS.Core.Application/Service/ImageAndDoc/DownloadAndUploadService.cs b/IRaCIS.Core.Application/Service/ImageAndDoc/DownloadAndUploadService.cs index 2dad6b397..4491fb6fe 100644 --- a/IRaCIS.Core.Application/Service/ImageAndDoc/DownloadAndUploadService.cs +++ b/IRaCIS.Core.Application/Service/ImageAndDoc/DownloadAndUploadService.cs @@ -1185,6 +1185,9 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc foreach (var item in list.GroupBy(t => new { t.StudyInstanceUid, t.DicomStudyId })) { + + var studyUid = item.Key.StudyInstanceUid; + var ossFolder = $"{pathInfo.TrialId}/Image/{pathInfo.SubjectId}/{pathInfo.VisitId}/{item.Key.StudyInstanceUid}"; var isSucess = await SafeBussinessHelper.RunAsync(async () => await DicomDIRHelper.GenerateStudyDIRAndUploadAsync(item.ToList(), dirDic, ossFolder, _oSSService)); @@ -1192,7 +1195,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc if (isSucess) { - await _dicomStudyRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Key.DicomStudyId, u => new DicomStudy() { StudyDIRPath = $"/{ossFolder}/{dirDic["DICOMDIR"]}" }); + await _dicomStudyRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Key.DicomStudyId, u => new DicomStudy() { StudyDIRPath = $"/{ossFolder}/{dirDic[$"{studyUid}_DICOMDIR"]}" }); } } @@ -1625,6 +1628,8 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc var ossFolder = $"{pathInfo.TrialId}/Image/{pathInfo.SubjectId}/{visitId}/{item.Key.StudyInstanceUid}"; + var studyUid = item.Key.StudyInstanceUid; + var isSucess = await SafeBussinessHelper.RunAsync(async () => await DicomDIRHelper.GenerateStudyDIRAndUploadAsync(item.ToList(), dirDic, ossFolder, _oSSService)); @@ -1632,11 +1637,11 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc { if (isTaskStudy) { - await _taskStudyRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Key.DicomStudyId, u => new TaskStudy() { StudyDIRPath = $"/{ossFolder}/{dirDic["DICOMDIR"]}" }); + await _taskStudyRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Key.DicomStudyId, u => new TaskStudy() { StudyDIRPath = $"/{ossFolder}/{dirDic[$"{studyUid}_DICOMDIR"]}" }); } else { - await _dicomStudyRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Key.DicomStudyId, u => new DicomStudy() { StudyDIRPath = $"/{ossFolder}/{dirDic["DICOMDIR"]}" }); + await _dicomStudyRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Key.DicomStudyId, u => new DicomStudy() { StudyDIRPath = $"/{ossFolder}/{dirDic[$"{studyUid}_DICOMDIR"]}" }); } } @@ -2303,6 +2308,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc var subjectId = item.First().SubjectId; + var studyUid = item.Key.StudyInstanceUid; var ossFolder = $"{inCommand.TrialId}/Image/{subjectId}/{visitId}/{item.Key.StudyInstanceUid}"; @@ -2310,7 +2316,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc if (isSucess) { - await _dicomStudyRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Key.DicomStudyId, u => new DicomStudy() { StudyDIRPath = $"/{ossFolder}/{dirDic["DICOMDIR"]}" }); + await _dicomStudyRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Key.DicomStudyId, u => new DicomStudy() { StudyDIRPath = $"/{ossFolder}/{dirDic[$"{studyUid}_DICOMDIR"]}" }); } } } diff --git a/IRaCIS.Core.Application/TestService.cs b/IRaCIS.Core.Application/TestService.cs index f7c4723e2..ecb3badd6 100644 --- a/IRaCIS.Core.Application/TestService.cs +++ b/IRaCIS.Core.Application/TestService.cs @@ -87,6 +87,8 @@ namespace IRaCIS.Core.Application.Service public string? StringNUllValue { get; set; } + public string StringBackDefaultValue { get; set; } = string.Empty; + public Guid GuidValue { get; set; } = NewId.NextSequentialGuid(); public Guid? GuidNUllValue { get; set; } @@ -104,12 +106,15 @@ namespace IRaCIS.Core.Application.Service public DateTime? DateTimeNUllValue { get; set; } } + //创建一个模型验证的方法 [AllowAnonymous] [HttpPost] public async Task PostModelVerify(ModelVerifyCommand modelVerify) { + return ResponseOutput.Ok(modelVerify); + } From e6458e66dffe0fa78ad0d27c9e8d9136f03502dc Mon Sep 17 00:00:00 2001 From: hang <87227557@qq.com> Date: Mon, 19 Jan 2026 20:52:42 +0800 Subject: [PATCH 10/14] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E6=A0=BC=E5=BC=8Fbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NewtonsoftJson/JSONTimeZoneConverter.cs | 5 +-- .../LegacyController/ModelActionFilter .cs | 35 ------------------- 2 files changed, 3 insertions(+), 37 deletions(-) diff --git a/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/JSONTimeZoneConverter.cs b/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/JSONTimeZoneConverter.cs index 9b289e8a4..87fda8456 100644 --- a/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/JSONTimeZoneConverter.cs +++ b/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/JSONTimeZoneConverter.cs @@ -89,7 +89,9 @@ namespace IRaCIS.Core.API { if (DateTime.TryParse((string)reader.Value, out dateTime) == false) { - return null; + + throw new JsonSerializationException($"Could not convert string to DateTime: {reader.Value} Path {reader.Path}"); + //return null; } } @@ -113,7 +115,6 @@ namespace IRaCIS.Core.API //第一个参数默认使用系统本地时区 也就是应用服务器的时区 DateTime clientZoneTime = TimeZoneInfo.ConvertTime(nullableDateTime.Value, _clientTimeZone); - //writer.WriteValue(clientZoneTime); writer.WriteValue(clientZoneTime.ToString(_dateFormat)); } diff --git a/IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelActionFilter .cs b/IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelActionFilter .cs index 7d7c885a3..ee1d392cf 100644 --- a/IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelActionFilter .cs +++ b/IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelActionFilter .cs @@ -36,38 +36,3 @@ public class ModelActionFilter(IStringLocalizer _localizer) : ActionFilterAttrib - -public class SpecificNullableBinderProvider : IModelBinderProvider -{ - public IModelBinder? GetBinder(ModelBinderProviderContext context) - { - var type = context.Metadata.ModelType; - - // 只处理 Guid? 和 int? - if (type == typeof(Guid?) || type == typeof(int?)) - { - return new SpecificNullableBinder(); - } - - return null; - } -} - -public class SpecificNullableBinder : IModelBinder -{ - public Task BindModelAsync(ModelBindingContext context) - { - var value = context.ValueProvider.GetValue(context.ModelName).FirstValue; - - if (string.IsNullOrWhiteSpace(value)) - { - context.Result = ModelBindingResult.Success(null); - } - else - { - context.Result = ModelBindingResult.Success(value); - } - - return Task.CompletedTask; - } -} \ No newline at end of file From 61ab33b3f3ebbb727c04d9fdd17e4c3f2ebbd105 Mon Sep 17 00:00:00 2001 From: hang <87227557@qq.com> Date: Mon, 19 Jan 2026 23:46:44 +0800 Subject: [PATCH 11/14] =?UTF-8?q?efcore=20=E9=87=8D=E5=A4=8D=E8=B7=9F?= =?UTF-8?q?=E8=B8=AA=E9=97=AE=E9=A2=98=E8=A7=A3=E5=86=B3=EF=BC=8C=E4=BB=A5?= =?UTF-8?q?=E5=8F=8A=E6=95=B0=E7=BB=84json=E5=88=97=EF=BC=8C=E6=97=A0?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E7=9A=84=E6=97=B6=E5=80=99=E4=BC=9A=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E5=A5=87=E6=80=AA=E9=97=AE=E9=A2=98=E5=8F=91=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Repository/IRaCISContextExtension.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/IRaCIS.Core.Infra.EFCore/Repository/IRaCISContextExtension.cs b/IRaCIS.Core.Infra.EFCore/Repository/IRaCISContextExtension.cs index 71fb563e1..ebd59fe2a 100644 --- a/IRaCIS.Core.Infra.EFCore/Repository/IRaCISContextExtension.cs +++ b/IRaCIS.Core.Infra.EFCore/Repository/IRaCISContextExtension.cs @@ -119,7 +119,10 @@ namespace IRaCIS.Core.Infra.EFCore /// EntityState.Detached的实体 修改 部分字段 public static void EntityModifyPartialFiled(this IRaCISDBContext _dbContext, T waitModifyEntity, Expression> updateFactory) where T : Entity { - var entityEntry = _dbContext.Entry(waitModifyEntity); + //解决重复跟踪问题 + var tracked = _dbContext.ChangeTracker.Entries().FirstOrDefault(e => e.Entity.Id.Equals(waitModifyEntity.Id)); + var entityEntry = tracked?? _dbContext.Entry(waitModifyEntity); + //entityEntry.State = EntityState.Detached; var list = ((MemberInitExpression)updateFactory.Body).Bindings.Select(mb => mb.Member.Name) @@ -134,7 +137,7 @@ namespace IRaCIS.Core.Infra.EFCore foreach (PropertyInfo prop in list) { - _dbContext.Entry(waitModifyEntity).Property(prop.Name).IsModified = true; + entityEntry.Property(prop.Name).IsModified = true; object value = prop.GetValue(applyObj); prop.SetValue(waitModifyEntity, value); From 9b90ded79f32474d4e4b337db180310f59669c62 Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Wed, 21 Jan 2026 09:42:51 +0800 Subject: [PATCH 12/14] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E9=AA=8C=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NewtonsoftJson/JSONTimeZoneConverter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/JSONTimeZoneConverter.cs b/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/JSONTimeZoneConverter.cs index 87fda8456..37853b02a 100644 --- a/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/JSONTimeZoneConverter.cs +++ b/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/JSONTimeZoneConverter.cs @@ -90,8 +90,8 @@ namespace IRaCIS.Core.API if (DateTime.TryParse((string)reader.Value, out dateTime) == false) { - throw new JsonSerializationException($"Could not convert string to DateTime: {reader.Value} Path {reader.Path}"); - //return null; + //throw new JsonSerializationException($"Could not convert string to DateTime: {reader.Value} Path {reader.Path}"); + return null; } } From 1aa1189ae80874aea98e12851950917f2c9b1057 Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Wed, 21 Jan 2026 10:23:30 +0800 Subject: [PATCH 13/14] =?UTF-8?q?=E6=97=B6=E9=97=B4=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E9=AA=8C=E8=AF=81=E5=A2=9E=E5=8A=A0=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NewtonsoftJson/JSONTimeZoneConverter.cs | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/JSONTimeZoneConverter.cs b/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/JSONTimeZoneConverter.cs index 37853b02a..6f2623694 100644 --- a/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/JSONTimeZoneConverter.cs +++ b/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/JSONTimeZoneConverter.cs @@ -71,32 +71,25 @@ namespace IRaCIS.Core.API DateTime dateTime; - if (reader.ValueType == typeof(DateTime) || reader.ValueType == typeof(DateTime?)) + // 2. 检查目标类型是否可空 + bool isNullable = objectType == typeof(DateTime?); + + var canConvert = DateTime.TryParse(reader.Value?.ToString(), out dateTime); + + + if (isNullable == false && canConvert == false) { - DateTime? nullableDateTime = reader.Value as DateTime?; - - if (nullableDateTime != null && nullableDateTime.HasValue) - { - dateTime = nullableDateTime.Value; - } - else - { - return null; - } + throw new JsonSerializationException($"Could not convert string to DateTime: {reader.Value} Path {reader.Path}"); } else { - if (DateTime.TryParse((string)reader.Value, out dateTime) == false) + if (canConvert == false) { - - //throw new JsonSerializationException($"Could not convert string to DateTime: {reader.Value} Path {reader.Path}"); return null; } } - - // 将客户端时间转换为服务器时区的时间 var serverZoneTime = TimeZoneInfo.ConvertTime(dateTime, _clientTimeZone, TimeZoneInfo.Local); From f8b7e7d764e53699e2c9c52ec00ae2eb1fa2a357 Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Wed, 21 Jan 2026 11:48:13 +0800 Subject: [PATCH 14/14] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=B8=A6=E6=97=B6=E5=8C=BA=E8=BF=94=E5=9B=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NewtonsoftJson/JSONTimeZoneConverter.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/JSONTimeZoneConverter.cs b/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/JSONTimeZoneConverter.cs index 6f2623694..16e4155c4 100644 --- a/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/JSONTimeZoneConverter.cs +++ b/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/JSONTimeZoneConverter.cs @@ -90,6 +90,9 @@ namespace IRaCIS.Core.API } } + ////如果前端传递带时区的,那么转换会报错,需要DateTimeKind.Unspecified + //dateTime = DateTime.SpecifyKind(dateTime, DateTimeKind.Unspecified); + // 将客户端时间转换为服务器时区的时间 var serverZoneTime = TimeZoneInfo.ConvertTime(dateTime, _clientTimeZone, TimeZoneInfo.Local); @@ -109,6 +112,10 @@ namespace IRaCIS.Core.API DateTime clientZoneTime = TimeZoneInfo.ConvertTime(nullableDateTime.Value, _clientTimeZone); + //// 最简单的方式:创建 DateTimeOffset + //DateTimeOffset dateTimeOffset = new DateTimeOffset(clientZoneTime, _clientTimeZone.GetUtcOffset(clientZoneTime)); + //writer.WriteValue(dateTimeOffset.ToString("yyyy-MM-dd HH:mm:sszzz")); + writer.WriteValue(clientZoneTime.ToString(_dateFormat)); } else