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
# 安装元包(包含 Abstractions + Attributes + Client + Resilience)
dotnet add package Mud.HttpUtils
# 安装源代码生成器
dotnet add package Mud.HttpUtils.Generatorusing 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);
}// 一站式注册:Client + 弹性策略
services.AddMudHttpUtils("userApi", "https://api.example.com", options =>
{
options.Retry.MaxRetryAttempts = 3;
options.Timeout.TimeoutSeconds = 30;
});
// 注册生成器生成的 API 接口
services.AddWebApiHttpClient();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);
}
}[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] 特性支持应用到参数、方法或接口级别:
// 参数级别
[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 |
遗留场景 |
HttpClient与TokenManage互斥,同时定义时HttpClient优先。
// 接口级 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");
}
}// 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] 支持将对象属性展开为查询参数,支持字典类型和 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 的属性 |
支持在接口级别定义统一的路径前缀:
[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> 类型同时返回响应内容和元数据(状态码、响应头):
[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 文件。