Skip to content

Latest commit

 

History

342 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Mud.HttpUtils

Mud.HttpUtils downloads Mud.HttpUtils.Abstractions downloads Mud.HttpUtils.Attributes downloads Mud.HttpUtils.Client downloads Mud.HttpUtils.Resilience downloads Mud.HttpUtils.Generator downloads Mud.HttpUtils.OpenTelemetry downloads Mud.HttpUtils.Newtonsoft.Json downloads Mud.HttpUtils.Xml downloads Mud.HttpUtils.Testing downloads Mud.HttpUtils.Analyzers downloads Mud.HttpUtils.CodeFixes downloads Mud.HttpUtils.JsonContextScaffolder downloads License

基于 Roslyn 的声明式 HTTP 客户端源代码生成器

📖 项目简介

Mud.HttpUtils 是一个基于 Roslyn 源代码生成器的声明式 HTTP 客户端框架,通过特性标注的方式自动生成类型安全的 HTTP API 客户端代码。无需手写 HttpClient 调用代码,只需定义接口并添加特性标注,编译器会自动生成完整的实现代码。

✨ 核心特性

  • 🚀 编译时生成,最小化运行时开销:编译时生成代码,核心路径零反射,性能优异。QueryMap 嵌套复杂类型已支持递归编译期展平(AOT 安全),仅极端深度回退到反射
  • 🎯 类型安全:强类型 API 调用,编译时检查错误
  • 🚀 Native AOT 兼容:统一序列化抽象 IHttpContentSerializer + JsonContextScaffolder 脚手架自动生成 JsonSerializerContext + AOT001AOT007 编译期诊断(CI 严格模式可升级为 Error),实现零反射 Native AOT 构建
  • 🔗 无 DI 入口:通过 RestService.ForGenerated<T>(HttpClient, GeneratedClientOptions?) 在不依赖 DI 容器的场景下创建 AOT 安全的客户端实例,配合 [ModuleInitializer] 自动注册工厂
  • 📝 声明式编程:通过特性标注定义 HTTP API,简洁直观
  • 🔧 功能丰富:支持多种 HTTP 方法、参数类型、内容格式、Token 认证、加密传输等
  • 🛡️ 弹性策略:内置重试、超时、熔断策略,基于 Polly 实现
  • 🔐 加密支持:可插拔的加密提供程序,内置 AES 加密实现
  • 🔄 令牌管理:并发安全的令牌刷新,支持持久化存储契约,内置内存存储默认实现
  • 🌐 多客户端:支持多命名客户端场景,通过 IHttpClientResolver 动态解析
  • 🎨 灵活配置:支持接口级、方法级、参数级的配置优先级
  • 🏗️ 接口级动态属性:在接口上定义 [Query]/[Path] 属性,实现全局参数
  • 🗺️ QueryMap 参数映射:将对象/字典展开为查询参数,支持序列化控制
  • 🔗 Base Path 支持:在接口级别定义统一路径前缀
  • 📦 Response<T> 包装类型:同时返回响应内容和元数据
  • 🤖 默认参数推断:未标注特性的参数根据类型自动推断为 [Query](简单类型)或 [Body](复杂类型)
  • 🔀 头部合并控制:通过 [HeaderMerge] 控制接口级与方法级同名头部的合并策略
  • 📐 序列化方法控制:通过 [SerializationMethod] 指定接口或方法级别的请求体序列化方式(JSON/XML/FormUrlEncoded)
  • 📊 OpenTelemetry 可观测性:内置分布式追踪与指标采集,一键开启
  • 📦 多框架支持:支持 .NET Standard 2.0、.NET 6.0、.NET 8.0、.NET 10.0

📦 NuGet 包

组件 描述 NuGet 下载
Mud.HttpUtils 元包:Abstractions + Attributes + Client + Resilience Nuget Nuget
Mud.HttpUtils.Abstractions 纯接口定义,最小依赖 Nuget Nuget
Mud.HttpUtils.Attributes 特性定义 Nuget Nuget
Mud.HttpUtils.Client 客户端实现 + DI 注册 Nuget Nuget
Mud.HttpUtils.Resilience 弹性策略(Polly) Nuget Nuget
Mud.HttpUtils.Generator 源代码生成器 Nuget Nuget
Mud.HttpUtils.OpenTelemetry OpenTelemetry 可观测性适配 Nuget Nuget
Mud.HttpUtils.Newtonsoft.Json Newtonsoft.Json 序列化器适配 Nuget Nuget
Mud.HttpUtils.Xml XML 序列化器适配 Nuget Nuget
Mud.HttpUtils.Testing 测试辅助包(StubHttp mock 服务器、网络行为模拟) Nuget Nuget
Mud.HttpUtils.Analyzers 独立分析器项目(接口规范诊断) Nuget Nuget
Mud.HttpUtils.CodeFixes 代码修复提供器 Nuget Nuget
Mud.HttpUtils.JsonContextScaffolder JsonSerializerContext 脚手架工具 Nuget Nuget

🏛️ 系统架构

Mud.HttpUtils 采用编译时生成 + 运行时装饰的分层架构:开发者仅声明带特性的接口,源代码生成器在编译期为每个接口生成强类型实现;运行时通过装饰器链叠加弹性策略、令牌管理、加解密与可观测性。

分层架构

graph TB
    subgraph Consumer["① 使用者代码"]
        IFace["声明式接口<br/>IUserApi(特性标注)"]
        Svc["业务服务注入 IUserApi"]
    end

    subgraph Compile["② 编译时(Mud.HttpUtils.Generator)"]
        SG["HttpInvokeClassSourceGenerator<br/>+ RegistrationGenerator"]
    end

    subgraph Runtime["③ 运行时核心"]
        Abs["<b>Mud.HttpUtils.Abstractions</b><br/>接口 / 契约 / 可观测性静态源"]
        Attr["Mud.HttpUtils.Attributes<br/>特性定义"]
        Client["Mud.HttpUtils.Client<br/>EnhancedHttpClient · 令牌管理<br/>加解密 · 命名客户端解析器"]
        Res["Mud.HttpUtils.Resilience<br/>ResilientHttpClient(Polly 装饰器)"]
        OTel["Mud.HttpUtils.OpenTelemetry<br/>Tracing + Metrics 导出"]
    end

    Meta["<b>Mud.HttpUtils(元包)</b><br/>AddMudHttpUtils 一站式注册"]

    IFace -->|"编译时扫描特性"| SG
    SG -->|"生成实现类 UserApi(.Internal 命名空间)"| Svc
    Svc --> IFace

    Meta -. "聚合引用" .- Abs
    Meta -. .- Attr
    Meta -. .- Client
    Meta -. .- Res

    Attr -. "依赖" .- Abs
    Client -. "实现接口" .- Abs
    Res -. "装饰 IEnhancedHttpClient" .- Client
    Res -. .- Abs
    OTel -. "采集可观测性源" .- Abs
    SG -. "引用" .- Abs
    SG -. .- Attr
Loading

一次 HTTP 请求的调用链路

sequenceDiagram
    participant B as 业务服务
    participant G as 生成代码 UserApi(.Internal)
    participant X as IHttpRequestExecutor
    participant T as Token / 加解密 / 拦截器
    participant R as IResiliencePolicyResolver
    participant RC as ResilientHttpClient(装饰器)
    participant H as EnhancedHttpClient
    participant N as HttpClient(System.Net.Http)

    B->>G: 调用 GetUserAsync(id)
    G->>X: 构建请求并执行
    X->>T: 注入 Token、头部合并、加解密
    X->>R: 解析方法级 / 全局弹性策略
    R-->>X: ResiliencePipeline
    X->>RC: 经弹性装饰执行
    RC->>H: 重试 / 超时 / 熔断外壳
    H->>N: 发送 HttpRequestMessage
    N-->>H: HttpResponseMessage
    H-->>X: 响应拦截 / 反序列化
    X-->>G: 返回 T / Response&lt;T&gt;
    G-->>B: 结果
Loading

关键设计

  • 零运行时反射:生成代码直接调用 IHttpRequestExecutorIEnhancedHttpClient,核心路径无反射(仅 FormUrlEncoded Body、QueryMap 复杂类型、XML 序列化等少数场景保留反射)。
  • 装饰器叠加ResilientHttpClient 实现 IEnhancedHttpClient 并包装内层客户端,因此弹性策略、令牌恢复、追踪等能力可逐层叠加而不侵入业务接口。
  • 多租户隔离IAppContextHolder / IAppManager<T> / AppResiliencePolicyResolver 为不同 App 维护独立的上下文与弹性策略。

🚀 快速开始

1. 安装 NuGet 包

# 安装元包(包含 Abstractions + Attributes + Client + Resilience)
dotnet add package Mud.HttpUtils

# 安装源代码生成器
dotnet add package Mud.HttpUtils.Generator

2. 定义 API 接口

using Mud.HttpUtils.Attributes;

[HttpClientApi(HttpClient = "IEnhancedHttpClient")]
public interface IUserApi
{
    [Get("/users/{id}")]
    Task<UserInfo> GetUserAsync([Path] int id);

    [Post("/users")]
    Task<UserInfo> CreateUserAsync([Body] CreateUserRequest request);

    [Get("/users")]
    Task<List<UserInfo>> GetUsersAsync(
        [Query] string? name = null,
        [Query] int page = 1,
        [Query] int pageSize = 20
    );

    [Put("/users/{id}")]
    Task<UserInfo> UpdateUserAsync([Path] int id, [Body] UpdateUserRequest request);

    [Delete("/users/{id}")]
    Task<bool> DeleteUserAsync([Path] int id);

    [Post("/upload")]
    Task<UploadResult> UploadAsync([Upload] IFormFile file);

    [Post("/login")]
    Task<LoginResult> LoginAsync([Form("username")] string user, [Form("password")] string pass);
}

3. 注册服务

// 一站式注册:Client + 弹性策略
services.AddMudHttpUtils("userApi", "https://api.example.com", options =>
{
    options.Retry.MaxRetryAttempts = 3;
    options.Timeout.TimeoutSeconds = 30;
});

// 注册生成器生成的 API 接口
services.AddWebApiHttpClient();

4. 使用 API

public class UserService
{
    private readonly IUserApi _userApi;

    public UserService(IUserApi userApi)
    {
        _userApi = userApi;
    }

    public async Task<UserInfo> GetUserByIdAsync(int id)
    {
        return await _userApi.GetUserAsync(id);
    }
}

🎯 功能特性

HTTP 方法支持

  • [Get] - GET 请求
  • [Post] - POST 请求
  • [Put] - PUT 请求
  • [Delete] - DELETE 请求(支持带请求体)
  • [Patch] - PATCH 请求
  • [Head] - HEAD 请求
  • [Options] - OPTIONS 请求

参数类型

特性 说明 示例
[Path] URL 路径参数 [Get("/users/{id}")] + [Path] int id
[Query] URL 查询参数 [Query] string? name
[QueryMap] 查询参数映射(对象/字典展开为查询参数) [QueryMap] SearchCriteria criteria
[ArrayQuery] 数组查询参数 [ArrayQuery] int[] ids
[RawQueryString] 原始查询字符串 [RawQueryString] string queryString
[Header] HTTP 请求头(支持参数/方法/接口级别) [Header("X-API-Key")] string apiKey
[Body] 请求体 [Body] UserRequest request
[Body(RawString = true)] 原始字符串请求体 [Body(RawString = true)] string content
[Body(UseStringContent = true)] 字符串内容请求体 [Body(UseStringContent = true)] string content
[FormContent] 表单数据 [FormContent] IFormContent formData
[Form] 表单字段(application/x-www-form-urlencoded [Form("username")] string user
[MultipartForm] 多部分表单字段(multipart/form-data [MultipartForm] IFormFile file
[Upload] 文件上传参数(支持自定义字段名/文件名/内容类型) [Upload(FieldName = "doc")] IFormFile file
[FilePath] 文件下载路径 [FilePath] string savePath
[Token] Token 认证(支持参数/接口/方法级别) [Token(TokenTypes.UserAccessToken)] string token
[Retry] 方法级重试策略标注 [Retry(MaxRetries = 3)]
[Timeout] 方法级超时策略标注 [Timeout(30000)]
[CircuitBreaker] 方法级熔断策略标注 [CircuitBreaker(FailureThreshold = 5)]
[HeaderMerge] 头部合并模式控制(接口/方法级别) [HeaderMerge(HeaderMergeMode.Replace)]
[SerializationMethod] 请求体序列化方法控制(接口/方法级别) [SerializationMethod(SerializationMethod.Xml)]
[InterfacePath] 接口级固定路径参数 [InterfacePath("tenantId", "default")]
[InterfaceQuery] 接口级固定查询参数 [InterfaceQuery("version", "2.0")]
[AllowAnyStatusCode] 允许任意 HTTP 状态码(不抛异常) [AllowAnyStatusCode]

内容类型管理

支持三级配置,优先级从高到低:

Body 参数级 > 方法级 > 接口级 > 默认值 (application/json)

请求头(Header)

[Header] 特性支持应用到参数、方法或接口级别:

// 参数级别
[Get("/users")]
Task<List<User>> GetUsersAsync([Header("X-API-Key")] string apiKey);

// 方法级别(添加固定请求头)
[Get("/users")]
[Header("Accept", "application/json")]
[Header("X-Request-Source", "Web")]
Task<List<User>> GetUsersAsync();

// 接口级别(所有方法自动携带)
[HttpClientApi]
[Header("X-API-Version", "v2")]
public interface IUserApi { }

HeaderAttribute 支持 AliasAs(别名映射)和 Replace(替换模式)属性。

弹性策略

基于 Polly 的弹性策略,通过装饰器模式包装 HTTP 客户端:

策略 默认状态 说明
重试 启用 默认 3 次重试,支持指数退避
超时 启用 默认 30 秒,悲观超时策略
熔断 关闭 连续失败阈值触发,支持半开状态

策略组合顺序:重试(外层) → 熔断 → 超时(内层)

services.AddMudHttpUtils("myApi", "https://api.example.com", options =>
{
    options.Retry.MaxRetryAttempts = 3;
    options.Retry.UseExponentialBackoff = true;
    options.Timeout.TimeoutSeconds = 30;
    options.CircuitBreaker.Enabled = true;
    options.CircuitBreaker.FailureThreshold = 5;
    options.CircuitBreaker.BreakDurationSeconds = 30;
});

非幂等方法防护:默认仅对幂等方法(GET/HEAD/OPTIONS/PUT/DELETE/TRACE)重试,POST/PATCH 等非幂等方法退化为超时+熔断(防重复提交)。超时与熔断对所有方法始终生效。如需对非幂等方法重试:

// 全局开关
options.Retry.AllowNonIdempotentRetry = true;

// 或方法级标注
[Post("/orders")]
[Retry(AllowNonIdempotent = true)]
Task<Order> CreateOrderAsync([Body] CreateOrderRequest request);

也支持从 appsettings.json 绑定:

{
  "MudHttpResilience": {
    "Retry": {
      "Enabled": true,
      "MaxRetryAttempts": 3,
      "UseExponentialBackoff": true
    },
    "Timeout": { "Enabled": true, "TimeoutSeconds": 30 },
    "CircuitBreaker": {
      "Enabled": true,
      "FailureThreshold": 5,
      "BreakDurationSeconds": 30
    }
  }
}

三种运行模式

模式 配置 构造函数依赖 适用场景
HttpClient(推荐) HttpClient = "IEnhancedHttpClient" IOptions<JsonSerializerOptions>, IEnhancedHttpClient 通用场景,配合 AddMudHttpUtils
TokenManager TokenManage = "IFeishuAppManager" IOptions<JsonSerializerOptions>, Token 管理器 飞书/钉钉等需要 Token 管理
默认 IOptions<JsonSerializerOptions>, IMudAppContext 遗留场景

HttpClientTokenManage 互斥,同时定义时 HttpClient 优先。

Token 认证

// 接口级 Token(建议使用 TokenTypes 常量)
[Token(TokenTypes.TenantAccessToken)]
public interface IApi { }

// 参数级 Token
[Get("/users/{id}")]
Task<User> GetUserAsync([Path] int id, [Token(TokenTypes.UserAccessToken)] string? token = null);

// Token 注入模式:Header(默认)、Query、Path、ApiKey、HmacSignature、BasicAuth、Cookie
[Token(TokenTypes.AppAccessToken, InjectionMode = TokenInjectionMode.Header, Name = "Authorization")]

// 使用 RequiresUserId 自动获取用户级令牌
[Token(TokenTypes.UserAccessToken, RequiresUserId = true)]
public interface IUserApi { }

// 使用 TokenManagerKey 解耦业务概念和技术查找键
[Token(TokenType = "UserAccessToken", TokenManagerKey = "FeishuUser")]
public interface IFeishuUserApi { }

加密支持

// 使用默认 AES 加密注册
services.AddMudHttpClient("myApi", encryption =>
{
    encryption.Key = Convert.FromBase64String("your-base64-key");
    // 注意:从 v1.8.0 起 IV 自动随机生成,无需手动设置
}, client =>
{
    client.BaseAddress = new Uri("https://api.example.com");
});

// 或注册自定义加密提供程序
services.AddSingleton<IEncryptionProvider, MyCustomEncryptionProvider>();

// 请求体加密
[Post("/api/secure")]
Task<Response> PostSecureAsync(
    [Body(EnableEncrypt = true, EncryptSerializeType = SerializeType.Json, EncryptPropertyName = "data")] Request request
);

// 响应解密
[Post("/api/secure-data", ResponseEnableDecrypt = true)]
Task<SecureData> GetSecureDataAsync([Body] Request request);

默认 AES 实现为认证加密(AES-GCM / AES-CBC+HMAC),密文带版本前缀,无需额外 MAC 配置。

SSRF 防护(.NET 6+)

URL 来自用户输入时,建议启用连接期 IP 准入校验,根治 DNS rebinding(URL 校验期与实际建连期解析结果可能不一致):

// 1. 注册 IP 准入策略(默认实现拒绝私网/回环/链路本地地址,fail-closed)
services.AddMudHttpClientSsrfProtection();

// 2. 为命中的 HttpClient 启用连接期校验(建连时对实际连接的 IP 执行准入校验)
services.AddMudHttpClient("myApi", "https://api.example.com")
    .AddMudHttpClientSsrfProtection();

// 自定义准入策略:注册自己的 IIpAddressPolicy 替换默认实现(如本地调试放行 localhost)
services.AddSingleton<IIpAddressPolicy, MyDebugIpPolicy>();
  • 被策略拒绝的连接抛出 InvalidOperationException
  • 默认 AllowCustomBaseUrls = false 时强制 HTTPS + 白名单(fail-closed);AllowCustomBaseUrls = true 放行自定义 URL 时,必须自行校验 URL 来源。
  • DNS 解析结果带 TTL 缓存(默认 5 分钟),并发场景下同域名解析受锁保护(单飞)。

遥测脱敏(默认开启)

Span tag、日志与诊断事件中的 URL 默认脱敏(掩码 access_token / refresh_token / api_key 等敏感 query 值),防止令牌随遥测泄漏:

// 全局开关(静态属性,需在进程启动时设置)
MudHttpObservabilityOptions.RedactUrlInTelemetry = true;   // 默认 true:URL 脱敏
MudHttpObservabilityOptions.RecordFullUrlOnSuccess = false; // 默认 false:成功请求仅记录 scheme://host/path(不含 query)
MudHttpObservabilityOptions.EmitDiagnosticEvents = true;    // 默认 true:诊断事件(ActivityEvent / DiagnosticSource)
  • 脱敏只掩码敏感 query 键的值,保留键名与 URL 结构,兼顾排障;未命中敏感词表的 query 原样保留。
  • RecordFullUrlOnSuccess = true 时成功请求也记录完整 URL,但仍受 RedactUrlInTelemetry 约束;错误路径(ApiException.RequestUri)始终保留完整 URI,由 IExceptionRedactor 兜底擦除。
  • 指标 tag 白名单:MudHttpObservabilityOptions.MetricTagAllowlist 控制所有指标维度(默认包含 client_name/method/host/outcome/status_code/policy_key/token_manager_key/retry_count),白名单之外的维度被丢弃,从机制上杜绝高基数 tag(如 cache_key)打爆时序后端。

错误内容上限(默认 10240)

错误响应体(ApiException.Content)与捕获的请求体(ApiException.RequestContent)默认在读取阶段截断为 10240 字符,防止恶意/超大响应导致 OOM:

o.MaxExceptionContentLength = 10240;  // 默认 10240;设为 0 或负数 = 不限制
  • 内置方法路径(EnhancedHttpClient)与生成代码路径(DefaultHttpRequestExecutor)默认值一致(10240),截断内容带 ...[已截断] 后缀。
  • MaxSuccessResponseBytes(默认 0 = 不限制)可为成功响应体设置字节级守卫:已知长度(Content-Length)预判超限即抛 ApiRequestException,chunked 无长度场景由守卫流在读取阶段拦截。流式下载(DownloadLargeAsync/流式枚举)不受此限。

令牌管理

// 核心接口
ITokenManager          // 通用令牌管理
IUserTokenManager      // 用户令牌管理
ITokenProvider         // Token 提供器(统一封装 Token 获取逻辑)
ICurrentUserContext     // 当前用户上下文(线程安全的用户 ID 传播,替代 CurrentUserId 属性)
TokenRequest           // Token 请求参数(TokenManagerKey, UserId, Scopes)
ITokenStore            // 令牌持久化存储契约
IUserTokenStore        // 用户级令牌持久化存储契约
TokenManagerBase          // 令牌管理器抽象基类(并发安全刷新,支持 MetricsKey 覆写)
UserTokenManagerBase      // 用户令牌管理器抽象基类(并发安全刷新)
TokenTypes                // 令牌类型常量(TenantAccessToken、UserAccessToken 等)
MemoryTokenStore          // 内存令牌存储默认实现(ITokenStore)
MemoryUserTokenStore      // 内存用户令牌存储默认实现(IUserTokenStore)
MemoryEncryptedTokenStore // 内存加密令牌存储默认实现(IEncryptedTokenStore)
DefaultFormContent        // 默认表单内容实现(IFormContent)

// 实现自定义令牌管理器
public class MyTokenManager : TokenManagerBase
{
    protected override Task<TokenInfo?> GetCachedTokenAsync(string tokenType, CancellationToken ct) { }
    protected override Task<TokenInfo> RefreshTokenCoreAsync(string tokenType, CancellationToken ct) { }
}

// 使用 RequiresUserId 自动获取用户级令牌
[HttpClientApi(TokenManage = "IFeishuAppManager")]
[Token(TokenType = "UserAccessToken", RequiresUserId = true)]
public interface IFeishuUserApi { }
// 生成的构造函数自动注入 ICurrentUserContext,CurrentUserId 属性委托给 _currentUserContext.UserId

// 使用 TokenManagerKey 解耦业务概念和技术查找键
[Token(TokenType = "UserAccessToken", TokenManagerKey = "FeishuUser")]
public interface IFeishuContactApi { }

// 覆写 MetricsKey 使指标维度可区分(多实例场景下避免监控数据混叠)
public class MyNamedTokenManager : TokenManagerBase
{
    public MyNamedTokenManager(string instanceName) => _instanceName = instanceName;
    protected override string MetricsKey => _instanceName;
    private readonly string _instanceName;
    // 必须实现:GetCachedTokenAsync / RefreshTokenCoreAsync
}

多命名客户端

// 注册多个客户端
services.AddMudHttpClient("userApi", "https://user-api.example.com");
services.AddMudHttpClient("orderApi", "https://order-api.example.com");

// 通过 IHttpClientResolver 动态获取
public class MultiApiService
{
    private readonly IHttpClientResolver _resolver;

    public MultiApiService(IHttpClientResolver resolver) => _resolver = resolver;

    public async Task CallUserApiAsync()
    {
        var client = _resolver.GetClient("userApi");
        await client.GetAsync<User>("/users/1");
    }
}

流式响应(.NET 6+)

// IAsyncEnumerable 流式处理
await foreach (var message in _httpClient.SendAsAsyncEnumerable<ChatMessage>(request, cancellationToken: ct))
{
    yield return message;
}

// 原始 HttpResponseMessage
var response = await _httpClient.SendRawAsync(request);

// 响应流
var stream = await _httpClient.SendStreamAsync(request);

SendStreamAsync 返回的流所有权归调用方(由调用方负责 Dispose,释放即同时释放底层 HttpResponseMessage)。 接口方法也可直接声明 Task<Stream> 返回 —— 生成器会发射 SendStreamAsync 直达调用; 该路径不参与 [Cache]/[Retry]/[CircuitBreaker]/[Timeout] 编排与 Response<T> 包装 (生成期以 HTTPCLIENT025 提示)。详见生成器包 README 的「直达返回」一节。

文件上传与下载

// 文件上传(支持 JsonPropertyName 属性名映射)
[Post("/upload")]
Task<UploadResult> UploadAsync([FormContent] IFormContent formData);

// 文件下载
[Get("/files/{fileId}")]
Task DownloadFileAsync([Path] string fileId, [FilePath(BufferSize = 81920)] string savePath);

// 二进制数据下载
[Get("/files/{fileId}/content")]
Task<byte[]> DownloadFileContentAsync([Path] string fileId);

成功响应体守卫(可选):限制反序列化路径的成功响应体大小,超限抛 ApiRequestException0(默认)= 不限制:

services.Configure<EnhancedHttpClientOptions>(o =>
{
    o.MaxSuccessResponseBytes = 10 * 1024 * 1024; // 10 MB
});

超大文件请改用流式落盘([FilePath] 下载路径不受守卫约束)。

接口级动态属性

支持在接口上定义 [Query][Path] 属性,作为所有方法的默认查询参数或路径参数。生成的实现类将包含对应的可读写属性:

[HttpClientApi(HttpClient = "IEnhancedHttpClient")]
[BasePath("{tenantId}/api/v1")]
public interface ITenantApi
{
    [Path("tenantId")]
    string TenantId { get; set; }

    [Query("apiKey")]
    string ApiKey { get; set; }

    [Get("users")]
    Task<List<User>> GetUsersAsync();
}

// 使用
var api = serviceProvider.GetRequiredService<ITenantApi>();
api.TenantId = "tenant-123";
api.ApiKey = "my-api-key";
await api.GetUsersAsync();
// 实际请求: /tenant-123/api/v1/users?apiKey=my-api-key

QueryMap 查询参数映射

[QueryMap] 支持将对象属性展开为查询参数,支持字典类型和 POCO 对象:

public class SearchCriteria
{
    public string? Keyword { get; set; }
    public int Page { get; set; }
}

[Get("/api/search")]
Task<SearchResult> SearchAsync(
    [QueryMap(PropertySeparator = "_", SerializationMethod = QuerySerializationMethod.ToString)]
    SearchCriteria criteria);

// 字典类型
[Get("/api/search")]
Task<SearchResult> SearchAsync([QueryMap] IDictionary<string, object> filters);

QueryMapAttribute 属性:

属性 类型 默认值 说明
PropertySeparator string "_" 嵌套属性名称分隔符
SerializationMethod QuerySerializationMethod ToString 序列化方法(ToString / Json
UrlEncode bool true 是否对查询参数值进行 URL 编码
IncludeNullValues bool false 是否包含值为 null 的属性

Base Path 支持

支持在接口级别定义统一的路径前缀:

[HttpClientApi(HttpClient = "IEnhancedHttpClient")]
[BasePath("api/v1")]
public interface IUserApi
{
    [Get("users/{id}")]       // 实际路径: /api/v1/users/{id}
    Task<User> GetUserAsync([Path] int id);

    [Get("/admin/users")]     // 以 / 开头,忽略 BasePath,实际路径: /admin/users
    Task<List<User>> GetAllUsersAsync();
}

Response<T> 包装类型

Response<T> 类型同时返回响应内容和元数据(状态码、响应头):

[Get("/users/{id}")]
Task<Response<User>> GetUserAsync([Path] int id);

// 使用
var response = await api.GetUserAsync(1);
var user = response.Data;           // 响应内容
var status = response.StatusCode;   // HTTP 状态码
var headers = response.Headers;     // 响应头

注意:不建议将 Response<T>[Cache] 特性组合使用,缓存会存储整个 Response<T> 对象(包括 StatusCode 和 Headers),可能导致后续请求返回过期的状态码和响应头。生成器会对此组合发出 HTTPCLIENT011 编译警告。

继承与事件处理器

// 继承
[HttpClientApi("https://api.example.com", IsAbstract = true)]
public interface IBaseApi { }

[HttpClientApi("https://api.example.com", InheritedFrom = "BaseApiClass")]
public interface IUserApi : IBaseApi { }

// 事件处理器
[GenerateEventHandler(EventType = "UserCreatedEvent", HandlerClassName = "UserCreatedEventHandler")]
public class UserCreatedEvent { }

🏗️ 项目结构

MudHttpUtils/
├── Mud.HttpUtils/                    # 元包:一站式引用 + DI 注册
│   └── ServiceCollectionExtensions   # AddMudHttpUtils() 一站式注册
├── Mud.HttpUtils.Abstractions/       # 接口定义层(最小依赖)
│   ├── IBaseHttpClient               # 基础 HTTP 操作接口
│   ├── IEnhancedHttpClient           # 增强客户端组合接口
│   ├── IEncryptionProvider           # 加密提供程序接口
│   ├── ITokenManager                 # 令牌管理接口
│   ├── ITokenProvider                # Token 提供器接口(统一封装 Token 获取逻辑)
│   ├── ICurrentUserContext           # 当前用户上下文接口(线程安全的用户 ID 传播)
│   ├── TokenRequest                  # Token 请求参数
│   ├── ITokenStore / IUserTokenStore # 令牌持久化存储契约
│   ├── IHttpClientResolver           # 命名客户端解析接口
│   ├── TokenManagerBase              # 令牌管理器抽象基类
│   ├── TokenTypes                    # 令牌类型常量
│   └── IMudAppContext                # 应用上下文接口
├── Mud.HttpUtils.Attributes/         # 特性定义层
│   ├── HttpClientApiAttribute        # API 接口标注
│   ├── Get/Post/Put/Delete/...       # HTTP 方法特性
│   └── Path/Query/Body/Token/...     # 参数特性
├── Mud.HttpUtils.Client/             # 客户端实现层
│   ├── EnhancedHttpClient              # 增强 HTTP 客户端基类
│   ├── DirectEnhancedHttpClient        # 直接构造的增强客户端
│   ├── HttpClientFactoryEnhancedClient # IHttpClientFactory 实现
│   ├── DefaultAesEncryptionProvider    # AES 加密默认实现
│   ├── HttpClientResolver              # 命名客户端解析器
│   ├── MemoryTokenStore                # 内存令牌存储默认实现
│   ├── MemoryUserTokenStore            # 内存用户令牌存储默认实现
│   ├── MemoryEncryptedTokenStore       # 内存加密令牌存储默认实现
│   ├── DefaultFormContent              # 默认表单内容实现
│   └── ServiceCollectionExtensions     # AddMudHttpClient() 注册
├── Mud.HttpUtils.Resilience/         # 弹性策略扩展包
│   ├── ResilientHttpClient           # 装饰器(重试/超时/熔断)
│   ├── PollyResiliencePolicyProvider # Polly 策略提供器
│   ├── HttpRequestMessageCloner      # 请求克隆工具
│   └── ServiceCollectionExtensions   # AddMudHttpResilienceDecorator() 注册
├── Mud.HttpUtils.Generator/          # 源代码生成器
│   ├── HttpInvokeClassSourceGenerator    # 实现类生成器
│   └── HttpInvokeRegistrationGenerator   # 注册代码生成器(含 Timeout 配置)
├── Mud.HttpUtils.OpenTelemetry/      # OpenTelemetry 可观测性适配
│   ├── MudHttpOpenTelemetryExtensions    # 一键开启 Tracing + Metrics
│   └── MudHttpOpenTelemetryOptions       # 配置选项
├── Mud.HttpUtils.Newtonsoft.Json/   # Newtonsoft.Json 序列化器适配
├── Mud.HttpUtils.Xml/              # XML 序列化器适配
├── Mud.HttpUtils.Testing/          # 测试辅助包(StubHttp + NetworkBehavior)
├── Mud.HttpUtils.Analyzers/         # 独立分析器项目
├── Mud.HttpUtils.CodeFixes/         # 代码修复提供器
├── Mud.HttpUtils.JsonContextScaffolder/ # JsonSerializerContext 脚手架
├── Demos/                            # 示例项目
└── Tests/                            # 测试项目

📚 详细文档

包名 说明 文档
Mud.HttpUtils 元包,一站式引用 + DI 注册 README
Mud.HttpUtils.Abstractions 接口定义,最小依赖 README
Mud.HttpUtils.Attributes 特性标注 README
Mud.HttpUtils.Client 客户端实现 README
Mud.HttpUtils.Resilience 弹性策略 README
Mud.HttpUtils.Generator 源代码生成器 README
Mud.HttpUtils.OpenTelemetry OpenTelemetry 可观测性 README
Mud.HttpUtils.Newtonsoft.Json Newtonsoft.Json 序列化器适配 README
Mud.HttpUtils.Xml XML 序列化器适配 README
Mud.HttpUtils.Testing 测试辅助包 README
Mud.HttpUtils.Analyzers 独立分析器 README
Mud.HttpUtils.CodeFixes 代码修复提供器 README
Mud.HttpUtils.JsonContextScaffolder JsonContext 脚手架 README
变更记录 行为基线与版本说明 CHANGELOG

⚡ 性能说明

Mud.HttpUtils 通过 Roslyn 源代码生成器在编译时生成强类型的 HTTP 调用代码,核心路径(JSON 序列化/反序列化、URL 构建、请求头处理)完全避免了运行时反射。

存在反射的场景(仅限以下高级特性):

场景 反射调用 影响范围
[Body(ContentType = "application/x-www-form-urlencoded")] 使用 FormUrlEncodedContent 时通过反射读取对象属性 仅限 FormUrlEncoded Body 模式
[QueryMap] 复杂类型展开 通过反射读取对象属性展开为查询参数 仅限 QueryMap 非字典类型
XML 序列化/反序列化 XmlSerializer 内部使用反射(已通过静态字段缓存优化) 仅限 XML Content-Type

对于性能敏感的场景,建议优先使用 JSON 序列化(System.Text.Json 原生支持 AOT)和简单类型的查询参数。

🔔 编译警告参考

源代码生成器在编译时会对不合理的 API 定义产生警告或错误,帮助开发者在编译阶段发现问题。

Diagnostic ID 严重级别 触发条件 解决方案
HTTPCLIENT001 Error 生成接口实现时发生异常 检查接口定义是否正确,查看内部异常信息
HTTPCLIENT003 Error 接口语法分析失败 确保接口定义符合 C# 语法规范
HTTPCLIENT004 Error 参数配置错误 检查参数特性配置是否正确
HTTPCLIENT005 Error URL 模板格式无效 检查 [Get]/[Post] 等特性中的 URL 模板
HTTPCLIENT007 Error 同时指定 HttpClientTokenManage 两者互斥,只设置其中一个
HTTPCLIENT008 Error 加密配置但 HttpClient 类型不支持加密 使用 IEnhancedHttpClient 或移除加密配置
HTTPCLIENT009 Warning XML 请求但 HttpClient 类型不支持 XML 使用 IEnhancedHttpClient 或修改 Content-Type
HTTPCLIENT011 Warning [Cache]Response<T> 返回类型组合 缓存会存储状态码和响应头,建议使用普通返回类型
HTTPCLIENT012 Info 泛型接口:生成器将转发类型参数与约束 无需处理,仅供感知(泛型接口已支持代码生成)
HTTPCLIENT013 Error URL 模板中的路径占位符与 [Path] 参数不匹配 确保 URL 模板中的 {placeholder} 与方法中的 [Path] 参数一一对应
HTTPCLIENT014 Warning 指定的 HttpClient 类型在当前编译中未找到 确认类型名称正确,或确保已注册对应命名客户端
HTTPCLIENT015 Error TokenManage 类型未找到 确认类型名称正确,或确保包含该类型的项目已引用
HTTPCLIENT016 Error TokenManage 类型缺少必需方法 提供 IMudAppContext GetDefaultApp() / GetApp(string) 或实现 IAppManager<T>
HTTPCLIENT017 Warning HttpClient 类型无法解析,加密/XML 兼容性校验被跳过 使用完全限定名确保类型可解析
HTTPCLIENT018 Warning TokenManagerKey 使用默认推断值 多接口共享同一 TokenManager 时显式指定 TokenManagerKeyTokenType
HTTPCLIENT019 ❌ 已移除(CFG-27):其唯一触发点 CacheAttribute.Priority 已删除 无需处理(ID 保留为未使用占位)
HTTPCLIENT020 Warning 非幂等方法声明 [Retry] 但未设 AllowNonIdempotent 运行时将跳过重试;如服务端可安全重复执行请显式开启
HTTPCLIENT021 Warning 方法级 [Timeout] 超过接口级 HttpClient 超时 HttpClient.Timeout 是硬上限,调小 [Timeout] 或提高 [HttpClientApi(Timeout=…)]
HTTPCLIENT022 Warning 方法使用 Path/HmacSignature 令牌注入模式 令牌恢复处理器(TokenRecoveryDelegatingHandler/TokenRecoveryEnhancedClient)不支持这两种模式,刷新后的新令牌无法重新注入,恢复将静默失败并返回 401。如需令牌恢复能力请改用 Header/Query/ApiKey/Cookie/BasicAuth 模式
HTTPCLIENTREG001 Error 注册代码生成失败 检查接口定义和 DI 注册配置
HTTPCLIENTREG002 Error RegistryGroupName 不是有效 C# 标识符 使用字母、数字、下划线组成,以字母或下划线开头
EHSG001 Error 事件处理器代码生成失败 检查被处理类型定义与配置
FORM001 Error FormContent 代码生成错误 检查 FormContent 类定义
FORM002 Error FormContent 缺少 [FilePath] 属性 必须且只能有一个属性标记 [FilePath]
FORM003 Error FormContent 存在多个 [FilePath] 属性 只保留一个 [FilePath] 属性
MUD004 Warning ITokenManager 实现未注册为 Singleton ITokenManager 的实现类内部维护令牌缓存与并发锁(如 SemaphoreSlim),Scoped/Transient 注册会使每个请求持有独立缓存实例,导致并发安全机制失效与重复刷新令牌。请改用 AddSingleton/TryAddSingleton

HTTPCLIENT002HTTPCLIENT006HTTPCLIENT010HTTPCLIENT019 当前未使用(ID 保留为占位,不重新分配)。

  • HTTPCLIENT010HttpClientApiAttribute.BaseAddress 已移除(CFG-27),使用直接编译错误 CS0117
  • HTTPCLIENT019CacheAttribute.Priority 已移除(CFG-27),[Cache] 已无被忽略的属性。

🧪 测试

# 运行所有测试
dotnet test

# 运行特定测试项目
dotnet test Tests/Mud.HttpUtils.Tests
dotnet test Tests/Mud.HttpUtils.Client.Tests
dotnet test Tests/Mud.HttpUtils.Resilience.Tests
dotnet test Tests/Mud.HttpUtils.Generator.Tests
dotnet test Tests/Mud.HttpUtils.OpenTelemetry.Tests

🤖 默认参数推断

未标注任何 HTTP 参数特性的方法参数,代码生成器会根据参数类型自动推断处理方式:

  • 简单类型stringintlongGuidDateTime 等及其数组和可空类型)→ 自动作为 [Query] 查询参数处理
  • 复杂类型(自定义对象、List<T>Dictionary<K,V> 等)→ 自动作为 [Body] 请求体进行 JSON 序列化处理
  • 特殊类型CancellationTokenIProgress<T>)→ 不参与推断,保持原有处理
[HttpClientApi(HttpClient = "IEnhancedHttpClient")]
public interface IUserApi
{
    // string keyword 自动推断为 [Query("keyword")]
    [Get("users/search")]
    Task<List<User>> SearchUsersAsync(string keyword, CancellationToken ct = default);

    // User user 自动推断为 [Body]
    [Post("users")]
    Task<User> CreateUserAsync(User user, CancellationToken ct = default);

    // 混合使用:keyword → [Query],criteria → [Body]
    [Post("users/advanced-search")]
    Task<List<User>> AdvancedSearchAsync(string keyword, SearchCriteria criteria, CancellationToken ct = default);
}

📊 OpenTelemetry 可观测性

通过 Mud.HttpUtils.OpenTelemetry 包一键开启分布式追踪与指标采集:

builder.Services.AddMudHttpOpenTelemetry(options =>
{
    options.OtlpEndpoint = new Uri("http://otel-collector:4317");
});

自动采集 HTTP 请求计数、请求耗时、缓存命中、令牌刷新、重试次数、熔断器状态、下载字节数、下载耗时等指标。

🤝 贡献

欢迎提交 Issue 和 Pull Request 来改进这个项目!

📄 许可证

本项目遵循 MIT 许可证。详细信息请参见 LICENSE-MIT 文件。


About

Mud.HttpUtils 是一个基于 Roslyn 源代码生成器的声明式 HTTP 客户端框架,通过特性标注的方式自动生成类型安全的 HTTP API 客户端代码。无需手写 HttpClient 调用代码,只需定义接口并添加特性标注,编译器会自动生成完整的实现代码。

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages