ABP - 事件总线之分布式事件总线
ABP - 事件总线之分布式事件总线
- 1. 分布式事件总线的集成
- 1.2 基于 RabbitMQ 的分布式事件总线
- 2. 分布式事件总线的使用
- 2.1 发布
- 2.2 订阅
- 2.3 事务和异常处理
- 3. 自己扩展的分布式事件总线实现
事件总线可以实现代码逻辑的解耦,使代码模块之间功能职责更清晰。而分布式事件总线的功能不止这些,它允许我们通过消息队列进行中转,发布和订阅跨应用/服务边界传输的事件,经常被用作微服务或不同应用程序之间异步发送和接收消息的手段,属于分布式应用通讯的方式之一。
分布式事件总线依赖于 消息队列 中间件,ABP 框架中提供了4种开箱即用的提供程序,我们也可以基于抽象的接口自行实现分布式事件总线提供程序,已有的四种分别适用于 RabbitMQ、Kafka、Rebus 等消息队列,还有一种是默认实现:进程内的分布式事件总线,它允许我们在没有接入消息队列时,也能够编写与分布式体系结构兼容的代码,方便日后可能的微服务拆分,这时它的工作方式与本地事件总线一样,整体的设计思想就和微软的分布式缓存一样。
1. 分布式事件总线的集成
以下的演示还是基于控制台程序,分布式事件总线不会默认集成在 ABP 启动模板之中,需要我们自行集成,Web 应用的集成方式也是一样的。
通过以下命令创建一个控制台程序启动模板:
abp new AbpDistributeEventBusSample -t console
之后再打开解决方案,由于分布式事件总线是在不同应用程序之间进行通讯的,所以还需要再创建一个控制台项目进行演示,将解决方案中的项目复制一份即可。

本地分布式事件总线
首先讲一下进程内的事件总线的集成,这个还是有些必要的,如果有考虑后续进行微服务拆分的情况下,前期对于事件总线的使用可以基于这个进行开发。当然并不是说本地事件总线就不推荐使用,从我自己的日常工作经验中,很多时候还是分布式事件总线和本地事件总线搭配使用的。
首先,分布式事件总线的核心依赖包为 Volo.Abp.EventBus,和本地事件总线一样。我们在需要集成的项目的根目录下,通过以下命令进行集成:
abp add-package Volo.Abp.EventBus
由于是本地分布式事件总线,所以这种方式下是没办法跨进程通讯的,使用方式和上一篇讲的本地事件总线类似,不过使用的时候不再通过 ILocalEventBus接口,而是通过 IDistributedEventBus 接口,主要是用于业务逻辑的解耦,同时也为后续可能的分布式拆分做好准备。具体的使用方式下面细讲。
1.2 基于 RabbitMQ 的分布式事件总线
ABP 框架提供了三种开箱即用的分布式事件总线提供程序,分别对应 RabbitMQ、Kafka、Rebus,通过结合第三方消息队列实现真正基于消息跨进程通讯的分布式事件总线,这里主要讲一下基于 RabbitMQ 的方式,其他方式用法类似。
首先,基于 RabbitMQ 的分布式事件总线需要安装 Volo.Abp.EventBus.RabbitMQ 驱动程序包。可通过一下方式安装:
abp add-package Volo.Abp.EventBus.RabbitMQ
上面创建的两个工程都要安装,因为我们要演示两个进程间的通讯。

之后,需要部署 RabbitMQ 消息队列,这里我通过 docker 快速启动一个带有管理平台的 RabbitMQ,命令如下:
docker run -d -p 5672:5672 -p 15672:15672 --name myrabbitmq rabbitmq:management
RabbitMQ 默认用户密码为:guest / guest,这里只是用于测试就直接使用了。生产环境中大家最好将默认用户禁用,另行创建自己的用户。RabbitMQ 相关的更详细的使用和配置这里就不细讲了,详细内容可见 [[2.1 RabbitMQ基本概念]] 系列文章。

然后,添加分布式事件总线相应的配置,我们可以在 appsettings.json 文件中添加以下配置节点:
"RabbitMQ": {"Connections": {// 这里的配置支持 RabbitMQ 官方 sdk 中的 ConnectionFactory 的任意属性的配置"Default": {"HostName": "localhost", // rabbitmq 地址,集群环境下多个ip用逗号分隔"Port": "5672", // rabbbitmq 端口,默认5672"UserName": "guest","Password": "guest"}// 允许配置多个 rabbitmq 连接,但只能有一个用于事件总线//"Second": {// "HostName": "xxx.xxx.xxx.xxx", // rabbitmq 地址,集群环境下多个ip用逗号分隔// "Port": "5672" // rabbbitmq 端口,默认5672//}},"EventBus": {"ClientName": "MyClientName", // 用于事件总线的队列名"ExchangeName": "MyExchangeName", // 用于事件总线的交换机名称// "ConnectionName": "Default" // 配置多个连接时,指定用于事件总线的 RabbitMQ 连接,默认是 Default}}
以上配置,最后都会被转换为 AbpRabbitMqOptions 和 AbpRabbitMqEventBusOptions,所以我们也可以直接在代码中对这两个选项进行配置:
Configure<AbpRabbitMqOptions>(options =>
{options.Connections.Default.UserName = "guest";options.Connections.Default.Password = "guest";options.Connections.Default.HostName = "localhost";options.Connections.Default.Port = 5672;
});Configure<AbpRabbitMqEventBusOptions>(options =>
{options.ClientName = "TestApp1";options.ExchangeName = "TestMessages";
});
两种方式选择一种即可,如果两种方式同时使用,代码配置优先于配置文件。解决方案的两个项目都需要进行配置,进行通讯的两个项目需要连接到同一个队列。
完成上面的配置之后,启动应用,即可看到 RabbitMQ 中创建了我们配置的交换机和队列:

2. 分布式事件总线的使用
2.1 发布
事件发布需要一个事件对象,官方将之称为 Eto(事件传输对象),这是一个普通类,用于保存和事件相关的数据,一般以 Eto 作为后缀。就算一个事件不需要传输任何数据,也必须创建一个空类,这和上一章的本地事件总线是一样的,由于在分布式事件触发之后,事件对象会被序列化传输到消息队列中,所以事件对象应避免循环引用、多态、私有setter,并提供默认(空)构造函数,如果你有其他的构造函数(虽然某些序列化器可能会正常工作)。 下面是一个用于测试的 Eto 对象的定义。
[EventName("helloEvent")]
public class HelloEto
{public string Who { get; set; }public DateTime When { get; set; }public string ToWho { get; set; }
}
默认情况下,事件名将事件名称将是事件类的全名,我们可以通过 EventNameattribute 特性指定事件名称。
分布式事件的发布通过 IDistributedEventBus 接口,只需将其注入到相应的类中使用即可,使用方式和本地事件总线一样。
public class HelloWorldService : ITransientDependency
{private readonly IDistributedEventBus _distributedEventBus;public HelloWorldService(IDistributedEventBus distributedEventBus){_distributedEventBus = distributedEventBus;}public async Task SayHelloAsync(){await _distributedEventBus.PublishAsync(new HelloEto{Who = "Jerry",When = DateTime.Now,ToWho = "Jack"});}
}
以上代码写在 AbpDistributeSample 项目中,这里是事件发布的进程。
2.2 订阅
事件的订阅也和本地事件总线类似,这里通过实现了 IDistributedEventHandler<TEvent> 接口的处理器来处理事件,当前像上一章本地事件总线中讲到的,我们也可以通过 IDistributedEventBus 来自己订阅事件。
public class HelloDistribuedEventHandler : IDistributedEventHandler<HelloEto>, ITransientDependency
{public Task HandleEventAsync(HelloEto eventData){Console.WriteLine($"{eventData.Who} Say Hello To {eventData.ToWho} at { eventData.When.ToString("yyyy-MM-dd HH:mm:ss") }");return Task.CompletedTask;}
}
一个事件处理程序可以同时处理多种事件,只需要实现多个针对不同 ETO 的 IDistributedEventHandler 泛型接口即可。
之后通过 vs 设置多项目启动:

应用启动之后,可以看到控制台的输出如下,一个简单的基于消息队列的跨进程通讯已经完成:

同时在 RabbitMQ 上可以看到已经连接上来了2个消费者,每个进程既作为生产者,也作为消费者:

通过源码可以看到,分布式事件总线中会进行初始化,其实就是根据配置连接了 RabbitMQ 队列,并创建了一个消费者自动订阅消息队列上的事件,当接受到消息之后会从消息中获取消息的类型和具体的数据,触发消息执行处理程序,如果事件处理程序成功执行(没有抛出任何异常),它将向消息代理发送确认(ACK)。



这里的 EventTypes 其实就是维护消息类型和它的类名的对应关系的一个字典,在 SubscribeHandlers 中初始化消息类型和执行器对应关系的时候维护的,这个就和前一篇的本地事件总线大同小异了。
而消息的发布过程就更简单了,也就是将消息序列化,发送到 RabbitMQ 队列中。


ABP 分布式事件总线从 5.0 版本开始,还加入了 Inbox 、Outbox 机制,我们可以通过 AbpDistributedEventBusOptions 选项进行配置,例如:
Configure<AbpDistributedEventBusOptions>(option =>
{option.Inboxes.Configure(config => config.UseDbContext<TDbContext>());option.Outboxes.Configure(config => config.UseDbContext<TDbContext>());
});
从配置方式也可以看出,这里是借助了数据库的,实际上 Inbox 就是从消息队列接收到消息之后,将消息先存入数据库中,没有直接执行。


而在应用启动的时候,消息队列模块会注册一个 InboxProcessManager,实际上这就是一个后台工作者,这里面又用到 IInboxProcessor


在 InboxProcessor 中再通过定时器,每个一段时间从数据库中读取消息真正地去执行,并且将数据库中的记录删除。


OutBox 的机制也是一样的,这样做大概就是起到缓冲的作用,避免短时间内大量的消息对消息队列或者我们的消费者造成冲击导致应用崩溃,而是将这些消息的发送、执行均匀地处理。
2.3 事务和异常处理
分布式事件总线默认实现是 LocalDistributedEventBus,在没有集成 RabbitMQ 等第三方消息队列中间件,并且没有使用发件箱/收件箱模式的情况,事件发布和事件订阅是在同一个进程的。如果当前事件发布的模块使用了工作单元,事件总线是在和发布事件的同一工作单元范围内执行事件处理程序,如果事件处理程序抛出异常,那么相关的工作单元(数据库事务)将被回滚。这样,我们的应用程序逻辑和事件处理逻辑就具有事务性(原子)。如果想忽略事件处理程序中的错误,则必须在处理程序中使用try-catch块,并且不应该重新抛出异常。
当我们切换到真正的分布式事件总线提供程序时,事件处理程序将在不同的进程/应用程序中执行。在这种情况下,实现事务性事件发布的唯一方法是使用发件箱/收件箱模式。这篇文章上面的内容简单地提了一下发件箱/收件箱,之后还会有文章详细梳理它的工作模式。
如果在未真正使用分布式事件总线的情况下,想在工作单元中立即发布事件,而不是等到工作单元中的逻辑执行完成之后再发布,可以在使用IDistributedEventBus.PublishAsync方法时将onUnitOfWorkComplete设置为false。如果接入 RabbitMQ 等第三方消息队列实现了分布式事件总线,想要立即发布事件,则还得将 useOutbox 设置为 false。
3. 自己扩展的分布式事件总线实现
要注意的一点是,ABP 框架的分布式事件总线只能配置使用一个队列,这就意味着如果一个进程需要和多个进程进行通讯时,是无法通过不同的队列进行区分的。当多个进程都连接到同一个队列中时,我们无法控制一个进程发送的消息会被哪个进程所消费,无法单独通知某一个消费者,这是需要注意的。
有一种折中的方式,那就是如果一个事件只想发送给某一个进程,那就只在这个进程中实现其处理程序,其他没有实现处理程序的进程接收到消息之后,因为没有处理会抛出异常,消息重新返回消息队列中。这种方式依旧是存在一些问题,只是将工作中的一点经验供大家参考一下。
public interface IWantDistributedEventBus
{Task PublishAsync(Type eventType, object eventData, string queueName);
}public class RabbitMqWantDistributedEventBus : IWantDistributedEventBus, ISingletonDependency
{protected ILocalEventBus LocalEventBus { get; }protected IConnectionPool ConnectionPool { get; }protected WantRabbitMqEventBusOptions WantRabbitMqEventBusOptions { get; }protected AbpLocalEventBusOptions AbpLocalEventBusOptions { get; }protected IRabbitMqSerializer Serializer { get; }protected IRabbitMqMessageConsumer Consumer { get; private set; }protected IRabbitMqMessageConsumerFactory MessageConsumerFactory { get; }protected ConcurrentDictionary<string, Type> EventTypes { get; }public RabbitMqWantDistributedEventBus(IConnectionPool connectionPool,IOptions<WantRabbitMqEventBusOptions> options,IOptions<AbpLocalEventBusOptions> localEventBusOptions,IRabbitMqSerializer serializer,IRabbitMqMessageConsumerFactory rabbitMqMessageConsumerFactory,ILocalEventBus localEventBus){ConnectionPool = connectionPool;WantRabbitMqEventBusOptions = options.Value;Serializer = serializer;MessageConsumerFactory = rabbitMqMessageConsumerFactory;LocalEventBus = localEventBus;AbpLocalEventBusOptions = localEventBusOptions.Value;EventTypes = new ConcurrentDictionary<string, Type>();}/// <summary>/// 队列消费者初始化/// </summary>public void Initialize(){foreach (var queueName in WantRabbitMqEventBusOptions.ConsumeQueueNames){Consumer = MessageConsumerFactory.Create(new ExchangeDeclareConfiguration(WantRabbitMqEventBusOptions.ExchangeName,type: "direct",durable: true),new QueueDeclareConfiguration(queueName,durable: true,exclusive: false,autoDelete: false),WantRabbitMqEventBusOptions.ConnectionName);Consumer.BindAsync(queueName);Consumer.OnMessageReceived(ProcessEventAsync);}LoadEventTypes(AbpLocalEventBusOptions.Handlers);}/// <summary>/// 消息消费逻辑/// </summary>/// <param name="message">从队列接收到的消息</param>/// <returns></returns>private async Task ProcessEventAsync(IModel channel, BasicDeliverEventArgs ea){var msgData = (MessageData)Serializer.Deserialize(ea.Body.ToArray(), typeof(MessageData));var eventName = msgData.Type;var eventType = EventTypes.GetOrDefault(eventName);// eventType为空, 即不存在类型对应的Handler,消息不应该被消费if (eventType == null){// return;throw new NotFoundHandlerException($"不存在{eventName}消息Handler!");}// 通过消息类型转发LocalHandler进行具体逻辑处理var eventData = Serializer.Deserialize(Serializer.Serialize(msgData.Data), eventType);await LocalEventBus.PublishAsync(eventType, eventData);}/// <summary>/// 根据现有注册的Handler构建消息类型集合/// </summary>/// <param name="handlers">当前应用中Handler类型集合</param>/// <returns></returns>public Task LoadEventTypes(ITypeList<IEventHandler> handlers){foreach (var handler in handlers){var interfaces = handler.GetInterfaces();foreach (var @interface in interfaces){if (!typeof(IEventHandler).GetTypeInfo().IsAssignableFrom(@interface)){continue;}var genericArgs = @interface.GetGenericArguments();if (genericArgs.Length == 1){var eventTypeName = genericArgs[0].FullName;if (!EventTypes.ContainsKey(eventTypeName)){EventTypes[eventTypeName] = genericArgs[0];}}}}return Task.CompletedTask;}/// <summary>/// 发布消息/// </summary>/// <param name="eventType">消息类型</param>/// <param name="eventData">消息内容</param>/// <param name="queueName">队列名称</param>/// <returns></returns>public Task PublishAsync(Type eventType, object eventData, string queueName){var msg = new MessageData { Type = eventType.FullName, Data = eventData };var body = Serializer.Serialize(msg);using (var channel = ConnectionPool.Get(WantRabbitMqEventBusOptions.ConnectionName).CreateModel()){channel.ExchangeDeclare(WantRabbitMqEventBusOptions.ExchangeName,"direct",durable: true);var properties = channel.CreateBasicProperties();properties.DeliveryMode = RabbitMqConsts.DeliveryModes.Persistent;var queue = channel.QueueDeclare(queueName, durable: true, exclusive: false, autoDelete: false);channel.QueueBind(queueName, WantRabbitMqEventBusOptions.ExchangeName, queueName);channel.BasicPublish(exchange: WantRabbitMqEventBusOptions.ExchangeName,routingKey: queueName,mandatory: true,basicProperties: properties,body: body);}return Task.CompletedTask;}
}public class WantRabbitMqEventBusOptions
{public string ConnectionName { get; set; }public string ClientName { get; set; }public string ExchangeName { get; set; }public IList<string> ConsumeQueueNames { get; }public WantRabbitMqEventBusOptions(){ConsumeQueueNames = new List<string>();}
}public class MessageData
{public string Type { get; set; }public object Data { get; set; }
}[DependsOn(typeof(AbpRabbitMqModule))]
[DependsOn(typeof(AbpEventBusModule))]
public class WantAbpEventBusRabbitMqModule : AbpModule
{public override void ConfigureServices(ServiceConfigurationContext context){var configuration = context.Services.GetConfiguration();Configure<WantRabbitMqEventBusOptions(configuration.GetSection("RabbitMQ:EventBus"));}public override void OnApplicationInitialization(ApplicationInitializationContext context){context.ServiceProvider.GetRequiredService<RabbitMqWantDistributedEventBus>().Initialize();}
}
以上就是 ABP 框架下分布式事件总线的基本知识点,其中也提到了 发件箱/收件箱 等新特性,ABP 框架经过长时间的发展,基本与 .NET 版本同步更新,到现在 9.0 版本,各种组件的特性也在逐步迭代更新。分布式事件总线除了这篇文章中提到的基本内容之外,还有一些新特性,后续会有一篇文章专门讲一下这些特性。
参考文档:
ABP 官方文档 - 分布式事件总线
相关文章:
ABP - 事件总线之分布式事件总线
ABP - 事件总线之分布式事件总线 1. 分布式事件总线的集成1.2 基于 RabbitMQ 的分布式事件总线 2. 分布式事件总线的使用2.1 发布2.2 订阅2.3 事务和异常处理 3. 自己扩展的分布式事件总线实现 事件总线可以实现代码逻辑的解耦,使代码模块之间功能职责更清晰。而分布…...
【硬件设计细节】缓冲驱动器使用注意事项
一、缓冲驱动器核心功能与选型原则 信号增强与隔离 驱动能力匹配:根据负载电流需求选择缓冲器,例如CMOS缓冲器驱动能力通常为4-8mA,需搭配大电流负载时选用图腾柱输出或专用驱动芯片(如TI的SN74LVC系列)。电压域转换&…...
驱动开发系列38 - Linux Graphics 3D 绘制流程(一)- 创建画布
一:概述 当应用程序创建 OpenGL 上下文时,它通常需要申请帧缓冲(Framebuffer,即画布)。在 X11 体系下,应用程序不会直接向内核的 DRM 模块请求创建帧缓冲,而是通过 X 服务器进行申请。 虽然从技术上讲,应用程序可以直接使用 DRM 接口创建帧缓冲对象(BO),但为了将其与…...
再谈SpringCloud Gateway源码
再谈SpringCloud Gateway源码 一、整体请求流程二、前置对象准备1、实例化HandlerMapping2、实例化Route3、实例化WebHandler 三、实践业务扩展点1、定义扩展Route对象2、Filter能做什么3、定义扩展Filter对象4、定义父类Filter简化请求参数处理 前言: 之前有阅读过…...
把 CSV 文件摄入到 Elasticsearch 中 - CSVES
在我们之前的很多文章里,我有讲到这个话题。在今天的文章中,我们就提重谈。我们使用一种新的方法来实现。这是一个基于 golang 的开源项目。项目的源码在 https://github.com/githubesson/csves/。由于这个原始的代码并不支持 basic security 及带有安全…...
3.【线性代数】——矩阵乘法和逆矩阵
三 矩阵乘法和逆矩阵 1. 矩阵乘法1.1 常规方法1.2 列向量组合1.3 行向量组合1.4 单行和单列的乘积和1.5 块乘法 2. 逆矩阵2.1 逆矩阵的定义2.2 奇异矩阵2.3 Gauss-Jordan 求逆矩阵2.3.1 求逆矩阵 ⟺ \Longleftrightarrow ⟺解方程组2.3.2 Gauss-Jordan求逆矩阵 1. 矩阵乘法 1.…...
SpringCloud面试题----如何对 Spring Cloud 微服务进行性能优化
架构层面 合理划分微服务 单一职责原则:确保每个微服务只负责单一的业务功能,这样可以降低服务的复杂度,提高可维护性和可扩展性。例如,将用户认证、订单管理、商品管理等不同功能拆分成独立的微服务。避免服务间过度耦合:减少微服务之间的依赖关系,避免因为某个服务的变…...
使用llama.cpp在gpu和cpu上运行deepseek-r1 7b的性能对比
使用deepseek-r1 7b模型的q5km量化版本进行测试, gpu上的token解码速度是cpu的近8倍. 测试环境: ubuntu22.04 x86llama.cpp cpu intel 10750h 4.41 tokens / s model size params backend threads test t/s qwen2 7B Q5_K - Medium 5.07 GiB 7.62 B CPU 6 pp512 …...
BMS项目-面试及答疑整理
1. SOC计算用的什么原理实现的? bms目前计算SOC使用的安时积分+开路电压首先得对电池有一个抽象得概念,把电池比作游泳池,电量比作游泳池里面的水,电流比作流入和流出得水流,那么充电也就是往游泳池里面灌入水流安时积分:对水流进行一个实时监测,比如1S一次监测,那么每…...
【virtiofs】ubuntu24.04+qemu7.0调试virtiofs
文章目录 编译qemu编译buildroot编译linux-6.8.1编译virtiofsd启动脚本qemu调试方法环境: win11 + vmware17 ubuntu24.04 buildroot git clone git://git.busybox.net/buildroot linux-6.8.1 https://mirrors.edge.kernel.org/pub/linux/kernel/v6.x/linux-6.8.1.tar.gz virti…...
【第2章:神经网络基础与实现——2.1 前馈神经网络的结构与工作原理】
老铁们好!今天我们要来一场长达两万字的超详细技术探险,我会像拆解乐高积木一样把前馈神经网络(Feedforward Neural Network)的每个零件摆在台面上,用最接地气的方式让你彻底搞懂这个深度学习基石的工作原理。准备好了吗?我们开始吧! 第一章:神经网络的 “乐高积木” 1…...
ARINC 429详解
ARINC 429 是航空电子系统中广泛应用的一种串行数据总线标准,由航空无线电公司(ARINC)于1977年制定(ARINC 429规范)。它定义了航空电子设备之间数据传输的电气特性、协议格式和通信规则,是民航和军用飞机中…...
C进阶 数据的存储
目录 前言 一,VS的知识储备 二,有趣的scanf()读取 三,数据的存储 引言 四,整数存储 五,小数存储 总结 前言 这里将深入计算机,看计算机是如何进行数据的存储的,怎么在计算机里面筑巢 为…...
第二十二章 P - R 开头的术语
文章目录 第二十二章 P - R 开头的术语程序员模式 (programmer mode)项目 (project)属性 (property)属性排序 (property collation)属性方法 (property method)公有 (public) 以 Q 开头的术语查询 (query)查询接口 (query interface) 以 R 开头的术语范围指示符 (range indicat…...
【C语言】第一期——数据类型变量常量
目录 1 字面量 2 整数类型 2.1 整数类型的取值范围 2.1.1 sizeof 运算符 2.2 GB、MB、KB、B之间的关系 2.3 定义整数类型的变量并打印 2.4 整数类型代码演示 3 浮点类型 3.1 浮点类型的取值范围 3.2 定义浮点类型变量并打印 3.3 保留2位小数点 4 char字符型 4.1…...
【c++】【Linux】【进程】线程终止/崩溃 会导致进程终止/崩溃 吗?
【c】【Linux】【进程】线程终止/崩溃 会导致进程终止/崩溃 吗? 1.线程终止会导致进程终止吗? 在操作系统中,线程是进程的基本执行单元,一个进程可以包含一个或多个线程。 当一个子线程终止时,进程并不会因此自动终…...
宝藏软件系列 篇一:My APK(Android)
文章目录 系列文章官方网站特色功能同类软件 系列文章 官方网站 My APK 官方版本是在 谷歌商店 中上架的。 官方下载地址:Google Play 商店页面。(需要外网) 2025.2最新版本的CSDN本地下载地址(因为是Android App Bundle&…...
springcloud集成gateway
本篇文章只介绍gateway模块的搭建步骤,并无gateway详细介绍 gateway详解请查看:SpringCloudGateway官方文档详解 前置处理 父模块中已指定版本 不知道如何选择版本看这篇: 手把手教你梳理springcloud与springboot与springcloudalibaba的版本…...
pandas(13 Caveats Gotchas和SQL比较)
前面内容:pandas(12 IO工具和稀松数据) 目录 一、Caveats警告 & Gotchas预见 1.1 在Pandas中使用if/Truth语句 1.2 位运算布尔 1.3 isin操作 1.4 重新索引reindex和 loc&iloc 使用注意事项 1.5 loc和iloc 二、Python Pandas 与SQL的比较 2.1 数…...
Android的Activity生命周期知识点总结,详情
一. Activity生命周期 1.1 返回栈知识点 二. Activity状态 2.1 启动状态 2.2 运行状态 2.3 暂停状态 2.4 停止状态 2.5 销毁状态 三. Activity生存期 3.1 回调方法 3.2 生存期 四. 体验Activity的生命周期 五. Activity被回收办法 引言: 掌握Acti…...
基于Python的Flask微博话题舆情分析可视化系统
2024数据 ✅️标价源码 远程部署加 20 ✅️爬虫可用 有六月数据 ✅️修复bug不会突然打不开网页 系统稳定 系统的功能如下: 1.数据的爬取 2.用户的登录注册 3.热词统计,舆情统计 4.文章统计分析 5.发布地址统计 6.评论统计 7.情感分类统计 编程语言:py…...
【Python爬虫(1)】专栏开篇:夯实Python基础
【Python爬虫】专栏简介:本专栏是 Python 爬虫领域的集大成之作,共 100 章节。从 Python 基础语法、爬虫入门知识讲起,深入探讨反爬虫、多线程、分布式等进阶技术。以大量实例为支撑,覆盖网页、图片、音频等各类数据爬取ÿ…...
【油漆面积——线段树,扫描线,不用pushdown的特例,pushup兼有cal的性质】
题目 分析 不用pushdown是因为: 对于modify,操作是互逆过程,因此不会存在向下结算的pushdown过程 对于query,操作始终针对最上层的tr[1],也不需要pushdown 对于pushdown,一则是怕不结算就标记,会…...
fps武器系统6:随机弹道
文章目录 设计随即弹道 思路连射时在一个范围内随机改变枪口旋转即可 实现改变枪口旋转改变旋转就是改变物体朝向即可随机:锥体随机 疑问旋转体与物体朝向(向量)间的关系为什么随即弹道调用两次 设计 随即弹道 思路 连射时在一个范围内随机改变枪口旋转即可 实现…...
深度学习(1)-简单神经网络示例
我们来看一个神经网络的具体实例:使用Python的Keras库来学习手写数字分类。在这个例子中,我们要解决的问题是,将手写数字的灰度图像(28像素28像素)划分到10个类别中(从0到9)。我们将使用MNIST…...
硬件学习笔记--42 电磁兼容试验-6 传导差模电流干扰试验介绍
目录 电磁兼容试验-传导差模电流试验 1.试验目的 2.试验方法 3.判定依据及意义 电磁兼容试验-传导差模电流干扰试验 驻留时间是在规定频率下影响量施加的持续时间。被试设备(EUT)在经受扫频频带的电磁影响量或电磁干扰的情况下,在每个步进…...
Shader示例 6: 卡渲基础 - 描边 + 着色
0 、获取原神模型: 【游戏开发实战】下载原神模型,PMX转FBX,导入到Unity中,卡通渲染,绑定人形动画(附Demo工程)-CSDN博客 《原神》公测视频征集计划 一、描边pass:Outline 1. …...
Cherno C++ P55 宏
这篇文章我们讲一下C当中的宏。其实接触过大型项目的朋友可能都被诡异的宏折磨过。 宏是在预处理当中,通过文本替换的方式来实现一些操作,这样可以不用反复的输入代码,帮助我们实现自动化。至于预处理的过程,其实就是文本编辑&am…...
(20)从strlen到strtok:解码C语言字符函数的“生存指南1”
❤个人主页:折枝寄北的博客 ❤专栏位置:简单入手C语言专栏 目录 前言1. 求字符串长度函数1.1 strlen 2. 长度不受限制的字符串函数2.1 strcpy2.2 strcat2.3 strcmp 3. 长度受限制的字符串函数3.1 strncpy3.2 strncat3.3 strncmp 4. 字符串查找函数4.1 st…...
基于deepseek api和openweather 天气API实现Function Calling技术讲解
以下是一个结合DeepSeek API和OpenWeather API的完整Function Calling示例,包含意图识别、API调用和结果整合: import requests import json import os# 配置API密钥(从环境变量获取) DEEPSEEK_API_KEY os.getenv("DEEPSEE…...
