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"]}");
//写入数据库和日志
}
}
}
