Lord.Service
7.10.1
See the version list below for details.
dotnet add package Lord.Service --version 7.10.1
NuGet\Install-Package Lord.Service -Version 7.10.1
<PackageReference Include="Lord.Service" Version="7.10.1" />
<PackageVersion Include="Lord.Service" Version="7.10.1" />
<PackageReference Include="Lord.Service" />
paket add Lord.Service --version 7.10.1
#r "nuget: Lord.Service, 7.10.1"
#:package Lord.Service@7.10.1
#addin nuget:?package=Lord.Service&version=7.10.1
#tool nuget:?package=Lord.Service&version=7.10.1
LordService 完整使用文档
企业级推送与消息队列集成库
支持钉钉/企业微信/飞书消息推送、RabbitMQ 发布订阅与死信队列、Redis 分布式缓存与锁、AES-GCM 认证加密、多日志框架。
版本:v7.11.0 | 目标框架:net6.0 / net8.0 / net9.0 / net10.0
目录
安装
dotnet add package Lord.Service
完整 appsettings.json 配置参考
以下是所有模块的完整配置示例,实际使用时按需选取:
{
"RabbitMQ": {
"HostName": "localhost",
"VirtualHost": "/",
"UserName": "guest",
"Password": "guest",
"Prefix": "MyApp",
"ServiceName": "订单服务"
},
"Redis": {
"ConnectionString": "localhost:6379",
"Prefix": "MyApp",
"Database": 0,
"ConnectTimeout": 5,
"SyncTimeout": 5,
"AllowAdmin": false,
"Ssl": false,
"Password": "",
"ClientName": "LordService"
},
"Encryption": {
"PublicKeyOrKey": "你的AES密钥Base64字符串",
"PrivateKeyOrIV": "备用IV的Base64字符串",
"EncryptType": "Aes"
},
"DingTalk": {
"Alias": "系统通知群",
"Url": "https://oapi.dingtalk.com",
"Token": "your_dingtalk_access_token",
"Secret": "your_dingtalk_secret"
},
"WeChatPush": {
"Alias": "运维群",
"Url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send",
"Token": "your_wechat_key"
},
"LarkPush": {
"Alias": "开发群",
"Url": "https://open.feishu.cn/open-apis/bot/v2/hook",
"Token": "your_lark_token",
"Secret": "your_lark_secret"
},
"DingApp": {
"AppKey": "your_ding_app_key",
"AppSecret": "your_ding_app_secret",
"AgentId": "your_agent_id"
},
"WeChatOfficial": {
"AppId": "your_app_id",
"AppSecret": "your_app_secret",
"Token": "your_token",
"EncodingAesKey": null
},
"WeChatMiniProgram": {
"AppId": "your_mini_program_appid",
"AppSecret": "your_mini_program_secret",
"BaseUrl": "https://api.weixin.qq.com"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
多群组配置
钉钉/企业微信/飞书支持多群组,使用数组配置:
{
"DingTalk": [
{
"Alias": "生产告警群",
"Url": "https://oapi.dingtalk.com",
"Token": "prod_alert_token",
"Secret": "prod_alert_secret"
},
{
"Alias": "运维群",
"Url": "https://oapi.dingtalk.com",
"Token": "ops_token",
"Secret": "ops_secret"
}
]
}
RabbitMQ 多连接池
不同业务可使用不同的 RabbitMQ 连接池:
{
"RabbitMQ": {
"HostName": "localhost",
"VirtualHost": "/",
"UserName": "guest",
"Password": "guest",
"Prefix": "MyApp"
},
"OrderMQ": {
"HostName": "mq-order.internal",
"VirtualHost": "/order",
"UserName": "order_user",
"Password": "order_pass",
"Prefix": "OrderApp"
},
"OrderQueue": {
"QueueName": "Order_Process_Queue",
"ExchangeName": "Order_Process_Topic",
"RouteKey": "Service.order.process",
"MQPool": "OrderMQ"
}
}
快速上手
最简配置(内存缓存 + NLog + AES)
// Program.cs
builder.Services.AddLordService(lord => lord.UseDefaults());
UseDefaults() 等价于:
builder.Services.AddLordService(lord => lord
.UseHttp() // RestSharp,超时 1 分钟
.UseLogging(log => log.UseNLog()) // NLog 日志
.UseCache(cache => cache.UseMemoryCache()) // 内存缓存
.UseEncryption(enc => enc.UseAES(a => a.FromConfiguration())) // AES-GCM
);
生产推荐配置
builder.Services.AddLordService(lord => lord
.UseHttp(h => h.UseNetHttp().WithTimeout(TimeSpan.FromSeconds(30)))
.UseLogging(log => log.UseNLog())
.UseCache(cache => cache.UseRedis(r => r.FromConfiguration("Redis")))
.UseEncryption(enc => enc.UseAES(a => a.FromConfiguration("Encryption")))
.UseMQ(mq => mq
.UseDeadLetter()
.UseRabbitMQ(r => r.FromConfiguration("RabbitMQ")))
.UsePush(push => push
.AddDingTalk(d => d.FromConfiguration("DingTalk"))
.AddWeChat(w => w.FromConfiguration("WeChatPush"))
.AddLark(l => l.FromConfiguration("LarkPush")))
);
模块详解
1. 缓存(Redis / Memory)
注册方式
// 方式1:从配置加载 Redis
.UseCache(cache => cache.UseRedis(r => r.FromConfiguration("Redis")))
// 方式2:直接传连接字符串
.UseCache(cache => cache.UseRedis("localhost:6379"))
// 方式3:使用内存缓存
.UseCache(cache => cache.UseMemoryCache())
// 方式4:自定义缓存实现
.UseCache(cache => cache.UseCustom<MyRedisCache>(c => c.FromConfiguration("Redis").AsSingleton()))
// 方式5:直接传实例
.UseCache(cache => cache.UseCustom(new MyRedisCache()))
Redis 配置项说明
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
ConnectionString |
string | localhost:6379 |
Redis 连接字符串 |
Prefix |
string | "" |
Key 前缀,用于多系统共用 Redis 隔离 |
Database |
int | 0 |
Redis 数据库编号 |
ConnectTimeout |
int | 5 |
连接超时(秒) |
SyncTimeout |
int | 5 |
同步操作超时(秒) |
AllowAdmin |
bool | false |
是否允许管理操作(如 FlushDatabase) |
Ssl |
bool | false |
是否启用 SSL |
Password |
string? | null |
Redis 密码 |
ClientName |
string? | LordService |
客户端名称 |
业务使用
public class UserService
{
private readonly ICache _cache;
public UserService(ICache cache) => _cache = cache;
// 基本读写
public async Task<User?> GetUserAsync(int userId)
{
return await _cache.GetItemAsync<User>($"user:{userId}");
}
public async Task SetUserAsync(int userId, User user)
{
await _cache.SetItemAsync($"user:{userId}", user, TimeSpan.FromMinutes(30));
}
// 缓存穿透保护:AddOrGetCacheItem 保证同 key 只回源一次
public async Task<User> GetOrLoadUserAsync(int userId)
{
return await _cache.AddOrGetCacheItemAsync(
$"user:{userId}",
async () => await LoadFromDbAsync(userId), // 只在缓存缺失时执行
TimeSpan.FromMinutes(30),
isSlidingExpiration: true); // 滑动过期:每次读取续期
}
// 批量操作(自动分批,每批 500 条)
public async Task SetUsersBatchAsync(Dictionary<string, User> users)
{
await _cache.SetBatchAsync(users, TimeSpan.FromHours(1));
}
public async Task<Dictionary<string, User?>> GetUsersBatchAsync(IEnumerable<string> keys)
{
return await _cache.GetBatchAsync<User>(keys);
}
// 分布式锁
public async Task DoWithLockAsync(string resourceKey, Func<Task> action)
{
await using var handle = await _cache.DistributedLock.AcquireAsync(
$"lock:{resourceKey}",
TimeSpan.FromMinutes(1));
if (handle.IsAcquired)
{
await action();
}
}
// 自动续期锁(看门狗模式,适合长时间任务)
public async Task DoWithAutoRenewalLockAsync(string resourceKey, Func<Task> action)
{
await using var handle = await _cache.DistributedLock.AcquireWithRenewalAsync(
$"lock:{resourceKey}",
TimeSpan.FromSeconds(30)); // 锁 30 秒,自动续期
if (handle.IsAcquired)
{
await action(); // 即使任务超过 30 秒,锁也会自动续期
}
}
}
安全提示:同步
AddOrGetCacheItem的回源等待有 30 秒超时保护,避免cachePopulate卡住时线程池饥饿。生产环境推荐使用异步AddOrGetCacheItemAsync。
2. 消息队列(RabbitMQ)
注册方式
.UseMQ(mq => mq
.UseDeadLetter() // 全局启用死信队列(所有队列自动生成 per-queue DLX)
.UseRabbitMQ(r => r
.FromConfiguration("RabbitMQ") // 从配置加载
// 或:.WithConnection("host", "/vhost", "user", "pass") // 直接配置
.WithPrefix("MyApp") // 队列名前缀,隔离多系统
.WithServiceName("订单服务")) // 连接名称,便于运维识别
)
RabbitMQ 配置项说明
| 配置项 | 说明 |
|---|---|
HostName |
RabbitMQ 主机地址 |
VirtualHost |
虚拟主机 |
UserName |
用户名 |
Password |
密码 |
Prefix |
队列/交换机/路由键前缀 |
ServiceName |
连接名称(便于 RabbitMQ 管理端识别) |
IMQHub 简化 API(推荐)
// 定义消息(队列名自动从类型名推导)
public record OrderCreated(string OrderId, decimal Amount);
// 发布
await _hub.PublishAsync(new OrderCreated("ORD-001", 99.9m));
// 订阅
await _hub.SubscribeAsync<OrderCreated>(async (msg, sp) =>
{
var logger = sp.GetRequiredService<ILogger<Program>>();
logger.LogInformation("收到订单: {OrderId}, 金额: {Amount}", msg.OrderId, msg.Amount);
return true; // true=Ack, false=Reject(触发重试或死信)
});
// 死信订阅
await _hub.SubscribeDeadLetterAsync<OrderCreated>(async (msg, sp) =>
{
var logger = sp.GetRequiredService<ILogger<Program>>();
logger.LogWarning("订单消息进入死信: {OrderId}", msg.OrderId);
return true;
});
IMQHub 完整 API
// 发布
await hub.PublishAsync(message); // 类型推导
await hub.PublishAsync(message, cfg => cfg.Qos = 20); // 自定义配置
await hub.PublishAsync("custom_name", message); // 自定义队列名
await hub.PublishAsync("custom_name", message, cfg => { }); // 自定义名 + 配置
await hub.PublishAsync(rawJsonString); // 原始字符串
// 订阅(5 种重载)
await hub.SubscribeAsync<T>(msg => Task.FromResult(true)); // 最简
await hub.SubscribeAsync<T>(msg => true); // 同步
await hub.SubscribeAsync<T>(async (msg, sp) => true); // 注入 ServiceProvider
await hub.SubscribeAsync<T>(handler, cfg => { }); // 自定义配置
await hub.SubscribeAsync<T>("name", handler); // 自定义队列名
// 死信订阅
await hub.SubscribeDeadLetterAsync<T>(async (msg, sp) => true);
await hub.SubscribeDeadLetterAsync<T>("name", handler, cfg => { });
MQ 拦截器
.UseMQ(mq => mq
.AddFilter<MyQueueArgsFilter>() // IMQQueueArgsFilter: 修改队列参数
.AddFilter<MyDeadLetterFilter>() // IMQDeadLetterFilter: 修改死信配置
.AddFilter<MyCryptoFilter>() // IMQCryptoFilter: 自定义加解密
.AddFilter<MyJsonFilter>() // IMQJsonFilter: 自定义序列化
.AddFilter<MyExceptionFilter>() // IMQExceptionFilter: 异常处理
.UseRabbitMQ(r => r.FromConfiguration("RabbitMQ"))
)
消费端看门狗
消费端内置看门狗机制:
- 心跳检测:每 10 秒检查 consumer 存活状态
- 自动恢复:consumer 关闭或 Channel 断开时自动重建
- 指数退避:恢复失败时 1s→2s→4s→8s→16s→30s 退避重试
- 低频持续:连续失败 20 次后进入低频模式(每 60 秒一次),永不永久停止
- 超时保护:handler 执行超过 3 分钟自动 Reject 并取消后台任务
3. 消息推送(钉钉/企业微信/飞书)
注册方式
.UsePush(push => push
// 钉钉机器人
.AddDingTalk(d => d
.FromConfiguration("DingTalk") // 从配置加载
// 或:.AddGroup("告警群", "token", "secret") // 直接添加群组
)
// 企业微信机器人
.AddWeChat(w => w
.FromConfiguration("WeChatPush")
// 或:.AddGroup("运维群", "key")
)
// 飞书机器人
.AddLark(l => l
.FromConfiguration("LarkPush")
// 或:.AddGroup("开发群", "token", "secret")
)
// 钉钉应用推送(工作通知)
.AddDingApp(a => a
.FromConfiguration("DingApp")
// 或:.WithCredentials("appKey", "appSecret", "agentId")
)
)
业务使用
public class NotificationService
{
private readonly IDingTalkApiFactory _dingFactory;
private readonly IWeChatApiFactory _weChatFactory;
private readonly ILarkApiFactory _larkFactory;
public NotificationService(
IDingTalkApiFactory dingFactory,
IWeChatApiFactory weChatFactory,
ILarkApiFactory larkFactory)
{
_dingFactory = dingFactory;
_weChatFactory = weChatFactory;
_larkFactory = larkFactory;
}
// 推送到默认群
public async Task<bool> NotifyDingTalkAsync(string content)
{
var push = _dingFactory.GetPushService();
return await push.PushAsync(content);
}
// 推送到指定群(按 Alias)
public async Task<bool> NotifyDingTalkGroupAsync(string alias, string content)
{
var push = _dingFactory.GetPushService(alias);
return await push.PushAsync(content);
}
// 使用消息格式化器
public async Task<bool> NotifyWithFormatAsync()
{
var push = _dingFactory.GetPushService();
return await push.PushAsync(format => format.Text("服务器 CPU 超过 90%"));
}
// 获取所有推送服务
public async Task BroadcastAsync(string content)
{
foreach (var push in _dingFactory.GetAllPushService())
{
await push.PushAsync(content);
}
}
}
安全提示:推送失败日志已自动脱敏,不会将完整消息内容写入日志。
4. 加密(AES-GCM / RSA)
注册方式
// AES-GCM(推荐,默认)
.UseEncryption(enc => enc.UseAES(a => a
.FromConfiguration("Encryption") // 从配置加载
// 或:.WithKeys("base64_key", "base64_iv") // 直接配置
))
// RSA
.UseEncryption(enc => enc.UseRSA(r => r
.FromConfiguration("Encryption")
// 或:.WithKeys("public_key_xml", "private_key_xml")
))
// DES(已标记 Obsolete,仅用于历史数据解密,不推荐生产新用)
// .UseEncryption(enc => enc.UseDES(d => d.FromConfiguration("Encryption")))
AES 密钥要求
| 项目 | 要求 |
|---|---|
| 密钥格式 | Base64 编码字符串 |
| 解码后长度 | 必须为 16 / 24 / 32 字节 |
| 加密算法 | AES-GCM(认证加密) |
| 密文格式 | [LRD 0x02][nonce(12)][tag(16)][ciphertext] |
| 压缩 | 加密前先 Deflate 压缩 |
重要:v7.11.0 起,AES 新加密统一使用 AES-GCM。密钥长度不合法时会抛
InvalidOperationException,不再静默截断/补零。
生成 AES 密钥
using System.Security.Cryptography;
var key = RandomNumberGenerator.GetBytes(32); // 256 位
var iv = RandomNumberGenerator.GetBytes(16); // 备用 IV(仅 legacy 解密使用)
var keyBase64 = Convert.ToBase64String(key);
var ivBase64 = Convert.ToBase64String(iv);
// 写入 appsettings.json
// "Encryption": { "PublicKeyOrKey": "...", "PrivateKeyOrIV": "...", "EncryptType": "Aes" }
业务使用
public class SecureService
{
private readonly IEncryptProvider _encryptor;
public SecureService(IEncryptProvider encryptor) => _encryptor = encryptor;
public byte[] Encrypt(string plainText)
{
var bytes = Encoding.UTF8.GetBytes(plainText);
return _encryptor.Encryption(bytes);
}
public string Decrypt(byte[] cipher)
{
var bytes = _encryptor.Decryption(cipher);
return Encoding.UTF8.GetString(bytes);
}
}
加密配置示例
{
"Encryption": {
"PublicKeyOrKey": "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=",
"PrivateKeyOrIV": "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=",
"EncryptType": "Aes"
}
}
5. HTTP 请求
注册方式
// RestSharp(默认)
.UseHttp(h => h.UseRestSharp().WithTimeout(TimeSpan.FromSeconds(30)))
// HttpClient
.UseHttp(h => h.UseNetHttp().WithTimeout(TimeSpan.FromSeconds(30)))
业务使用
public class ApiService
{
private readonly IHttpFactory _httpFactory;
public ApiService(IHttpFactory httpFactory) => _httpFactory = httpFactory;
public async Task<MyResult?> GetDataAsync()
{
var factory = _httpFactory.CreateFactory("https://api.example.com");
var response = await factory.CreateRequest("/api/v1/data")
.AddHeader("Authorization", "Bearer token")
.AddQueryParameter("page", "1")
.GetAsync<MyResult>();
return response.Content;
}
public async Task PostDataAsync(object payload)
{
var factory = _httpFactory.CreateFactory("https://api.example.com");
await factory.CreateRequest("/api/v1/submit")
.AddJsonBody(payload)
.PostAsync();
}
}
HTTP 重试策略
| 方法 | 默认重试 | 说明 |
|---|---|---|
| GET / HEAD / OPTIONS / PUT / DELETE | 3 次 | 幂等方法自动重试 |
| POST / PATCH | 不重试 | 防止重复提交 |
| 4xx(除 429) | 不重试 | 客户端错误不可恢复 |
| 429 / 5xx | 重试 | 服务端临时错误 |
| 超时/连接失败 | 重试 | 网络问题可恢复 |
如需对 POST 启用重试:
var request = factory.CreateRequest("/api/submit")
.AddJsonBody(payload)
.SetRetryCount(3)
.AllowRetryNonIdempotent() // 显式允许 POST 重试
.PostAsync<MyResult>();
安全提示:HttpClient 缓存 key 已从
url.Host改为scheme://host:port,避免同 host 不同端口复用错误客户端。响应消息在读取内容后立即释放,避免连接池耗尽。
6. 日志(NLog / Log4Net)
注册方式
// NLog(默认)
.UseLogging(log => log.UseNLog())
// Log4Net
.UseLogging(log => log.UseLog4Net())
// 自定义日志配置
.UseLogging(log => log.UseNLog(builder =>
{
builder.SetMinimumLevel(LogLevel.Information);
builder.AddFilter<Microsoft.Hosting.Lifetime>("Microsoft", LogLevel.Warning);
}))
日志配置文件
nlog.config/log4net.config优先从应用根目录加载,不存在时使用内置默认配置。
7. 微信公众号
注册方式
.UseWeChatOfficial(wx =>
{
wx.FromConfiguration("WeChatOfficial");
// 或:wx.WithSettings("appId", "appSecret", "token", "encodingAesKey?");
// 可选:wx.UseMessageHandler<MyCustomHandler>();
})
微信公众号配置
{
"WeChatOfficial": {
"AppId": "your_app_id",
"AppSecret": "your_app_secret",
"Token": "your_token",
"EncodingAesKey": null
}
}
Controller 接入(安全 POST 入口)
[ApiController]
[Route("api/wechat")]
public class WeChatController : ControllerBase
{
private readonly IWeChatSecureMessageHandler _handler;
public WeChatController(IWeChatSecureMessageHandler handler)
=> _handler = handler;
// GET:微信 URL 验证
[HttpGet]
public string Get(
[FromQuery] string signature,
[FromQuery] string timestamp,
[FromQuery] string nonce,
[FromQuery] string echostr)
{
var service = _handler.MsgOfficialService;
return service.ValidateSignature(signature, timestamp, nonce, echostr, out var response)
? response : "fail";
}
// POST:微信消息回调(自动验签)
[HttpPost]
public async Task<string> Post(
[FromQuery] string signature,
[FromQuery] string timestamp,
[FromQuery] string nonce)
{
using var reader = new StreamReader(Request.Body);
var xml = await reader.ReadToEndAsync();
// 先验签,再处理 XML
return await _handler.HandleMessageAsync(xml, signature, timestamp, nonce);
}
}
安全提示:
IWeChatSecureMessageHandler在处理 XML 前会先校验微信签名。签名校验使用固定时间比较(CryptographicOperations.FixedTimeEquals),防止时序攻击。
事件订阅
public class WeChatEventService
{
private readonly IWeChatEventHandler _events;
public WeChatEventService(IWeChatEventHandler events)
{
_events = events;
// 用户关注
_events.OnUserSubscribed += async args =>
{
Console.WriteLine($"用户关注: {args.FromUser}");
await Task.CompletedTask;
};
// 文本消息
_events.OnTextMessageReceived += async args =>
{
Console.WriteLine($"收到文本: {args.Content}");
await Task.CompletedTask;
};
}
}
8. 微信小程序
注册方式
.UseWeChatMiniProgram(wx =>
{
wx.FromConfiguration("WeChatMiniProgram");
// 或:wx.WithSettings("appId", "appSecret");
// 多小程序:wx.AddProgram("alias", "appId", "appSecret");
})
配置
{
"WeChatMiniProgram": {
"AppId": "your_appid",
"AppSecret": "your_secret",
"BaseUrl": "https://api.weixin.qq.com"
}
}
9. JSON 序列化
// Newtonsoft.Json(默认)
.UseJson(j => j.UseNewtonsoftJson(settings =>
{
settings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
settings.NullValueHandling = NullValueHandling.Ignore;
}))
// System.Text.Json
.UseJson(j => j.UseTextJson(opts =>
{
opts.Serialize = o => o.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
}))
安全最佳实践
1. 加密
- 生产环境必须使用 AES-GCM,不要使用 DES
- AES 密钥长度必须为 16/24/32 字节(推荐 32 字节 = AES-256)
- 密钥不要硬编码在代码中,使用
appsettings.json或环境变量 - 定期轮换密钥
2. 日志脱敏
- 推送失败日志已自动脱敏,不会记录完整消息内容
- 微信 XML 日志已自动脱敏
Content、Recognition、FromUserName等敏感字段 - 如需自定义脱敏,使用
LogSanitizer工具类
3. 微信公众号
- POST 回调必须使用
IWeChatSecureMessageHandler进行签名校验 - 签名校验使用固定时间比较,防止时序攻击
EncodingAesKey配置后可支持加密模式
4. HTTP 请求
- POST/PATCH 默认不重试,防止重复提交
- 响应消息在读取内容后立即释放,避免连接池耗尽
- HttpClient 缓存按
scheme://host:port隔离
5. RabbitMQ
- 连接创建有 30 秒总超时,不会无限阻塞
- 消费端 handler 超时 3 分钟后自动取消,避免资源泄漏
- 看门狗永不永久停止,失败后进入低频持续重试
6. Redis
- 同步
AddOrGetCacheItem有 30 秒超时保护 - 分布式锁自动续期有重入保护,防止续期任务堆积
- 批量操作自动分批(每批 500 条),避免大命令阻塞 Redis
v7.11.0 变更与迁移
安全增强
| 变更项 | 旧行为 | 新行为 |
|---|---|---|
| 默认加密 | DES | AES-GCM |
| AES 模式 | CBC 无认证 | GCM 认证加密 |
| AES 密钥 | 静默截断/补零 | 严格校验 16/24/32 字节 |
| 推送日志 | 完整记录 content | 自动脱敏 |
| 微信日志 | 完整记录 XML | 自动脱敏敏感字段 |
| 微信 POST | 无签名校验入口 | 新增 IWeChatSecureMessageHandler |
| 签名比较 | 字符串相等 | 固定时间比较 |
稳定性增强
| 变更项 | 旧行为 | 新行为 |
|---|---|---|
| MQ 连接创建 | CancellationToken.None |
30 秒内部超时 |
| MQ 看门狗 | 20 次失败后永久停止 | 低频持续重试(60 秒一次) |
| MQ handler 超时 | 后台任务继续运行 | 取消后台任务 |
| HTTP 响应 | 未释放 | using var 确保释放 |
| HTTP POST | 默认重试 3 次 | 默认不重试 |
| HTTP 缓存 key | url.Host |
scheme://host:port |
性能增强
| 变更项 | 旧行为 | 新行为 |
|---|---|---|
| Redis RemoveBatch | 一次性删除 | 分批 500 条 |
| Redis SetBatch | 一次性写入 | 分批 500 条 |
| Redis 锁续期 | 可能重入堆积 | 重入保护 |
| Redis singleflight | 无超时 | 30 秒超时 |
架构改进
| 变更项 | 旧行为 | 新行为 |
|---|---|---|
| Builder 注册 | 8 处 BuildServiceProvider |
全部移除,改用 BindConfiguration 或懒加载 |
迁移指南
DES → AES:如果旧数据是用 DES 加密的,解密时仍可使用
UseDES。新数据必须使用UseAES。推送配置:
FromConfiguration()的配置在首次调用GetPushService(sectionName)时懒加载,行为与之前一致。HTTP POST 重试:如果业务依赖 POST 自动重试,调用
.AllowRetryNonIdempotent()显式开启。微信 POST:将 Controller 中的
HandleMessageAsync(xml)替换为HandleMessageAsync(xml, signature, timestamp, nonce)。
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. 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
- log4net (>= 3.3.0)
- Microsoft.Extensions.Configuration (>= 10.0.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Configuration.Binder (>= 10.0.0)
- Microsoft.Extensions.Configuration.Json (>= 10.0.0)
- Microsoft.Extensions.DependencyInjection (>= 10.0.0)
- Microsoft.Extensions.Http (>= 10.0.0)
- Microsoft.Extensions.Logging.Log4Net.AspNetCore (>= 8.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.0)
- Newtonsoft.Json (>= 13.0.4)
- NLog (>= 6.1.2)
- NLog.Extensions.Logging (>= 6.1.2)
- Polly (>= 8.4.1)
- RabbitMQ.Client (>= 7.2.1)
- RestSharp (>= 113.0.0)
- StackExchange.Redis (>= 2.9.25)
- System.Runtime.Caching (>= 10.0.0)
-
net6.0
- log4net (>= 3.3.0)
- Microsoft.Extensions.Configuration (>= 6.0.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 6.0.0)
- Microsoft.Extensions.Configuration.Binder (>= 6.0.0)
- Microsoft.Extensions.Configuration.Json (>= 6.0.0)
- Microsoft.Extensions.DependencyInjection (>= 6.0.0)
- Microsoft.Extensions.Http (>= 6.0.0)
- Microsoft.Extensions.Logging.Log4Net.AspNetCore (>= 8.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 6.0.0)
- Newtonsoft.Json (>= 13.0.4)
- NLog (>= 6.1.2)
- NLog.Extensions.Logging (>= 6.1.2)
- Polly (>= 8.4.1)
- RabbitMQ.Client (>= 7.2.1)
- RestSharp (>= 112.0.0)
- StackExchange.Redis (>= 2.9.25)
- System.Runtime.Caching (>= 6.0.0)
-
net8.0
- log4net (>= 3.3.0)
- Microsoft.Extensions.Configuration (>= 8.0.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Configuration.Binder (>= 8.0.0)
- Microsoft.Extensions.Configuration.Json (>= 8.0.1)
- Microsoft.Extensions.DependencyInjection (>= 8.0.0)
- Microsoft.Extensions.Http (>= 8.0.0)
- Microsoft.Extensions.Logging.Log4Net.AspNetCore (>= 8.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 8.0.0)
- Newtonsoft.Json (>= 13.0.4)
- NLog (>= 6.1.2)
- NLog.Extensions.Logging (>= 6.1.2)
- Polly (>= 8.4.1)
- RabbitMQ.Client (>= 7.2.1)
- RestSharp (>= 113.0.0)
- StackExchange.Redis (>= 2.9.25)
- System.Runtime.Caching (>= 8.0.0)
-
net9.0
- log4net (>= 3.3.0)
- Microsoft.Extensions.Configuration (>= 9.0.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Configuration.Binder (>= 9.0.0)
- Microsoft.Extensions.Configuration.Json (>= 9.0.0)
- Microsoft.Extensions.DependencyInjection (>= 9.0.0)
- Microsoft.Extensions.Http (>= 9.0.0)
- Microsoft.Extensions.Logging.Log4Net.AspNetCore (>= 8.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 9.0.0)
- Newtonsoft.Json (>= 13.0.4)
- NLog (>= 6.1.2)
- NLog.Extensions.Logging (>= 6.1.2)
- Polly (>= 8.4.1)
- RabbitMQ.Client (>= 7.2.1)
- RestSharp (>= 113.0.0)
- StackExchange.Redis (>= 2.9.25)
- System.Runtime.Caching (>= 9.0.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 |
|---|---|---|
| 7.10.8 | 40 | 8/11/2026 |
| 7.10.7 | 94 | 8/6/2026 |
| 7.10.6 | 95 | 8/5/2026 |
| 7.10.5 | 92 | 8/4/2026 |
| 7.10.1 | 103 | 8/1/2026 |
| 7.10.0 | 99 | 7/30/2026 |
| 7.0.8 | 114 | 7/29/2026 |
| 7.0.7 | 103 | 7/29/2026 |
| 7.0.6 | 103 | 7/16/2026 |
| 7.0.5 | 118 | 4/24/2026 |
| 7.0.3 | 145 | 2/10/2026 |
| 7.0.2 | 136 | 2/6/2026 |
| 7.0.1 | 138 | 2/4/2026 |
| 7.0.0 | 150 | 1/26/2026 |
| 6.2.0 | 146 | 1/23/2026 |
| 6.0.9 | 142 | 1/12/2026 |
| 6.0.8 | 313 | 11/30/2025 |
7.10.0:
- AES 加密改用随机 IV(每次加密生成新 IV,向后兼容旧数据)
- 新增 Polly 8 弹性策略(LordResilience),MQ 连接重试改为指数退避+抖动
- HTTP 重试改为指数退避+随机抖动(RestSharp + HttpClient 双实现)
- 移除 RabbitMQFactory/RabbitMQService 终结器(消除 GC 线程死锁风险)
- _timedOutTags 添加基于时间的自动淘汰(心跳中清理超过 5 分钟的泄漏条目)
- 修复 LarkPush.PushAsync 空路径 bug(异步飞书推送发送到错误端点)
- 修复 DingTalkPush/LarkPush 并发竞态条件(GetAuthentication 修改共享对象)
- 修复 WeChatPush/LarkPush 死代码和 null 检查不一致
7.0.8:
- IMQHub 新增显式名称参数重载(PublishAsync/SubscribeAsync/SubscribeDeadLetterAsync)
- 新增 MQNameAttribute,支持为消息类型指定自定义名称前缀
- IMQHub 中文 XML 注释
- RabbitMQ Prefix 前缀隔离(多系统共用 broker 时区分队列名)
- RabbitMQ 消费端看门狗重写(心跳检测+无限重试+指数退避+Channel 重建)
- 生产端 RabbitMQPush 新增 Channel 自动恢复机制
- 死信队列(DLX)纳入看门狗保护
- RSA 改为标准保密模式(加密用公钥,解密用私钥)
- 默认加密算法从 DES 改为 AES
- 修复缓存 AddOrGetCacheItem 默认值误判问题
- 修复服务工厂缓存 token 污染问题
- 修复消费端异常消息无限 requeue 问题
- 移除 FreeRedis 支持(聚焦 StackExchange.Redis)