Lord.Service 7.10.1

There is a newer version of this package available.
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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Lord.Service" Version="7.10.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Lord.Service" Version="7.10.1" />
                    
Directory.Packages.props
<PackageReference Include="Lord.Service" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Lord.Service --version 7.10.1
                    
#r "nuget: Lord.Service, 7.10.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Lord.Service@7.10.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Lord.Service&version=7.10.1
                    
Install as a Cake Addin
#tool nuget:?package=Lord.Service&version=7.10.1
                    
Install as a Cake Tool

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 日志已自动脱敏 ContentRecognitionFromUserName 等敏感字段
  • 如需自定义脱敏,使用 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 或懒加载

迁移指南

  1. DES → AES:如果旧数据是用 DES 加密的,解密时仍可使用 UseDES。新数据必须使用 UseAES

  2. 推送配置FromConfiguration() 的配置在首次调用 GetPushService(sectionName) 时懒加载,行为与之前一致。

  3. HTTP POST 重试:如果业务依赖 POST 自动重试,调用 .AllowRetryNonIdempotent() 显式开启。

  4. 微信 POST:将 Controller 中的 HandleMessageAsync(xml) 替换为 HandleMessageAsync(xml, signature, timestamp, nonce)

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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
Loading failed

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)