Lord.Service 7.0.7

There is a newer version of this package available.
See the version list below for details.
dotnet add package Lord.Service --version 7.0.7
                    
NuGet\Install-Package Lord.Service -Version 7.0.7
                    
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.0.7" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Lord.Service" Version="7.0.7" />
                    
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.0.7
                    
#r "nuget: Lord.Service, 7.0.7"
                    
#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.0.7
                    
#: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.0.7
                    
Install as a Cake Addin
#tool nuget:?package=Lord.Service&version=7.0.7
                    
Install as a Cake Tool

LordService 使用说明

企业级推送与消息队列集成库 — 支持钉钉/企业微信/飞书推送、RabbitMQ 发布订阅、Redis 分布式缓存与锁、多日志框架。

当前文档版本:v7.0.7

快速上手

安装

dotnet add package Lord.Service

5 分钟跑通 RabbitMQ

// 1. 注册(Program.cs)
services.AddLordService(lord => lord
    .UseMQ(mq => mq.UseRabbitMQ(rabbit =>
        rabbit.WithConnection("localhost", "/", "guest", "guest"))));

// 2. 定义消息(任意类型即可,队列名/交换机/路由键自动推导类型名)
public record OrderCreated(string OrderId, decimal Amount);

// 3. 发布(一行代码)
public class OrderService
{
    private readonly IMQHub _hub;
    public OrderService(IMQHub hub) => _hub = hub;

    public async Task CreateOrder(OrderCreated order)
    {
        await _hub.PublishAsync(order); // 自动推送到 OrderCreated_Queue
    }
}

// 4. 订阅(一行代码,支持 IServiceProvider 注入)
public class OrderWorker : BackgroundService
{
    private readonly IMQHub _hub;
    public OrderWorker(IMQHub hub) => _hub = hub;

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await _hub.SubscribeAsync<OrderCreated>(async (msg, sp) =>
        {
            var logger = sp.GetRequiredService<ILogger<OrderWorker>>();
            logger.LogInformation("收到订单: {OrderId}", msg.OrderId);
            return true; // 处理成功则 ack
        }, stoppingToken);
    }
}

一行代码发布/订阅(IMQHub 完整 API)

// 发布
await hub.PublishAsync(new MyMsg { ... });                    // 类型推导
await hub.PublishAsync(new MyMsg { ... }, cfg => { ... });    // 自定义配置
await hub.PublishAsync(rawJsonString);                        // 原始字符串

// 订阅(5 种重载,按需选用)
await hub.SubscribeAsync<MyMsg>(msg => Task.FromResult(true));            // 最简单
await hub.SubscribeAsync<MyMsg>(msg => true);                             // 同步 handler
await hub.SubscribeAsync<MyMsg>(async (msg, sp) => true);                 // 注入 IServiceProvider
await hub.SubscribeAsync<MyMsg>(handler, cfg => { ... });                 // 自定义队列配置
await hub.SubscribeDeadLetterAsync<MyMsg>(async (msg, sp) => true);       // 死信队列

IMQHub vs IMQFactory:IMQHub 是简化 API,自动从类型名推导队列名,适合快速开发。IMQFactory 是底层 API,支持按配置文件节点精确控制队列参数,适合复杂场景。两者可同时使用。

目录


项目简介

支持微信、钉钉、飞书等多种推送方式的消息推送服务。 支持企业微信应用推送、钉钉 APP 应用推送。 支持多种日志记录方式。 支持多种加密方式。 支持 RabbitMQ 消息队列,支持加密传输。 支持 Redis 缓存。 封装 RestSharp HTTP 请求组件,实现重试等功能。

开发目的

用于解决单位现有“低代码平台”导致的消息推送不稳定问题。由于钉钉或微信群消息有限制,引入 MQ 作为缓冲中间层,实现解耦与限流控制,将事件推送到钉钉或企业微信群。

环境依赖

  1. Visual Studio 2022
  2. .NET Core 3.1 / .NET 5(建议升级到 .NET 6+)
  3. RabbitMQ(如使用 MQ 功能)
  4. Redis(如使用分布式缓存)

使用框架说明

  • RabbitMQ:推送到消费端(解耦消息生产与消费)。
  • HttpPush:推送到钉钉群 / 企业微信群 / 飞书群等。
  • 可选死信队列(DLX)。

部署步骤

  1. 添加引用 / 拷贝项目。
  2. 配置 appsettings.*.json
  3. 编写 Worker / 控制台 / WebHost,注入并运行服务。

依赖注入示例

services.AddLordService(builder =>
{
    builder
        // Http 集成:RestSharp / System.Net.Http 双实现,可自定义超时
        // .UseHttp(h => h.UseRestSharp().WithTimeout(TimeSpan.FromSeconds(30))) // 使用 RestSharp(默认)
        .UseHttp(h => h.UseNetHttp().WithTimeout(TimeSpan.FromSeconds(30))) // 使用 HttpClient
        .UseLogging(s => s.UseNLog()) // NLog / Log4Net / Serilog 三选一
        .UseCache(s => s.UseCustom(provider =>
        {
            var config = provider.GetRequiredService<IConfiguration>();
            var prefix = config.GetValue<string>("Redis:Prefix");
            var redisString = config.GetValue<string>("Redis:Connection");
            var csRedis = new CSRedisClient($"{redisString},prefix={prefix}");
            RedisHelper.Initialization(csRedis);
            return new RedisCahce(); // 自定义缓存实现
        }))
        // .UseCache(s => s.UseRedis("localhost:6379")) // 内置 Redis 示例
        .UseEncryption(s => s.UseDES(m => m.FromConfiguration("Encryption")))
        .UseMQ(s => s
            // MQ 拦截器:队列参数/死信/加解密/序列化扩展
             .AddFilter<MQQueueArgsFilter>()
             .AddFilter<MQDeadLetterFilter>()
             .AddFilter<AesCryptoFilter>()
             .AddFilter<MQJsonFilter>()
            .UseRabbitMQ(m => m.FromConfiguration("RabbitMQ"))
            .UseDeadLetterExchange(m => m.FromConfiguration("DlxConfig")))
        // 推送用 Add,因为可能需要添加多个类型(钉钉 / 微信 / 飞书 / 应用等)
        .UsePush(s => s.AddDingTalk(config => config.FromConfiguration("CommonPushApi")));
});

微信公众号集成
services.AddLordService(builder =>
{
    builder.UseWeChatOfficial(wx =>
    {
        // 方式1:从配置加载
        wx.FromConfiguration("WeChatOfficial");
        
        // 方式2:直接配置
        // wx.WithSettings("wx123...", "secret...", "token...");
        
        // 可选:使用自定义消息处理器
        // wx.UseMessageHandler<MyCustomHandler>();
    });
});

业务中使用

public class NotificationService
{
    private readonly IDingTalkApiFactory _dingFactory;      // 钉钉机器人工厂
    private readonly IWeChatApiFactory _weChatFactory;      // 企业微信机器人工厂
    private readonly IWeChatOfficialService _weChatOfficial; // 微信公众号服务

    public NotificationService(
        IDingTalkApiFactory dingFactory,
        IWeChatApiFactory weChatFactory,
        IWeChatOfficialService weChatOfficial)
    {
        _dingFactory = dingFactory;
        _weChatFactory = weChatFactory;
        _weChatOfficial = weChatOfficial;
    }

    public async Task NotifyAsync()
    {
        // 1. 钉钉群机器人
        var dingPush = _dingFactory.GetPushService("DevGroup");
        // dingPush.Push(formatter => formatter.Format("服务器启动完成")); // 实际调用方式取决于IApiPush扩展

        // 2. 企业微信群机器人
        var wechatPush = _weChatFactory.GetPushService("AlertGroup");
        // wechatPush.Push(formatter => formatter.Format("# 告警通知\n> CPU 使用率过高"));

        // 3. 微信公众号 - 模板消息
        var templateData = new
        {
            first = new { value = "新订单通知", color = "#173177" },
            keyword1 = new { value = "OD20230101", color = "#173177" },
            remark = new { value = "请及时处理", color = "#173177" }
        };
        await _weChatOfficial.SendTemplateMessageAsync("USER_OPENID", "TEMPLATE_ID", templateData);

        // 4. [新增] 微信公众号 - 客服消息(48小时内回复)
        // msgType支持: text, image, voice, video, music, news, mpnews, wxcard, miniprogrampage
        await _weChatOfficial.SendCustomMessageAsync("USER_OPENID", "text", new { content = "您好,这是客服自动回复" });

        // 5. [新增] 微信公众号 - 一次性订阅消息
        // 需用户先在小程序或移动端订阅
        var subData = new 
        { 
            thing01 = new { value = "活动开始" },
            date01 = new { value = "2023-10-01 10:00" } 
        };
        await _weChatOfficial.SendSubscribeMessageAsync("USER_OPENID", "SUBSCRIBE_TEMPLATE_ID", subData);
    }
}

说明:

  • 拦截器通过 UseMQ(...).AddFilter<T>()UseMQ(...).AddExceptionFilter<T>() 注册,支持多实例,按需在实现里区分队列名/路由键等做差异化处理。
  • IMQPush / IMQReceive 构造时会自动订阅拦截器,无需在业务代码里手动挂事件,保持开箱即用又可扩展。

缓存使用示例(Redis / MemoryCache)

统一接口 ICache,Redis 与内存缓存可无缝切换。RedisCache 必须单例使用。

注册

// 方式一:通过 AddLordService 统一注册
services.AddLordService(builder =>
{
    builder.UseCache(s => s.UseRedis("localhost:6379")); // 内置 Redis
    // builder.UseCache(s => s.UseMemory());              // 进程内内存缓存
});

// 方式二:直接注册 Redis 缓存
services.AddRedisCache("localhost:6379", "myapp"); // 连接串 + key 前缀
services.AddRedisCache("Redis");                    // 从 appsettings.json 的 "Redis" 节点加载
services.AddRedisCache(options =>
{
    options.ConnectionString = "localhost:6379";
    options.Prefix           = "lordservice"; // key 前缀
    options.Database         = 1;
    options.ConnectTimeout   = 10;
    options.SyncTimeout      = 10;
    options.AllowAdmin       = true;          // GetKeys/Clear 全量扫描需要
    options.ClientName       = "LordService";
});

appsettings.json 示例:

{
  "Redis": {
    "ConnectionString": "localhost:6379",
    "Prefix": "lordservice",
    "Database": 0,
    "ConnectTimeout": 5,
    "SyncTimeout": 5,
    "AllowAdmin": true,
    "ClientName": "LordService"
  }
}

业务中使用

public class ProductService
{
    private readonly ICache _cache;
    public ProductService(ICache cache) => _cache = cache;

    public async Task<Product> GetProductAsync(int id)
    {
        // 读不到则回源,并写入缓存 10 分钟。
        // 组件内置防缓存击穿(cache stampede):同一 key 失效瞬间只会有一个线程回源,
        // 其余请求等待并复用结果,无需业务方额外加锁。
        return await _cache.AddOrGetCacheItemAsync(
            key: $"product:{id}",
            cachePopulate: () => LoadFromDbAsync(id),
            expiration: TimeSpan.FromMinutes(10));
    }

    public async Task RefreshAsync(int id)
    {
        var p = await LoadFromDbAsync(id);
        await _cache.SetItemAsync($"product:{id}", p, TimeSpan.FromMinutes(10));
    }

    public Task InvalidateAsync(int id) => _cache.RemoveKeyAsync($"product:{id}");

    // Hash 结构
    public void SaveField(string userId, string field, string value)
        => _cache.HashSet($"user:{userId}", field, value);

    // 原子计数
    public Task<long> IncrViewsAsync(int id) => _cache.IncrementAsync($"views:{id}");
}

性能提示:GetKeys / Clear / RemoveKey(filter, pattern) 会全量扫描键空间(SCAN), 高并发热路径上请避免调用;优先使用带具体 key 的 RemoveKey / RemoveBatch。 同步版本已改为直接使用同步 SCAN(不再 sync-over-async),异步版本推荐用 *Async 后缀方法。

MQ 使用示例(RabbitMQ)

通过 IMQFactory 获取推送 / 接收服务,所有 API 均为异步。

注册

services.AddLordService(builder =>
{
    builder.UseMQ(s => s.UseRabbitMQ(m => m.FromConfiguration("RabbitMQ")));
});

appsettings.json 示例:

{
  "RabbitMQ": {
    "HostName": "127.0.0.1",
    "VirtualHost": "/",
    "UserName": "guest",
    "Password": "guest"
  },
  "OrderQueue": {
    "MQPool": "RabbitMQ",
    "QueueName": "order.created",
    "ExchangeName": "order.exchange",
    "RouteKey": "order.created",
    "ServiceName": "OrderService"
  }
}

推送(生产)

public class OrderPublisher
{
    private readonly IMQFactory _mqFactory;
    public OrderPublisher(IMQFactory mqFactory) => _mqFactory = mqFactory;

    public async Task PublishAsync(OrderCreated msg)
    {
        // 按配置节名获取推送服务
        var push = await _mqFactory.GetPushServiceAsync("OrderQueue");
        await push.PublishAsync(msg);

        // 或按类型自动映射配置:
        // var push = await _mqFactory.GetPushServiceAsync<OrderCreated>();
        // await push.PublishAsync(msg);
    }
}

接收(消费)

public class OrderConsumer
{
    private readonly IMQFactory _mqFactory;
    public OrderConsumer(IMQFactory mqFactory) => _mqFactory = mqFactory;

    public async Task StartAsync()
    {
        var receive = await _mqFactory.GetReceiveServiceAsync("OrderQueue");
        await receive.ReceiveAsync<OrderCreated>(msg =>
        {
            // 处理消息
            Console.WriteLine($"收到订单: {msg.OrderId}");
        });
    }
}

死信队列 + 拦截器(可选)

services.AddLordService(builder =>
{
    builder.UseMQ(s => s
        .AddFilter<MQQueueArgsFilter>()   // 队列参数
        .AddFilter<MQDeadLetterFilter>()  // 死信
        .AddFilter<AesCryptoFilter>()     // 加解密
        .AddFilter<MQJsonFilter>()        // 序列化
        .UseRabbitMQ(m => m.FromConfiguration("RabbitMQ"))
        .UseDeadLetterExchange(m => m.FromConfiguration("DlxConfig")));
});

HTTP 使用示例(RestSharp 封装)

每个 factoryName(默认域名)共享一个 RestClient;HttpRequest/RestRequest 为一次性对象,可通过 Reset() 重新开始。

注册

services.AddLordService(builder =>
{
    builder.UseHttp(options =>
    {
        options.DefaultTimeout = TimeSpan.FromSeconds(30);
    });
});

基础用法(GET/POST)

var httpFactory = provider.GetRequiredService<IHttpFactory>()
    .CreateFactory("https://api.example.com"); // factoryName 默认使用域名,单例 RestClient

var resp = await httpFactory
    .CreateRequest("/v1/data")
    .AddQueryParameter("id", "123")
    .SetRetryCount(3)
    .SetRetryTime(TimeSpan.FromSeconds(1))
    .GetAsync();

var resp2 = await httpFactory
    .CreateRequest("/v1/data")
    .AddJsonBody(new { Name = "foo" })
    .PostAsync();

复用入口但清除旧参数

var req = httpFactory.CreateRequest("/v1/search")
    .AddQueryParameter("q", "hello")
    .SetRetryCount(2);

var r1 = await req.GetAsync();

// 想复用但不带旧参数,调用 Reset 后重新配置
req = req.Reset()
    .AddQueryParameter("q", "world")
    .SetRetryTime(TimeSpan.FromMilliseconds(500));
var r2 = await req.GetAsync();

使用证书 / 自定义 HttpMessageHandler

// 证书(pfx)
var factoryWithCert = httpFactory.CreateFactory(
    factoryName: "api.example.com-cert",
    url: new Uri("https://api.example.com"),
    path: "certs/client.pfx",
    pwd: "123456"
);

// 自定义 handler(如代理、日志、压缩等)
var handler = new HttpClientHandler { Proxy = new WebProxy("http://127.0.0.1:8888") };
var factoryWithHandler = httpFactory.CreateFactory(
    factoryName: "api.example.com-proxy",
    url: new Uri("https://api.example.com"),
    handler: handler
);

清理 RestClient(显式释放缓存)

// 释放单个 factory 对应的 RestClient
httpFactory.DisposeFactory("api.example.com");

// 释放所有缓存的 RestClient(应用停机时调用)
httpFactory.DisposeAllFactories();

推送服务(多渠道消息推送)

钉钉工作通知

通过企业内部应用(或第三方应用)向指定用户推送工作通知。

services.AddLordService(builder =>
{
    builder.UsePush(push =>
    {
        push.AddDingApp(config =>
        {
            // 配置应用凭证
            config.WithCredentials(
                appKey: "dingxx...",    // AppKey
                appSecret: "xxx...",    // AppSecret
                agentId: "123456"       // AgentId
            );
            
            // 或从配置文件加载:
            // config.FromConfiguration("DingApp");
        });
    });
});

// 业务中使用
public class MyService
{
    private readonly DingAppClient _dingAppClient;
    
    public MyService(DingAppClient dingAppClient)
    {
        _dingAppClient = dingAppClient;
    }

    public async Task SendAsync()
    {
        // 推送文本消息给指定用户(UserId列表)
        await _dingAppClient.SendTextAsync(
            new List<string> { "user001", "user002" }, 
            "你好,这是一条工作通知测试消息"
        );

        // 推送 Markdown 消息
        await _dingAppClient.SendMarkdownAsync(
            new List<string> { "user001" },
            "周报提醒",
            "## 本周工作汇报\n- 完成项目A\n- 修复Bug B"
        );
    }
}

多系统共用 RabbitMQ 时加前缀

当多个业务系统共用同一个 RabbitMQ 集群时,为避免队列/交换机/死信队列名称冲突,可在配置中设置 Prefix

{
  "RabbitMQ": {
    "HostName": "127.0.0.1",
    "VirtualHost": "/",
    "UserName": "guest",
    "Password": "guest",
    "Prefix": "SysA"
  }
}

加了 Prefix: SysA 后,OrderCreated 类型的队列名会自动变成 SysA_OrderCreated_Queue,死信队列变成 SysA.dlx.queue

加密配置(7.0.7+)

重要:从 v7.0.7 起,默认加密算法由 DES 改为 AES,默认密钥改为空字符串。不再提供硬编码内置密钥。

AES 配置

{
  "Encryption": {
    "EncryptType": "Aes",
    "PublicKeyOrKey": "你的16位密钥",
    "PrivateKeyOrIV": "你的16位向量"
  }
}

RSA 配置(7.0.7+ 已改为标准模式)

重要:v7.0.7 将 RSA 改为标准保密模式:加密用公钥,解密用私钥。如果从旧版本升级,需要把配置里的 PublicKeyOrKeyPrivateKeyOrIV 的值交换。

{
  "Encryption": {
    "EncryptType": "Rsa",
    "PublicKeyOrKey": "<RSA公钥XML>",
    "PrivateKeyOrIV": "<RSA私钥XML>"
  }
}

更新日志

v7.0.7

  • 新增 IMQHub 简化 API:类型推断队列名,一行代码发布/订阅
  • RabbitMQ 消费端看门狗重写:心跳检测 + 无限重试 + 指数退避 + Channel 重建
  • 生产端 RabbitMQPush 新增 Channel 自动恢复
  • 死信队列(DLX)纳入看门狗保护
  • 新增 RabbitMQ Prefix 多系统前缀隔离
  • 修复缓存 AddOrGetCacheItem 默认值误判
  • 修复服务工厂缓存 token 污染
  • 修复消费端异常消息无限 requeue
  • 修复 RabbitMQFactory 静态缓存跨实例污染
  • 修复 RSA 密钥使用模式为标准保密模式(加密用公钥,解密用私钥)
  • 修复 RedisCache Clear() 无前缀时误 flush 整库
  • 修复 RedisCache 连接字符串含密码泄漏到异常消息
  • 修复 WeChatMiniProgramService static SemaphoreSlim 跨实例污染
  • 修复 DingTalkPush 并发线程安全
  • 修复 HttpClientRequest 4xx 重复重试
  • 修复 TextJson 数字全部转字符串,改为仅 long/ulong 转字符串(雪花ID)
  • 移除 FreeRedis 支持(聚焦 StackExchange.Redis)

v7.0.6

  • 升级为 NET6/NET8/NET9/NET10 多目标支持
  • 加入 Serilog/NLog/Log4Net 官方扩展
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.7 48 8/6/2026
7.10.6 49 8/5/2026
7.10.5 52 8/4/2026
7.10.1 97 8/1/2026
7.10.0 94 7/30/2026
7.0.8 109 7/29/2026
7.0.7 97 7/29/2026
7.0.6 99 7/16/2026
7.0.5 115 4/24/2026
7.0.3 142 2/10/2026
7.0.2 133 2/6/2026
7.0.1 135 2/4/2026
7.0.0 146 1/26/2026
6.2.0 143 1/23/2026
6.0.9 139 1/12/2026
6.0.8 310 11/30/2025
6.0.7 227 9/28/2025
Loading failed

7.0.7:
- 新增 IMQHub 简化 API(类型推断队列名,一行代码发布/订阅)
- RabbitMQ 消费端看门狗重写(心跳检测+无限重试+指数退避+Channel 重建)
- 生产端 RabbitMQPush 新增 Channel 自动恢复机制
- 死信队列(DLX)纳入看门狗保护
- 修复缓存 AddOrGetCacheItem 默认值误判问题
- 修复服务工厂缓存 token 污染问题
- 修复消费端异常消息无限 requeue 问题
- 移除 FreeRedis 支持(聚焦 StackExchange.Redis)