跳到主要内容
版本:3.0.0

系统参数(Aegis.SystemSettings)

Aegis.SystemSettings 用于按系统、院区和分组读取业务参数。业务代码可以使用 .NET 标准的 IOptionsMonitor<T> 强类型读取,也可以通过 ISystemSettings 按运行时坐标读取。

如何引入

安装 NuGet

dotnet add package Aegis.SystemSettings

启用组件

在宿主项目的 Component.deps.json 中,将 SystemSettings 加入 Services

{
"Components": {
"Services": [
"SystemSettings"
],
"Middlewares": []
}
}

SystemSettings 不提供中间件,不需要放入 Middlewares。使用 ISystemSettingsSystemSettingsBindingAttribute 等公共类型的项目直接引用当前 NuGet 包即可。

配置当前系统编码

{
"SystemSettings": {
"SystemCode": "OP-Nurse"
}
}

SystemSettings:SystemCode 是必填项,需要与系统参数中登记的系统编码保持一致。

组件概览

字段说明
组件名称系统参数
真实类库Aegis.SystemSettings
组件定位按系统、院区和分组读取业务参数
引入方式安装 NuGet,并在 Component.deps.json 中启用 SystemSettings
默认参数来源宿主已加载的 IConfiguration
强类型入口IOptionsMonitor<T>
动态入口ISystemSettings
集中管理Redis 系统参数

什么时候要用它

适合以下场景:

  • 参数属于可调整的业务规则,而不是代码常量。
  • 同一套默认参数需要被不同院区局部覆盖。
  • 业务代码希望使用强类型配置类,并在参数变化后读取新值。
  • 需要在运行时切换系统、院区或分组。

以下内容继续使用其他配置能力:

  • 数据库连接串、密钥和宿主端口等基础设施配置,应使用 appsettings.json、环境变量或专用密钥管理。
  • 与当前系统无关的技术开关,不应为了集中展示而改造成业务参数。

最小可运行路径

第一步:在 appsettings.json 中准备参数

只使用当前包时,参数直接来自宿主已加载的 IConfiguration

{
"SystemSettings": {
"SystemCode": "OP-Nurse",
"OP-Nurse": {
"_default": {
"Registration": {
"TimeoutSeconds": 60,
"AllowCrossSlot": false
}
},
"A": {
"Registration": {
"TimeoutSeconds": 30
}
}
}
}
}

_default 是全院区默认值,A 是 A 院区覆盖值。院区未配置的字段会继续使用默认值。

第二步:声明强类型配置

using Aegis.SystemSettings.Bindings;

[SystemSettingsBinding(GroupCode = "Registration")]
public sealed class RegistrationSettings
{
public int TimeoutSeconds { get; set; } = 60;

public bool AllowCrossSlot { get; set; }
}

GroupCode 需要与参数分组编码保持一致。未维护的字段保留 C# 属性初始值。

第三步:在业务服务中读取

using Microsoft.Extensions.Options;

public sealed class RegistrationService(
IOptionsMonitor<RegistrationSettings> settings)
{
public RegistrationSettings GetDefault()
{
return settings.CurrentValue;
}

public RegistrationSettings GetForHospital(string hospitalCode)
{
return settings.Get(hospitalCode);
}
}

CurrentValue 读取全院区默认值;Get(hospitalCode) 先加载默认值,再叠加指定院区的覆盖值。院区编码由业务上下文显式传入,组件不读取隐式请求上下文。

院区参数覆盖

取值顺序是“全院区默认值打底,指定院区局部覆盖”。

读取方式TimeoutSecondsAllowCrossSlot
settings.CurrentValue60false
settings.Get("A")30false
settings.Get("B")60false

B 院区没有单独配置时,整个结果来自全院区默认值。

按运行时坐标读取

编译期无法确定分组或参数编码时,注入 ISystemSettings

using Aegis.SystemSettings.Bindings;

public sealed class DynamicSettingsService(ISystemSettings settings)
{
public int GetDefaultTimeout()
{
return settings.Get("Registration", "TimeoutSeconds", 60);
}

public int GetHospitalTimeout(string hospitalCode)
{
return settings
.For(hospitalCode)
.Get("Registration", "TimeoutSeconds", 60);
}

public string GetRequiredValue(string groupCode, string key)
{
return settings.Require<string>(groupCode, key);
}
}
API语义
Get<T>(groupCode, key, defaultValue)参数缺失或无法转换时返回默认值
Require<T>(groupCode, key)参数缺失或无法转换时抛出异常
For(hospitalCode)切换到当前系统的指定院区
ForSystem(systemCode, hospitalCode)切换到指定系统和院区
Group(groupCode).As<T>()将分组绑定为一次性强类型快照

Group(groupCode).As<T>() 返回调用时的快照,后续参数变更不会修改已返回的对象。

跨系统读取

配置类固定读取另一个系统时,在绑定特性中指定 SystemCode

using Aegis.SystemSettings.Bindings;

[SystemSettingsBinding(
SystemCode = "Settle",
GroupCode = "Discount")]
public sealed class BillingDiscountSettings
{
public decimal SelfPayDiscountRate { get; set; }
}

系统编码由运行时参数决定时,使用 ISystemSettings

var rate = settings
.ForSystem("Settle", hospitalCode)
.Get("Discount", "SelfPayDiscountRate", 1m);

一个强类型配置类只能对应一个系统和分组坐标。

显式注册配置类

配置类不适合添加 [SystemSettingsBinding] 特性时,可以在宿主中显式声明绑定:

services.AddSystemSettingsBinding<RegistrationSettings>(groupCode: "Registration");

指定另一个系统时:

services.AddSystemSettingsBinding<BillingDiscountSettings>(groupCode: "Discount", systemCode: "Settle");

同一类型不能同时使用特性绑定和显式绑定,冲突会在宿主启动时报错。

读取参数变化

IOptionsMonitor<T> 可以被任意生命周期的服务注入。参数来源发生变化后,默认值和已读取过的院区缓存会失效,下一次读取会重新绑定。

业务需要在变化时重建本地资源,可以订阅 OnChange

using Microsoft.Extensions.Options;

public class RegistrationPolicyCache : IDisposable
{
private readonly IOptionsMonitor<RegistrationSettings> _settings;
private readonly IDisposable? _subscription;

public RegistrationPolicyCache(IOptionsMonitor<RegistrationSettings> settings)
{
_settings = settings;
_subscription = settings.OnChange((_, _) =>
{
RebuildPolicy(_settings.Get("A"));
});
}

public void Dispose()
{
_subscription?.Dispose();
}

private static void RebuildPolicy(RegistrationSettings settings)
{
// 根据最新参数重建本地策略
}
}

OnChange 表示已发生配置变化,不应依赖回调中的 name 一定是某个院区编码。需要指定院区的新值时,在回调中重新调用 Get(hospitalCode)。订阅返回的 IDisposable 应在服务停止时释放。

JSON 字符串参数

IConfiguration.Bind 不会将一个值中的 JSON 文本自动反序列化为列表或嵌套对象。参数值本身是 JSON 字符串时,先读取字符串,再显式反序列化:

using System.Text.Json;

var raw = settings
.For(hospitalCode)
.Get<string>("Registration", "ExcludedDepartments");

var departments = string.IsNullOrEmpty(raw)
? []
: JsonSerializer.Deserialize<List<string>>(raw) ?? [];

intbooldecimalstring 等基本类型可以直接读取。

怎么选择参数来源

维度默认实现Redis 实现
参数来源宿主 IConfiguration管理后台发布并同步到 Redis
参数更新由宿主配置提供程序决定发布后通知实例,并由周期校准恢复遗漏变更
集中管理不提供提供
业务读取 APIIOptionsMonitor<T> / ISystemSettings不变

参数需要在管理后台统一维护、多实例自动更新时,使用 Redis 系统参数

接入后怎么确认生效

  1. 启动宿主,确认 ISystemSettings 和已标注配置类的 IOptionsMonitor<T> 可以从 DI 解析。
  2. 读取 CurrentValue,确认与全院区默认值一致。
  3. 传入已配置的院区编码,确认只覆盖该院区已维护的字段。
  4. 读取不存在的参数,分别确认 Get 返回默认值、Require 抛出异常。

边界与限制

  • SystemSettings:SystemCode 是当前进程的系统身份,区分大小写。
  • 院区编码、分组编码和参数编码由业务代码与参数来源约定,应保持稳定。
  • IOptionsMonitor<T> 适合已知字段的强类型读取;ISystemSettings 适合运行时坐标,不提供编译期字段检查。
  • Group(groupCode).As<T>() 是一次性快照,不会自动更新已返回的对象。
  • 一个配置类只能使用一种绑定声明,不能同时使用特性和显式注册。
  • 将 JSON 存入单个参数值时,需要由业务代码显式反序列化。
  • 当前包不提供参数管理页面或跨进程变更通知。

常见问题

为什么读到的都是配置类默认值

按顺序检查 SystemCodeGroupCode、院区编码和属性名。系统编码需要匹配 SystemSettings 下的系统节,分组编码需要匹配绑定声明,配置类属性名需要与参数编码对应。

业务层需要引用 Redis 扩展吗

不需要。业务层继续引用 Aegis.SystemSettings 并使用原有读取 API;Redis 扩展只安装在最终宿主。

参数变化后会修改已取得的配置对象吗

不会。IOptionsMonitor<T> 在下一次读取时返回重新绑定的对象;业务代码长期保留的旧对象和 Group(groupCode).As<T>() 已返回的快照不会就地变化。

配套阅读