.netcore集成CAP事件总线,消息队列

cs 复制代码
using DotNetCore.CAP;
using DotNetCore.CAP.Messages;
using DotNetCore.CAP.RedisStreams;
using Hyzx.Cxy.Api.Filter;
using Hyzx.Cxy.Common;
using Hyzx.Cxy.Common.ConfigOptions;
using Hyzx.Cxy.Common.Helper;
using Hyzx.Cxy.Services;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.DependencyInjection;
using System.Text.Encodings.Web;
using System.Text.Unicode;

namespace Hyzx.Cxy.Api.Extensions
{
    /// <summary>
    /// CAP事件总线  (https://cap.dotnetcore.xyz/user-guide/zh/cap/configuration/)
    /// </summary>
    public static class CapSetup
    {
        public static void AddCapSetup(this IServiceCollection services)
        {
            var RedisOptions = App.GetOptions<RedisOptions>();
            var DataConnectionOptions = JsonFileHelper.ReadList<DataConnectionOptions>("ConnectionItem", "appsettings.json");

            var dbConnection = DataConnectionOptions.Where(v => v.ConnId == SqlSugarConfigOptions.Master).FirstOrDefault()?.ConnectionString;

            //CAP 默认只扫描入口程序集(通常是启动项目)。如果你的订阅类写在另一个类库中,就需要手动指定
            services.AddSingleton<CapService>();
            services.AddCap(options =>
            {
                //使用 Redis 传输消息
                options.UseRedis(r =>
                {
                    r.Configuration = new StackExchange.Redis.ConfigurationOptions
                    {
                        EndPoints = { RedisOptions.Host, RedisOptions.Port.ToString() }
                    };
                    r.StreamEntriesCount = 10; //读取时从 stream 返回的条目数	
                    r.ConnectionPoolSize = 10; //连接池数
                });
                //使用Dashboard,这是一个Cap的可视化管理界面;默认地址:http://localhost:端口/cap
                options.UseDashboard(dashoptions =>
                {
                    dashoptions.PathMatch = "/cap";                      // 访问路径
                });

                options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
                //使用 SqlServer 存储执行情况
                options.UseSqlServer(dbConnection);
                options.DefaultGroupName = "capgroup";   //默认组名称
                options.GroupNamePrefix = null; //全局组名称前缀
                options.TopicNamePrefix = null; //Topic 统一前缀
                options.Version = "v1";
                options.FailedRetryInterval = 60;   //失败时重试间隔时间(秒),默认60秒在默认情况下,重试将在发送和消费消息失败的 FallbackWindowLookbackSeconds(4分钟后) 开始,这是为了避免设置消息状态延迟导致可能出现的问题。发送和消费消息的过程中失败会立即重试 3 次,在 3 次以后将进入重试轮询,此时 FailedRetryInterval 配置才会生效。

                options.ConsumerThreadCount = 1;    //消费者线程并行处理消息的线程数,当这个值大于1时,将不能保证消息执行的顺序
                                                    //   options.UseDispatchingPerGroup = true;//CAP会将所有消费者组的消息都先放置到内存同一个Channel中,然后线性处理。 如果设置为 true,则每个消费者组都会根据 ConsumerThreadCount 设置的值创建单独的线程进行处理。
                options.FailedRetryCount = 10;  //失败时重试的最大次数
                options.FailedThresholdCallback = (failedinfo) =>  //如果重试完还是失败会进入这里,这里就处理进死信队列里面,后期可以手动处理
                {
                    // 从服务容器中获取 CAP 发布器实例
                    var _capPublisher = failedinfo.ServiceProvider.GetService<ICapPublisher>();
                    var header = new Dictionary<string, string>()
                    {
                        ["header.error.msgid"] = failedinfo.Message.Headers["cap-msg-id"],
                        ["header.error.msgname"] = failedinfo.Message.Headers["cap-msg-name"]
                    };
                    // 根据失败类型(发布还是订阅)分别投递到不同的死信主题
                    if (failedinfo.MessageType == MessageType.Publish)//发布失败
                    {
                        _capPublisher.Publish("publish-dead-letter-queue", failedinfo.Message.Value, header);
                    }
                    if (failedinfo.MessageType == MessageType.Subscribe)//订阅失败
                    {
                        _capPublisher.Publish("subscribe-dead-letter-queue", failedinfo.Message.Value, header);
                    }
                };
                options.SucceedMessageExpiredAfter = 24 * 3600; //成功消息的过期时间(秒)
                options.FailedMessageExpiredAfter = 15 * 24 * 3600; //失败消息的过期时间(秒)
            }).AddSubscribeFilter<CapSubscribeFilter>();
        }
    }
}

builder.Services.AddCapSetup();

cs 复制代码
using DotNetCore.CAP;
using Hyzx.Cxy.Common;
using Hyzx.Cxy.Common.ConfigOptions;
using Hyzx.Cxy.Common.Core;
using Hyzx.Cxy.IServices;
using Hyzx.Cxy.IServices.FeieYun;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel;

namespace Hyzx.Cxy.Api.Controllers
{
    [Route("/api/[controller]/[action]")]
    [ApiController]
    [ApiExplorerSettings(GroupName = nameof(SwaggerVersionOption.v1))]
    public class CapController : ControllerBase
    {
        private readonly ILogger<AuthorizationController> _logger;
        private readonly ICapPublisher _capPublisher;
        public CapController(ILogger<AuthorizationController> logger,ICapPublisher capPublisher)
        {
            _logger = logger;
            _capPublisher = capPublisher;
        }


        [HttpPost]
        [AllowAnonymous]
        [Description("Cap发布事件")]
        public async Task<APIResult> TestPublish1()
        {
            _capPublisher.Publish("addcity", "广安市");
            //await App.GetService<ISignalRMsgService>().SendConnIdMsg("ReceiveMessage", App.HttpUser.UID.ToString(),"你好");

            return APIResult.Success();
        }

        [HttpPost]
        [AllowAnonymous]
        [Description("发布延时消息")]
        public async Task<APIResult> TestPublish2()
        {
            _capPublisher.PublishDelay(TimeSpan.FromSeconds(20), "testdelaysubscribe", "发布延时消息");

            return APIResult.Success();
        }

        [HttpPost]
        [AllowAnonymous]
        [Description("发布消息(抛出异常进入死信队列)")]
        public async Task<APIResult> TestPublish3()
        {
            _capPublisher.Publish("testsubscribe", "hello");

            return APIResult.Success();
        }
    }
}
cs 复制代码
using DotNetCore.CAP;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Hyzx.Cxy.Services
{
    public class CapService : ICapSubscribe
    {

        [CapSubscribe("addcity")]
        public async Task AddCity(string city)
        {
            Console.WriteLine($"接受内容:{city}");
        }

        [CapSubscribe("testdelaysubscribe")]
        public async Task testdelaysubscribe(string city)
        {
            Console.WriteLine($"接受延时内容:{city}");
        }

        [CapSubscribe("testsubscribe")]
        public void TestSubscribe(string body, [FromCap] CapHeader header)
        {
            Console.WriteLine($"已经订阅了消息:{body}");

            throw new Exception("手动抛出异常");
        }

        /// <summary>
        /// 发布死信队列监控
        /// </summary>
        /// <param name="body"></param>
        [CapSubscribe("publish-dead-letter-queue")]
        public async Task PublishDeadQueue(string body, [FromCap] CapHeader header)
        {
            Console.WriteLine("发布异常");
            Console.WriteLine($"进入了发布死信队列,消息内容:{body}");
            Console.WriteLine($"异常的消息id:{header["header.error.msgid"]}");
            Console.WriteLine($"异常的消息方法名:{header["header.error.msgname"]}");
            Console.WriteLine($"当前消费时间:{header["cap-senttime"]}");
            //写入数据库和日志


        }
        /// <summary>
        /// 订阅死信队列监控
        /// </summary>
        /// <param name="body"></param>
        [CapSubscribe("subscribe-dead-letter-queue")]
        public async Task SubscribeDeadQueue(string body, [FromCap] CapHeader header)
        {
            Console.WriteLine("订阅异常");
            Console.WriteLine($"进入了订阅死信队列,消息内容:{body}");
            Console.WriteLine($"异常的消息id:{header["header.error.msgid"]}");
            Console.WriteLine($"异常的消息方法名:{header["header.error.msgname"]}");
            Console.WriteLine($"当前消费时间:{header["cap-senttime"]}");
            //写入数据库和日志


        }
    }
}
相关推荐
时光追逐者8 小时前
一个基于 .NET Core + Vue3 构建的开源全栈平台 Admin 系统
开源·c#·.net·.netcore·admin系统
武藤一雄1 天前
C#:nameof 运算符全指南
开发语言·microsoft·c#·.net·.netcore
武藤一雄1 天前
C# 核心技术解析:Parse vs TryParse 实战指南
开发语言·windows·microsoft·微软·c#·.netcore
武藤一雄1 天前
深入理解 C# 中的 sizeof 与非托管类型约束
开发语言·windows·c#·.net·.netcore
武藤一雄1 天前
C# 中精准锁定类型信息指南:typeof vs GetType()
开发语言·windows·c#·.net·.netcore
csdn_aspnet3 天前
ASP.NET Core:使用 JavaScript 加密并在控制器中解密
javascript·asp.net·.netcore
武藤一雄4 天前
从零构建C# OOP 知识体系
windows·microsoft·c#·.net·.netcore·oop
武藤一雄9 天前
C#常见面试题100问 (第一弹)
windows·microsoft·面试·c#·.net·.netcore
猹叉叉(学习版)11 天前
【ASP.NET CORE】 14. RabbitMQ、洋葱架构
笔记·后端·架构·c#·rabbitmq·asp.net·.netcore