配置项完善

This commit is contained in:
ztx
2024-11-28 14:27:17 +08:00
parent eb95d77c1d
commit 940d863704
6 changed files with 143 additions and 40 deletions

View File

@@ -6,7 +6,7 @@ namespace DicomSCP.Configuration;
public class DicomSettings
{
[Required]
[RegularExpression(@"^[A-Za-z0-9\-_]{1,16}$", ErrorMessage = "AE Title must be 1-16 characters and contain only letters, numbers, hyphen and underscore")]
[RegularExpression(@"^[A-Za-z0-9\-_]{1,16}$")]
public string AeTitle { get; set; } = "STORESCP";
[Range(1, 65535)]
@@ -15,13 +15,11 @@ public class DicomSettings
[Required]
public string StoragePath { get; set; } = "./received_files";
public int MaxPDULength { get; set; } = 16384;
public bool ValidateCallingAE { get; set; } = false;
public string[] AllowedCallingAEs { get; set; } = Array.Empty<string>();
public LogSettings Logging { get; set; } = new();
public AdvancedSettings Advanced { get; set; } = new();
public SwaggerSettings Swagger { get; set; } = new();
}
public class LogSettings
@@ -31,4 +29,20 @@ public class LogSettings
public LogEventLevel FileLogLevel { get; set; } = LogEventLevel.Debug;
public int RetainedDays { get; set; } = 31;
public string LogPath { get; set; } = "logs";
}
public class AdvancedSettings
{
public bool ValidateCallingAE { get; set; } = false;
public string[] AllowedCallingAEs { get; set; } = Array.Empty<string>();
public int ConcurrentStoreLimit { get; set; } = 8;
public int TempFileCleanupDelay { get; set; } = 300;
}
public class SwaggerSettings
{
public bool Enabled { get; set; } = true;
public string Title { get; set; } = "DICOM SCP API";
public string Version { get; set; } = "v1";
public string Description { get; set; } = "DICOM SCP服务器的REST API";
}

View File

@@ -4,9 +4,14 @@ using Serilog.Events;
using Serilog.Filters;
using DicomSCP.Configuration;
using DicomSCP.Services;
using Microsoft.OpenApi.Models;
var builder = WebApplication.CreateBuilder(args);
// 获取配置
var settings = builder.Configuration.GetSection("DicomSettings").Get<DicomSettings>()
?? new DicomSettings();
// 配置日志
var logSettings = builder.Configuration
.GetSection("DicomSettings:Logging")
@@ -47,7 +52,6 @@ if (logSettings.EnableFileLog)
}
Log.Logger = logConfig.CreateLogger();
builder.Host.UseSerilog();
// 添加日志服务
@@ -57,18 +61,31 @@ builder.Services.AddLogging(loggingBuilder =>
// 添加服务
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// 配置 Swagger
if (settings.Swagger.Enabled)
{
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc(settings.Swagger.Version, new OpenApiInfo
{
Title = settings.Swagger.Title,
Version = settings.Swagger.Version,
Description = settings.Swagger.Description
});
});
}
builder.Services.Configure<DicomSettings>(builder.Configuration.GetSection("DicomSettings"));
builder.Services.AddSingleton<DicomServer>();
// 配置 DICOM
var settings = builder.Configuration.GetSection("DicomSettings").Get<DicomSettings>()
?? new DicomSettings();
CStoreSCP.Configure(settings.StoragePath);
// 优化线程池
ThreadPool.SetMinThreads(Environment.ProcessorCount * 2, Environment.ProcessorCount);
ThreadPool.SetMaxThreads(Environment.ProcessorCount * 4, Environment.ProcessorCount * 2);
// 优化线程池 - 基于CPU核心数
int processorCount = Environment.ProcessorCount;
ThreadPool.SetMinThreads(processorCount * 4, processorCount * 2); // 增加最小线程数
ThreadPool.SetMaxThreads(processorCount * 8, processorCount * 4); // 增加最大线程数
var app = builder.Build();
@@ -78,12 +95,19 @@ await dicomServer.StartAsync();
app.Lifetime.ApplicationStopping.Register(() => dicomServer.StopAsync().GetAwaiter().GetResult());
// 配置中间件
if (app.Environment.IsDevelopment())
if (app.Environment.IsDevelopment() && settings.Swagger.Enabled)
{
// Swagger 中间件应该在其他中间件之前
app.UseSwagger();
app.UseSwaggerUI();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", $"{settings.Swagger.Title} {settings.Swagger.Version}");
// 可以设置为根路径
c.RoutePrefix = "swagger";
});
}
// 其他中间件
app.UseAuthorization();
app.MapControllers();

View File

@@ -0,0 +1,13 @@
{
"profiles": {
"DicomSCP": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -30,8 +30,25 @@
### Web服务器配置
- `Kestrel.Endpoints.Http.Url`: Web API监听地址
### DICOM高级配置
- `Advanced.ValidateCallingAE`: 是否验证调用方AE
- `Advanced.AllowedCallingAEs`: 允许的调用方AE列表
- `Advanced.ConcurrentStoreLimit`: 并发存储限制0表示自动使用CPU核心数 * 2
- `Advanced.TempFileCleanupDelay`: 临时文件清理延迟(秒)
### Swagger配置
- `Swagger.Enabled`: 是否启用Swagger
- `Swagger.Title`: API文档标题
- `Swagger.Version`: API版本
- `Swagger.Description`: API描述
## API接口
+ API文档可以通过Swagger UI访问
+ ```
+ http://localhost:5000/swagger
+ ```
+
### 获取服务器状态
```
GET /api/dicom/status

View File

@@ -4,6 +4,8 @@ using FellowOakDicom;
using FellowOakDicom.Network;
using Serilog;
using ILogger = Serilog.ILogger;
using Microsoft.Extensions.Options;
using DicomSCP.Configuration;
namespace DicomSCP.Services;
@@ -32,7 +34,7 @@ public class CStoreSCP : DicomService, IDicomServiceProvider, IDicomCStoreProvid
private static string StoragePath = "./received_files";
// 修改为实例级别的信号量和文件锁,以便能够正确释放
private readonly DicomSettings _settings;
private readonly SemaphoreSlim _concurrentLimit;
private readonly ConcurrentDictionary<string, SemaphoreSlim> _fileLocks;
private readonly ILogger _logger = Log.ForContext<CStoreSCP>();
@@ -47,10 +49,16 @@ public class CStoreSCP : DicomService, IDicomServiceProvider, IDicomCStoreProvid
INetworkStream stream,
Encoding fallbackEncoding,
Microsoft.Extensions.Logging.ILogger log,
DicomServiceDependencies dependencies)
DicomServiceDependencies dependencies,
IOptions<DicomSettings> settings)
: base(stream, fallbackEncoding, log, dependencies)
{
_concurrentLimit = new SemaphoreSlim(Environment.ProcessorCount * 2);
_settings = settings.Value;
var advancedSettings = _settings.Advanced;
int concurrentLimit = advancedSettings.ConcurrentStoreLimit > 0
? advancedSettings.ConcurrentStoreLimit
: Environment.ProcessorCount * 2;
_concurrentLimit = new SemaphoreSlim(concurrentLimit);
_fileLocks = new ConcurrentDictionary<string, SemaphoreSlim>();
}
@@ -61,6 +69,19 @@ public class CStoreSCP : DicomService, IDicomServiceProvider, IDicomCStoreProvid
association.CalledAE,
association.CallingAE);
var advancedSettings = _settings.Advanced;
// 应用验证配置
if (advancedSettings.ValidateCallingAE &&
!advancedSettings.AllowedCallingAEs.Contains(association.CallingAE))
{
_logger.Warning("拒绝未授权的调用方AE: {CallingAE}", association.CallingAE);
return SendAssociationRejectAsync(
DicomRejectResult.Permanent,
DicomRejectSource.ServiceUser,
DicomRejectReason.CallingAENotRecognized);
}
foreach (var pc in association.PresentationContexts)
{
if (pc.AbstractSyntax == DicomUID.Verification)
@@ -209,6 +230,8 @@ public class CStoreSCP : DicomService, IDicomServiceProvider, IDicomCStoreProvid
// 移动到最终位置
File.Move(tempFilePath, filePath);
_logger.Information("文件保存成功 - 路径: {FilePath}", filePath);
// 启动异步清理
_ = Task.Run(() => CleanupTempFileAsync(tempFilePath)); // 使用 Task.Run 避免阻塞
return new DicomCStoreResponse(request, DicomStatus.Success);
}
catch (Exception ex)
@@ -332,4 +355,25 @@ public class CStoreSCP : DicomService, IDicomServiceProvider, IDicomCStoreProvid
{
Dispose(false);
}
private async Task CleanupTempFileAsync(string tempFilePath)
{
var advancedSettings = _settings.Advanced;
if (advancedSettings.TempFileCleanupDelay > 0)
{
try
{
await Task.Delay(TimeSpan.FromSeconds(advancedSettings.TempFileCleanupDelay));
if (File.Exists(tempFilePath))
{
File.Delete(tempFilePath);
_logger.Debug("清理临时文件: {TempFile}", tempFilePath);
}
}
catch (Exception ex)
{
_logger.Error(ex, "清理临时文件失败: {TempFile}", tempFilePath);
}
}
}
}

View File

@@ -1,41 +1,32 @@
{
"DicomSettings": {
// DICOM服务器的AE标题长度1-16字符只能包含字母、数字、连字符和下划线
"AeTitle": "STORESCP",
// DICOM服务器监听端口范围1-65535
"Port": 11112,
// DICOM文件存储路径支持相对或绝对路径
"StoragePath": "./received_files",
// 日志配置
"Logging": {
// 是否启用控制台日志(仅显示服务状态变化和错误)
"EnableConsoleLog": true,
// 是否启用文件日志
"EnableFileLog": true,
// 文件日志级别Verbose|Debug|Information|Warning|Error|Fatal
"FileLogLevel": "Debug",
// 日志文件保留天数
"RetainedDays": 31,
// 日志文件存储路径
"LogPath": "logs"
},
"Advanced": {
"ValidateCallingAE": false,
"AllowedCallingAEs": [],
"ConcurrentStoreLimit": 0,
"TempFileCleanupDelay": 300
},
"Swagger": {
"Enabled": true,
"Title": "DICOM SCP API",
"Version": "v1",
"Description": "DICOM SCP服务器的REST API"
}
},
// 允许的主机配置,"*" 表示允许所有
"AllowedHosts": "*",
// Kestrel Web服务器配置
"Kestrel": {
"Endpoints": {
"Http": {
// Web API监听地址
"Url": "http://localhost:5000"
}
}