Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
commit
6874459046
|
|
@ -194,6 +194,7 @@ app.MapControllers();
|
|||
Log.Logger = new LoggerConfiguration()
|
||||
//.MinimumLevel.Information()
|
||||
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
|
||||
.MinimumLevel.Override("ZiggyCreatures.Caching.Fusion", LogEventLevel.Warning)
|
||||
.WriteTo.Console()
|
||||
.WriteTo.File($"{AppContext.BaseDirectory}Serilogs/.log", rollingInterval: RollingInterval.Day)
|
||||
.CreateLogger();
|
||||
|
|
|
|||
|
|
@ -1,28 +1,29 @@
|
|||
using FellowOakDicom.Network;
|
||||
using FellowOakDicom;
|
||||
using FellowOakDicom;
|
||||
using FellowOakDicom.Imaging;
|
||||
using FellowOakDicom.IO.Buffer;
|
||||
using FellowOakDicom.Network;
|
||||
using IRaCIS.Core.Domain.Models;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using IRaCIS.Core.Infra.EFCore;
|
||||
using IRaCIS.Core.Infrastructure;
|
||||
using IRaCIS.Core.Infrastructure.Extention;
|
||||
using IRaCIS.Core.SCP.Service;
|
||||
using Medallion.Threading;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Serilog;
|
||||
using SharpCompress.Common;
|
||||
using SixLabors.ImageSharp.Formats.Jpeg;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using IRaCIS.Core.SCP.Service;
|
||||
using IRaCIS.Core.Domain.Models;
|
||||
using IRaCIS.Core.Infra.EFCore;
|
||||
using Medallion.Threading;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using Serilog;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Data;
|
||||
using FellowOakDicom.Imaging;
|
||||
using SharpCompress.Common;
|
||||
using SixLabors.ImageSharp.Formats.Jpeg;
|
||||
using IRaCIS.Core.Infrastructure;
|
||||
using IRaCIS.Core.Infrastructure.Extention;
|
||||
using FellowOakDicom.IO.Buffer;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
|
||||
namespace IRaCIS.Core.SCP.Service
|
||||
{
|
||||
|
|
@ -328,6 +329,11 @@ namespace IRaCIS.Core.SCP.Service
|
|||
var _seriesRepository = _serviceProvider.GetService<IRepository<SCPSeries>>();
|
||||
|
||||
var _distributedLockProvider = _serviceProvider.GetService<IDistributedLockProvider>();
|
||||
var _fusionCache = _serviceProvider.GetService<IFusionCache>();
|
||||
var _trialSiteRepository = _serviceProvider.GetService<IRepository<TrialSite>>();
|
||||
var _systemAnonymizationRepository = _serviceProvider.GetService<IRepository<SystemAnonymization>>();
|
||||
|
||||
|
||||
|
||||
var storeRelativePath = string.Empty;
|
||||
var ossFolderPath = $"{_trialId}/Image/PACS/{_trialSiteId}/{studyInstanceUid}";
|
||||
|
|
@ -336,15 +342,73 @@ namespace IRaCIS.Core.SCP.Service
|
|||
long fileSize = 0;
|
||||
try
|
||||
{
|
||||
using (MemoryStream ms = new MemoryStream())
|
||||
// 直接拿 Dataset(已经完整)
|
||||
var dataset = request.Dataset;
|
||||
|
||||
#region 匿名化
|
||||
|
||||
|
||||
var anonymizeList = await _fusionCache.GetOrSetAsync(CacheKeys.SystemAnonymization, _ => CacheHelper.GetSystemAnonymizationListAsync(_systemAnonymizationRepository), TimeSpan.FromDays(7));
|
||||
|
||||
var trialSiteInfo = await _fusionCache.GetOrSetAsync(CacheKeys.TrialSiteInfo(_trialSiteId), _ => CacheHelper.GetTrialSiteInfo(_trialSiteId, _trialSiteRepository), TimeSpan.FromMinutes(2));
|
||||
|
||||
var fixedFiledList = anonymizeList.Where(t => t.IsFixed).ToList();
|
||||
|
||||
var ircFiledList = anonymizeList.Where(t => t.IsFixed == false).ToList();
|
||||
|
||||
foreach (var item in fixedFiledList)
|
||||
{
|
||||
await request.File.SaveAsync(ms);
|
||||
|
||||
var dicomTag = new DicomTag(Convert.ToUInt16(item.Group, 16), Convert.ToUInt16(item.Element, 16));
|
||||
|
||||
dataset.AddOrUpdate(dicomTag, item.ReplaceValue);
|
||||
}
|
||||
|
||||
foreach (var item in ircFiledList)
|
||||
{
|
||||
|
||||
var dicomTag = new DicomTag(Convert.ToUInt16(item.Group, 16), Convert.ToUInt16(item.Element, 16));
|
||||
|
||||
if (dicomTag == DicomTag.ClinicalTrialProtocolID)
|
||||
{
|
||||
dataset.AddOrUpdate(DicomTag.ClinicalTrialProtocolID, trialSiteInfo.TrialCode);
|
||||
|
||||
}
|
||||
if (dicomTag == DicomTag.ClinicalTrialSiteID)
|
||||
{
|
||||
dataset.AddOrUpdate(DicomTag.ClinicalTrialSiteID, trialSiteInfo.TrialSiteCode);
|
||||
|
||||
}
|
||||
|
||||
if (dicomTag == DicomTag.ClinicalTrialSubjectID)
|
||||
{
|
||||
dataset.AddOrUpdate(DicomTag.ClinicalTrialSubjectID, "");
|
||||
|
||||
}
|
||||
if (dicomTag == DicomTag.ClinicalTrialTimePointID)
|
||||
{
|
||||
dataset.AddOrUpdate(DicomTag.ClinicalTrialTimePointID, "");
|
||||
}
|
||||
|
||||
if (dicomTag == DicomTag.PatientID)
|
||||
{
|
||||
var pid = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty);
|
||||
|
||||
dataset.AddOrUpdate(DicomTag.PatientID, trialSiteInfo.TrialCode + "-" + pid);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
// 构造 DicomFile(不用 Open)
|
||||
var dicomFile = new DicomFile(dataset);
|
||||
|
||||
|
||||
|
||||
#region 1帧拆成多个固定大小的,方便移动端浏览
|
||||
|
||||
// 回到开头,读取 dicom
|
||||
ms.Position = 0;
|
||||
var dicomFile = DicomFile.Open(ms);
|
||||
|
||||
|
||||
var numberOfFrames = dicomFile.Dataset.GetSingleValueOrDefault(DicomTag.NumberOfFrames, 1);
|
||||
|
||||
|
|
@ -488,10 +552,6 @@ namespace IRaCIS.Core.SCP.Service
|
|||
dicomFile.Dataset.AddOrUpdate(newFragments);
|
||||
|
||||
|
||||
// 重新保存 dicom 到流
|
||||
ms.SetLength(0);
|
||||
|
||||
dicomFile.Save(ms);
|
||||
}
|
||||
//传递过来的就是拆分的,但是是没有偏移表的,我需要自己创建偏移表,不然生成缩略图失败
|
||||
else if (syntax.IsEncapsulated && fragmentCount > pixelData.NumberOfFrames && originOffsetTable.Count == 0)
|
||||
|
|
@ -545,10 +605,7 @@ namespace IRaCIS.Core.SCP.Service
|
|||
frag.OffsetTable.AddRange(bot.ToArray());
|
||||
|
||||
|
||||
// 重新保存 DICOM 到流
|
||||
ms.SetLength(0);
|
||||
|
||||
dicomFile.Save(ms);
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -558,19 +615,12 @@ namespace IRaCIS.Core.SCP.Service
|
|||
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} 处理多帧失败,上传原始文件:{mutiEx.ToString()}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region 本地测试
|
||||
//// --- 保存到本地文件测试 ---
|
||||
//var localPath = @"D:\TestDicom.dcm";
|
||||
|
|
@ -582,6 +632,9 @@ namespace IRaCIS.Core.SCP.Service
|
|||
|
||||
#endregion
|
||||
|
||||
// 直接写入内存
|
||||
await using var ms = new MemoryStream();
|
||||
await dicomFile.SaveAsync(ms);
|
||||
ms.Position = 0;
|
||||
|
||||
//irc 从路径最后一截取Guid
|
||||
|
|
@ -667,7 +720,6 @@ namespace IRaCIS.Core.SCP.Service
|
|||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Log.Logger.Information($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE} {request.SOPInstanceUID} 上传完成 ");
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,101 @@
|
|||
using IRaCIS.Core.Domain.Models;
|
||||
using IRaCIS.Core.Infra.EFCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace IRaCIS.Core.SCP.Service;
|
||||
|
||||
|
||||
public static class CacheKeys
|
||||
{
|
||||
//项目缓存
|
||||
public static string Trial(string trialIdStr) => $"TrialId:{trialIdStr}";
|
||||
|
||||
//检查编号递增锁
|
||||
public static string TrialStudyMaxCode(Guid trialId) => $"TrialStudyMaxCode:{trialId}";
|
||||
|
||||
public static string TrialStudyUidUploading(Guid trialId, string studyUid) => $"TrialStudyUid:{trialId}_{studyUid}";
|
||||
//CRC上传影像提交锁key
|
||||
public static string TrialStudyUidDBLock(Guid trialId, string studyUid) => $"TrialStudyUidDBLock:{trialId}_{studyUid}";
|
||||
|
||||
public static string TrialTaskStudyUidUploading(Guid trialId, Guid visiTaskId, string studyUid) => $"TrialStudyUid:{trialId}_{visiTaskId}_{studyUid}";
|
||||
//影像后处理上传提交锁key
|
||||
public static string TrialTaskStudyUidDBLock(Guid trialId, Guid visiTaskId, string studyUid) => $"TrialTaskStudyUidDBLock:{trialId}_{visiTaskId}_{studyUid}";
|
||||
//系统匿名化
|
||||
public static string SystemAnonymization => $"SystemAnonymization";
|
||||
//前端国际化
|
||||
public static string FrontInternational => $"FrontInternationalList";
|
||||
|
||||
//登录挤账号
|
||||
public static string UserToken(Guid userId) => $"UserToken:{userId}";
|
||||
|
||||
//超时没请求接口自动退出
|
||||
public static string UserAutoLoginOut(Guid userId) => $"UserAutoLoginOut:{userId}";
|
||||
|
||||
|
||||
public static string UserDisable(Guid userId) => $"UserDisable:{userId}";
|
||||
|
||||
public static string UserRoleDisable(Guid userRoleId) => $"UserRoleDisable:{userRoleId}";
|
||||
|
||||
/// <summary>
|
||||
/// 用户登录错误 限制登录
|
||||
/// </summary>
|
||||
/// <param name="userName"></param>
|
||||
/// <returns></returns>
|
||||
public static string UserLoginError(string userName) => $"login-failures:{userName}";
|
||||
|
||||
/// <summary>
|
||||
/// 跳过阅片
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns></returns>
|
||||
public static string SkipReadingCacheKey(Guid userId) => $"{userId}SkipReadingCache";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 开始阅片时间
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns></returns>
|
||||
public static string StartReadingTimeKey(Guid userId) => $"{userId}StartReadingTime";
|
||||
|
||||
/// <summary>
|
||||
/// 开始休息时间
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns></returns>
|
||||
public static string StartRestTime(Guid userId) => $"{userId}StartRestTime";
|
||||
|
||||
//每个用户 每个浏览器独立时间
|
||||
public static string UserMFAVerifyPass(Guid userId, string browserFingerprint) => $"UserMFAVerifyPass:{userId}:{browserFingerprint}";
|
||||
|
||||
public static string TrialSiteInfo(Guid trialSiteId) => $"{trialSiteId}TrialSiteInfo";
|
||||
}
|
||||
|
||||
public static class CacheHelper
|
||||
{
|
||||
public static async Task<string?> GetTrialStatusAsync(Guid trialId, IRepository<Trial> _trialRepository)
|
||||
{
|
||||
var statusStr = await _trialRepository.Where(t => t.Id == trialId, ignoreQueryFilters: true).Select(t => t.TrialStatusStr).FirstOrDefaultAsync();
|
||||
|
||||
return statusStr;
|
||||
}
|
||||
|
||||
public class TrialSiteInfoDTO
|
||||
{
|
||||
public string TrialSiteCode { get; set; }
|
||||
public string TrialCode { get; set; }
|
||||
}
|
||||
public static async Task<TrialSiteInfoDTO> GetTrialSiteInfo(Guid trialSiteId, IRepository<TrialSite> _trialSiteRepository)
|
||||
{
|
||||
var obj = await _trialSiteRepository.Where(t => t.Id == trialSiteId, ignoreQueryFilters: true).Select(t => new TrialSiteInfoDTO { TrialCode= t.Trial.TrialCode, TrialSiteCode= t.TrialSiteCode }).FirstOrDefaultAsync();
|
||||
|
||||
return obj??new TrialSiteInfoDTO();
|
||||
}
|
||||
|
||||
public static async Task<List<SystemAnonymization>> GetSystemAnonymizationListAsync(IRepository<SystemAnonymization> _systemAnonymizationRepository)
|
||||
{
|
||||
var list = await _systemAnonymizationRepository.Where(t => t.IsEnable).ToListAsync();
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,49 +1,32 @@
|
|||
using IRaCIS.Core.Domain.Share;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using IRaCIS.Core.Infrastructure;
|
||||
using Medallion.Threading;
|
||||
using FellowOakDicom;
|
||||
using FellowOakDicom;
|
||||
using FellowOakDicom.Imaging.Codec;
|
||||
using System.Data;
|
||||
using IRaCIS.Core.Domain.Models;
|
||||
using FellowOakDicom.Network;
|
||||
using IRaCIS.Core.SCP.Service;
|
||||
using IRaCIS.Core.Domain.Models;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using IRaCIS.Core.Infra.EFCore;
|
||||
using MassTransit;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
using Serilog.Sinks.File;
|
||||
using IRaCIS.Core.Infrastructure;
|
||||
using IRaCIS.Core.Infrastructure.Extention;
|
||||
using IRaCIS.Core.SCP.Service;
|
||||
using MassTransit;
|
||||
using Medallion.Threading;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Serilog.Sinks.File;
|
||||
using System.Data;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
using System.Text;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
using static IRaCIS.Core.SCP.Service.CacheHelper;
|
||||
|
||||
namespace IRaCIS.Core.SCP.Service
|
||||
{
|
||||
public class DicomArchiveService : BaseService, IDicomArchiveService
|
||||
public class DicomArchiveService(IRepository<SCPPatient> _patientRepository,
|
||||
IRepository<SCPStudy> _studyRepository,
|
||||
IRepository<SCPSeries> _seriesRepository,
|
||||
IRepository<SCPInstance> _instanceRepository
|
||||
) : BaseService, IDicomArchiveService
|
||||
{
|
||||
private readonly IRepository<SCPPatient> _patientRepository;
|
||||
private readonly IRepository<SCPStudy> _studyRepository;
|
||||
private readonly IRepository<SCPSeries> _seriesRepository;
|
||||
private readonly IRepository<SCPInstance> _instanceRepository;
|
||||
private readonly IRepository<Dictionary> _dictionaryRepository;
|
||||
private readonly IDistributedLockProvider _distributedLockProvider;
|
||||
|
||||
|
||||
private List<Guid> _instanceIdList = new List<Guid>();
|
||||
|
||||
public DicomArchiveService(IRepository<SCPPatient> patientRepository, IRepository<SCPStudy> studyRepository,
|
||||
IRepository<SCPSeries> seriesRepository,
|
||||
IRepository<SCPInstance> instanceRepository,
|
||||
IRepository<Dictionary> dictionaryRepository,
|
||||
IDistributedLockProvider distributedLockProvider)
|
||||
{
|
||||
_distributedLockProvider = distributedLockProvider;
|
||||
_studyRepository = studyRepository;
|
||||
_patientRepository = patientRepository;
|
||||
_seriesRepository = seriesRepository;
|
||||
_instanceRepository = instanceRepository;
|
||||
_dictionaryRepository = dictionaryRepository;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -55,6 +38,7 @@ namespace IRaCIS.Core.SCP.Service
|
|||
/// <exception cref="NotImplementedException"></exception>
|
||||
public async Task<Guid> ArchiveDicomFileAsync(DicomFile dicomFile, Guid trialId, Guid trialSiteId, string fileRelativePath, string callingAE, string calledAE, long fileSize)
|
||||
{
|
||||
|
||||
var dataset = dicomFile.Dataset;
|
||||
|
||||
string studyInstanceUid = dataset.GetString(DicomTag.StudyInstanceUID);
|
||||
|
|
@ -245,6 +229,7 @@ namespace IRaCIS.Core.SCP.Service
|
|||
findStudy.DicomStudyTime = dataset.GetSingleValueOrDefault(DicomTag.StudyTime, string.Empty);
|
||||
findStudy.CalledAE = calledAE;
|
||||
findStudy.CallingAE = callingAE;
|
||||
findStudy.PatientIdStr = patientIdStr;
|
||||
findStudy.PatientName = dataset.GetSingleValueOrDefault(DicomTag.PatientName, string.Empty);
|
||||
findStudy.PatientSex = dataset.GetSingleValueOrDefault(DicomTag.PatientSex, string.Empty);
|
||||
findStudy.PatientAge = dataset.GetSingleValueOrDefault(DicomTag.PatientAge, string.Empty);
|
||||
|
|
|
|||
|
|
@ -57,8 +57,8 @@
|
|||
|
||||
// 模板类型 1 Elevate 2 Extensive
|
||||
"TemplateType": 2,
|
||||
//MFA免验证发送天数
|
||||
"UserMFAVerifyDays": 1
|
||||
|
||||
"UserMFAVerifyMinutes": 1440
|
||||
},
|
||||
"SystemEmailSendConfig": {
|
||||
"Port": 465,
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@
|
|||
// 模板类型 1 Elevate 2 Extensive
|
||||
"TemplateType": 2,
|
||||
//MFA免验证发送天数
|
||||
"UserMFAVerifyDays": 1
|
||||
"UserMFAVerifyMinutes": 1440
|
||||
},
|
||||
|
||||
"SystemEmailSendConfig": {
|
||||
|
|
|
|||
|
|
@ -59,8 +59,8 @@
|
|||
"TemplateType": 1,
|
||||
|
||||
"OpenTrialRelationDelete": false,
|
||||
//MFA免验证发送天数
|
||||
"UserMFAVerifyDays": 1
|
||||
//MFA免验证
|
||||
"UserMFAVerifyMinutes": 1440
|
||||
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -69,7 +69,8 @@
|
|||
"TemplateType": 1,
|
||||
"OpenLoginMFA": true,
|
||||
//MFA免验证发送天数
|
||||
"UserMFAVerifyDays": 1
|
||||
"UserMFAVerifyMinutes": 1440
|
||||
|
||||
},
|
||||
|
||||
"SystemEmailSendConfig": {
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@
|
|||
// 模板类型 1 Elevate 2 Extensive
|
||||
"TemplateType": 1,
|
||||
//MFA免验证发送天数
|
||||
"UserMFAVerifyDays": 1
|
||||
"UserMFAVerifyMinutes": 1440
|
||||
},
|
||||
|
||||
"SystemEmailSendConfig": {
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@
|
|||
// 模板类型 1 Elevate 2 Extensive
|
||||
"TemplateType": 2,
|
||||
//MFA免验证发送天数
|
||||
"UserMFAVerifyDays": 1
|
||||
"UserMFAVerifyMinutes": 1440
|
||||
|
||||
},
|
||||
"SystemEmailSendConfig": {
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ public class ProjectExceptionFilter(ILogger<ProjectExceptionFilter> _logger, ISt
|
|||
else
|
||||
{
|
||||
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["Project_ExceptionContactDeveloper"] + (exception.InnerException is null ? (exception.Message)
|
||||
: (exception.InnerException?.Message )), ApiResponseCodeEnum.ProgramException));
|
||||
: (exception.Message + "Inner ExceptionMsg:" + exception.InnerException?.Message)), ApiResponseCodeEnum.ProgramException));
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -128,7 +128,9 @@ namespace IRaCIS.Core.Application.Helper
|
|||
// 重置流位置
|
||||
memoryStream.Position = 0;
|
||||
|
||||
await _oSSService.UploadToOSSAsync(memoryStream, ossFolder, "DICOMDIR", false);
|
||||
var relativePath= await _oSSService.UploadToOSSAsync(memoryStream, ossFolder, "DICOMDIR", true);
|
||||
|
||||
dic.Add("DICOMDIR" , relativePath.Split('/').Last());
|
||||
}
|
||||
|
||||
//清理临时文件
|
||||
|
|
|
|||
|
|
@ -143,9 +143,8 @@ public enum ObjectStoreUse
|
|||
|
||||
public interface IOSSService
|
||||
{
|
||||
public void SetImmediateArchiveRule(string prefix,
|
||||
StorageClass targetStorageClass,
|
||||
string ruleId = "immediate-archive");
|
||||
public Task SetImmediateArchiveRule(string prefix, string ruleId = "immediate-archive", bool isDelete = false);
|
||||
public Task RestoreFilesByPrefixAsync(string prefix, int restoreDays = 3, int batchSize = 100);
|
||||
|
||||
|
||||
public Task<string> UploadToOSSAsync(Stream fileStream, string oosFolderPath, string fileRealName, bool isFileNameAddGuid = true);
|
||||
|
|
@ -161,7 +160,7 @@ public interface IOSSService
|
|||
|
||||
public Task DeleteFromPrefix(string prefix, bool isCache = false);
|
||||
|
||||
public Task DeleteObjects(List<string> objectKeys);
|
||||
public Task DeleteObjects(List<string> objectKeys, bool isCache = false);
|
||||
|
||||
List<string> GetRootFolderNames();
|
||||
|
||||
|
|
@ -190,18 +189,19 @@ public class OSSService : IOSSService
|
|||
|
||||
/// <summary>
|
||||
/// 将指定前缀下的所有现有文件立即转为目标存储类型
|
||||
/// 核心:Days = 0 表示对所有存量文件立即生效
|
||||
/// </summary>
|
||||
/// <param name="prefix">要转换的文件前缀,如 "project-a/logs/"</param>
|
||||
/// <param name="targetStorageClass">目标存储类型</param>
|
||||
/// <param name="ruleId">规则ID,默认为"immediate-archive"</param>
|
||||
public void SetImmediateArchiveRule(string prefix,
|
||||
StorageClass targetStorageClass,
|
||||
string ruleId = "immediate-archive")
|
||||
/// <param name="isDelete">默认是添加/更新 </param>
|
||||
public async Task SetImmediateArchiveRule(string prefix, string ruleId = "immediate-archive", bool isDelete = false)
|
||||
{
|
||||
|
||||
BackBatchGetToken();
|
||||
|
||||
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
|
||||
{
|
||||
|
||||
|
||||
var aliConfig = ObjectStoreServiceOptions.AliyunOSS;
|
||||
|
||||
var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint, AliyunOSSTempToken.AccessKeyId, AliyunOSSTempToken.AccessKeySecret, AliyunOSSTempToken.SecurityToken);
|
||||
|
|
@ -242,7 +242,16 @@ public class OSSService : IOSSService
|
|||
{
|
||||
Days = 1
|
||||
},
|
||||
StorageClass = targetStorageClass
|
||||
StorageClass = StorageClass.IA
|
||||
},
|
||||
new Aliyun.OSS.LifecycleRule.LifeCycleTransition
|
||||
{
|
||||
|
||||
LifeCycleExpiration =
|
||||
{
|
||||
Days = 30 //最后一次修改时间
|
||||
},
|
||||
StorageClass = StorageClass.Archive
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -254,8 +263,13 @@ public class OSSService : IOSSService
|
|||
existingRules.RemoveAll(r => r.ID == ruleId);
|
||||
|
||||
// 4. 添加新规则到规则列表
|
||||
if (isDelete == false)
|
||||
{
|
||||
existingRules.Add(immediateRule);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
var request = new SetBucketLifecycleRequest(aliConfig.BucketName)
|
||||
{
|
||||
|
|
@ -265,107 +279,426 @@ public class OSSService : IOSSService
|
|||
|
||||
_ossClient.SetBucketLifecycle(request);
|
||||
|
||||
Console.WriteLine("✅ 立即归档规则设置成功!");
|
||||
Console.WriteLine($" 规则ID: {ruleId}");
|
||||
Console.WriteLine($" 前缀: {prefix}");
|
||||
Console.WriteLine($" 目标存储类型: {targetStorageClass}");
|
||||
Console.WriteLine($" 生效时间: 将在下次生命周期扫描时生效(通常24小时内)");
|
||||
|
||||
}
|
||||
catch (OssException ex)
|
||||
{
|
||||
Console.WriteLine($"❌ 设置失败 [错误码: {ex.ErrorCode}]");
|
||||
Console.WriteLine($" 详细: {ex.Message}");
|
||||
Log.Logger.Error($"❌ 设置失败 [错误码: {ex.ErrorCode}] 详细: {ex.Message}");
|
||||
|
||||
// 处理特定错误
|
||||
if (ex.ErrorCode == "InvalidArgument")
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(" 可能原因:存储类型不支持或参数格式错误");
|
||||
Log.Logger.Error($"❌ 发生未知错误: {ex.Message}");
|
||||
}
|
||||
}
|
||||
else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS")
|
||||
{
|
||||
var awsConfig = ObjectStoreServiceOptions.AWS;
|
||||
|
||||
var credentials = new SessionAWSCredentials(AWSTempToken.AccessKeyId, AWSTempToken.SecretAccessKey, AWSTempToken.SessionToken);
|
||||
|
||||
|
||||
|
||||
//提供awsEndPoint(域名)进行访问配置
|
||||
var clientConfig = new AmazonS3Config
|
||||
{
|
||||
RegionEndpoint = RegionEndpoint.GetBySystemName(awsConfig.Region)
|
||||
//,UseHttp = true,
|
||||
};
|
||||
|
||||
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
|
||||
|
||||
// 1. 获取现有的生命周期配置(避免覆盖)
|
||||
LifecycleConfiguration existingConfig = null;
|
||||
|
||||
var getRequest = new GetLifecycleConfigurationRequest { BucketName = awsConfig.BucketName };
|
||||
var response = await amazonS3Client.GetLifecycleConfigurationAsync(getRequest);
|
||||
existingConfig = response.Configuration;
|
||||
Console.WriteLine($"找到 {existingConfig?.Rules?.Count ?? 0} 条现有规则");
|
||||
|
||||
// 2. 生成唯一的规则ID
|
||||
ruleId = $"{ruleId}_{prefix.Replace('/', '_').Trim('_')}";
|
||||
|
||||
// 3. 创建新的生命周期规则
|
||||
var immediateRule = new Amazon.S3.Model.LifecycleRule
|
||||
{
|
||||
Id = ruleId,
|
||||
Filter = new LifecycleFilter
|
||||
{
|
||||
// 使用前缀筛选对象
|
||||
LifecycleFilterPredicate = new LifecyclePrefixPredicate { Prefix = prefix }
|
||||
},
|
||||
Status = LifecycleRuleStatus.Enabled,
|
||||
// 定义多个转换阶段
|
||||
Transitions = new List<LifecycleTransition>
|
||||
{
|
||||
// 1天后转为低频访问 (Standard-IA)
|
||||
//new LifecycleTransition
|
||||
//{
|
||||
// Days = 1, //Days' in Transition action must be greater than or equal to 30 for storageClass 'STANDARD_IA'"
|
||||
// StorageClass = S3StorageClass.StandardInfrequentAccess // 对应S3 Standard-IA
|
||||
//},
|
||||
// 30天后转为归档 (Glacier Instant Retrieval)
|
||||
new LifecycleTransition
|
||||
{
|
||||
Days = 30, //创建时间
|
||||
StorageClass = S3StorageClass.GlacierInstantRetrieval // 对应归档(即时检索)
|
||||
}
|
||||
// 如果需要更深的归档,可以继续添加:
|
||||
// new LifecycleTransition { Days = 90, StorageClass = S3StorageClass.GlacierFlexibleRetrieval },
|
||||
// new LifecycleTransition { Days = 180, StorageClass = S3StorageClass.DeepArchive }
|
||||
}
|
||||
// 注意:S3的生命周期规则不支持设置“立即生效(Days=0)”。
|
||||
// 如果要对存量文件立即生效,需要配合其他方法(如批量修改存储类型)。
|
||||
};
|
||||
|
||||
// 4. 更新规则列表(移除同名旧规则,添加新规则)
|
||||
var existingRules = existingConfig.Rules ?? new List<Amazon.S3.Model.LifecycleRule>();
|
||||
existingRules.RemoveAll(r => r.Id == ruleId);
|
||||
|
||||
if (isDelete == false)
|
||||
{
|
||||
existingRules.Add(immediateRule);
|
||||
|
||||
}
|
||||
|
||||
// 5. 提交新的生命周期配置
|
||||
var putRequest = new PutLifecycleConfigurationRequest
|
||||
{
|
||||
BucketName = awsConfig.BucketName,
|
||||
Configuration = new LifecycleConfiguration { Rules = existingRules }
|
||||
};
|
||||
|
||||
await amazonS3Client.PutLifecycleConfigurationAsync(putRequest);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new BusinessValidationFailedException("未定义的存储介质类型");
|
||||
}
|
||||
}
|
||||
|
||||
/// 解冻指定前缀下的所有归档/冷归档文件
|
||||
/// </summary>
|
||||
/// <param name="prefix">要解冻的文件前缀</param>
|
||||
/// <param name="restoreDays">解冻后文件保持可读的天数(默认3天)</param>
|
||||
/// <param name="restoreTier">解冻优先级(仅AWS有效)</param>
|
||||
/// <param name="batchSize">批量处理大小(默认100)</param>
|
||||
public async Task RestoreFilesByPrefixAsync(string prefix, int restoreDays = 3, int batchSize = 100)
|
||||
{
|
||||
BackBatchGetToken();
|
||||
|
||||
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
|
||||
{
|
||||
var aliConfig = ObjectStoreServiceOptions.AliyunOSS;
|
||||
var client = new OssClient(
|
||||
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint,
|
||||
AliyunOSSTempToken.AccessKeyId,
|
||||
AliyunOSSTempToken.AccessKeySecret,
|
||||
AliyunOSSTempToken.SecurityToken
|
||||
);
|
||||
|
||||
var bucketName = aliConfig.BucketName;
|
||||
int totalRestored = 0;
|
||||
int totalSkipped = 0;
|
||||
int totalFailed = 0;
|
||||
|
||||
try
|
||||
{
|
||||
Console.WriteLine($"开始解冻阿里云OSS文件,前缀: {prefix}");
|
||||
|
||||
var allObjects = new List<OssObjectSummary>();
|
||||
|
||||
// 1. 分页列举文件
|
||||
string nextMarker = null;
|
||||
ObjectListing result = null;
|
||||
|
||||
do
|
||||
{
|
||||
var listRequest = new Aliyun.OSS.ListObjectsRequest(bucketName)
|
||||
{
|
||||
Prefix = prefix,
|
||||
Marker = nextMarker,
|
||||
MaxKeys = batchSize
|
||||
};
|
||||
|
||||
result = client.ListObjects(listRequest);
|
||||
|
||||
allObjects.AddRange(result.ObjectSummaries);
|
||||
|
||||
|
||||
|
||||
nextMarker = result.NextMarker;
|
||||
|
||||
} while (result.IsTruncated);
|
||||
|
||||
// 2️⃣ 并行解冻(控制并发)
|
||||
Parallel.ForEach(
|
||||
allObjects,
|
||||
new ParallelOptions
|
||||
{
|
||||
MaxDegreeOfParallelism = 5 // ⭐ 推荐 5~10
|
||||
},
|
||||
obj =>
|
||||
{
|
||||
// 只处理归档
|
||||
if (obj.StorageClass != StorageClass.Archive.ToString())
|
||||
{
|
||||
Interlocked.Increment(ref totalSkipped);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var restoreRequest = new Aliyun.OSS.RestoreObjectRequest(bucketName, obj.Key)
|
||||
{
|
||||
Days = restoreDays
|
||||
};
|
||||
|
||||
client.RestoreObject(restoreRequest);
|
||||
|
||||
Interlocked.Increment(ref totalRestored);
|
||||
Console.WriteLine($"✅ 提交解冻: {obj.Key}");
|
||||
}
|
||||
catch (OssException ex) when (ex.ErrorCode == "RestoreAlreadyInProgress")
|
||||
{
|
||||
// 已在解冻中,算成功
|
||||
Interlocked.Increment(ref totalSkipped);
|
||||
Console.WriteLine($"⚠️ 已在解冻中: {obj.Key}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Interlocked.Increment(ref totalFailed);
|
||||
Console.WriteLine($"❌ 解冻失败: {obj.Key} - {ex.Message}");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 3. 输出统计结果
|
||||
Console.WriteLine("\n================ 解冻完成 ================");
|
||||
Console.WriteLine($"总计处理: {totalRestored + totalSkipped + totalFailed} 个文件");
|
||||
Console.WriteLine($"成功解冻: {totalRestored} 个");
|
||||
Console.WriteLine($"跳过文件: {totalSkipped} 个 (非归档类型)");
|
||||
Console.WriteLine($"解冻失败: {totalFailed} 个");
|
||||
|
||||
if (totalRestored > 0)
|
||||
{
|
||||
Console.WriteLine($"\n📋 解冻说明:");
|
||||
Console.WriteLine($" • 解冻任务已提交,文件将在后台处理");
|
||||
Console.WriteLine($" • 解冻完成后,文件将保持可读状态 {restoreDays} 天");
|
||||
Console.WriteLine($" • 归档文件约需1分钟,冷归档需数小时");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"❌ 发生未知错误: {ex.Message}");
|
||||
Log.Logger.Error($"❌ 阿里云解冻操作失败: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS")
|
||||
{
|
||||
var awsConfig = ObjectStoreServiceOptions.AWS;
|
||||
var credentials = new SessionAWSCredentials(
|
||||
AWSTempToken.AccessKeyId,
|
||||
AWSTempToken.SecretAccessKey,
|
||||
AWSTempToken.SessionToken
|
||||
);
|
||||
|
||||
var clientConfig = new AmazonS3Config
|
||||
{
|
||||
RegionEndpoint = RegionEndpoint.GetBySystemName(awsConfig.Region),
|
||||
UseHttp = true,
|
||||
};
|
||||
|
||||
using var client = new AmazonS3Client(credentials, clientConfig);
|
||||
|
||||
var bucketName = awsConfig.BucketName;
|
||||
int totalRestored = 0;
|
||||
int totalSkipped = 0;
|
||||
int totalFailed = 0;
|
||||
|
||||
try
|
||||
{
|
||||
Console.WriteLine($"开始解冻AWS S3文件,前缀: {prefix}");
|
||||
|
||||
var allObjects = new List<S3Object>();
|
||||
|
||||
// 1. 分页列举文件
|
||||
string continuationToken = null;
|
||||
ListObjectsV2Response response = null;
|
||||
|
||||
do
|
||||
{
|
||||
var listRequest = new ListObjectsV2Request
|
||||
{
|
||||
BucketName = bucketName,
|
||||
Prefix = prefix,
|
||||
ContinuationToken = continuationToken,
|
||||
MaxKeys = batchSize
|
||||
};
|
||||
|
||||
response = await client.ListObjectsV2Async(listRequest);
|
||||
|
||||
allObjects.AddRange(response.S3Objects);
|
||||
|
||||
|
||||
continuationToken = response.NextContinuationToken;
|
||||
|
||||
} while (response.IsTruncated == true);
|
||||
|
||||
// 2️⃣ 并行解冻(控制并发)
|
||||
await Parallel.ForEachAsync(
|
||||
allObjects,
|
||||
new ParallelOptions
|
||||
{
|
||||
MaxDegreeOfParallelism = 5 // ⭐ 推荐 5~10
|
||||
},
|
||||
async (obj, ct) =>
|
||||
{
|
||||
// 只处理归档
|
||||
if (obj.StorageClass != S3StorageClass.Glacier)
|
||||
{
|
||||
Interlocked.Increment(ref totalSkipped);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var restoreRequest = new Amazon.S3.Model.RestoreObjectRequest
|
||||
{
|
||||
BucketName = bucketName,
|
||||
Key = obj.Key,
|
||||
Days = restoreDays,
|
||||
|
||||
};
|
||||
|
||||
await client.RestoreObjectAsync(restoreRequest);
|
||||
|
||||
Interlocked.Increment(ref totalRestored);
|
||||
Console.WriteLine($"✅ 提交解冻: {obj.Key}");
|
||||
}
|
||||
catch (OssException ex) when (ex.ErrorCode == "RestoreAlreadyInProgress")
|
||||
{
|
||||
// 已在解冻中,算成功
|
||||
Interlocked.Increment(ref totalSkipped);
|
||||
Console.WriteLine($"⚠️ 已在解冻中: {obj.Key}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Interlocked.Increment(ref totalFailed);
|
||||
Console.WriteLine($"❌ 解冻失败: {obj.Key} - {ex.Message}");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 3. 输出统计结果
|
||||
Console.WriteLine("\n================ 解冻完成 ================");
|
||||
Console.WriteLine($"总计处理: {totalRestored + totalSkipped + totalFailed} 个文件");
|
||||
Console.WriteLine($"成功解冻: {totalRestored} 个");
|
||||
Console.WriteLine($"跳过文件: {totalSkipped} 个 (非归档类型)");
|
||||
Console.WriteLine($"解冻失败: {totalFailed} 个");
|
||||
|
||||
if (totalRestored > 0)
|
||||
{
|
||||
Console.WriteLine($"\n📋 AWS解冻说明:");
|
||||
Console.WriteLine($" • 解冻任务已提交到Glacier服务");
|
||||
Console.WriteLine($" • 标准解冻: 3-5小时 (Glacier Flexible Retrieval)");
|
||||
Console.WriteLine($" • 加急解冻: 1-5分钟 (额外收费)");
|
||||
Console.WriteLine($" • 解冻后文件可读 {restoreDays} 天");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Logger.Error($"❌ AWS解冻操作失败: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new BusinessValidationFailedException("未定义的存储介质类型");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//public async Task SetLifecycle(string lifecycle)
|
||||
//{
|
||||
/// <summary>
|
||||
/// 坑方法,会清空之前的规则
|
||||
/// </summary>
|
||||
/// <param name="prefix"></param>
|
||||
/// <param name="ruleId"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="BusinessValidationFailedException"></exception>
|
||||
public async Task SetLifecycle(string prefix, string ruleId = "immediate-archive")
|
||||
{
|
||||
BackBatchGetToken();
|
||||
|
||||
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
|
||||
{
|
||||
var aliConfig = ObjectStoreServiceOptions.AliyunOSS;
|
||||
|
||||
var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint, AliyunOSSTempToken.AccessKeyId, AliyunOSSTempToken.AccessKeySecret, AliyunOSSTempToken.SecurityToken);
|
||||
|
||||
ruleId = $"{ruleId}_{prefix}";
|
||||
var rule = new Aliyun.OSS.LifecycleRule
|
||||
{
|
||||
ID = ruleId,
|
||||
Prefix = prefix,
|
||||
Status = RuleStatus.Enabled,
|
||||
Transitions = new Aliyun.OSS.LifecycleRule.LifeCycleTransition[]
|
||||
{
|
||||
new Aliyun.OSS.LifecycleRule.LifeCycleTransition
|
||||
{
|
||||
|
||||
LifeCycleExpiration =
|
||||
{
|
||||
Days = 1
|
||||
},
|
||||
StorageClass = StorageClass.IA
|
||||
},
|
||||
new Aliyun.OSS.LifecycleRule.LifeCycleTransition
|
||||
{
|
||||
|
||||
LifeCycleExpiration =
|
||||
{
|
||||
Days = 30
|
||||
},
|
||||
StorageClass = StorageClass.Archive
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
|
||||
// {
|
||||
// var aliConfig = ObjectStoreServiceOptions.AliyunOSS;
|
||||
//会清空之前历史的规则,不能用。。。
|
||||
var request = new SetBucketLifecycleRequest(aliConfig.BucketName);
|
||||
request.AddLifecycleRule(rule);
|
||||
|
||||
// var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint, AliyunOSSTempToken.AccessKeyId, AliyunOSSTempToken.AccessKeySecret, AliyunOSSTempToken.SecurityToken);
|
||||
_ossClient.SetBucketLifecycle(request);
|
||||
|
||||
|
||||
// var rule = new Aliyun.OSS.LifecycleRule
|
||||
// {
|
||||
// ID = "ArchiveOldFiles",
|
||||
// Prefix = "", // 全 bucket 生效
|
||||
// Status = RuleStatus.Enabled
|
||||
// };
|
||||
}
|
||||
else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS")
|
||||
{
|
||||
var awsConfig = ObjectStoreServiceOptions.AWS;
|
||||
|
||||
// // 30 天转低频
|
||||
// rule.Transitions.Add(new Aliyun.OSS.LifecycleRule.LifeCycleTransition
|
||||
// {
|
||||
// Days = 30,
|
||||
// StorageClass = StorageClass.IA
|
||||
// });
|
||||
|
||||
// // 180 天转归档
|
||||
// rule.Transitions.Add(new Aliyun.OSS.LifecycleRule.LifeCycleTransition
|
||||
// {
|
||||
// Days = 180,
|
||||
// StorageClass = StorageClass.Archive
|
||||
// });
|
||||
|
||||
// // 365 天转冷归档
|
||||
// rule.Transitions.Add(new Aliyun.OSS.LifecycleRule.LifeCycleTransition
|
||||
// {
|
||||
// Days = 365,
|
||||
// StorageClass = StorageClass.ColdArchive
|
||||
// });
|
||||
|
||||
// // 730 天转深度归档
|
||||
// rule.Transitions.Add(new Aliyun.OSS.LifecycleRule.LifeCycleTransition
|
||||
// {
|
||||
// Days = 730,
|
||||
// StorageClass = StorageClass.DeepColdArchive
|
||||
// });
|
||||
|
||||
// var request = new SetBucketLifecycleRequest(aliConfig.BucketName);
|
||||
// request.AddLifecycleRule(rule);
|
||||
|
||||
// _ossClient.SetBucketLifecycle(request);
|
||||
|
||||
|
||||
// }
|
||||
// else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS")
|
||||
// {
|
||||
// var awsConfig = ObjectStoreServiceOptions.AWS;
|
||||
|
||||
// var credentials = new SessionAWSCredentials(AWSTempToken.AccessKeyId, AWSTempToken.SecretAccessKey, AWSTempToken.SessionToken);
|
||||
var credentials = new SessionAWSCredentials(AWSTempToken.AccessKeyId, AWSTempToken.SecretAccessKey, AWSTempToken.SessionToken);
|
||||
|
||||
|
||||
|
||||
// //提供awsEndPoint(域名)进行访问配置
|
||||
// var clientConfig = new AmazonS3Config
|
||||
// {
|
||||
// RegionEndpoint = RegionEndpoint.USEast1,
|
||||
// UseHttp = true,
|
||||
// };
|
||||
//提供awsEndPoint(域名)进行访问配置
|
||||
var clientConfig = new AmazonS3Config
|
||||
{
|
||||
RegionEndpoint = RegionEndpoint.USEast1,
|
||||
UseHttp = true,
|
||||
};
|
||||
|
||||
// var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
|
||||
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
|
||||
|
||||
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// throw new BusinessValidationFailedException("未定义的存储介质类型");
|
||||
// }
|
||||
//}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new BusinessValidationFailedException("未定义的存储介质类型");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// oosFolderPath 不要 "/ "开头 应该: TempFolder/ChildFolder
|
||||
|
|
@ -432,8 +765,8 @@ public class OSSService : IOSSService
|
|||
//提供awsEndPoint(域名)进行访问配置
|
||||
var clientConfig = new AmazonS3Config
|
||||
{
|
||||
RegionEndpoint = RegionEndpoint.USEast1,
|
||||
UseHttp = true,
|
||||
RegionEndpoint = RegionEndpoint.GetBySystemName(awsConfig.Region)
|
||||
//,UseHttp = true,
|
||||
};
|
||||
|
||||
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
|
||||
|
|
@ -558,8 +891,8 @@ public class OSSService : IOSSService
|
|||
//提供awsEndPoint(域名)进行访问配置
|
||||
var clientConfig = new AmazonS3Config
|
||||
{
|
||||
RegionEndpoint = RegionEndpoint.USEast1,
|
||||
UseHttp = true,
|
||||
RegionEndpoint = RegionEndpoint.GetBySystemName(awsConfig.Region)
|
||||
//,UseHttp = true,
|
||||
};
|
||||
|
||||
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
|
||||
|
|
@ -633,8 +966,8 @@ public class OSSService : IOSSService
|
|||
//提供awsEndPoint(域名)进行访问配置
|
||||
var clientConfig = new AmazonS3Config
|
||||
{
|
||||
RegionEndpoint = RegionEndpoint.USEast1,
|
||||
UseHttp = true,
|
||||
RegionEndpoint = RegionEndpoint.GetBySystemName(awsConfig.Region)
|
||||
//,UseHttp = true,
|
||||
};
|
||||
|
||||
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
|
||||
|
|
@ -839,8 +1172,8 @@ public class OSSService : IOSSService
|
|||
//提供awsEndPoint(域名)进行访问配置
|
||||
var clientConfig = new AmazonS3Config
|
||||
{
|
||||
RegionEndpoint = RegionEndpoint.USEast1,
|
||||
UseHttp = true,
|
||||
RegionEndpoint = RegionEndpoint.GetBySystemName(awsConfig.Region)
|
||||
//,UseHttp = true,
|
||||
};
|
||||
|
||||
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
|
||||
|
|
@ -1213,8 +1546,8 @@ public class OSSService : IOSSService
|
|||
//提供awsEndPoint(域名)进行访问配置
|
||||
var clientConfig = new AmazonS3Config
|
||||
{
|
||||
RegionEndpoint = RegionEndpoint.USEast1,
|
||||
UseHttp = true,
|
||||
RegionEndpoint = RegionEndpoint.GetBySystemName(awsConfig.Region)
|
||||
//,UseHttp = true,
|
||||
};
|
||||
|
||||
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
|
||||
|
|
@ -1228,7 +1561,7 @@ public class OSSService : IOSSService
|
|||
|
||||
var listObjectsResponse = await amazonS3Client.ListObjectsV2Async(listObjectsRequest);
|
||||
|
||||
if (listObjectsResponse.S3Objects.Count > 0)
|
||||
if (listObjectsResponse.S3Objects?.Count > 0)
|
||||
{
|
||||
// 准备删除请求
|
||||
var deleteObjectsRequest = new Amazon.S3.Model.DeleteObjectsRequest
|
||||
|
|
@ -1258,7 +1591,7 @@ public class OSSService : IOSSService
|
|||
}
|
||||
}
|
||||
|
||||
public async Task DeleteObjects(List<string> objectKeys)
|
||||
public async Task DeleteObjects(List<string> objectKeys, bool isCache = false)
|
||||
{
|
||||
GetObjectStoreTempToken();
|
||||
|
||||
|
|
@ -1268,9 +1601,23 @@ public class OSSService : IOSSService
|
|||
|
||||
var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint, AliyunOSSTempToken.AccessKeyId, AliyunOSSTempToken.AccessKeySecret, AliyunOSSTempToken.SecurityToken);
|
||||
|
||||
var bucketName = string.Empty;
|
||||
|
||||
if (isCache)
|
||||
{
|
||||
Uri uri = new Uri(aliConfig.ViewEndpoint);
|
||||
string host = uri.Host; // 获取 "zy-irc-test-dev-cache.oss-cn-shanghai.aliyuncs.com"
|
||||
string[] parts = host.Split('.');
|
||||
bucketName = parts[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
bucketName = aliConfig.BucketName;
|
||||
}
|
||||
|
||||
if (objectKeys.Count > 0)
|
||||
{
|
||||
var result = _ossClient.DeleteObjects(new Aliyun.OSS.DeleteObjectsRequest(aliConfig.BucketName, objectKeys, false));
|
||||
var result = _ossClient.DeleteObjects(new Aliyun.OSS.DeleteObjectsRequest(bucketName, objectKeys, false));
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1306,8 +1653,8 @@ public class OSSService : IOSSService
|
|||
//提供awsEndPoint(域名)进行访问配置
|
||||
var clientConfig = new AmazonS3Config
|
||||
{
|
||||
RegionEndpoint = RegionEndpoint.USEast1,
|
||||
UseHttp = true,
|
||||
RegionEndpoint = RegionEndpoint.GetBySystemName(awsConfig.Region)
|
||||
//,UseHttp = true,
|
||||
};
|
||||
|
||||
var amazonS3Client = new AmazonS3Client(credentials, clientConfig);
|
||||
|
|
@ -1381,8 +1728,8 @@ public class OSSService : IOSSService
|
|||
//提供awsEndPoint(域名)进行访问配置
|
||||
var clientConfig = new AmazonS3Config
|
||||
{
|
||||
RegionEndpoint = RegionEndpoint.USEast1,
|
||||
UseHttp = true,
|
||||
RegionEndpoint = RegionEndpoint.GetBySystemName(awsConfig.Region)
|
||||
//,UseHttp = true,
|
||||
};
|
||||
|
||||
var request = new Amazon.S3.Model.GetObjectMetadataRequest
|
||||
|
|
@ -1466,9 +1813,16 @@ public class OSSService : IOSSService
|
|||
{
|
||||
var awsOptions = ObjectStoreServiceOptions.AWS;
|
||||
|
||||
|
||||
// 创建 STS 客户端(考虑使用 RegionEndpoint)
|
||||
var stsConfig = new AmazonSecurityTokenServiceConfig
|
||||
{
|
||||
RegionEndpoint = RegionEndpoint.GetBySystemName(awsOptions.Region)
|
||||
};
|
||||
|
||||
//aws 临时凭证
|
||||
// 创建 STS 客户端
|
||||
var stsClient = new AmazonSecurityTokenServiceClient(awsOptions.AccessKeyId, awsOptions.SecretAccessKey);
|
||||
var stsClient = new AmazonSecurityTokenServiceClient(awsOptions.AccessKeyId, awsOptions.SecretAccessKey, stsConfig);
|
||||
|
||||
// 使用 AssumeRole 请求临时凭证
|
||||
var assumeRoleRequest = new AssumeRoleRequest
|
||||
|
|
|
|||
|
|
@ -15740,14 +15740,23 @@
|
|||
利用DocX 库 处理word国际化模板
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:IRaCIS.Core.Application.Helper.OSSService.SetImmediateArchiveRule(System.String,Aliyun.OSS.StorageClass,System.String)">
|
||||
<member name="M:IRaCIS.Core.Application.Helper.OSSService.SetImmediateArchiveRule(System.String,System.String,System.Boolean)">
|
||||
<summary>
|
||||
将指定前缀下的所有现有文件立即转为目标存储类型
|
||||
核心:Days = 0 表示对所有存量文件立即生效
|
||||
</summary>
|
||||
<param name="prefix">要转换的文件前缀,如 "project-a/logs/"</param>
|
||||
<param name="targetStorageClass">目标存储类型</param>
|
||||
<param name="ruleId">规则ID,默认为"immediate-archive"</param>
|
||||
<param name="isDelete">默认是添加/更新 </param>
|
||||
</member>
|
||||
<!-- Badly formed XML comment ignored for member "M:IRaCIS.Core.Application.Helper.OSSService.RestoreFilesByPrefixAsync(System.String,System.Int32,System.Int32)" -->
|
||||
<member name="M:IRaCIS.Core.Application.Helper.OSSService.SetLifecycle(System.String,System.String)">
|
||||
<summary>
|
||||
坑方法,会清空之前的规则
|
||||
</summary>
|
||||
<param name="prefix"></param>
|
||||
<param name="ruleId"></param>
|
||||
<returns></returns>
|
||||
<exception cref="T:IRaCIS.Core.Infrastructure.BusinessValidationFailedException"></exception>
|
||||
</member>
|
||||
<member name="M:IRaCIS.Core.Application.Helper.OSSService.UploadToOSSAsync(System.IO.Stream,System.String,System.String,System.Boolean)">
|
||||
<summary>
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ namespace IRaCIS.Core.Application.ViewModel
|
|||
public new List<UserTypeEnum> CopyUserTypeList => TrialEmailNoticeUserList.Where(t => t.EmailUserType == EmailUserType.Copy).Select(t => t.UserType).ToList();
|
||||
|
||||
public List<CriterionType>? SysCriterionTypeList { get; set; }
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -127,7 +128,7 @@ namespace IRaCIS.Core.Application.ViewModel
|
|||
{
|
||||
public Guid SubjectId { get; set; }
|
||||
|
||||
public Guid TrialReadingCriterionId { get; set; }
|
||||
public CriterionType CriterionType { get; set; }
|
||||
|
||||
public EmailBusinessScenario BusinessScenarioEnum { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -713,7 +713,8 @@ namespace IRaCIS.Core.Application.Services
|
|||
.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(inQuery.IsDeleted != null, t => t.IsDeleted == inQuery.IsDeleted)
|
||||
.WhereIf(_userInfo.UserTypeEnumInt == (int)UserTypeEnum.EA, t => t.ConfirmTime != null);
|
||||
|
||||
var result = await unionQuery.ToPagedListAsync(inQuery);
|
||||
|
||||
|
|
|
|||
|
|
@ -773,10 +773,12 @@ namespace IRaCIS.Core.Application.Service
|
|||
{
|
||||
var subjectId = generateEmailCommand.SubjectId;
|
||||
var businessScenarioEnum = generateEmailCommand.BusinessScenarioEnum;
|
||||
var trialReadingCriterionId = generateEmailCommand.TrialReadingCriterionId;
|
||||
var criterionType = generateEmailCommand.CriterionType;
|
||||
|
||||
|
||||
var trialConfig = await _subjectRepository.Where(t => t.Id == subjectId).Select(t => new { t.Trial.IsEnrollementQualificationConfirm, t.Trial.IsPDProgressView }).FirstNotNullAsync();
|
||||
|
||||
var trialConfig = await _subjectRepository.Where(t => t.Id == subjectId).Select(t => new { t.Trial.IsEnrollementQualificationConfirm, t.Trial.IsPDProgressView, t.TrialId }).FirstNotNullAsync();
|
||||
var trialReadingCriterionId = _readingQuestionCriterionTrialRepository.Where(t => t.CriterionType == criterionType && t.TrialId == trialConfig.TrialId).Select(t => t.Id).FirstOrDefault();
|
||||
|
||||
//找到入组确认 或者Pd 进展 已生成任务的 访视
|
||||
var subjectVisitList = await _subjectVisitRepository.Where(t => t.SubjectId == subjectId & t.CheckState == CheckStateEnum.CVPassed && (t.IsEnrollmentConfirm == true || t.PDState == PDStateEnum.PDProgress)).ToListAsync();
|
||||
|
|
@ -1671,7 +1673,7 @@ x.ReadingTableQuestionTrial.QuestionMark == QuestionMark.LesionNumber && x.Readi
|
|||
{
|
||||
//await SyncSystemEmainCofigDocListAsync(inQuery.TrialId);
|
||||
|
||||
var trialConfig = _trialRepository.Where(t => t.Id == inQuery.TrialId).Select(t => new { t.IsEnrollementQualificationConfirm, t.IsPDProgressView }).First();
|
||||
var trialConfig = _trialRepository.Where(t => t.Id == inQuery.TrialId).Select(t => new { t.IsEnrollementQualificationConfirm, t.IsPDProgressView, TrialCriterionTypeList = t.TrialReadingCriterionList.Where(t => t.IsSigned).Select(t => t.CriterionType).ToList() }).First();
|
||||
|
||||
var trialEmailNoticeConfigQueryable = _trialEmailNoticeConfigRepository.Where(t => t.TrialId == inQuery.TrialId)
|
||||
.WhereIf(inQuery.EmailTopic.IsNotNullOrEmpty(), t => t.EmailTopic.Contains(inQuery.EmailTopic) || t.EmailTopicCN.Contains(inQuery.EmailTopic))
|
||||
|
|
@ -1692,6 +1694,7 @@ x.ReadingTableQuestionTrial.QuestionMark == QuestionMark.LesionNumber && x.Readi
|
|||
var orderQuery = inQuery.Asc ? trialEmailNoticeConfigQueryable.OrderBy(sortField) : trialEmailNoticeConfigQueryable.OrderBy(sortField + " desc");
|
||||
var list = await orderQuery.ToListAsync();
|
||||
|
||||
|
||||
return ResponseOutput.Ok(list, trialConfig);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -134,6 +134,8 @@ namespace IRaCIS.Core.Application.Contracts
|
|||
|
||||
public string Bodypart { get; set; } = string.Empty;
|
||||
|
||||
public string BodyPartForEditOther { get; set; } = string.Empty;
|
||||
|
||||
public DateTime? StudyTime { get; set; }
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1192,7 +1192,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
|
|||
|
||||
if (isSucess)
|
||||
{
|
||||
await _dicomStudyRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Key.DicomStudyId, u => new DicomStudy() { StudyDIRPath = $"/{ossFolder}/DICOMDIR" });
|
||||
await _dicomStudyRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Key.DicomStudyId, u => new DicomStudy() { StudyDIRPath = $"/{ossFolder}/{dirDic["DICOMDIR"]}" });
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1632,11 +1632,11 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
|
|||
{
|
||||
if (isTaskStudy)
|
||||
{
|
||||
await _taskStudyRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Key.DicomStudyId, u => new TaskStudy() { StudyDIRPath = $"/{ossFolder}/DICOMDIR" });
|
||||
await _taskStudyRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Key.DicomStudyId, u => new TaskStudy() { StudyDIRPath = $"/{ossFolder}/{dirDic["DICOMDIR"]}" });
|
||||
}
|
||||
else
|
||||
{
|
||||
await _dicomStudyRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Key.DicomStudyId, u => new DicomStudy() { StudyDIRPath = $"/{ossFolder}/DICOMDIR" });
|
||||
await _dicomStudyRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Key.DicomStudyId, u => new DicomStudy() { StudyDIRPath = $"/{ossFolder}/{dirDic["DICOMDIR"]}" });
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -2310,7 +2310,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
|
|||
|
||||
if (isSucess)
|
||||
{
|
||||
await _dicomStudyRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Key.DicomStudyId, u => new DicomStudy() { StudyDIRPath = $"/{ossFolder}/DICOMDIR" });
|
||||
await _dicomStudyRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Key.DicomStudyId, u => new DicomStudy() { StudyDIRPath = $"/{ossFolder}/{dirDic["DICOMDIR"]}" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -382,7 +382,8 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
|
|||
|
||||
Id = t.Id,
|
||||
|
||||
Bodypart = t.BodyPartExamined,
|
||||
Bodypart = t.BodyPartForEdit,
|
||||
BodyPartForEditOther = t.BodyPartForEditOther,
|
||||
|
||||
Modalities = t.Modalities,
|
||||
|
||||
|
|
@ -433,6 +434,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
|
|||
Id = t.Id,
|
||||
|
||||
Bodypart = t.BodyPart,
|
||||
BodyPartForEditOther=t.BodyPartForEditOther,
|
||||
|
||||
Modalities = t.Modality,
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using IRaCIS.Application.Contracts;
|
||||
using Aliyun.OSS;
|
||||
using IRaCIS.Application.Contracts;
|
||||
using IRaCIS.Application.Interfaces;
|
||||
using IRaCIS.Core.Application.Contracts;
|
||||
using IRaCIS.Core.Application.Filter;
|
||||
|
|
@ -32,7 +33,7 @@ namespace IRaCIS.Core.Application
|
|||
IRepository<ClinicalDataTrialSet> _clinicalDataTrialSetRepository,
|
||||
IRepository<ReadingCriterionPage> _readingCriterionPageRepository,
|
||||
IRepository<SystemCriterionKeyFile> _systemCriterionKeyFileRepository,
|
||||
IOSSService oSSService,
|
||||
IOSSService _oSSService,
|
||||
IRepository<TrialCriterionKeyFile> _trialCriterionKeyFileRepository,
|
||||
IOrganInfoService _iOrganInfoService,
|
||||
IRepository<TrialBodyPart> _trialBodyPartRepository,
|
||||
|
|
@ -340,7 +341,7 @@ namespace IRaCIS.Core.Application
|
|||
foreach (var item in systemCriterionKeyFile)
|
||||
{
|
||||
|
||||
var path= await oSSService.UploadToOSSAsync(item.FilePath, $"{trialCriterion.TrialId}/ReadingModule/{trialCriterion.CriterionName}", true,true);
|
||||
var path = await _oSSService.UploadToOSSAsync(item.FilePath, $"{trialCriterion.TrialId}/ReadingModule/{trialCriterion.CriterionName}", true, true);
|
||||
|
||||
trialCriterionKeyFiles.Add(new TrialCriterionKeyFile
|
||||
{
|
||||
|
|
@ -1325,6 +1326,18 @@ namespace IRaCIS.Core.Application
|
|||
|
||||
await _trialRepository.BatchUpdateNoTrackingAsync(u => u.Id == trialId, s => new Trial { TrialFinishedTime = DateTime.Now });
|
||||
|
||||
|
||||
if (_readingQuestionCriterionTrialRepository.Any(t => t.IsSigned && t.ImageUploadEnum != ReadingImageUpload.None))
|
||||
{
|
||||
await _oSSService.SetImmediateArchiveRule($"{trial.Id}/Image/");
|
||||
await _oSSService.SetImmediateArchiveRule($"{trial.Id}/TaskImage/");
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
await _oSSService.SetImmediateArchiveRule($"{trial.Id}/Image/");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
await _fusionCache.SetAsync(CacheKeys.Trial(trial.Id.ToString()), trialStatusStr, TimeSpan.FromDays(7));
|
||||
|
|
|
|||
|
|
@ -338,6 +338,7 @@ namespace IRaCIS.Core.Application.Service
|
|||
trial.DeclarationTypes = $"|{string.Join('|', updateModel.DeclarationTypeEnumList.Select(x => ((int)x).ToString()).ToList())}|";
|
||||
trial.AttendedReviewerTypes = $"|{string.Join('|', updateModel.AttendedReviewerTypeEnumList.Select(x => ((int)x).ToString()).ToList())}|";
|
||||
|
||||
trial.EmailFromName = $"{_systemEmailConfig.FromName}-{trial.TrialCode}";
|
||||
trial.UpdateTime = DateTime.Now;
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ using IRaCIS.Core.Domain;
|
|||
using IRaCIS.Core.Domain.Models;
|
||||
using IRaCIS.Core.Domain.Share;
|
||||
using IRaCIS.Core.Infra.EFCore;
|
||||
using IRaCIS.Core.Infra.EFCore.Context;
|
||||
using IRaCIS.Core.Infrastructure;
|
||||
using IRaCIS.Core.Infrastructure.Encryption;
|
||||
using IRaCIS.Core.Infrastructure.NewtonsoftJson;
|
||||
|
|
@ -75,12 +76,45 @@ namespace IRaCIS.Core.Application.Service
|
|||
{
|
||||
public static int IntValue = 100;
|
||||
|
||||
|
||||
[AllowAnonymous]
|
||||
|
||||
public async Task<IResponseOutput> CreatNewDBStruct()
|
||||
{
|
||||
var factory = new IRaCISDBContextFactory();
|
||||
|
||||
using var db = factory.CreateDbContext(Array.Empty<string>());
|
||||
|
||||
// ⚠️ 临时用,确认是测试库
|
||||
// db.Database.EnsureDeleted();
|
||||
|
||||
db.Database.EnsureCreated();
|
||||
|
||||
Console.WriteLine("数据库结构已创建完成");
|
||||
|
||||
return ResponseOutput.Ok();
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
|
||||
public async Task<IResponseOutput> DeleteCacheDIR()
|
||||
{
|
||||
var list= _dicomStudyRepository.Where(t => t.StudyDIRPath!="").Select(t => t.StudyDIRPath).ToList();
|
||||
|
||||
|
||||
await _IOSSService.DeleteObjects(list.Select(t => t.TrimStart('/')).ToList(),true);
|
||||
|
||||
return ResponseOutput.Ok();
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
public async Task<IResponseOutput> TestOSS(StorageClass storageClass)
|
||||
{
|
||||
if (storageClass == StorageClass.IA || storageClass == StorageClass.Archive || storageClass == StorageClass.ColdArchive || storageClass == StorageClass.DeepColdArchive)
|
||||
{
|
||||
_IOSSService.SetImmediateArchiveRule($"Test-Archive/Archive{(int)storageClass}", storageClass);
|
||||
await _IOSSService.SetImmediateArchiveRule($"Test-Archive/Archive{(int)storageClass}/");
|
||||
|
||||
//await _IOSSService.RestoreFilesByPrefixAsync($"Test-Archive/Archive{(int)storageClass}/");
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,3 +53,6 @@
|
|||
5、以下命令将生成一个从指定 from 迁移到指定 to 迁移的 SQL 脚本。
|
||||
|
||||
dotnet ef migrations script from to -p IRaCIS.Core.Infra.EFCore
|
||||
|
||||
6、查看迁移列表
|
||||
dotnet ef migrations list -p IRaCIS.Core.Infra.EFCore
|
||||
|
|
@ -6,6 +6,7 @@ using IRaCIS.Core.Infrastructure.Extention;
|
|||
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Internal;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Microsoft.VisualBasic;
|
||||
using Newtonsoft.Json;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
|
@ -60,8 +61,13 @@ public class IRaCISDBContext : DbContext
|
|||
//针对字符串使用默认的长度配置为200,如果标注了StringLength 其他长度,就是标注的长度,如果标注了MaxLength 那么就是nvarcharMax
|
||||
configurationBuilder.Conventions.Add(_ => new DefaultStringLengthConvention(400));
|
||||
|
||||
//configurationBuilder.Conventions.Add(_ => new RemoveForeignKeyConvention());
|
||||
|
||||
//控制外键索引生成与否
|
||||
//https://learn.microsoft.com/zh-cn/ef/core/modeling/relationships/conventions?utm_source=chatgpt.com
|
||||
//configurationBuilder.Conventions.Remove(typeof(ForeignKeyIndexConvention));
|
||||
configurationBuilder.Conventions.Remove(typeof(ForeignKeyIndexConvention));
|
||||
|
||||
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
|
|
@ -116,6 +122,7 @@ public class IRaCISDBContext : DbContext
|
|||
|
||||
|
||||
#region decimal 自定义精度,适配多种数据库
|
||||
|
||||
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
|
||||
{
|
||||
foreach (var property in entityType.GetProperties())
|
||||
|
|
@ -142,6 +149,12 @@ public class IRaCISDBContext : DbContext
|
|||
}
|
||||
}
|
||||
|
||||
//用户名区分大小写
|
||||
modelBuilder.Entity<IdentityUser>(entity =>
|
||||
{
|
||||
entity.Property(e => e.UserName)
|
||||
.UseCollation("Chinese_PRC_CS_AS");
|
||||
});
|
||||
#endregion
|
||||
|
||||
//遍历实体模型手动配置
|
||||
|
|
@ -160,6 +173,26 @@ public class IRaCISDBContext : DbContext
|
|||
|
||||
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
|
||||
{
|
||||
#region 修改Id 聚集索引-> Id非聚集,CreateTime聚集索引,方便分区
|
||||
|
||||
// 1️ 所有 Entity:Id 为主键(非聚集)
|
||||
if (typeof(Entity).IsAssignableFrom(entityType.ClrType))
|
||||
{
|
||||
modelBuilder.Entity(entityType.ClrType)
|
||||
.HasKey(nameof(Entity.Id))
|
||||
.IsClustered(false);
|
||||
}
|
||||
|
||||
// 2️ 所有 IAuditAdd:CreateTime 为聚集索引
|
||||
if (typeof(IAuditAdd).IsAssignableFrom(entityType.ClrType))
|
||||
{
|
||||
modelBuilder.Entity(entityType.ClrType)
|
||||
.HasIndex(nameof(IAuditAdd.CreateTime))
|
||||
.IsClustered();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// 软删除配置
|
||||
if (typeof(ISoftDelete).IsAssignableFrom(entityType.ClrType))
|
||||
{
|
||||
|
|
|
|||
21464
IRaCIS.Core.Infra.EFCore/Migrations/20260110112346_clusterModify.Designer.cs
generated
Normal file
21464
IRaCIS.Core.Infra.EFCore/Migrations/20260110112346_clusterModify.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue