Healnet.Infrastructure
6.0.0
dotnet add package Healnet.Infrastructure --version 6.0.0
NuGet\Install-Package Healnet.Infrastructure -Version 6.0.0
<PackageReference Include="Healnet.Infrastructure" Version="6.0.0" />
<PackageVersion Include="Healnet.Infrastructure" Version="6.0.0" />
<PackageReference Include="Healnet.Infrastructure" />
paket add Healnet.Infrastructure --version 6.0.0
#r "nuget: Healnet.Infrastructure, 6.0.0"
#:package Healnet.Infrastructure@6.0.0
#addin nuget:?package=Healnet.Infrastructure&version=6.0.0
#tool nuget:?package=Healnet.Infrastructure&version=6.0.0
Healnet.Infrastructure 基础设施层
版本: 5.0.0 | 依赖: Healnet.Core → Healnet.Common | TFM: net10.0 | License: MIT
Healnet.Infrastructure 是 Healnet 微服务框架的基础设施实现层:对 26 个中间件/基础设施组件提供统一封装与 DI 注册约定,实现 Healnet.Core 定义的抽象契约(ICacheService、IUnitOfWork、ITokenService、IHttpClientService、IConsulService、ISessionChatClient 等)。契约在 Core、实现在本层(DIP);AI 抽象位于 Healnet.Core.AI / Healnet.Core.AI.Memory,本层 AI/ 仅含实现。
安装与最小接入
dotnet add package Healnet.Infrastructure
// Program.cs —— 业务服务推荐的两行入口(Service Defaults)
builder.AddHealnetServiceDefaults(); // 日志 / Consul / gRPC 转码 / MVC / JWT / 健康检查 / Swagger / Kestrel
// ...
app.UseHealnetServiceDefaults(); // 请求日志 → 全局异常 → CorrelationId → 认证授权 → 端点映射 → Consul 注册
按需细粒度接入时,各模块独立 AddXxx(见下表),配置节点统一约定在 Healnet:<模块>。
模块目录
| 模块 | 一句话定位 | README |
|---|---|---|
| Extensions | 聚合注册入口 + Service Defaults(推荐接入点) | Extensions/README.md |
| Authentication | JWT 令牌 + API Key 认证 | Authentication/README.md |
| Security | 安全响应头 + CORS | Security/README.md |
| SqlSugar | ORM:多库连接 + 泛型仓储 | SqlSugar/README.md |
| UnitOfWork | SqlSugar 事务工作单元 | UnitOfWork/README.md |
| MongoDB | 多租户文档数据库 | MongoDB/README.md |
| ElasticSearch | NEST 全文搜索封装 | ElasticSearch/README.md |
| Milvus | 向量数据库(ANN 检索) | Milvus/README.md |
| Redis | 分布式缓存 + Lua 分布式锁 | Redis/README.md |
| RabbitMQ | 消息队列(发布/订阅/死信) | RabbitMQ/README.md |
| OSS | 阿里云对象存储(IFileService 实现) | OSS/README.md |
| Consul | 服务注册/发现/健康检查 | Consul/README.md |
| Http | HttpClient + Polly 弹性 + Consul 发现 | Http/README.md |
| YARP | API 网关(静态路由/Consul 动态发现) | YARP/README.md |
| gRPC | 通道缓存 + 强类型客户端封装 | gRPC/README.md |
| Logging | Serilog 结构化日志 | Logging/README.md |
| OpenTelemetry | Tracing/Metrics/Logging 三件套 | OpenTelemetry/README.md |
| HealthChecks | 多组件健康检查(统一 JSON) | HealthChecks/README.md |
| Middleware | 请求日志 / 关联 ID 中间件 | Middleware/README.md |
| Resilience | Polly 重试/熔断/限流策略 | Resilience/README.md |
| AOP | AspectCore 内置拦截器族 | AOP/README.md |
| Swagger | API 文档(含 gRPC Transcoding 文档) | Swagger/README.md |
| AI | MEAI 统一会话链路 + 记忆分层 | AI/README.md |
| Skill | Skill 模板加载/解析/渲染(一句话 README) | Skill/README.md |
| Helpers | gRPC 客户端 IP 提取等静态助手(一句话 README) | Helpers/README.md |
| Mvc | 模型校验统一 ApiResponse(一句话 README) | Mvc/README.md |
gRPC 客户端完整调用案例(多层嵌套消息 / 流式 / Swagger JSON 对照)见 Swagger/GRPC_EXAMPLE.md。
版本策略
- 三库(Common / Core / Infrastructure)统一版本号发布;
PackageReleaseNotes逐版本记录缺陷修复与兼容性说明,历史明细用git log追溯。 - 破坏性变更集中在主版本(3.0 / 5.0);4.x 内的 DI 生命周期调整等行为变化均在该版本 ReleaseNotes 中显式标注兼容性。
架构约定(精简版)
- 契约在 Core,实现在 Infrastructure:本层各模块 README 顶部均标注对应的 Core 抽象来源。
- OSS 替换 Common 的
IFileService:AddAliyunOssFileService经services.Replace切换实现。 - 过时成员已标
[Obsolete]:如 Middleware 的UseExceptionHandling/UseHealthCheck,替代指引见各模块 README「⚠️ 常见坑」。 - 各模块 README 只保留「快速上手」:一句话定位 + 3 步接入 + 最小配置 + 常见坑;API 签名细节看代码 IntelliSense(包内随带 XML 注释)。
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Aliyun.OSS.SDK.NetCore (>= 2.14.0)
- AspectCore.Core (>= 2.4.0)
- AspectCore.Extensions.DependencyInjection (>= 2.4.0)
- Azure.AI.OpenAI (>= 2.9.0-beta.1)
- Consul (>= 1.7.14.1)
- Cronos (>= 0.13.0)
- Google.Api.CommonProtos (>= 2.15.0)
- Google.Protobuf (>= 3.27.2)
- Grpc.AspNetCore (>= 2.65.0)
- Grpc.Net.Client (>= 2.66.0)
- Healnet.Common (>= 5.0.0)
- Healnet.Core (>= 6.0.0)
- Microsoft.AspNetCore.Authentication.JwtBearer (>= 10.0.2)
- Microsoft.AspNetCore.Grpc.JsonTranscoding (>= 10.0.2)
- Microsoft.Extensions.AI (>= 10.3.0)
- Microsoft.Extensions.AI.OpenAI (>= 10.3.0)
- Microsoft.Extensions.Caching.Memory (>= 10.0.2)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.2)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.3)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 10.0.2)
- Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions (>= 10.0.2)
- Microsoft.Extensions.Http.Polly (>= 10.0.2)
- Microsoft.Extensions.Logging (>= 10.0.2)
- Milvus.Client (>= 2.3.0-preview.1)
- MongoDB.Driver (>= 2.29.0)
- NEST (>= 7.17.0)
- OpenTelemetry.Exporter.Console (>= 1.7.0)
- OpenTelemetry.Exporter.OpenTelemetryProtocol (>= 1.7.0)
- OpenTelemetry.Exporter.Zipkin (>= 1.7.0)
- OpenTelemetry.Extensions.Hosting (>= 1.7.0)
- OpenTelemetry.Instrumentation.AspNetCore (>= 1.7.0)
- OpenTelemetry.Instrumentation.Http (>= 1.7.0)
- Oracle.ManagedDataAccess.Core (>= 23.8.0)
- Polly (>= 8.3.0)
- Polly.RateLimiting (>= 8.0.0)
- RabbitMQ.Client (>= 6.8.0)
- Serilog.AspNetCore (>= 10.0.0)
- Serilog.Extensions.Hosting (>= 10.0.0)
- Serilog.Sinks.Console (>= 6.1.1)
- Serilog.Sinks.File (>= 7.0.0)
- Serilog.Sinks.OpenSearch (>= 1.2.0)
- SqlSugar.ClickHouseCore (>= 5.1.4.1)
- SqlSugarCore (>= 5.1.4.215)
- StackExchange.Redis (>= 2.9.11)
- Swashbuckle.AspNetCore.SwaggerGen (>= 6.5.0)
- Swashbuckle.AspNetCore.SwaggerUI (>= 6.5.0)
- System.DirectoryServices.Protocols (>= 10.0.0)
- System.Drawing.Common (>= 10.0.0)
- System.IdentityModel.Tokens.Jwt (>= 8.0.1)
- System.Interactive.Async (>= 6.0.1)
- Winton.Extensions.Configuration.Consul (>= 3.4.0)
- Yarp.ReverseProxy (>= 2.2.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 6.0.0 | 0 | 11/5/2026 |
| 5.3.0 | 0 | 11/5/2026 |
| 5.2.0 | 0 | 11/5/2026 |
| 5.1.1 | 0 | 11/5/2026 |
| 5.1.0 | 0 | 11/5/2026 |
| 5.0.0 | 6 | 11/4/2026 |
| 4.0.40 | 41 | 11/4/2026 |
| 4.0.39 | 42 | 11/4/2026 |
| 4.0.38 | 46 | 11/3/2026 |
| 4.0.37 | 44 | 11/3/2026 |
| 4.0.36 | 43 | 11/3/2026 |
| 4.0.35 | 47 | 11/3/2026 |
| 4.0.34 | 40 | 11/3/2026 |
| 4.0.33 | 43 | 11/3/2026 |
| 4.0.32 | 39 | 11/3/2026 |
| 4.0.31 | 42 | 11/3/2026 |
| 4.0.30 | 43 | 11/3/2026 |
| 4.0.27 | 42 | 11/3/2026 |
| 4.0.26 | 40 | 11/3/2026 |
| 4.0.25 | 44 | 11/3/2026 |
v6.0.0: 破坏性批次(BREAKING:删除到期废弃中间件 + API 收敛)。① 移除 [Obsolete] 到期的旧中间件(全生态零引用实证:src/tests/examples + 5 个消费方仓库):ExceptionHandlingMiddleware(旧异常中间件)、HealthCheckMiddleware(旧健康检查中间件,请用 app.MapHealthChecks("/health"))、MiddlewareExtensions.UseHealthCheck 扩展(引用被删类)。保留 app.UseExceptionHandling()——它是 Core ExceptionHandlerMiddleware 的便捷包装,healnet.server 8 服务 + agentos 2 仓共 10 处活跃调用,README 原先误标 Obsolete 已修正。② API 收敛:7 处 UsageExample 空壳占位类 public→internal。依赖:Healnet.Core 6.0.0 / Healnet.Common 5.0.0。 v5.3.0: SqlSugar 封装审查优化批(P2 四项 + 过程中实证发现的 P1 一项)。① 【P1】SqlSugarRepository.GetClient 走「子对象租户链」的调度依赖崩溃修复:原实现 base.Context.AsTenant().GetConnectionScope(resolved),反编译 SqlSugarCore 5.1.4.215 实证 SqlSugarProvider.AsTenant() 在 Root==null 时无条件抛「Child objects do not support tenant methods」(Root 仅在 master SqlSugarClient 经 GetConnection 创建 provider 时赋值;SqlSugarScopeProvider 的上下文存储为每次调用 StackTrace 扫描 + ThreadLocal(CallContextThread)/AsyncLocal(CallContextAsync) 混合 + GetKey 线程身份 key 复用——跨线程/异步上下文自动新建的 provider 不带 Root),该缺陷随线程/上下文调度时好时坏(既有 SqlSugarIntegrationTests 的手工转移注册形态恰好命中安全路径而掩盖);现改为与构造函数同走 master 链(持有 _masterClient,SqlSugarClient.AsTenant() 恒返回自身、GetConnectionScope 恒设 Root——均反编译确认),链路确定安全;变异证伪:改回子对象链 → 生产容器形态(AddSqlSugar 直建 + CreateScope)分页用例稳定抓红。② 仓储分页改官方 RefAsync 模式:两个 PageAsync 重载原实现 CountAsync 后复用同一 queryable 实例(可变 builder,脆弱),改 ToPageListAsync(pageIndex, pageSize, RefAsync<int>) 内部 count+取数,TotalCount / PageModel.TotalCount 背传语义不变。③ 新增慢 SQL 监控:ConfigureAop 挂 Aop.OnLogExecuted,经 Ado.SqlExecutionTime 取真实耗时,每条记 Debug([SQL] {耗时}ms | {SQL},不记参数值同 LogExecuting 脱敏约定);新配置 Healnet:SqlSugar:SlowQueryThresholdMs(默认 1000,≤0 关闭升级),达阈值升 Warning([SlowSQL] 前缀);行为体抽 internal LogExecuted 共享(与 LogExecuting 同一组合契约——AopProvider 委托只写无 get),与 5.2.0 审计日志(只组合 OnLogExecuting)正交不冲突。④ AddSqlSugar 重复注册防护:RegisterSqlSugar 统一入口检测 ISqlSugarClient 已注册即抛带修复指引异常(原二次调用静默新建 Scope 覆盖,DI last-wins,多模块/测试场景易产生难排查的路由错乱)。⑤ PagedResult.TotalPages 除零防护:PageSize<=0 或 TotalCount<=0 返回 0(原 (int)Infinity 抛 OverflowException)。新增守卫测试 SqlSugarSlowSqlGuardTests 9 项(重复注册 1 + LogExecuted 阈值 sink 断言 3 + TotalPages 边界 3 + 生产容器形态分页回归 2),SqlSugar 全套 28/28 绿、变异证伪 GetClient 子对象链 2/2 抓红。README 同步 SlowQueryThresholdMs 与重复注册说明。仅本包变更,依赖 Core 5.1.0 不变。 v5.2.0: P2 能力批 —— Consul KV 配置中心(动态刷新)+ SqlSugar 审计日志(CUD → Serilog → ES)。① Consul/ConsulKVExtensions:AddHealnetConsulKV 在 IConfigurationBuilder 上挂 Consul KV 配置源(Winton.Extensions.Configuration.Consul 3.4.0,MIT,新增依赖);设计红线——只挂 ConfigurationSource 完全不碰 DI 注册(与 AddConsul 正交无双注册)、KV 根键未配置完全不启用(no-op)、Optional=true(默认)Consul 不可达启动不崩、ReloadOnChange=true watch 动态刷新(⚠️ 仅运行时读取生效,SqlSugarOptions 等启动快照进 Singleton 的配置不随刷新传播需重启,已文档化)。② SqlSugar/AuditLog/:AuditLogCollector 经 Aop.OnLogExecuting 语句级拦截 DML(回调次数=SQL 数,无每行每列放大)——⚠️ SqlSugar AopProvider 委托只写无 get,组合经共享行为体 SqlSugarExtensions.LogExecuting 实现(框架 SQL Debug 日志语义保留);参数按名脱敏(黑名单 password/token/idcard/mobile 等,匿名参数 @p0 无法按名判断已文档化);有界 Channel(满载显式容量检查丢弃计数,.NET 10 DropWrite 下 TryWrite 返回值不可靠)后台批量写 Serilog——复用既有 Serilog.Sinks.OpenSearch 管道落 ES(独立索引按 SourceContext "Healnet.AuditLog" 路由),零新增 sink 依赖、绝不阻塞 CUD 线程;AddHealnetAuditLog 默认 Enabled=false 零注入(不注册服务不挂 Aop)。③ CPM:Serilog 浮动下限 [4.0.0,)→[4.3.0,)(Serilog.AspNetCore 10.0.0 传递要求,消 NU1605)。新增 24 项守卫测试(ConsulKVTests 6 + AuditLogTests 18),变异证伪 4/4 抓红。依赖 Core 5.1.0 不变。 v5.1.1: 热修 SqlSugarOutboxStore 多库连接名边缘 bug。① 缺陷:EventBus 的 SqlSugarOutboxStore.GetConnectionScope(OutboxDbName) 字面量直查——多库且首连接名非 "Default"、又未显式配 OutboxDbName(默认占位 "Default")的消费方,OutboxProcessor 启动后即抛「ConfigId was not found Default」(与 SqlSugarRepository v4.0.31 修复前的同源缺陷)。② 修复:解析逻辑抽到共享的 SqlSugarConnectionResolver(internal,经 InternalsVisibleTo 供守卫测试断言)——占位默认值 "Default" 在连接配置中不存在时回退首个 ConfigId(对齐仓储 v4.0.31 别名映射语义);显式配置的连接名保持直查/不存在时 fail-fast(防静默改路由);空配置视同未配置。③ SqlSugarRepository.ResolveDefaultDbName 改为调用共享实现,行为不变(守卫测试回归保护)。新增 8 项守卫测试(SqlSugarConnectionResolverTests),变异证伪 2/2 抓红。仅本包变更,依赖 Core 5.1.0 不变。 v5.1.0: 第一梯队能力批(事件总线闭环 + Cron 后台任务 + 自动 DI)。① Background/:CronBackgroundService——Cronos 调度、优雅停机、单次失败吞掉记日志(宿主不死)、重叠保护(TryExecuteOnceAsync 跳过语义)、时区换算、AddCronJob 注册;新增 Cronos 0.13.0 依赖。② EventBus/:InMemoryEventDispatcher(救活 Core 的 IDomainEventDispatcher 死契约,按运行时类型路由、单处理器失败继续)、SqlSugarOutboxStore+OutboxEntityMapper(救活 IOutboxStore,表 outbox_message,失败指数退避 2^n 秒封顶 300s、超限 DeadLetter)、OutboxEventBus(IEventBus 实现,进程内分发 + 集成事件 Web(camelCase) 序列化写箱)、OutboxProcessor(后台轮询投递,JsonElement 原样透传无双重编码,公开 ProcessPendingAsync 测试钩子)、InboxGuard(复用 IIdempotencyStore SETNX 语义,WithInbox 包装订阅处理器)、EventBusExtensions(AddEventBus/AddOutboxProcessor)。③ Extensions/AutoDIExtensions:AddHealnetAutoDIServices 按标记接口扫描注册(TryAdd 幂等、多标记取最高生命周期、泛型/抽象/编译器生成类型跳过)。依赖 Core 5.1.0。 v5.0.0: 5.0 契约 2.0 正式版(BREAKING,双 provider 收敛为唯一 MEAI 路径)。① 新增 SessionChatClient 会话装饰器(复用 ChatSessionStore:租约 API AcquireAsync/SessionLease 门横跨流枚举期、SessionEvicted 淘汰事件、IsSessionLive)与 NullSessionChatClient(未配 ApiKey 降级注册、调用时抛配置指引,与 NullCopilotClient 同范式);AddAIServices 统一注册 ISessionChatClient(单例),MicrosoftAI 分支复用已注册 IChatClient、SK 分支经共享工厂 CreateMeaiChatClient 直连 OpenAI 兼容端点;AIOptions 推理参数正式接线(随 Core 5.0.0)。② BREAKING 删除 Legacy/ 隔离区整目录 7 实现:UnifiedAIService/AgentService/AgentContextManager/ChatContextManager/RAGService/CopilotClient(SK)/LegacyChatHistory。③ 改名:MicrosoftAIClient → CopilotClient(ICopilotClient 唯一实现)。④ AI 目录按功能拆分:Chat/Copilot/Legacy(已删)/Memory,Chat 与 Copilot 命名空间保持 Healnet.Infrastructure.AI(消费方 using 零变化)。⑤ DX:26 模块 README 精简为「定位+快速调用」,目录按功能拆子目录(gRPC Client/Exceptions、Swagger Filters/Grpc、AOP Interceptors、HealthChecks Checks、Redis Services),namespace 全部不动。依赖 Core 5.0.0。 v4.0.40: AI 层架构评审兼容修复批(随 Core 4.0.15 的 [Obsolete] 标注,评审结论见 AI/README.md 文件职责表)。① AgentService 失败路径同样删除上下文:原仅成功后删除,而执行链路当前恒抛 NotSupportedException,等于每次调用泄漏一个 AgentContext(1h TTL 兜底);失败路径的排查依据改由异常本身承担。② RegisterAgentAsync/RegisterToolAsync 同名注册由 TryAdd 改为覆盖:原实现把运行时注册的同名 Agent/Tool 静默挡掉(默认内置项反而不可替换)。③ CopilotClient/MicrosoftAIClient 会话裁剪移到消息追加之后,修复 off-by-one:原实现先裁剪再追加,发送给模型的历史可达 单会话上限+1 条(守卫测试断言由 [1,3,3] 修正为 [1,2,2]);保存前再裁一次,内存持久历史同样不超过上限;单会话上限下限钳制为 2(一轮对话至少 user+assistant 两条)。④ 遗留兼容类型 ChatHistory/ChatHistoryRole 从 MicrosoftAIClient.cs 拆分至独立文件 LegacyChatHistory.cs 并随 Core 4.0.15 语义标 [Obsolete](框架内部零消费,5.0 删除)。⑤ SK 分支 HttpClient 由手工 new 统一走 IHttpClientFactory(命名客户端 Healnet.SemanticKernel + SetHandlerLifetime(InfiniteTimeSpan)——Kernel 单例长期持有该客户端,禁用 handler 轮换防捕获已处置 handler),与 EmbeddingService 模式对齐。⑥ 实现侧对废弃成员的链路内调用(AgentService.ChatAsync→ExecuteAsync、UnifiedAIService 转发)以 #pragma CS0618 显式抑制并注明 5.0 随类删除。依赖 Core 4.0.15。 v4.0.39: AI 服务面快修批(P1)+ 接入便利性。① IAgentService 注册由 Scoped 改为 Singleton:AgentService 内部维护随实例持久的注册表(_agents/_tools,构造时 InitializeDefaultAgents(),并公开 RegisterAgentAsync/RegisterToolAsync 运行时注册),Scoped 会导致每个请求重跑默认初始化、运行时注册的 Agent/Tool 下个请求即蒸发——与 RAGService 空知识库(4.0.37)、ICopilotClient 有状态(4.0.38)同类缺陷;其构造依赖 AIOptions 与 IAgentContextManager 均为单例,改 Singleton 安全。② AddAIServices 现在直接注册 IEmbeddingService(Scoped,复用 Healnet:AI 的 Endpoint/ApiKey,经 IHttpClientFactory 复用连接池):此前只在 AddMemoryServices 内注册,仅需向量化能力的项目必须知道还要另调 AddMemoryServices 才能解析——隐蔽接入门槛已消除;工厂逻辑收敛到共享 CreateEmbeddingService(MemoryOptions 未注册时回退默认模型 text-embedding-v3),AddMemoryServices 路径行为不变(其内存配置仍优先生效)。③ 新增 4 项守卫测试(共 20 项):IAgentService 跨作用域同实例、运行时注册的 Agent 跨作用域存活(反射断言 _agents)、不调 AddMemoryServices 时 IEmbeddingService 可解析。QA 变异证伪:IAgentService 改回 AddScoped 被 2 项抓住。兼容性:DI 行为变化仅 IAgentService Singleton 化与 IEmbeddingService 新注册;AddMemoryServices 的既有消费方行为不变。依赖 Core 4.0.14。 v4.0.38: AI 服务生命周期缺陷修复(P0)+ DI 完整性守卫测试随批落地。① ICopilotClient 注册由 Scoped 改为 Singleton:IRAGService(Singleton,4.0.24 起)构造依赖它,两者组合自 4.0.24 起即非法——ValidateScopes/ValidateOnBuild(ASP.NET Core 在 Development 默认开启)下应用启动即抛 "Cannot consume scoped service 'ICopilotClient' from singleton 'IRAGService'"(生产不校验则单例隐式捕获一份永久化);且 CopilotClient/MicrosoftAIClient 是有状态实现(按 contextId 累积会话历史),Scoped 令每次请求丢历史、ClearContext 形同虚设。② 有状态单例必须有界:新增 ChatSessionStore<THistory>(会话数上限默认 1024、空闲 30 分钟淘汰、单会话消息数上限默认 100 裁剪最旧、同一 contextId 并发调用以会话门串行化);淘汰永不踢掉本次请求的会话(否则活跃但恰好最旧的会话会在自己的调用里历史归零);容量淘汰只在严格超过上限时触发(count == max 为合法满载)。③ 未配置 ApiKey 时降级注册 NullCopilotClient:容器图始终可解析、应用可正常启动,真正调用 AI 时才抛带配置指引的 InvalidOperationException(含 Healnet:AI:ApiKey 配置提示)——业务服务不再需要自行 GetService 判空 / try-catch 兜底(这正是每个新服务复制同一段防御代码的根因);其 ClearContext 为无副作用空操作。④ RabbitMQService(Singleton)的可变共享状态 _handlers/_declaredExchanges/_declaredQueues 由 Dictionary/List 改为 ConcurrentDictionary/ConcurrentBag(原无任何锁保护,并发订阅/声明存在竞态)。⑤ 新增 DiCompletenessTests 16 项守卫测试:ValidateOnBuild 全 provider 容器校验、NullCopilotClient 降级语义、根容器单例断言、跨调用历史保留 / ClearContext 生效 / 单会话裁剪 / 容量淘汰、同 contextId 并发串行化、RabbitMQ 并发容器反射断言。QA 变异证伪:改回 AddScoped 被 8 项抓住、移除 Null 兜底被 4 项抓住。兼容性:DI 行为变化仅 ICopilotClient Singleton 化与未配置降级;直接 new CopilotClient/MicrosoftAIClient 的使用方签名不变(新增可选参数)。依赖 Core 4.0.14。 v4.0.37: 开启 XML 注释文档生成 + 修净本包全部 XML 注释格式告警(只动注释与项目属性,IL 无变化)。① 此前本包不产 Healnet.Infrastructure.xml,包消费方引用 SwaggerOptions / GrpcChannelCache / AddHealnetInfrastructure / IMilvusService 等公开类型时【没有任何 IntelliSense 注释】;现启用 GenerateDocumentationFile,随包提供 lib/net10.0/Healnet.Infrastructure.xml(实测 974 个 member 条目),服务端模板的 XML 复制 Target 会一并带上(Swagger 里 gRPC/Consul/SqlSugar 等类型也有了描述)。② 修净 12 条 CS1570(未转义泛型尖括号)、7 条 CS1573(param 缺项:AddHealnetHealthChecks 两个重载的 milvusEnabled、IMilvusService 四个方法的 cancellationToken、MilvusField.FloatVector 的 description)、2 条 CS1587、1 条 CS0419(IEntityMapper 的 cref 指向 AddSqlSugar 重载不明确,改为带完整签名的 cref)。③ 一处结构性错位:私有常量 ConsulOperationTimeoutMs 的 summary 被夹在 AddConsulHttpClient 的 example/code 块内部,使 example 永不闭合、</example> 落到另一个私有方法之后(连带 2 条 CS1570)——已把该文档块移回 AddConsulHttpClient 上方;SqlSugarExtensions 把 summary 写在 ConfigureExternalServices 的 lambda 赋值上(非合法文档目标),改为普通注释。④ NoWarn 只保留 1591(公开面较大,补全缺注释不在本批范围);其余 XML 注释类告警不再抑制,将来格式错误会重新告警而非静默丢文档。依赖 Core 4.0.14。 v4.0.36: Swagger 文档三项 P3 修复(只影响文档生成,运行时行为不变)。① 移除 c.AddSecurityRequirement 的「全局安全要求」:它让文档里每个操作都带 security,Swagger UI 给所有端点画锁图标——包括模板中完全匿名的 GET /api/products(读者会误以为必须带 token)。改由新增的 AuthorizeAwareSecurityOperationFilter 逐操作声明:方法或其 Controller 上存在 [Authorize](IAuthorizeData)且任意一处都没有 [AllowAnonymous](IAllowAnonymous,优先于 Authorize)时才写 security;判定来源取 ActionDescriptor.EndpointMetadata ∪ 反射(MethodInfo/ControllerTypeInfo,inherit: true),兼顾 MVC 与 Minimal API/手工描述符。② 新增 DefaultResponsesOperationFilter,为 Controller 端点补齐框架实际会返回的 400/401/403/404/500(Swashbuckle 默认只生成 200),错误体经 ISchemaGenerator 生成 $ref 指向非泛型 ApiResponse(避免悬空引用、兼容 CustomSchemaIds);已声明同名状态码的操作(手写 [ProducesResponseType])与 gRPC 转码端点(/api/grpc 前缀或 GrpcTag 分组,其错误响应已由 GrpcSwaggerDocumentFilter 声明)自动跳过。③ 两个过滤器各由 SwaggerOptions 开关控制:DeclareAuthOnEndpoints / DeclareDefaultResponses(默认 true;置 false 可回退到「全局 security」/「只声明 200」的旧行为),并已在这两处配置绑定路径(AddSwagger(IConfiguration)、AddSwaggerWithGrpcSupport(configuration, ...))一并透传,避免配置里关开关静默失效。依赖 Core 4.0.13(ApiResponse 语义注释 + XML 文档生成,schema description 才有内容)。 ④ QA 复验修复:GetXmlCommentPaths 原把输出目录【所有】*.xml 全喂给 IncludeXmlComments,模板开启 CopyDocumentationFilesFromPackages 后要解析 100+ 个三方包文档(109 个 / 46MB),swagger.json 首/次耗时 0.354s→1.012s、0.124s→1.196s;现只选 Healnet.*.xml(框架契约)+ 入口程序集同名 .xml(业务 DTO),实测回到 0.159s / 0.035s,且不影响显式配置的 XmlCommentPaths(仍优先)。 ⑤ 同批加固(主理人返工要求):① 显式配置 XmlCommentPaths 时【完全采用配置值】、不再叠加自动扫描(否则「想收窄范围」的意图会被自动扫描打破);② 输出目录扫描结果按进程 Lazy 缓存,避免 AddSwagger 多次调用重复扫盘;③ 被服务端模板改为用 GeneratePathProperty 从 NuGet 包目录定向复制 Healnet.Core.xml(模板输出目录 xml 从 109 个 / 46MB 降到 1 个 / 152KB)。 v4.0.35: 依赖升级至 Healnet.Core 4.0.12。核查发现 4.0.34 打包时依赖 Core 4.0.11,而 4.0.11 的线上包缺少 ExceptionHandlerMiddleware 的「响应已开始写出」保护(该修复晚于 4.0.11 打包时刻),只升本包会拿到不含修复的 Core;故 Core 补发 4.0.12、本包随之升 4.0.35 以保证依赖解析到修复版本。本包自身代码与 4.0.34 完全一致(模型校验统一响应等能力不变)。 v4.0.34: 模型校验统一响应(兼容性接管,默认关闭)。此前 [ApiController] 的模型校验失败返回 RFC7807 ProblemDetails(HTTP 400),而框架其余错误统一走 ApiResponse,同一个「字段必填」错误存在两套 body 形状,调用方要写两份解析逻辑。现提供可选统一:新增 AddHealnetUnifiedValidationResponse 扩展与 Healnet:Validation 配置节(UnifyResponse / HttpStatusCode / ErrorCode / Message / IncludeFieldErrors / ForceJsonContentType)。UnifyResponse=false(默认)时不注册任何 MVC 行为,老调用方零影响;置 true 后校验失败返回 ApiResponse(code=400、success=false、字段明细放 data,结构与 ProblemDetails.errors 同构,前端把 body.errors.Name 改成 body.data.Name 即可迁移)。HttpStatusCode 默认仍 400(既有「看到 400 走失败分支」的判断不用改),可配 200 完全对齐 ApiResponse「HTTP 恒 200、错误看 body.code」的惯例;ForceJsonContentType 默认 true,避免项目注册 XML formatter 后同一响应因 Accept 不同而形状漂移。另在模板文档中警示:不支持给 record 主构造参数加 [property: Required](ASP.NET Core 会抛 InvalidOperationException,导致该接口所有 POST 变 500)。另供 E2E 使用的 ServiceProcess 增加 extraEnv 参数(验证开关两态),withGrpc=false 时显式关闭 GrpcPort 避免撞本机端口。 v4.0.31: 代码审查修复 3 项。① SqlSugarRepository 多库默认连接名不一致(P1):构造已按「连接配置首个 ConfigId」绑定 Context,但 GetClient 及全部 CRUD 方法默认参数恒为字面量 Default——多库配置首连接名非 Default(如 Master)时所有增删改查走 GetConnectionScope(Default) 抛「未找到 ConfigId」。现统一在 GetClient 入口做别名映射(请求 Default 且真实默认名不同时改用真实名),方法签名保持兼容。② GetSingleAsync/GetFirstAsync 性能缺陷:原实现 ToListAsync() 后 SingleOrDefault/FirstOrDefault 会把整表拉进内存,改为下推 SQL 的 SingleAsync/FirstAsync。③ ExceptionHandlerMiddleware 响应已开始写出时(SSE/大文件下载中途失败)再设 ContentType/StatusCode 会抛 InvalidOperationException 导致中间件自身崩溃,补 response.HasStarted 保护。另修复 ServiceDefaults 的 Swagger 生产门禁缺失(文档声明生产默认不挂载但实现未校验 AllowNonDevelopment,与网关门禁对齐,非 Development 环境需显式开启才挂载)。 v4.0.30: 彻底重写 gRPC 注解路由解析为强类型实现并新增依赖 Google.Protobuf 3.27.2 与 Google.Api.CommonProtos 2.15.0。4.0.29 的基类链 Descriptor 查找修复仍不够:MethodDescriptor 根本没有名为 Options 的属性(旧反射链在此恒断),且 Extensions.ContainsField API 形状也不存在,导致 google.api.http 注解永远读不到。现改为强类型:ServiceDescriptor.Methods → MethodDescriptor.CustomOptions.TryGetMessage(72295728) 读取 Google.Api.HttpRule,按 PatternCase(Get/Post/Put/Delete/Patch/Custom)成对取路径与 HTTP 方法,消除全部脆弱反射。 v4.0.29: 修复 gRPC 转码端点 Swagger 文档路由与真实路由不一致(P1,E2E 实测发现)。GetHttpRouteFromProtoDescriptor 的静态 Descriptor 查找只回溯实现类及其 DeclaringType,而 grpc-dotnet 生成结构中 Descriptor 位于基类链上某一层的 DeclaringType(实现类 UserGrpcService → 基类 UserGrpc.UserGrpcBase → 外层容器类 UserGrpc),导致 google.api.http 注解路由永远解析不到,Swagger 退化为合成回退路由(/grpc/{service}/{method},Try it out 必 404)。现沿整条基类链及其 DeclaringType 查找。另修复模板 ProductsController 实例字段保存跨请求状态缺陷(Controller 每请求新建,Create 后 Get 必 404),改为 Singleton InMemoryProductStore 注入。 v4.0.28: 修复 gRPC 服务类型识别规则在 grpc-dotnet 生成结构下恒不命中的缺陷(4.0.26 引入的自动探测/自动映射与 Swagger 自动发现均受影响)。此前 IsGrpcService 判定为'基类链上存在名以 Base 结尾且有 BindService 方法',但 grpc-dotnet 实际生成的结构是:服务实现类继承嵌套抽象类(如 UserGrpc.UserGrpcBase),BindService 定义在外层静态类(UserGrpc)上而非基类链上——导致自动探测 EnableGrpc 恒 false(ServiceDefaults 不启用 gRPC)、Swagger 自动发现也恒为空(此前未暴露是因为业务均显式传入服务类型)。现识别规则改为:基类链上存在名以 Base 结尾的类型,且该类型自身或其 DeclaringType(外层静态类)上存在 BindService 方法。ServiceDefaultsExtensions.IsGrpcServiceType 与 GrpcServiceScanner.IsGrpcService 同步修复。注意 BindService 在外层类上存在两个重载(BindService(base) / BindService(base, builder)),判定按方法名而非 GetMethod 精确签名(原写法触发 AmbiguousMatchException 启动崩溃)。 v4.0.27: 修复 4.0.26 Service Defaults 三处缺陷(发布后经三模板实机启动验证发现)。① MapAllDiscoveredGrpcServices 程序集排除规则误伤业务程序集:原笼统排除 FullName 以 Healnet 开头的程序集,导致 Healnet.Example.* 等业务服务的 gRPC 服务不被自动映射(转码端点 404);现改为精确排除 Healnet.Common/Healnet.Core/Healnet.Infrastructure 三个框架程序集(并补 mscorlib/netstandard)。② UseHealnetServiceDefaults 与 AddHealnetServiceDefaults 能力开关脱节:原 Use 侧从 Healnet:ServiceDefaults 配置新建 options(默认全 false),Add 侧的自动探测结果丢失,导致 Controller 端点不映射;现 Add 侧将探测后的 options 写入 DI(AddSingleton),Use 侧优先取 DI 实例,配置节点仍可覆盖。③ Kestrel 双端口策略修正并显式告警:明文端口的 Http1AndHttp2 无 TLS 时 HTTP/2 被禁用(Kestrel 事件 Http2DisabledWithHttp1AndNoTls,连接退化为 HTTP/1.1),此前'HTTP 与 gRPC 同端口共存'的原生 gRPC 调用实际不可用;现明确——EnableGrpc 且 GrpcPort 配置为独立端口(异于 ServicePort)时双端口(HTTP: Http1AndHttp2 + gRPC: HTTP/2 Only,原生 gRPC 唯一通路);GrpcPort 缺失或与 ServicePort 相同则记 Error 提示补配置(JSON 转码 HTTP 面不受影响)。混合宿主模板已改为双端口(5000 HTTP + 5001 gRPC),Consul 注册 grpc-port Meta 指向 5001,发现/通信全链路正确。 v4.0.26: 新增服务托管默认值 Service Defaults(HealnetServiceDefaultsExtensions),框架样板全收编,业务服务 Program.cs 只需两行调用。① AddHealnetServiceDefaults(WebApplicationBuilder):自动完成 Serilog 日志(UseHealnetSerilog)、Consul 客户端、gRPC JSON 转码 + 统一异常拦截器(按需)、MVC(按需)、JWT、健康检查、Swagger(含 gRPC 文档,服务类型自动发现)、Kestrel 端口策略(读 Healnet:Consul:ServicePort/GrpcPort——GrpcPort 大于 0 且异于 ServicePort 时双端口(HTTP: Http1AndHttp2 + gRPC: HTTP/2 Only),否则单端口 Http1AndHttp2(HTTP 与 gRPC 同端口共存,content-type 协商));能力开关默认自动探测入口程序集(存在 Controller 则启用 MVC、存在 gRPC 服务类则启用 gRPC),可经 HealnetServiceDefaultsOptions 委托显式覆盖。② UseHealnetServiceDefaults(WebApplication):按生产顺序挂载请求日志(/health 降噪)、全局异常(ExceptionHandlerMiddleware)、CorrelationId、认证授权、/health、MapControllers(按需)、自动发现并映射 gRPC 服务(MapAllDiscoveredGrpcServices,扫描非系统程序集中基类链含 BindService 的类型,逐个反射调用 MapGrpcService,业务无需手写 MapGrpcService)、Swagger UI、Consul 注册(Enabled 开关跳过)。③ 业务服务最终形态:builder.AddHealnetServiceDefaults() + app.UseHealnetServiceDefaults() + Run,业务代码只写 proto/gRPC 实现/Controller。④ 注意事项:混合宿主(HTTP 与 gRPC 同端口)必须将 Healnet:Consul:GrpcPort 配为与 ServicePort 相同值,Consul 注册才会写入 grpc-port Meta,其他服务的 gRPC 服务发现才能命中(发现侧跳过无 grpc-port 的实例,4.0.13 同样约束);自动映射 gRPC 服务后不要再手动调用 MapGrpcService<T>(同类型重复映射会抛端点重复异常)。模板案例见 examples/(MixedHost 混合宿主 / GrpcOnly 纯 gRPC 双端口 / ApiOnly 纯 HTTP) v4.0.25: gRPC 统一封装(业务服务开箱即得,无需自行注册)。① 服务端:AddGrpcJsonTranscodingSupport 现已默认注册 HealnetGrpcExceptionInterceptor(此前该拦截器为孤儿封装、全库无注册点,业务代码在 gRPC 服务中抛 HealnetException/BusinessException 会落入 Unknown/500);现自动转换异常为 gRPC Status(trailers 携带 x-error-code/x-error-retryable),JSON Transcoding 端点的 HTTP 状态码随之正确映射(NotFound→404、InvalidArgument→400、FailedPrecondition→400、PermissionDenied→403、ResourceExhausted→429、DeadlineExceeded→504、Unauthenticated→401),业务服务在 gRPC 方法中直接抛 Core 异常体系即可。② 客户端:GrpcClientBase 新增统一调用入口 ExecuteAsync——自动构建调用 metadata(x-correlation-id 链路透传,取自当前 Activity.TraceId;ForwardAuthorization=true 时透传调用方 Authorization header 到下游 gRPC metadata,供内部接口鉴权);并把 RpcException 还原为 HealnetException(服务端为 Healnet 框架时按 trailers x-error-code 精确还原原始错误码,否则按 StatusCode 退化映射:InvalidArgument→400、PermissionDenied→403、NotFound→404、ResourceExhausted→429、Internal→500、DeadlineExceeded→504、FailedPrecondition→400、Unauthenticated→401、Unavailable→503 可重试、Unimplemented→501、Aborted→409),消费方用统一 HTTP 语义错误码处理远程失败,不再感知 gRPC StatusCode/trailers 细节。新增 GrpcExceptionMapper(还原映射器)。③ 兼容性:GrpcClientBase 构造函数新增可选参数 IHttpContextAccessor(默认 null),既有子类无需改动;ExecuteAsync 为新增方法,原 GetClientAsync/GetRequiredClientAsync 用法不变;行为变化仅一处——此前 gRPC 服务中抛 Core 异常返回 Unknown 的场景现在返回精确 Status,属缺陷修复。 v4.0.24: 代码审查缺陷批量修复(正确性/安全/资源管理)。① GrpcChannelCache:健康检查失败的坏 Channel 先从缓存移除再重建(原 GetOrCreate 对已存在 key 直接返回旧坏实例,坏 Channel 永不重建);凭据按 URL scheme 选择(https→SecureSsl,原硬编码 Insecure 导致 https 目标握手失败)。② RabbitMQProducerBase:BasicPublishBatch/BasicPublish 发布操作加锁串行化(共享单例 channel 非线程安全,对照 RabbitMQService._publishLock 模式)。③ RabbitMQConsumerBase:重试耗尽与反序列化失败路径改为 DLQ 发布成功才 Ack,否则 BasicNack(requeue:false) 交给 broker 端 DLX 策略(原 DLQ 未启用或发布失败时无条件 Ack 静默丢消息)。④ DistributedLockService:锁值字典改 TryAdd 语义(原索引器赋值覆盖同键旧 token);释放时本地无 token 不再 KeyDelete 兜底(防误删其他实例持有的锁),仅告警,键由过期时间或真正持有者释放。⑤ MemoryOrchestrator:L2 持久化失败(保存失败或锁未获取)置 l2Persisted=false 如实返回(原静默吞掉仍返回 true);TurnNo 改 COUNT 聚合查询(原 GetRecentTurnsAsync(sessionId, 100000) 拉 10 万条仅为计数);Milvus FilterExpression 的 domain 加白名单校验(字母数字下划线中划线,不合法抛 ArgumentException,防表达式注入)。⑥ ConversationRepository:SaveSessionAsync/SaveTurnAsync 失败返回 false(配合 Core 4.0.11 的 IConversationRepository 契约),新增 CountTurnsAsync。⑦ RAGService 注册改 AddSingleton(其内部知识库字典随实例持久,Scoped 导致每个请求拿到空知识库)。⑧ AgentContextManager 加 TTL 惰性清理(1 小时,Get/Create/Exists/Count 路径触发,原只增不减内存泄漏);AgentService.ExecuteAsync/ExecuteWithToolsAsync 成功完成后调用 DeleteContextAsync。⑨ HttpClientService 全部方法(POST/PUT/DELETE/PATCH/表单 POST/下载字符串与字节数组/带状态码变体)统一经 BuildRequestUrlAsync 走 Consul 服务发现(原仅 GET 走,Consul 模式下其余方法相对路径请求必然失败)。⑩ AiMemoryEntityMapper:补 ConversationArchive.Id 列 IsPrimarykey/IsIdentity 映射(原空分支,CodeFirst 建表缺主键)。⑪ SqlSugarRepository:默认连接名取连接配置中的首个 ConfigId(原硬编码 "Default",多库首连接名非 Default 时仓储绑错库),取不到回退 "Default"。⑫ SqlSugarExtensions.OnLogExecuting 不再拼接 SQL 参数值(防手机号/金额等敏感数据泄漏进日志),仅保留 SQL 文本 Debug。⑬ ResiliencePolicyProvider 重试策略补 TaskCanceledException 覆盖(HttpClient 超时表现为该异常,原不重试),并排除外部取消(CancellationToken 已触发时不重试)。⑭ RabbitMQService 消费者由 EventingBasicConsumer(async void 事件,异常崩进程)改 AsyncEventingBasicConsumer;失败路径由 Nack(requeue:true) 改为 republish 带 x-retry-count=原值+1 header 的新消息并 Ack 原消息(原实现只读不写计数,Nack 重回队列 header 不变,毒消息无限循环),超过 MaxRetryCount(默认 3,新公开属性)转 BasicNack(requeue:false) 交 broker 端 DLX;Subscribe(queue, routingKey, handler) 重载标记 Obsolete(该重载错误地将 routingKey 用作 exchange 名称),5.0 移除。⑮ RedisService.GetAsync 区分 JsonException(Warning 日志,视为未命中)与连接类异常(Error 日志);仍返回 default,兼容性不变。⑯ ApiKeyAuthenticationHandler:API Key 比对由 string.Equals 改 CryptographicOperations.FixedTimeEquals 恒定时间比较(长度不等直接 false),防时序侧信道。⑰ ConsulService 实现 IDisposable 释放 ConsulClient(单例生命周期下原 HTTP 连接永不释放)。已知限制:全部契约接口补 CancellationToken 归 5.0;RedisShortTermMemoryStore 读改写改 Redis List 需数据迁移;MemoryOrchestrator 的 L3 蒸馏 fire-and-forget 需背景队列架构,另批处理。v4.0.23: 修复 Swagger gRPC 封装 4 处缺陷并增强 proto3 JSON 文档准确性(QA 团队双通道验证后实施)。① GetMethodType 流式识别失效:原实现对 Task`1 泛型定义名做 Contains 判断恒不命中,所有流式方法(ServerStreaming/ClientStreaming/DuplexStreaming)被误判为 Unary、x-grpc-type 恒为 Unary;现先解包 Task<T> 再与 Grpc.Core 的 AsyncServerStreamingCall<>/AsyncClientStreamingCall<,>/AsyncDuplexStreamingCall<,> 精确比较。② 流式假端点:GrpcSwaggerDocumentFilter 现跳过 ClientStreaming/DuplexStreaming(JSON Transcoding 官方不支持、经 HTTP 调用必然 404),并记 Information 日志;ServerStreaming 保留但在描述中标注响应为流式 JSON。③ int64 映射对齐 proto3 JSON:long/ulong 的 Schema 从 integer/int64 改为 string/int64、string/uint64(proto3 JSON 将 64 位整数编码为字符串,Transcoding 实际输出 "123");顺带补 uint→integer/int32(原落入默认 string 分支);MapField<long> 值同步修正。④ WellKnownTypes 盲区:Any 从空 object 改为带 @type 键提示与说明的开放对象(Any 打包类型为实例级信息,静态文档原理上无法展开,标注请按 @type 对应消息定义);Struct→任意 JSON 对象;Value→任意 JSON 值(不设 type);ListValue→任意值数组;FieldMask→逗号分隔字段路径字符串;NullValue→NULL_VALUE 枚举;Empty→空对象。⑤ 深度截断显式标注:嵌套超过 10 层降级为字符串时,Schema description 注明「[嵌套截断]」与被截断消息名,避免文档读者误读为真实字符串字段。⑥ 悬空 $ref:_currentDoc 为 null 时(如反射调用场景)CreateSchema 此前返回从未注册进 Components 的 Reference.Id,渲染解析失败;现改为返回已展开的内联 schema。⑦ ByteString Schema 补 format:byte,Swagger UI 可生成合法 base64 示例。⑧ 新增 gRPC 文件上传调试端点模板:MapGrpcFileUploadEndpoint(Swagger/GrpcFileUploadExtensions.cs)在 gRPC 服务同进程映射 multipart/form-data 上传端点(默认 /grpc-file-upload,可配置路由/大小上限/扩展名白名单/业务 Handler),返回统一 ApiResponse(code=200),Swagger UI 独立分组展示并支持文件选择框 Try it out——背景:JSON Transcoding 无 multipart,bytes 仅适合 base64 小内容,真实文件上传需传统 REST 端点。已冒烟实测:上传成功回显元信息/扩展名白名单拒绝 400/空请求 400/swagger.json 含 multipart requestBody 与独立分组。以上均已通过引用真实源码的验证 harness(伪 gRPC 服务 + 私有方法反射调用)回归实测。无公开 API 签名变更,行为向后兼容(文档描述更准确)。v4.0.22: 在 4.0.21 基础上补一处混合形状处理:当标量 MinimumLevel 与嵌套子节点同时存在时(例如环境变量 Healnet__Serilog__MinimumLevel=Error 叠加在 JSON 的嵌套对象之上),级别取标量,但嵌套里的 Override 仍然合并——此前只取标量会丢掉嵌套 Override。4.0.21/4.0.20 未公开该方法且无使用方,请直接用 4.0.22。v4.0.21: 修复 Healnet:Serilog 日志级别配置静默失效(4.0.20 为同一修复的中间发布,未公开解析方法且无使用方,请直接用 4.0.21)。LoggingOptions.MinimumLevel 是 string,而 Serilog.Settings.Configuration 的标准写法是嵌套对象 { "Default": .., "Override": { .. } };配置绑定遇到对象转 string 时会静默跳过该属性且不抛异常,最终 MinimumLevel 保持默认 "Information"、Override 为空字典——等价于"根本没配日志级别"。表现为:生产 Apollo 配 Warning 仍全量输出 Microsoft.AspNetCore 的 INF,本地配 Debug 却看不到任何 LogDebug。现新增公开的 LoggingExtensions.LoadLoggingOptions(IConfiguration) 显式兼容两种形状:标量形状直接采用;嵌套形状取 Default 并合并其 Override,同时通过 SelfLog 提示改回平级写法。Override 合并时平级(框架契约)优先于嵌套,同名键以平级为准,比较器改为 OrdinalIgnoreCase 以匹配 Serilog 的前缀匹配语义。公开该方法也便于服务在启动时直接打印"实际生效的日志配置"做诊断。无破坏性 API 变更,旧配置无需修改即可生效。v4.0.20: 同 4.0.21,中间发布。v4.0.19: IRedisService 新增 GetSubScriber() 方法,返回 StackExchange.Redis 的 ISubscriber,用于直接操作 Redis 发布/订阅(pub/sub)通道;RedisService 基于 _connection.GetSubscriber() 实现。注意:IRedisService 为公开接口,自行实现该接口的外部代码需补齐此方法。v4.0.16: 修复 YARP 服务发现"一缺全废"缺陷:Consul 中任一已配置服务无健康实例时,ConsulServiceDiscoveryConfigProvider.LoadConfigAsync 会在遍历中因 ConsulService.ResolveAllHttpAddressesAsync 抛 InvalidOperationException 而整体中断(异常被循环外层 catch 吞掉),导致此前已生成的路由全部丢弃、且每轮刷新均失败无法自愈——表现为"必须把所有下游服务全部启动,网关路由/Swagger 聚合才可用"。现按服务隔离异常:服务无实例(InvalidOperationException)仅记 Warning 并跳过该服务,其余服务路由照常生成(健康实例出现后 30s 内自动补齐);Consul 不可达等其他异常保留上一份配置,避免瞬时故障清空全部路由。无公开 API 签名变更,行为向后兼容。v4.0.13: gRPC 服务发现(ResolveAllGrpcAddressesAsync)支持遗留回退:实例无 grpc-port Meta 但 Tags 含 GrpcLegacyFallbackTag(默认 server,需 Healnet:Consul:GrpcLegacyTagFallback=true 开启)时,回退使用 Address:Port 作为 gRPC 地址,用于与历史 .NET Core 3.1 MicroCloud 框架注册的 gRPC 服务(不带 Meta、主端口即 h2c)互通;fail-fast 报错文案同步更新。默认关闭,行为完全向后兼容。:实例无 grpc-port Meta 但 Tags 含 GrpcLegacyFallbackTag(默认 server,需 Healnet:Consul:GrpcLegacyTagFallback=true 开启)时,回退使用 Address:Port 作为 gRPC 地址,用于与历史 .NET Core 3.1 MicroCloud 框架注册的 gRPC 服务(不带 Meta、主端口即 h2c)互通;fail-fast 报错文案同步更新。默认关闭,行为完全向后兼容。v4.0.12: 修复 RabbitMQ channel 泄漏导致连接触顶 ChannelAllocationException 的故障。此前 IRabbitMQService 注册为 Scoped,调用方用根 IServiceProvider.GetService 解析时每次新建 RabbitMQService 并在构造中 connection.CreateModel() 分配一个 channel;RabbitMQService 未实现 IDisposable 导致 scope 永不回收 channel,长时间运行后连接上 channel 只增不减,触顶 2047 上限即抛 ChannelAllocationException,进而拖垮依赖 MQ 的服务(如 identity 的 ValidateToken/AccountLogin/RefreshToken 空体 500)。现改为 AddSingleton(全进程复用 1 个 channel),RabbitMQService 实现 IDisposable(关闭时回收 channel),并对共享 channel 的发布/确认路径加锁以保证并发安全。无公开 API 签名变更,行为对既有调用方完全兼容(建议调用方仍用 GetService 懒获取而非构造硬注入)。v4.0.6: 三库版本重新对齐至统一 4.0.6(消除版本分叉,无新增代码变更/公开 API 变更,源码与 4.0.5 一致)。v4.0.2: GrpcChannelCache 创建的 HttpClient.Timeout 改为可配置(配置键 Grpc:HttpClientTimeoutSeconds,默认 100s 与 .NET 默认一致,保持既有行为)。此前 HttpClient 未显式设置 Timeout,恒为 100s 硬上限,且优先级高于 CallOptions.Deadline(grpc-dotnet 中 HttpClient.Timeout 先触发),导致调用方设置的更长 deadline(如编排层 DownstreamTimeoutSeconds=160,覆盖 LLM 生成 + 重试 >100s 的长查询)被 100s 硬截断,表现为客户端收到 "HttpClient.Timeout of 100 seconds elapsing" 的 Cancelled 状态。需要长超时的调用方现可在自身配置中调大该值(如 "gRPC": {"HttpClientTimeoutSeconds": 200})。无公开 API 签名变更,默认行为与 4.0.1 完全兼容。v4.0.1: 修复 AddMemoryServices 无条件注册 L4 归档服务的缺陷。此前 IArchiveRepository/IArchiveService 恒被注册,而 ArchiveService 硬依赖 IFileService,导致未注册文件存储的使用方即便 EnableArchive=false 也会在 DI 解析 MemoryOrchestrator 时抛 "Unable to resolve service for type 'IFileService' while attempting to activate 'ArchiveService'",只能在使用方 RemoveAll<IArchiveService>() 兜底。现改为仅当 MemoryOptions.EnableArchive 为 true 时才注册这两个服务,并在开启归档却缺失 IFileService 时抛出带注册指引的 InvalidOperationException。无公开 API 签名变更,行为对 EnableArchive=true 的使用方完全兼容。v4.0.0: 破坏性主版本(破坏性变更来自 Healnet.Core)。RedisService 实现 ICacheService.GetAndDeleteAsync<T>(Lua 原子 GET+DEL,防并发重复消费);OssFileService 剥离 endpoint 中重复的 bucket 子域前缀;LoggingExtensions 将 ES sink 由 Serilog.Sinks.Elasticsearch 切换为 Serilog.Sinks.OpenSearch 并启用 SelfLog 诊断(公开方法签名未变);AIServiceExtensions 调整 OpenAI HTTP 客户端超时逻辑。v3.1.0: OssFileService 实现 GeneratePresignedUrl(...) 预签名下载 URL(OSS 直连下载,配合 Healnet.Common 3.1.0 的 IFileService 扩展)。v3.0.1: ConsulService gRPC 发现改为 fail-fast 并支持唯一实例 ID 注册与 gRPC 健康检查;SqlSugarExtensions 注册 ITenant 多租户接口、AiMemoryEntityMapper DatabaseName 列标记可空;新增 GrpcClientBase 泛型基类;JwtBearer 跳过非 JWT 格式 Bearer token 避免 IDX14100 噪声、JWT bearer 对 /health 跳过校验;conversation_turn ErrorMessage 列映射标记 IsNullable。v3.0.0: 随 Healnet 统一升版至 3.0.0(破坏性变更来自 Healnet.Common:AESCrypto 移除无参重载、AESDecrypt 改返回元组)。v2.0.7: SqlSugar EntityMapper 可插拔化(IEntityMapper 接口)/ BusinessConfig + BusinessExample 迁出至业务项目 / ConversationArchive 泛化 / EntityService PropertyInfo 类型比较 bug 修复。v2.0.6: 新增 Milvus 向量数据库封装(IMilvusService / MilvusService / MilvusOptions / MilvusExtensions / MilvusHealthCheck),支持集合管理、向量索引、近似检索(ANN)、标量查询、批量写入与健康检查。v2.0.3: 统一三库版本号;升级目标框架至 net10.0;更新所有依赖包至最新稳定版;移除 Infrastructure 项目中多余的 MSBuild 属性。v2.0.1: 依赖包版本升级,修复若干已知问题。v2.0.0: 重大版本更新,目标框架从 net8.0 升级至 net10.0,全面更新依赖包版本,优化 API 设计。