Skip to content

mudtools/MudHttpUtils

Repository files navigation

Mud.HttpUtils

NuGet NuGet License

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

📖 项目简介

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

✨ 核心特性

  • 🚀 零运行时开销:编译时生成代码,无反射,性能优异
  • 🎯 类型安全:强类型 API 调用,编译时检查错误
  • 📝 声明式编程:通过特性标注定义 HTTP API,简洁直观
  • 🔧 功能丰富:支持多种 HTTP 方法、参数类型、内容格式、Token 认证、加密传输等
  • 🛡️ 弹性策略:内置重试、超时、熔断策略,基于 Polly 实现
  • 🔐 加密支持:可插拔的加密提供程序,内置 AES 加密实现
  • 🔄 令牌管理:并发安全的令牌刷新,支持持久化存储契约
  • 🌐 多客户端:支持多命名客户端场景,通过 IHttpClientResolver 动态解析
  • 🎨 灵活配置:支持接口级、方法级、参数级的配置优先级
  • 🏗️ 接口级动态属性:在接口上定义 [Query]/[Path] 属性,实现全局参数
  • 🗺️ QueryMap 参数映射:将对象/字典展开为查询参数,支持序列化控制
  • 🔗 Base Path 支持:在接口级别定义统一路径前缀
  • 📦 Response<T> 包装类型:同时返回响应内容和元数据
  • 📦 多框架支持:支持 .NET Standard 2.0、.NET 6.0、.NET 8.0、.NET 10.0

📦 NuGet 包

包名 说明 NuGet
Mud.HttpUtils 元包:Abstractions + Attributes + Client + Resilience NuGet
Mud.HttpUtils.Abstractions 纯接口定义,最小依赖 NuGet
Mud.HttpUtils.Attributes 特性定义 NuGet
Mud.HttpUtils.Client 客户端实现 + DI 注册 NuGet
Mud.HttpUtils.Resilience 弹性策略(Polly) NuGet
Mud.HttpUtils.Generator 源代码生成器 NuGet

🚀 快速开始

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

内容类型管理

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

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;
});

也支持从 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
[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");
    encryption.IV = Convert.FromBase64String("your-base64-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);

令牌管理

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

// 实现自定义令牌管理器
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 { }

多命名客户端

// 注册多个客户端
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);

文件上传与下载

// 文件上传(支持 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);

接口级动态属性

支持在接口上定义 [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            # 抽象基类
│   ├── HttpClientFactoryEnhancedClient # IHttpClientFactory 实现
│   ├── DefaultAesEncryptionProvider  # AES 加密默认实现
│   ├── HttpClientResolver            # 命名客户端解析器
│   └── ServiceCollectionExtensions   # AddMudHttpClient() 注册
├── Mud.HttpUtils.Resilience/         # 弹性策略扩展包
│   ├── ResilientHttpClient           # 装饰器(重试/超时/熔断)
│   ├── PollyResiliencePolicyProvider # Polly 策略提供器
│   ├── HttpRequestMessageCloner      # 请求克隆工具
│   └── ServiceCollectionExtensions   # AddMudHttpResilienceDecorator() 注册
├── Mud.HttpUtils.Generator/          # 源代码生成器
│   ├── HttpInvokeClassSourceGenerator    # 实现类生成器
│   └── HttpInvokeRegistrationGenerator   # 注册代码生成器(含 Timeout 配置)
├── 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

🧪 测试

# 运行所有测试
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

🤝 贡献

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

📄 许可证

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


About

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

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors