Browse Source

GetOrDefault=>GetValue

黄中银 4 ngày trước cách đây
mục cha
commit
afeb04720a

+ 5 - 1
Apq.Cfg/CfgRootExtensions.cs

@@ -38,7 +38,11 @@ public static class CfgRootExtensions
     /// <summary>
     /// 获取配置值,如果不存在则返回默认值
     /// </summary>
-    public static T? GetOrDefault<T>(this ICfgRoot root, string key, T? defaultValue = default)
+    /// <remarks>
+    /// 此方法是 <see cref="ICfgRoot.GetValue{T}(string)"/> 的重载版本,
+    /// 允许指定自定义默认值。命名与 Microsoft.Extensions.Configuration 保持一致。
+    /// </remarks>
+    public static T? GetValue<T>(this ICfgRoot root, string key, T? defaultValue)
     {
         var rawValue = root[key];
         if (rawValue == null)

+ 6 - 6
benchmarks/Apq.Cfg.Benchmarks/TypeConversionBenchmarks.cs

@@ -317,23 +317,23 @@ public class TypeConversionBenchmarks : IDisposable
     }
 
     /// <summary>
-    /// GetOrDefault 存在键场景
+    /// GetValue 带默认值 - 存在键场景
     /// </summary>
     [Benchmark]
     [BenchmarkCategory("Extensions")]
-    public int GetOrDefault_ExistingKey()
+    public int GetValue_WithDefault_ExistingKey()
     {
-        return _cfg.GetOrDefault("Types:Int", 0);
+        return _cfg.GetValue("Types:Int", 0);
     }
 
     /// <summary>
-    /// GetOrDefault 不存在键场景
+    /// GetValue 带默认值 - 不存在键场景
     /// </summary>
     [Benchmark]
     [BenchmarkCategory("Extensions")]
-    public int GetOrDefault_NonExistingKey()
+    public int GetValue_WithDefault_NonExistingKey()
     {
-        return _cfg.GetOrDefault("Types:NonExistent", 100);
+        return _cfg.GetValue("Types:NonExistent", 100);
     }
 
     #endregion

+ 1 - 1
benchmarks/README.md

@@ -220,7 +220,7 @@ dotnet run -c Release --project benchmarks/Apq.Cfg.Benchmarks -f net10.0 -- --li
 | ComplexTypes | Get_Guid, Get_DateTime, Get_Enum, Get_NullableInt |
 | Batch | Get_*_Multiple(批量转换) |
 | SpecialValues | Get_LongString, Get_Unicode, Get_SpecialChars, Get_EmptyString |
-| Extensions | TryGetValue_Success/Failure, GetRequired_Success, GetOrDefault_ExistingKey/NonExistingKey |
+| Extensions | TryGetValue_Success/Failure, GetRequired_Success, GetValue_WithDefault_ExistingKey/NonExistingKey |
 | Mixed | Get_MixedTypes(混合类型读取) |
 
 ### 9. CacheBenchmarks - 缓存效果测试

+ 1 - 1
docs/docfx/api/.manifest

@@ -44,8 +44,8 @@
   "Apq.Cfg.CfgRootExtensions": "Apq.Cfg.CfgRootExtensions.yml",
   "Apq.Cfg.CfgRootExtensions.GetMasked(Apq.Cfg.ICfgRoot,System.String)": "Apq.Cfg.CfgRootExtensions.yml",
   "Apq.Cfg.CfgRootExtensions.GetMaskedSnapshot(Apq.Cfg.ICfgRoot)": "Apq.Cfg.CfgRootExtensions.yml",
-  "Apq.Cfg.CfgRootExtensions.GetOrDefault``1(Apq.Cfg.ICfgRoot,System.String,``0)": "Apq.Cfg.CfgRootExtensions.yml",
   "Apq.Cfg.CfgRootExtensions.GetRequired``1(Apq.Cfg.ICfgRoot,System.String)": "Apq.Cfg.CfgRootExtensions.yml",
+  "Apq.Cfg.CfgRootExtensions.GetValue``1(Apq.Cfg.ICfgRoot,System.String,``0)": "Apq.Cfg.CfgRootExtensions.yml",
   "Apq.Cfg.CfgRootExtensions.TryGetValue``1(Apq.Cfg.ICfgRoot,System.String,``0@)": "Apq.Cfg.CfgRootExtensions.yml",
   "Apq.Cfg.CfgSectionAttribute": "Apq.Cfg.CfgSectionAttribute.yml",
   "Apq.Cfg.CfgSectionAttribute.#ctor(System.String)": "Apq.Cfg.CfgSectionAttribute.yml",

+ 14 - 0
docs/site/.vitepress/config.ts

@@ -35,6 +35,13 @@ const zhSidebar = {
         { text: '扩展开发', link: '/guide/extension' }
       ]
     },
+    {
+      text: 'Web 管理',
+      items: [
+        { text: 'Web API', link: '/guide/webapi' },
+        { text: 'Web 管理界面', link: '/guide/webui' }
+      ]
+    },
     {
       text: '深入',
       items: [
@@ -141,6 +148,13 @@ const enSidebar = {
         { text: 'Extension Development', link: '/en/guide/extension' }
       ]
     },
+    {
+      text: 'Web Management',
+      items: [
+        { text: 'Web API', link: '/en/guide/webapi' },
+        { text: 'Web UI', link: '/en/guide/webui' }
+      ]
+    },
     {
       text: 'Deep Dive',
       items: [

+ 6 - 6
docs/site/api/extensions.md

@@ -242,18 +242,18 @@ public static T GetRequired<T>(this ICfgRoot root, string key)
 var connectionString = cfg.GetRequired<string>("Database:ConnectionString");
 ```
 
-### GetOrDefault
+### GetValue(带默认值)
 
 ```csharp
-public static T? GetOrDefault<T>(this ICfgRoot root, string key, T? defaultValue = default)
+public static T? GetValue<T>(this ICfgRoot root, string key, T? defaultValue)
 ```
 
-获取配置值,如果不存在则返回默认值。
+获取配置值,如果不存在则返回默认值。此方法是 `ICfgRoot.GetValue<T>(string)` 的重载版本,命名与 Microsoft.Extensions.Configuration 保持一致。
 
 **示例:**
 ```csharp
-var timeout = cfg.GetOrDefault("Database:Timeout", 30);
-var retryCount = cfg.GetOrDefault<int>("Database:RetryCount", 3);
+var timeout = cfg.GetValue("Database:Timeout", 30);
+var retryCount = cfg.GetValue<int>("Database:RetryCount", 3);
 ```
 
 ### GetMasked
@@ -423,7 +423,7 @@ var host = cfg["Database:Host"];
 var port = cfg.GetValue<int>("Database:Port");
 
 // 使用扩展方法
-var timeout = cfg.GetOrDefault("Database:Timeout", 30);
+var timeout = cfg.GetValue("Database:Timeout", 30);
 var connStr = cfg.GetRequired<string>("Database:ConnectionString");
 
 // 检查配置是否存在

+ 6 - 6
docs/site/api/icfg-root.md

@@ -338,18 +338,18 @@ T GetRequired<T>(this ICfgRoot root, string key)
 var connectionString = cfg.GetRequired<string>("Database:ConnectionString");
 ```
 
-### GetOrDefault
+### GetValue(带默认值)
 
 ```csharp
-T? GetOrDefault<T>(this ICfgRoot root, string key, T? defaultValue = default)
+T? GetValue<T>(this ICfgRoot root, string key, T? defaultValue)
 ```
 
-获取配置值,如果不存在则返回默认值。
+获取配置值,如果不存在则返回默认值。此方法是 `ICfgRoot.GetValue<T>(string)` 的重载版本,命名与 Microsoft.Extensions.Configuration 保持一致。
 
 **示例:**
 ```csharp
-var timeout = cfg.GetOrDefault("Database:Timeout", 30);
-var retryCount = cfg.GetOrDefault<int>("Database:RetryCount", 3);
+var timeout = cfg.GetValue("Database:Timeout", 30);
+var retryCount = cfg.GetValue<int>("Database:RetryCount", 3);
 ```
 
 ### GetMasked
@@ -416,7 +416,7 @@ var connString = $"Host={dbSection["Host"]};Port={dbSection["Port"]}";
 var values = cfg.GetMany(new[] { "App:Name", "App:Version" });
 
 // 使用扩展方法
-var timeout = cfg.GetOrDefault("Database:Timeout", 30);
+var timeout = cfg.GetValue("Database:Timeout", 30);
 var connStr = cfg.GetRequired<string>("Database:ConnectionString");
 
 // 修改配置

+ 2 - 2
docs/site/en/guide/migration.md

@@ -71,7 +71,7 @@ var section = cfg.GetSection("Section");
 var port = config.GetValue<int>("Server:Port", 8080);
 
 // After
-var port = cfg.GetOrDefault("Server:Port", 8080);
+var port = cfg.GetValue("Server:Port", 8080);
 ```
 
 ### Safe Reading (TryGetValue)
@@ -201,7 +201,7 @@ cfg.GetMany(new[] { "Key1", "Key2", "Key3" }, (key, value) =>
 |-----------------------------------|---------|-------|
 | `config["Key"]` | `cfg["Key"]` | Same |
 | `config.GetValue<T>("Key")` | `cfg.GetValue<T>("Key")` | Shorter method name |
-| `config.GetValue<T>("Key", default)` | `cfg.GetOrDefault("Key", default)` | Clearer semantics |
+| `config.GetValue<T>("Key", default)` | `cfg.GetValue("Key", default)` | Same |
 | `config.GetSection("Path")` | `cfg.GetSection("Path")` | Same |
 | `config.GetChildren()` | `cfg.GetChildKeys()` | Returns key names |
 | `config.GetReloadToken()` | `cfg.ConfigChanges` | Rx subscription |

+ 1 - 1
docs/site/guide/basic-usage.md

@@ -87,7 +87,7 @@ var maxSize = cfg.GetValue<int?>("Cache:MaxSize");
 
 ```csharp
 // 带默认值
-var timeout = cfg.GetOrDefault<int>("Database:Timeout", defaultValue: 30);
+var timeout = cfg.GetValue<int>("Database:Timeout", 30);
 
 // 必需的配置(不存在时抛出异常)
 var connectionString = cfg.GetRequired<string>("Database:ConnectionString");

+ 2 - 2
docs/site/guide/migration.md

@@ -71,7 +71,7 @@ var section = cfg.GetSection("Section");
 var port = config.GetValue<int>("Server:Port", 8080);
 
 // 之后
-var port = cfg.GetOrDefault("Server:Port", 8080);
+var port = cfg.GetValue("Server:Port", 8080);
 ```
 
 ### 安全读取(TryGetValue)
@@ -201,7 +201,7 @@ cfg.GetMany(new[] { "Key1", "Key2", "Key3" }, (key, value) =>
 |-----------------------------------|---------|------|
 | `config["Key"]` | `cfg["Key"]` | 相同 |
 | `config.GetValue<T>("Key")` | `cfg.GetValue<T>("Key")` | 方法名更简洁 |
-| `config.GetValue<T>("Key", default)` | `cfg.GetOrDefault("Key", default)` | 更明确的语义 |
+| `config.GetValue<T>("Key", default)` | `cfg.GetValue("Key", default)` | 相同 |
 | `config.GetSection("Path")` | `cfg.GetSection("Path")` | 相同 |
 | `config.GetChildren()` | `cfg.GetChildKeys()` | 返回键名列表 |
 | `config.GetReloadToken()` | `cfg.ConfigChanges` | Rx 订阅方式 |

+ 197 - 0
docs/site/guide/webapi.md

@@ -0,0 +1,197 @@
+# Web API 集成
+
+Apq.Cfg.WebApi 提供 RESTful API 接口,支持远程配置管理。
+
+## 安装
+
+```bash
+dotnet add package Apq.Cfg.WebApi
+```
+
+## 快速开始
+
+```csharp
+using Apq.Cfg;
+using Apq.Cfg.WebApi;
+
+var builder = WebApplication.CreateBuilder(args);
+
+// 构建配置
+var cfg = new CfgBuilder()
+    .AddJson("config.json")
+    .AddJson("config.local.json", level: 5, writeable: true, isPrimaryWriter: true)
+    .Build();
+
+// 注册配置和 API 服务
+builder.Services.AddSingleton<ICfgRoot>(cfg);
+builder.Services.AddApqCfgWebApi(options =>
+{
+    options.Authentication = AuthenticationType.ApiKey;
+    options.ApiKey = "your-secret-key";
+});
+
+var app = builder.Build();
+app.UseApqCfgWebApi();
+app.MapApqCfgWebApi();
+app.Run();
+```
+
+## API 文档
+
+API 文档 UI 根据目标框架自动选择:
+- **.NET 8**:Swagger UI (`/swagger`)
+- **.NET 10+**:Scalar (`/scalar/v1`)
+
+## 配置选项
+
+```csharp
+builder.Services.AddApqCfgWebApi(options =>
+{
+    // 启用/禁用 API
+    options.Enabled = true;
+
+    // 路由前缀
+    options.RoutePrefix = "/api/apqcfg";
+
+    // 认证方式
+    options.Authentication = AuthenticationType.ApiKey;
+    options.ApiKey = "your-secret-key";
+    options.ApiKeyHeaderName = "X-Api-Key";
+
+    // 权限控制
+    options.AllowRead = true;
+    options.AllowWrite = false;
+    options.AllowDelete = false;
+
+    // 敏感值脱敏
+    options.MaskSensitiveValues = true;
+    options.SensitiveKeyPatterns = ["*Password*", "*Secret*", "*Key*", "*Token*", "*ConnectionString*"];
+
+    // CORS 配置
+    options.EnableCors = false;
+    options.CorsOrigins = ["*"];
+
+    // OpenAPI 配置
+    options.OpenApiEnabled = true;
+    options.OpenApiTitle = "Apq.Cfg Web API";
+    options.OpenApiDescription = "Apq.Cfg 配置管理 RESTful API";
+    options.OpenApiVersion = "v1";
+});
+```
+
+## 认证方式
+
+### 无认证
+
+```csharp
+options.Authentication = AuthenticationType.None;
+```
+
+### API Key 认证
+
+```csharp
+options.Authentication = AuthenticationType.ApiKey;
+options.ApiKey = "your-secret-key";
+options.ApiKeyHeaderName = "X-Api-Key"; // 默认
+```
+
+请求时在 Header 中添加:
+```
+X-Api-Key: your-secret-key
+```
+
+### JWT Bearer 认证
+
+```csharp
+options.Authentication = AuthenticationType.JwtBearer;
+options.Jwt = new JwtOptions
+{
+    Authority = "https://auth.example.com",
+    Audience = "my-api",
+    RequireHttpsMetadata = true,
+    ValidateIssuer = true,
+    ValidateAudience = true
+};
+```
+
+## API 端点
+
+| 方法 | 路径 | 说明 |
+|------|------|------|
+| GET | `/api/apqcfg/config` | 获取所有配置 |
+| GET | `/api/apqcfg/config/{key}` | 获取单个配置值 |
+| GET | `/api/apqcfg/config/section/{path}` | 获取配置节 |
+| GET | `/api/apqcfg/config/tree` | 获取配置树结构 |
+| GET | `/api/apqcfg/sources` | 获取配置源列表 |
+| PUT | `/api/apqcfg/config/{key}` | 设置配置值 |
+| PUT | `/api/apqcfg/config/batch` | 批量更新配置 |
+| DELETE | `/api/apqcfg/config/{key}` | 删除配置键 |
+| POST | `/api/apqcfg/reload` | 重新加载配置 |
+| GET | `/api/apqcfg/export/{format}` | 导出配置(json/env/kv) |
+
+## 敏感值脱敏
+
+默认情况下,包含以下关键字的配置键会被自动脱敏:
+- `*Password*`
+- `*Secret*`
+- `*Key*`
+- `*Token*`
+- `*ConnectionString*`
+
+脱敏后的值显示为 `***`。
+
+可以自定义敏感键模式:
+
+```csharp
+options.SensitiveKeyPatterns = ["*Credential*", "*Private*"];
+```
+
+或禁用脱敏:
+
+```csharp
+options.MaskSensitiveValues = false;
+```
+
+## 导出格式
+
+### JSON 格式
+
+```
+GET /api/apqcfg/export/json
+```
+
+返回嵌套的 JSON 结构。
+
+### 环境变量格式
+
+```
+GET /api/apqcfg/export/env
+```
+
+返回:
+```
+APP__NAME=MyApp
+DATABASE__HOST=localhost
+DATABASE__PORT=5432
+```
+
+### Key-Value 格式
+
+```
+GET /api/apqcfg/export/kv
+```
+
+返回:
+```
+App:Name=MyApp
+Database:Host=localhost
+Database:Port=5432
+```
+
+## 安全建议
+
+1. **生产环境必须启用认证**:不要在生产环境使用 `AuthenticationType.None`
+2. **限制写入权限**:默认 `AllowWrite = false`,仅在需要时开启
+3. **使用 HTTPS**:确保 API 通过 HTTPS 访问
+4. **定期轮换 API Key**:定期更换 API Key 以提高安全性
+5. **启用敏感值脱敏**:保持 `MaskSensitiveValues = true`

+ 114 - 0
docs/site/guide/webui.md

@@ -0,0 +1,114 @@
+# Web 管理界面
+
+Apq.Cfg.WebUI 提供 Web 管理界面,用于集中管理多个应用的配置。
+
+## 概述
+
+WebUI 是一个独立的 Web 应用,通过 Docker 镜像部署,可以:
+- 集中管理多个应用的配置
+- 可视化配置编辑
+- 配置版本历史
+- 配置对比和回滚
+- 多环境配置管理
+
+## 部署方式
+
+### Docker 部署
+
+```bash
+docker run -d \
+  --name apqcfg-webui \
+  -p 8080:8080 \
+  -e APQCFG_ADMIN_PASSWORD=your-password \
+  apq/apqcfg-webui:latest
+```
+
+### Docker Compose
+
+```yaml
+version: '3.8'
+services:
+  apqcfg-webui:
+    image: apq/apqcfg-webui:latest
+    ports:
+      - "8080:8080"
+    environment:
+      - APQCFG_ADMIN_PASSWORD=your-password
+      - APQCFG_DATABASE_TYPE=sqlite
+      - APQCFG_DATABASE_PATH=/data/apqcfg.db
+    volumes:
+      - apqcfg-data:/data
+
+volumes:
+  apqcfg-data:
+```
+
+## 环境变量
+
+| 变量 | 说明 | 默认值 |
+|------|------|--------|
+| `APQCFG_ADMIN_PASSWORD` | 管理员密码 | (必填) |
+| `APQCFG_DATABASE_TYPE` | 数据库类型 (sqlite/mysql/postgres) | sqlite |
+| `APQCFG_DATABASE_PATH` | SQLite 数据库路径 | /data/apqcfg.db |
+| `APQCFG_DATABASE_CONNECTION` | 数据库连接字符串 | - |
+| `APQCFG_JWT_SECRET` | JWT 密钥 | (自动生成) |
+| `APQCFG_JWT_EXPIRY` | JWT 过期时间(小时) | 24 |
+
+## 功能特性
+
+### 应用管理
+
+- 注册和管理多个应用
+- 为每个应用配置独立的配置源
+- 支持应用分组
+
+### 配置编辑
+
+- 可视化配置编辑器
+- 支持 JSON/YAML/TOML 格式
+- 语法高亮和验证
+- 配置键搜索
+
+### 版本控制
+
+- 配置变更历史记录
+- 版本对比
+- 一键回滚
+
+### 多环境支持
+
+- 开发/测试/生产环境隔离
+- 环境间配置复制
+- 环境变量覆盖
+
+### 安全特性
+
+- 基于角色的访问控制
+- 敏感值自动脱敏
+- 操作审计日志
+
+## 与 WebApi 集成
+
+WebUI 通过 WebApi 与各应用通信。在应用中配置 WebApi:
+
+```csharp
+builder.Services.AddApqCfgWebApi(options =>
+{
+    options.Authentication = AuthenticationType.ApiKey;
+    options.ApiKey = Environment.GetEnvironmentVariable("APQCFG_API_KEY");
+    options.AllowRead = true;
+    options.AllowWrite = true;
+});
+```
+
+然后在 WebUI 中注册应用时,填入应用的 WebApi 地址和 API Key。
+
+## 截图
+
+> 截图待添加
+
+## 注意事项
+
+1. **生产环境安全**:确保使用强密码和 HTTPS
+2. **数据备份**:定期备份 SQLite 数据库或使用外部数据库
+3. **网络隔离**:WebUI 应部署在内网,不直接暴露到公网

+ 16 - 12
docs/site/public/api-reference/api/Apq.Cfg.CfgRootExtensions.html

@@ -247,19 +247,19 @@ foreach (var (key, value) in snapshot)
 
 
 
-  <a id="Apq_Cfg_CfgRootExtensions_GetOrDefault_" data-uid="Apq.Cfg.CfgRootExtensions.GetOrDefault*"></a>
+  <a id="Apq_Cfg_CfgRootExtensions_GetRequired_" data-uid="Apq.Cfg.CfgRootExtensions.GetRequired*"></a>
 
-  <h3 id="Apq_Cfg_CfgRootExtensions_GetOrDefault__1_Apq_Cfg_ICfgRoot_System_String___0_" data-uid="Apq.Cfg.CfgRootExtensions.GetOrDefault``1(Apq.Cfg.ICfgRoot,System.String,``0)">
-  GetOrDefault&lt;T&gt;(ICfgRoot, string, T?)
+  <h3 id="Apq_Cfg_CfgRootExtensions_GetRequired__1_Apq_Cfg_ICfgRoot_System_String_" data-uid="Apq.Cfg.CfgRootExtensions.GetRequired``1(Apq.Cfg.ICfgRoot,System.String)">
+  GetRequired&lt;T&gt;(ICfgRoot, string)
   
   </h3>
 
-  <div class="markdown level1 summary"><p>获取配置值,如果不存在则返回默认值</p>
+  <div class="markdown level1 summary"><p>获取必需的配置值,如果不存在则抛出异常</p>
 </div>
   <div class="markdown level1 conceptual"></div>
 
   <div class="codewrapper">
-    <pre><code class="lang-csharp hljs">public static T? GetOrDefault&lt;T&gt;(this ICfgRoot root, string key, T? defaultValue = default)</code></pre>
+    <pre><code class="lang-csharp hljs">public static T GetRequired&lt;T&gt;(this ICfgRoot root, string key)</code></pre>
   </div>
 
   <h4 class="section">Parameters</h4>
@@ -268,8 +268,6 @@ foreach (var (key, value) in snapshot)
     <dd></dd>
     <dt><code>key</code> <a class="xref" href="https://learn.microsoft.com/dotnet/api/system.string">string</a></dt>
     <dd></dd>
-    <dt><code>defaultValue</code> <span class="xref">T</span></dt>
-    <dd></dd>
   </dl>
 
   <h4 class="section">Returns</h4>
@@ -293,19 +291,19 @@ foreach (var (key, value) in snapshot)
 
 
 
-  <a id="Apq_Cfg_CfgRootExtensions_GetRequired_" data-uid="Apq.Cfg.CfgRootExtensions.GetRequired*"></a>
+  <a id="Apq_Cfg_CfgRootExtensions_GetValue_" data-uid="Apq.Cfg.CfgRootExtensions.GetValue*"></a>
 
-  <h3 id="Apq_Cfg_CfgRootExtensions_GetRequired__1_Apq_Cfg_ICfgRoot_System_String_" data-uid="Apq.Cfg.CfgRootExtensions.GetRequired``1(Apq.Cfg.ICfgRoot,System.String)">
-  GetRequired&lt;T&gt;(ICfgRoot, string)
+  <h3 id="Apq_Cfg_CfgRootExtensions_GetValue__1_Apq_Cfg_ICfgRoot_System_String___0_" data-uid="Apq.Cfg.CfgRootExtensions.GetValue``1(Apq.Cfg.ICfgRoot,System.String,``0)">
+  GetValue&lt;T&gt;(ICfgRoot, string, T?)
   
   </h3>
 
-  <div class="markdown level1 summary"><p>获取必需的配置值,如果不存在则抛出异常</p>
+  <div class="markdown level1 summary"><p>获取配置值,如果不存在则返回默认值</p>
 </div>
   <div class="markdown level1 conceptual"></div>
 
   <div class="codewrapper">
-    <pre><code class="lang-csharp hljs">public static T GetRequired&lt;T&gt;(this ICfgRoot root, string key)</code></pre>
+    <pre><code class="lang-csharp hljs">public static T? GetValue&lt;T&gt;(this ICfgRoot root, string key, T? defaultValue)</code></pre>
   </div>
 
   <h4 class="section">Parameters</h4>
@@ -314,6 +312,8 @@ foreach (var (key, value) in snapshot)
     <dd></dd>
     <dt><code>key</code> <a class="xref" href="https://learn.microsoft.com/dotnet/api/system.string">string</a></dt>
     <dd></dd>
+    <dt><code>defaultValue</code> <span class="xref">T</span></dt>
+    <dd></dd>
   </dl>
 
   <h4 class="section">Returns</h4>
@@ -333,6 +333,10 @@ foreach (var (key, value) in snapshot)
 
 
 
+  <h4 class="section" id="Apq_Cfg_CfgRootExtensions_GetValue__1_Apq_Cfg_ICfgRoot_System_String___0__remarks">Remarks</h4>
+  <div class="markdown level1 remarks"><p>此方法是 <a class="xref" href="Apq.Cfg.ICfgRoot.html#Apq_Cfg_ICfgRoot_GetValue__1_System_String_">GetValue&lt;T&gt;(string)</a> 的重载版本,
+允许指定自定义默认值。命名与 Microsoft.Extensions.Configuration 保持一致。</p>
+</div>
 
 
 

+ 2 - 2
docs/site/public/api-reference/api/Apq.Cfg.ICfgRoot.html

@@ -132,10 +132,10 @@ Interface ICfgRoot
       <a class="xref" href="Apq.Cfg.CfgRootExtensions.html#Apq_Cfg_CfgRootExtensions_GetMaskedSnapshot_Apq_Cfg_ICfgRoot_">CfgRootExtensions.GetMaskedSnapshot(ICfgRoot)</a>
   </div>
   <div>
-      <a class="xref" href="Apq.Cfg.CfgRootExtensions.html#Apq_Cfg_CfgRootExtensions_GetOrDefault__1_Apq_Cfg_ICfgRoot_System_String___0_">CfgRootExtensions.GetOrDefault&lt;T&gt;(ICfgRoot, string, T?)</a>
+      <a class="xref" href="Apq.Cfg.CfgRootExtensions.html#Apq_Cfg_CfgRootExtensions_GetRequired__1_Apq_Cfg_ICfgRoot_System_String_">CfgRootExtensions.GetRequired&lt;T&gt;(ICfgRoot, string)</a>
   </div>
   <div>
-      <a class="xref" href="Apq.Cfg.CfgRootExtensions.html#Apq_Cfg_CfgRootExtensions_GetRequired__1_Apq_Cfg_ICfgRoot_System_String_">CfgRootExtensions.GetRequired&lt;T&gt;(ICfgRoot, string)</a>
+      <a class="xref" href="Apq.Cfg.CfgRootExtensions.html#Apq_Cfg_CfgRootExtensions_GetValue__1_Apq_Cfg_ICfgRoot_System_String___0_">CfgRootExtensions.GetValue&lt;T&gt;(ICfgRoot, string, T?)</a>
   </div>
   <div>
       <a class="xref" href="Apq.Cfg.CfgRootExtensions.html#Apq_Cfg_CfgRootExtensions_TryGetValue__1_Apq_Cfg_ICfgRoot_System_String___0__">CfgRootExtensions.TryGetValue&lt;T&gt;(ICfgRoot, string, out T?)</a>

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 1
docs/site/public/api-reference/index.json


+ 16 - 16
docs/site/public/api-reference/xrefmap.yml

@@ -559,22 +559,6 @@ references:
   isSpec: "True"
   fullName: Apq.Cfg.CfgRootExtensions.GetMaskedSnapshot
   nameWithType: CfgRootExtensions.GetMaskedSnapshot
-- uid: Apq.Cfg.CfgRootExtensions.GetOrDefault*
-  name: GetOrDefault
-  href: api/Apq.Cfg.CfgRootExtensions.html#Apq_Cfg_CfgRootExtensions_GetOrDefault_
-  commentId: Overload:Apq.Cfg.CfgRootExtensions.GetOrDefault
-  isSpec: "True"
-  fullName: Apq.Cfg.CfgRootExtensions.GetOrDefault
-  nameWithType: CfgRootExtensions.GetOrDefault
-- uid: Apq.Cfg.CfgRootExtensions.GetOrDefault``1(Apq.Cfg.ICfgRoot,System.String,``0)
-  name: GetOrDefault<T>(ICfgRoot, string, T?)
-  href: api/Apq.Cfg.CfgRootExtensions.html#Apq_Cfg_CfgRootExtensions_GetOrDefault__1_Apq_Cfg_ICfgRoot_System_String___0_
-  commentId: M:Apq.Cfg.CfgRootExtensions.GetOrDefault``1(Apq.Cfg.ICfgRoot,System.String,``0)
-  name.vb: GetOrDefault(Of T)(ICfgRoot, String, T)
-  fullName: Apq.Cfg.CfgRootExtensions.GetOrDefault<T>(Apq.Cfg.ICfgRoot, string, T?)
-  fullName.vb: Apq.Cfg.CfgRootExtensions.GetOrDefault(Of T)(Apq.Cfg.ICfgRoot, String, T)
-  nameWithType: CfgRootExtensions.GetOrDefault<T>(ICfgRoot, string, T?)
-  nameWithType.vb: CfgRootExtensions.GetOrDefault(Of T)(ICfgRoot, String, T)
 - uid: Apq.Cfg.CfgRootExtensions.GetRequired*
   name: GetRequired
   href: api/Apq.Cfg.CfgRootExtensions.html#Apq_Cfg_CfgRootExtensions_GetRequired_
@@ -591,6 +575,22 @@ references:
   fullName.vb: Apq.Cfg.CfgRootExtensions.GetRequired(Of T)(Apq.Cfg.ICfgRoot, String)
   nameWithType: CfgRootExtensions.GetRequired<T>(ICfgRoot, string)
   nameWithType.vb: CfgRootExtensions.GetRequired(Of T)(ICfgRoot, String)
+- uid: Apq.Cfg.CfgRootExtensions.GetValue*
+  name: GetValue
+  href: api/Apq.Cfg.CfgRootExtensions.html#Apq_Cfg_CfgRootExtensions_GetValue_
+  commentId: Overload:Apq.Cfg.CfgRootExtensions.GetValue
+  isSpec: "True"
+  fullName: Apq.Cfg.CfgRootExtensions.GetValue
+  nameWithType: CfgRootExtensions.GetValue
+- uid: Apq.Cfg.CfgRootExtensions.GetValue``1(Apq.Cfg.ICfgRoot,System.String,``0)
+  name: GetValue<T>(ICfgRoot, string, T?)
+  href: api/Apq.Cfg.CfgRootExtensions.html#Apq_Cfg_CfgRootExtensions_GetValue__1_Apq_Cfg_ICfgRoot_System_String___0_
+  commentId: M:Apq.Cfg.CfgRootExtensions.GetValue``1(Apq.Cfg.ICfgRoot,System.String,``0)
+  name.vb: GetValue(Of T)(ICfgRoot, String, T)
+  fullName: Apq.Cfg.CfgRootExtensions.GetValue<T>(Apq.Cfg.ICfgRoot, string, T?)
+  fullName.vb: Apq.Cfg.CfgRootExtensions.GetValue(Of T)(Apq.Cfg.ICfgRoot, String, T)
+  nameWithType: CfgRootExtensions.GetValue<T>(ICfgRoot, string, T?)
+  nameWithType.vb: CfgRootExtensions.GetValue(Of T)(ICfgRoot, String, T)
 - uid: Apq.Cfg.CfgRootExtensions.TryGetValue*
   name: TryGetValue
   href: api/Apq.Cfg.CfgRootExtensions.html#Apq_Cfg_CfgRootExtensions_TryGetValue_

+ 1 - 1
docs/单元测试覆盖分析报告.md

@@ -133,7 +133,7 @@
 |-----|----------|----------|
 | `TryGetValue<T>()` | ✅ | CfgRootExtensionsTests |
 | `GetRequired<T>()` | ✅ | CfgRootExtensionsTests |
-| `GetOrDefault<T>()` | ✅ | CfgSectionTests |
+| `GetValue<T>()` | ✅ | CfgSectionTests |
 
 ### ServiceCollectionExtensions 扩展方法
 

+ 6 - 6
tests/Apq.Cfg.Tests.Shared/CfgSectionTests.cs

@@ -345,10 +345,10 @@ public class CfgSectionTests : IDisposable
 
     #endregion
 
-    #region GetOrDefault 扩展方法
+    #region GetValue 带默认值扩展方法
 
     [Fact]
-    public void GetOrDefault_ExistingKey_ReturnsValue()
+    public void GetValue_WithDefault_ExistingKey_ReturnsValue()
     {
         // Arrange
         var jsonPath = Path.Combine(_testDir, "config.json");
@@ -359,11 +359,11 @@ public class CfgSectionTests : IDisposable
             .Build();
 
         // Act & Assert
-        Assert.Equal(42, cfg.GetOrDefault("IntValue", 0));
+        Assert.Equal(42, cfg.GetValue("IntValue", 0));
     }
 
     [Fact]
-    public void GetOrDefault_NonExistingKey_ReturnsDefault()
+    public void GetValue_WithDefault_NonExistingKey_ReturnsDefault()
     {
         // Arrange
         var jsonPath = Path.Combine(_testDir, "config.json");
@@ -374,8 +374,8 @@ public class CfgSectionTests : IDisposable
             .Build();
 
         // Act & Assert
-        Assert.Equal(100, cfg.GetOrDefault("NonExistent", 100));
-        Assert.Equal("DefaultString", cfg.GetOrDefault("NonExistent", "DefaultString"));
+        Assert.Equal(100, cfg.GetValue("NonExistent", 100));
+        Assert.Equal("DefaultString", cfg.GetValue("NonExistent", "DefaultString"));
     }
 
     #endregion

+ 3 - 3
tests/README.md

@@ -57,7 +57,7 @@ dotnet test --filter "FullyQualifiedName~JsonCfgTests"
 | BoundaryConditionTests | 25 | 0 | 边界条件测试 |
 | ExceptionHandlingTests | 18 | 0 | 异常处理测试 |
 | ConfigChangesSubscriptionTests | 28 | 0 | 配置变更订阅测试 |
-| CfgSectionTests | 13 | 0 | 配置节(GetSection/GetChildKeys/GetOrDefault)测试 |
+| CfgSectionTests | 13 | 0 | 配置节(GetSection/GetChildKeys/GetValue)测试 |
 | ServiceCollectionExtensionsTests | 21 | 0 | 依赖注入扩展测试(IOptions/IOptionsMonitor/IOptionsSnapshot/嵌套对象/集合绑定)|
 | EncodingTests | 33 | 0 | 编码映射测试 |
 | PerformanceOptimizationTests | 30 | 0 | 性能优化测试(GetMany/SetMany/GetMany回调/缓存)|
@@ -147,7 +147,7 @@ dotnet test --filter "FullyQualifiedName~JsonCfgTests"
 | **CfgRootExtensions** |
 | `TryGetValue<T>()` | ✅ | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
 | `GetRequired<T>()` | ✅ | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
-| `GetOrDefault<T>()` | ✅ | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
+| `GetValue<T>()` | ✅ | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
 | `GetMasked()` | ✅ | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
 | `GetMaskedSnapshot()` | ✅ | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
 | **FileCfgSourceBase** |
@@ -329,7 +329,7 @@ CryptoTests 包含 58 个测试,覆盖所有加密脱敏公开功能:
 |--------------|------|
 | ReadWriteBenchmarks | 不同配置源的 Get/Set/Exists 性能对比 |
 | CacheBenchmarks | 缓存效果测试(热路径、缓存命中/未命中)|
-| TypeConversionBenchmarks | 类型转换性能测试(含 TryGetValue/GetRequired/GetOrDefault)|
+| TypeConversionBenchmarks | 类型转换性能测试(含 TryGetValue/GetRequired/GetValue)|
 | ConcurrencyBenchmarks | 并发读写性能测试 |
 | GetSectionBenchmarks | GetSection/GetChildKeys 性能测试 |
 | SaveBenchmarks | SaveAsync 持久化性能测试 |

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác