Lord.Service 7.0.6 License Info

Lord.Service 7.0.6

LordService 使用说明(自用模板)

支持多渠道消息推送、MQ、缓存、加密、日志等的组件化服务。

目录


项目简介

支持微信、钉钉、飞书等多种推送方式的消息推送服务。 支持企业微信应用推送、钉钉 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"
        );
    }
}