Merge branch 'Test_IRC_Net10' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net10
continuous-integration/drone/push Build is running

This commit is contained in:
he
2026-06-29 15:35:34 +08:00
14 changed files with 22660 additions and 7 deletions
@@ -3,8 +3,11 @@ using FellowOakDicom.Imaging;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.PixelFormats;
using IRaCIS.Core.Application.Helper;
using SixLabors.ImageSharp.Drawing.Processing;
namespace IRaCIS.Core.Application.Helper;
public static class ImageHelper
{
@@ -28,7 +31,7 @@ public static class ImageHelper
image.Save(fileStorePath, new JpegEncoder());
}
}
@@ -60,6 +63,36 @@ public static class ImageHelper
}
public static class ImageMaskHelper
{
public static async Task MaskAsync(Stream input, Stream output, IEnumerable<MaskRegion> regions)
{
// 确保输入流从起始位置读取
if (input.CanSeek && input.Position != 0)
{
input.Position = 0;
}
using var image = await Image.LoadAsync(input);
// 保存原始图片的编码格式
var originalFormat = image.Metadata.DecodedImageFormat;
image.Mutate(ctx =>
{
foreach (var r in regions)
{
ctx.Fill(Color.White, new Rectangle(r.X, r.Y, r.Width, r.Height));
}
});
// 使用原始格式保存,保持格式不变
await image.SaveAsync(output, originalFormat);
}
}
}
@@ -61,6 +61,7 @@
<PackageReference Include="Panda.DynamicWebApi" Version="1.2.2" />
<PackageReference Include="RestSharp" Version="114.0.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.7" />
<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="10.0.1" />
</ItemGroup>
@@ -18804,11 +18804,24 @@
NoneDicomStudyService
</summary>
</member>
<member name="M:IRaCIS.Core.Application.Contracts.NoneDicomStudyService.#ctor(IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.NoneDicomStudy},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.NoneDicomStudyFile},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.Trial},Medallion.Threading.IDistributedLockProvider,IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.SubjectVisit},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.VisitTask},IRaCIS.Core.Application.Service.QCCommon,AutoMapper.IMapper,IRaCIS.Core.Domain.Share.IUserInfo,Microsoft.Extensions.Localization.IStringLocalizer)">
<member name="M:IRaCIS.Core.Application.Contracts.NoneDicomStudyService.#ctor(IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.NoneDicomStudy},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.NoneDicomStudyFile},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.Trial},Medallion.Threading.IDistributedLockProvider,IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.SubjectVisit},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.VisitTask},IRaCIS.Core.Application.Helper.IOSSService,IRaCIS.Core.Application.Service.QCCommon,AutoMapper.IMapper,IRaCIS.Core.Domain.Share.IUserInfo,Microsoft.Extensions.Localization.IStringLocalizer)">
<summary>
NoneDicomStudyService
</summary>
</member>
<member name="M:IRaCIS.Core.Application.Contracts.NoneDicomStudyService.NoneDicomStudyMaskImage(IRaCIS.Core.Application.Contracts.NoneDicomStudyMaskImageCommand)">
<summary>
标注遮盖影像 路径后面加了.MaskNoneDicom_ 就是遮盖的新路径
</summary>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.Application.Contracts.NoneDicomStudyService.NoneDicomStudyUndoMaskImage(IRaCIS.Core.Application.Contracts.NoneDicomStudyUndoMaskImageCommand)">
<summary>
撤销遮盖的影像,可以单张,也可以整个检查
</summary>
<param name="inCommand"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.Application.Contracts.NoneDicomStudyService.GetNoneDicomStudyFileList(System.Guid)">
<summary>
非Dicom检查 文件列表
@@ -749,6 +749,17 @@ namespace IRaCIS.Core.Application.Contracts
public long? FileSize { get; set; }
}
public class DownloadFileDto
{
public Guid Id { get; set; }
public string Path { get; set; }
public string FileName { get; set; }
public string FileType { get; set; }
}
public class DownloadNoneDicomStudyDto
{
public string Modality { get; set; }
@@ -1164,6 +1175,20 @@ namespace IRaCIS.Core.Application.Contracts
public List<MaskRegion> MaskRegionList { get; set; }
}
public class NoneDicomStudyMaskImageCommand
{
public Guid? NodicomStudyId { get; set; }
public List<Guid>? NoneDicomStudyFileIdList { get; set; }
public List<MaskRegion> MaskRegionList { get; set; }
}
public class NoneDicomStudyUndoMaskImageCommand
{
public Guid? NodicomStudyId { get; set; }
public List<Guid>? NoneDicomStudyFileIdList { get; set; }
}
public class StudyUndoMaskImageCommand
@@ -3,10 +3,12 @@
// 生成时间 2021-12-06 10:54:55
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//--------------------------------------------------------------------
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Infrastructure;
using Medallion.Threading;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
using static ImageHelper;
namespace IRaCIS.Core.Application.Contracts
{
@@ -19,11 +21,176 @@ namespace IRaCIS.Core.Application.Contracts
IRepository<Trial> _trialRepository,
IDistributedLockProvider _distributedLockProvider,
IRepository<SubjectVisit> _subjectVisitRepository,
IRepository<VisitTask> _visitTaskRepository,
IRepository<VisitTask> _visitTaskRepository, IOSSService _oSSService,
QCCommon _qCCommon, IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer) : BaseService, INoneDicomStudyService
{
/// <summary>
/// 标注遮盖影像 路径后面加了.MaskNoneDicom_ 就是遮盖的新路径
/// </summary>
/// <returns></returns>
[HttpPost]
public async Task<IResponseOutput> NoneDicomStudyMaskImage(NoneDicomStudyMaskImageCommand inCommand)
{
if (inCommand.NodicomStudyId == null && inCommand.NoneDicomStudyFileIdList == null)
{
return ResponseOutput.NotOk("NodicomStudyId and NoneDicomStudyFileIdList can not both be null");
}
var idPathList = new List<DownloadFileDto>();
if (inCommand.NodicomStudyId == null && inCommand.NoneDicomStudyFileIdList != null)
{
idPathList = await _noneDicomStudyFileRepository.Where(t => inCommand.NoneDicomStudyFileIdList.Contains(t.Id)).ProjectTo<DownloadFileDto>(_mapper.ConfigurationProvider).ToListAsync();
}
else
{
idPathList = await _noneDicomStudyFileRepository.Where(t => inCommand.NodicomStudyId == t.NoneDicomStudyId).ProjectTo<DownloadFileDto>(_mapper.ConfigurationProvider).ToListAsync();
}
var errorList = new List<DownloadFileDto>();
var okList = new List<DownloadFileDto>();
var batchId = Guid.NewGuid();
foreach (var item in idPathList)
{
var path = item.Path;
try
{
// 1. 下载文件
using var inputStream = new MemoryStream();
await (await _oSSService.GetStreamFromOSSAsync(path)).CopyToAsync(inputStream);
inputStream.Position = 0;
// 2. 处理图片
using var outputStream = new MemoryStream();
await ImageMaskHelper.MaskAsync(inputStream, outputStream, inCommand.MaskRegionList);
outputStream.Position = 0;
var prefix = path.Substring(1, path.LastIndexOf('/') - 1);
//每次都用一个新的名字
var maskFileName = string.Empty;
if (path.Contains(".MaskNoneDicom_"))
{
//清理缓存的里面的遮盖图,多次遮盖同一张图时,清除缓存很重要
await _oSSService.DeleteFromPrefix(path, true); //清理缓存的里面的遮盖图,多次遮盖同一张图时,清除缓存很重要
//本身就是遮盖的图,那么就要要替换guid
var length = Guid.Empty.ToString().Length + ".MaskNoneDicom_".Length;
var restorePath = item.Path[..^length];
maskFileName = $"{Path.GetFileName(restorePath)}.MaskNoneDicom_{batchId}";
}
else
{
//没有遮盖的,直接加上.批次_MaskDicom
maskFileName = $"{Path.GetFileName(path)}.MaskNoneDicom_{batchId}";
}
await _oSSService.UploadToOSSAsync(outputStream, prefix, maskFileName, false);
var newPath = $"/{prefix}/{maskFileName}";
okList.Add(new DownloadFileDto() { Id = item.Id, Path = newPath });
await _noneDicomStudyFileRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Id, u => new NoneDicomStudyFile() { Path = newPath, IsMasked = true });
}
catch (Exception ex)
{
errorList.Add(new DownloadFileDto() { Id = item.Id, Path = path });
Log.Logger.Error(ex, $"NoneDicomStudyMaskImage Error for NoneDicomStudyFileIdList Path:{path} {ex.Message}");
}
}
await _noneDicomStudyFileRepository.SaveChangesAsync();
return ResponseOutput.Ok(new { OkList = okList, ErrorList = errorList });
}
/// <summary>
/// 撤销遮盖的影像,可以单张,也可以整个检查
/// </summary>
/// <param name="inCommand"></param>
/// <returns></returns>
[HttpPost]
public async Task<IResponseOutput> NoneDicomStudyUndoMaskImage(NoneDicomStudyUndoMaskImageCommand inCommand)
{
if (inCommand.NodicomStudyId == null && inCommand.NoneDicomStudyFileIdList == null)
{
return ResponseOutput.NotOk("NodicomStudyId and NoneDicomStudyFileIdList can not both be null");
}
var idPathList = new List<DownloadFileDto>();
if (inCommand.NodicomStudyId == null && inCommand.NoneDicomStudyFileIdList != null)
{
idPathList = await _noneDicomStudyFileRepository.Where(t => inCommand.NoneDicomStudyFileIdList.Contains(t.Id)).ProjectTo<DownloadFileDto>(_mapper.ConfigurationProvider).ToListAsync();
}
else
{
idPathList = await _noneDicomStudyFileRepository.Where(t => inCommand.NodicomStudyId == t.NoneDicomStudyId).ProjectTo<DownloadFileDto>(_mapper.ConfigurationProvider).ToListAsync();
}
var okList = new List<DownloadFileDto>();
foreach (var item in idPathList)
{
if (item.Path.Contains(".MaskNoneDicom_"))
{
await _oSSService.DeleteFromPrefix(item.Path, true);
var length = Guid.Empty.ToString().Length + ".MaskNoneDicom_".Length;
var newPath = item.Path[..^length];
okList.Add(new DownloadFileDto() { Id = item.Id, Path = newPath });
await _noneDicomStudyFileRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Id, u => new NoneDicomStudyFile() { Path = newPath, IsMasked = false });
}
//if (item.Path.EndsWith("."))
//{
// await _oSSService.DeleteFromPrefix(item.Path, true);
// var newPath = item.Path[..^1];
// okList.Add(new InstanceIdPath() { Id = item.Id, Path = newPath });
// await _dicomInstanceRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Id, u => new DicomInstance() { Path = newPath, IsMasked = false });
//}
}
await _noneDicomStudyFileRepository.SaveChangesAsync();
return ResponseOutput.Ok(new { OkList = okList });
}
[HttpGet]
public async Task<IResponseOutput<List<NoneDicomStudyView>>> GetNoneDicomStudyList(
@@ -187,8 +187,13 @@ namespace IRaCIS.Core.Application.Service
CreateMap<DicomInstance, DownloadDicomInstanceDto>()
.ForMember(d => d.InstanceId, u => u.MapFrom(s => s.Id));
CreateMap<NoneDicomStudyFile, DownloadFileDto>();
}
}
@@ -103,6 +103,8 @@ namespace IRaCIS.Application.Contracts
public bool IsZhiZhun { get; set; }
public bool IsFirstAdd { get; set; }
public bool IsNeedResetPwd { get; set; }
public bool NeedChangePassWord { get; set; } = false;
@@ -301,7 +301,7 @@ namespace IRaCIS.Core.Application.Service
await _identityUserRepository.UpdatePartialFromQueryAsync(t => t.Id == identityUserId, u => new IdentityUser()
{
Password = MD5Helper.Md5(pwd),
IsFirstAdd = true
IsNeedResetPwd = true
});
var userName = _identityUserRepository.Where(t => t.Id == identityUserId).Select(t => t.UserName).FirstOrDefault();
@@ -489,7 +489,7 @@ namespace IRaCIS.Core.Application.Service
{
Password = editPwModel.NewPassWord,
LastChangePassWordTime = DateTime.Now,
IsFirstAdd = false
IsNeedResetPwd = false
});
await _userPassWordLogRepository.AddAsync(new UserPassWordLog()