仿RabbitMQ实现消息队列客户端
文章目录
- 客⼾端模块实现
- 订阅者模块
- 信道管理模块
- 异步⼯作线程实现
- 连接管理模块
- 生产者客户端
- 消费者客户端
客⼾端模块实现
在RabbitMQ中,提供服务的是信道,因此在客⼾端的实现中,弱化了Client客⼾端的概念,也就是说在RabbitMQ中并不会向⽤⼾展⽰⽹络通信的概念出来,⽽是以⼀种提供服务的形式来体现。其实现思想类似于普通的功能接⼝封装,⼀个接⼝实现⼀个功能,接⼝内部完成向客⼾端请求的过程,但是对外并不需要体现出客⼾端与服务端通信的概念,⽤⼾需要什么服务就调⽤什么接⼝就⾏。
基于以上的思想,客⼾端的实现共分为四⼤模块:
基于以上模块,实现⼀个客⼾端的流程也就⽐较简单了
- 实例化异步线程对象
- 实例化连接对象
- 通过连接对象,创建信道
- 根据信道获取⾃⼰所需服务
- 关闭信道
- 关闭连接
订阅者模块
与服务端,并⽆太⼤差别,客⼾端这边虽然订阅者的存在感微弱了很多,但是还是有的,当进⾏队列消息订阅的时候,会伴随着⼀个订阅者对象的创建,⽽这个订阅者对象有以下⼏个作⽤:
- 描述当前信道订阅了哪个队列的消息。
- 描述了收到消息后该如何对这条消息进⾏处理
- 描述收到消息后是否需要进⾏确认回复
- 订阅者信息:
- 订阅者标识
- 订阅队列名
- 是否⾃动确认标志
- 回调处理函数(收到消息后该如何处理的回调函数对象)
using ConsumerCallback = std::function<void(const std::string, const BasicProperties *bp, const std::string)>;struct Consumer {using ptr = std::shared_ptr<Consumer>;std::string tag; //消费者标识std::string qname; //消费者订阅的队列名称bool auto_ack; //自动确认标志ConsumerCallback callback;Consumer(){DLOG("new Consumer: %p", this);}Consumer(const std::string &ctag, const std::string &queue_name, bool ack_flag, const ConsumerCallback &cb):tag(ctag), qname(queue_name), auto_ack(ack_flag), callback(std::move(cb)) {DLOG("new Consumer: %p", this);}~Consumer() {DLOG("del Consumer: %p", this);}};
信道管理模块
同样的,客⼾端也有信道,其功能与服务端⼏乎⼀致,或者说不管是客⼾端的channel还是服务端的channel都是为了⽤⼾提供具体服务⽽存在的,只不过服务端是为客⼾端的对应请求提供服务,⽽客⼾端的接⼝服务是为了⽤⼾具体需要服务,也可以理解是⽤⼾通过客⼾端channel的接⼝调⽤来向服务端发送对应请求,获取请求的服务。
- 信道信息:
- a. 信道ID
- b. 信道关联的⽹络通信连接对象
- c. protobuf协议处理对象
- d. 信道关联的消费者
- e. 请求对应的响应信息队列(这⾥队列使⽤<请求ID,响应>hash表,以便于查找指定的响应)
- f. 互斥锁&条件变量(⼤部分的请求都是阻塞操作,发送请求后需要等到响应才能继续,但是muduo库的通信是异步的,因此需要我们⾃⼰在收到响应后,通过判断是否是等待的指定响应来进⾏同步)
- 信道操作:
- a. 提供创建信道操作
- b. 提供删除信道操作
- c. 提供声明交换机操作(强断⾔-有则OK,没有则创建)
- d. 提供删除交换机
- e. 提供创建队列操作(强断⾔-有则OK,没有则创建)
- f. 提供删除队列操作
- g. 提供交换机-队列绑定操作
- h. 提供交换机-队列解除绑定操作
- i . 提供添加订阅操作
- j. 提供取消订阅操作
- k. 提供发布消息操作
- l. 提供确认消息操作
typedef std::shared_ptr<google::protobuf::Message> MessagePtr;using ProtobufCodecPtr = std::shared_ptr<ProtobufCodec>;using basicConsumeResponsePtr = std::shared_ptr<basicConsumeResponse>;using basicCommonResponsePtr = std::shared_ptr<basicCommonResponse>;class Channel {public:using ptr = std::shared_ptr<Channel>;Channel(const muduo::net::TcpConnectionPtr& conn, const ProtobufCodecPtr& codec):_cid(UUIDHelper::uuid()), _conn(conn), _codec(codec) {}~Channel() { basicCancel(); }std::string cid() { return _cid; }bool openChannel() {std::string rid = UUIDHelper::uuid();openChannelRequest req; req.set_rid(rid);req.set_cid(_cid);_codec->send(_conn, req);basicCommonResponsePtr resp = waitResponse(rid);return resp->ok();}void closeChannel() {std::string rid = UUIDHelper::uuid();closeChannelRequest req; req.set_rid(rid);req.set_cid(_cid);_codec->send(_conn, req);waitResponse(rid);return ;}bool declareExchange(const std::string &name,ExchangeType type, bool durable, bool auto_delete,google::protobuf::Map<std::string, std::string> &args) {//构造一个声明虚拟机的请求对象,std::string rid = UUIDHelper::uuid();declareExchangeRequest req;req.set_rid(rid);req.set_cid(_cid);req.set_exchange_name(name);req.set_exchange_type(type);req.set_durable(durable);req.set_auto_delete(auto_delete);req.mutable_args()->swap(args);//然后向服务器发送请求_codec->send(_conn, req);//等待服务器的响应basicCommonResponsePtr resp = waitResponse(rid);//返回return resp->ok();}void deleteExchange(const std::string &name) {std::string rid = UUIDHelper::uuid();deleteExchangeRequest req;req.set_rid(rid);req.set_cid(_cid);req.set_exchange_name(name);_codec->send(_conn, req);waitResponse(rid);return ;}bool declareQueue(const std::string &qname, bool qdurable, bool qexclusive,bool qauto_delete,google::protobuf::Map<std::string, std::string> &qargs) {std::string rid = UUIDHelper::uuid();declareQueueRequest req;req.set_rid(rid);req.set_cid(_cid);req.set_queue_name(qname);req.set_durable(qdurable);req.set_auto_delete(qauto_delete);req.set_exclusive(qexclusive);req.mutable_args()->swap(qargs);_codec->send(_conn, req);basicCommonResponsePtr resp = waitResponse(rid);return resp->ok();}void deleteQueue(const std::string &qname) {std::string rid = UUIDHelper::uuid();deleteQueueRequest req;req.set_rid(rid);req.set_cid(_cid);req.set_queue_name(qname);_codec->send(_conn, req);waitResponse(rid);return ;}bool queueBind(const std::string &ename, const std::string &qname, const std::string &key) {std::string rid = UUIDHelper::uuid();queueBindRequest req;req.set_rid(rid);req.set_cid(_cid);req.set_exchange_name(ename);req.set_queue_name(qname);req.set_binding_key(key);_codec->send(_conn, req);basicCommonResponsePtr resp = waitResponse(rid);return resp->ok();}void queueUnBind(const std::string &ename, const std::string &qname) {std::string rid = UUIDHelper::uuid();queueUnBindRequest req;req.set_rid(rid);req.set_cid(_cid);req.set_exchange_name(ename);req.set_queue_name(qname);_codec->send(_conn, req);waitResponse(rid);return ;}void basicPublish(const std::string &ename,const BasicProperties *bp,const std::string &body) {std::string rid = UUIDHelper::uuid();basicPublishRequest req;req.set_rid(rid);req.set_cid(_cid);req.set_body(body);req.set_exchange_name(ename);if (bp != nullptr) {req.mutable_properties()->set_id(bp->id());req.mutable_properties()->set_delivery_mode(bp->delivery_mode());req.mutable_properties()->set_routing_key(bp->routing_key());}_codec->send(_conn, req);waitResponse(rid);return ;}void basicAck(const std::string &msgid) {if (_consumer.get() == nullptr) {DLOG("消息确认时,找不到消费者信息!");return ;}std::string rid = UUIDHelper::uuid();basicAckRequest req;req.set_rid(rid);req.set_cid(_cid);req.set_queue_name(_consumer->qname);req.set_message_id(msgid);_codec->send(_conn, req);waitResponse(rid);return;}void basicCancel() {if (_consumer.get() == nullptr) {return ;}std::string rid = UUIDHelper::uuid();basicCancelRequest req;req.set_rid(rid);req.set_cid(_cid);req.set_queue_name(_consumer->qname);req.set_consumer_tag(_consumer->tag);_codec->send(_conn, req);waitResponse(rid);_consumer.reset();return;}bool basicConsume(const std::string &consumer_tag,const std::string &queue_name,bool auto_ack,const ConsumerCallback &cb) {if (_consumer.get() != nullptr) {DLOG("当前信道已订阅其他队列消息!");return false;}std::string rid = UUIDHelper::uuid();basicConsumeRequest req;req.set_rid(rid);req.set_cid(_cid);req.set_queue_name(queue_name);req.set_consumer_tag(consumer_tag);req.set_auto_ack(auto_ack);_codec->send(_conn, req);basicCommonResponsePtr resp = waitResponse(rid);if (resp->ok() == false) {DLOG("添加订阅失败!");return false;}_consumer = std::make_shared<Consumer>(consumer_tag, queue_name, auto_ack, cb);return true;}public: //连接收到基础响应后,向hash_map中添加响应void putBasicResponse(const basicCommonResponsePtr& resp) {std::unique_lock<std::mutex> lock(_mutex);_basic_resp.insert(std::make_pair(resp->rid(), resp));_cv.notify_all();}//连接收到消息推送后,需要通过信道找到对应的消费者对象,通过回调函数进行消息处理void consume(const basicConsumeResponsePtr& resp) {if (_consumer.get() == nullptr) {DLOG("消息处理时,未找到订阅者信息!");return;}if (_consumer->tag != resp->consumer_tag()) {DLOG("收到的推送消息中的消费者标识,与当前信道消费者标识不一致!");return ;}_consumer->callback(resp->consumer_tag(), resp->mutable_properties(), resp->body());}private:basicCommonResponsePtr waitResponse(const std::string &rid) {std::unique_lock<std::mutex> lock(_mutex);_cv.wait(lock, [&rid, this](){return _basic_resp.find(rid) != _basic_resp.end();});//while(condition()) _cv.wait();basicCommonResponsePtr basic_resp = _basic_resp[rid];_basic_resp.erase(rid);return basic_resp;}private:std::string _cid;muduo::net::TcpConnectionPtr _conn;ProtobufCodecPtr _codec;Consumer::ptr _consumer;std::mutex _mutex;std::condition_variable _cv;std::unordered_map<std::string, basicCommonResponsePtr> _basic_resp;};
- 信道管理:
- a. 创建信道
- b. 查询信道
- c. 删除信道
class ChannelManager {public:using ptr = std::shared_ptr<ChannelManager>;ChannelManager(){}Channel::ptr create(const muduo::net::TcpConnectionPtr &conn, const ProtobufCodecPtr &codec) {std::unique_lock<std::mutex> lock(_mutex);auto channel = std::make_shared<Channel>(conn, codec);_channels.insert(std::make_pair(channel->cid(), channel));return channel;}void remove(const std::string &cid) {std::unique_lock<std::mutex> lock(_mutex);_channels.erase(cid);}Channel::ptr get(const std::string &cid) {std::unique_lock<std::mutex> lock(_mutex);auto it = _channels.find(cid);if (it == _channels.end()) {return Channel::ptr();}return it->second;}private:std::mutex _mutex;std::unordered_map<std::string, Channel::ptr> _channels;};
异步⼯作线程实现
客⼾端这边存在两个异步⼯作线程,
- ⼀个是muduo库中客⼾端连接的异步循环线程EventLoopThread
- ⼀个是当收到消息后进⾏异步处理的⼯作线程池。
这两项都不是以连接为单元进⾏创建的,⽽是创建后,可以⽤以多个连接中,因此单独进⾏封装。
class AsyncWorker {public:using ptr = std::shared_ptr<AsyncWorker>;muduo::net::EventLoopThread loopthread;threadpool pool;};
连接管理模块
在客⼾端这边,RabbitMQ弱化了客⼾端的概念,因为⽤⼾所需的服务都是通过信道来提供的,因此操作思想转换为先创建连接,通过连接创建信道,通过信道提供服务这⼀流程。
这个模块同样是针对muduo库客⼾端连接的⼆次封装,向⽤⼾提供创建channel信道的接⼝,创建信道后,可以通过信道来获取指定服务。
class Connection{public:using ptr = std::shared_ptr<Connection>;Connection(const std::string &sip, int sport, const AsyncWorker::ptr &worker): _latch(1), _client(worker->loopthread.startLoop(), muduo::net::InetAddress(sip, sport), "Client"),_dispatcher(std::bind(&Connection::onUnknownMessage, this, std::placeholders::_1,std::placeholders::_2, std::placeholders::_3)),_codec(std::make_shared<ProtobufCodec>(std::bind(&ProtobufDispatcher::onProtobufMessage, &_dispatcher,std::placeholders::_1, std::placeholders::_2, std::placeholders::_3))),_worker(worker),_channel_manager(std::make_shared<ChannelManager>()){_dispatcher.registerMessageCallback<basicCommonResponse>(std::bind(&Connection::basicResponse, this,std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));_dispatcher.registerMessageCallback<basicConsumeResponse>(std::bind(&Connection::consumeResponse, this,std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));_client.setMessageCallback(std::bind(&ProtobufCodec::onMessage, _codec.get(),std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));_client.setConnectionCallback(std::bind(&Connection::onConnection, this, std::placeholders::_1));_client.connect();_latch.wait(); // 阻塞等待,直到连接建立成功}Channel::ptr openChannel(){Channel::ptr channel = _channel_manager->create(_conn, _codec);bool ret = channel->openChannel();if (ret == false){DLOG("打开信道失败!");return Channel::ptr();}return channel;}void closeChannel(const Channel::ptr &channel){channel->closeChannel();_channel_manager->remove(channel->cid());}private:void basicResponse(const muduo::net::TcpConnectionPtr &conn, const basicCommonResponsePtr &message, muduo::Timestamp){// 1. 找到信道Channel::ptr channel = _channel_manager->get(message->cid());if (channel.get() == nullptr){DLOG("未找到信道信息!");return;}// 2. 将得到的响应对象,添加到信道的基础响应hash_map中channel->putBasicResponse(message);}void consumeResponse(const muduo::net::TcpConnectionPtr &conn, const basicConsumeResponsePtr &message, muduo::Timestamp){// 1. 找到信道Channel::ptr channel = _channel_manager->get(message->cid());if (channel.get() == nullptr){DLOG("未找到信道信息!");return;}// 2. 封装异步任务(消息处理任务),抛入线程池_worker->pool.push([channel, message](){ channel->consume(message); });}void onUnknownMessage(const muduo::net::TcpConnectionPtr &conn, const MessagePtr &message, muduo::Timestamp){LOG_INFO << "onUnknownMessage: " << message->GetTypeName();conn->shutdown();}void onConnection(const muduo::net::TcpConnectionPtr &conn){if (conn->connected()){_latch.countDown(); // 唤醒主线程中的阻塞_conn = conn;}else{// 连接关闭时的操作_conn.reset();}}private:muduo::CountDownLatch _latch; // 实现同步的muduo::net::TcpConnectionPtr _conn; // 客户端对应的连接muduo::net::TcpClient _client; // 客户端ProtobufDispatcher _dispatcher; // 请求分发器ProtobufCodecPtr _codec; // 协议处理器AsyncWorker::ptr _worker;ChannelManager::ptr _channel_manager;};
生产者客户端
publish_client.cc
- 实例化异步工作线程对象
- 实例化连接对象
- 通过连接创建信道
- 通过信道提供的服务完成所需
- 循环向交换机发布消息
- 关闭信道
#include "connection.hpp"int main()
{//1. 实例化异步工作线程对象nzq::AsyncWorker::ptr awp = std::make_shared<nzq::AsyncWorker>();//2. 实例化连接对象nzq::Connection::ptr conn = std::make_shared<nzq::Connection>("127.0.0.1",8085,awp);//3. 通过连接创建信道nzq::Channel::ptr channel = conn->openChannel();//4. 通过信道提供的服务完成所需// 1. 声明一个交换机exchange1, 交换机类型为广播模式google::protobuf::Map<std::string,std::string> tmp_map;channel->declareExchange("exchange1", nzq::ExchangeType::TOPIC, true, false, tmp_map);// 2. 声明一个队列queue1channel->declareQueue("queue1", true, false, false, tmp_map);// 3. 声明一个队列queue2channel->declareQueue("queue2", true, false, false, tmp_map);// 4. 绑定queue1-exchange1,且binding_key设置为queue1channel->queueBind("exchange1", "queue1", "queue1");// 5. 绑定queue2-exchange1,且binding_key设置为news.music.#channel->queueBind("exchange1", "queue2", "news.music.#");//5. 循环向交换机发布消息for (int i = 0; i < 10; i++) {nzq::BasicProperties bp;bp.set_id(nzq::UUIDHelper::uuid());bp.set_delivery_mode(nzq::DeliveryMode::DURABLE);bp.set_routing_key("news.music.pop");channel->basicPublish("exchange1", &bp, "Hello World-" + std::to_string(i));}nzq::BasicProperties bp;bp.set_id(nzq::UUIDHelper::uuid());bp.set_delivery_mode(nzq::DeliveryMode::DURABLE);bp.set_routing_key("news.music.sport");channel->basicPublish("exchange1", &bp, "Hello linux");bp.set_routing_key("news.sport");channel->basicPublish("exchange1", &bp, "Hello chileme?");//6. 关闭信道conn->closeChannel(channel);return 0;return 0;
}
消费者客户端
同生产者客户端,如何消费需要自己设置,下面只是其中一种
void cb(nzq::Channel::ptr &channel, const std::string consumer_tag, const nzq::BasicProperties *bp, const std::string &body)
{std::cout << consumer_tag << "消费了消息:" << body << std::endl;channel->basicAck(bp->id());
}
int main(int argc, char *argv[])
{if (argc != 2) {std::cout << "usage: ./consume_client queue1\n";return -1;}//1. 实例化异步工作线程对象nzq::AsyncWorker::ptr awp = std::make_shared<nzq::AsyncWorker>();//2. 实例化连接对象nzq::Connection::ptr conn = std::make_shared<nzq::Connection>("127.0.0.1", 8085, awp);//3. 通过连接创建信道nzq::Channel::ptr channel = conn->openChannel();//4. 通过信道提供的服务完成所需// 1. 声明一个交换机exchange1, 交换机类型为广播模式google::protobuf::Map<std::string, std::string> tmp_map;channel->declareExchange("exchange1", nzq::ExchangeType::TOPIC, true, false, tmp_map);// 2. 声明一个队列queue1channel->declareQueue("queue1", true, false, false, tmp_map);// 3. 声明一个队列queue2channel->declareQueue("queue2", true, false, false, tmp_map);// 4. 绑定queue1-exchange1,且binding_key设置为queue1channel->queueBind("exchange1", "queue1", "queue1");// 5. 绑定queue2-exchange1,且binding_key设置为news.music.#channel->queueBind("exchange1", "queue2", "news.music.#");auto functor = std::bind(cb, channel, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);channel->basicConsume("consumer1", argv[1], false, functor);while(1) std::this_thread::sleep_for(std::chrono::seconds(3));conn->closeChannel(channel);return 0;
}
相关文章:

仿RabbitMQ实现消息队列客户端
文章目录 客⼾端模块实现订阅者模块信道管理模块异步⼯作线程实现连接管理模块生产者客户端消费者客户端 客⼾端模块实现 在RabbitMQ中,提供服务的是信道,因此在客⼾端的实现中,弱化了Client客⼾端的概念,也就是说在RabbitMQ中并…...

CSS | 面试题:你知道几种移动端适配方案?
目录 一、自适应和响应式 二、为什么要做移动端适配? 三、当前流行的几种适配方案 (1) 方案一:百分比设置(不推荐) (2) 方案二:rem 动态设置 font-size px 与 rem 的单位换算 手动换算 less/scss函数 webpac…...

【web安全】——XSS漏洞
1.XSS漏洞基础 1.1.漏洞成因 XSS(Cross-site scripting)被称为跨站脚本攻击,由于与层叠样式表的缩写一样,因此被缩写为XSS.XSS漏洞形成的原因是网站/程序对前端用户的输入过滤不严格,导致攻击者可以将恶意的is/html代码注入到网页中&#x…...

JAVA基础语法 Day11
一、Set集合 Set特点:无序(添加数据的顺序和获取出的数据顺序不一致),不重复,无索引 public class demo1 {public static void main(String[] args) {//1.创建一个集合//HashSet特点:无序,不重…...

知识图谱入门——7:阶段案例:使用 Protégé、Jupyter Notebook 中的 spaCy 和 Neo4j Desktop 搭建知识图谱
在 Windows 环境中结合使用 Protg、Jupyter Notebook 中的 spaCy 和 Neo4j Desktop,可以高效地实现从自然语言处理(NLP)到知识图谱构建的全过程。本案例将详细论述环境配置、步骤实现以及一些扩展和不足之处。 文章目录 1. 环境准备1.1 Neo4j…...

【AIGC】VoiceControl for ChatGPT指南:轻松开启ChatGPT语音对话模式
博客主页: [小ᶻZ࿆] 本文专栏: AIGC | ChatGPT 文章目录 💯前言💯安装VoiceControl for ChatGPT插件💯如何使用VoiceControl for ChatGPT进行语音输入VoiceControl for ChatGPT快捷键注意点 💯VoiceControl for C…...

基于SpringCloud的微服务架构下安全开发运维准则
为什么要进行安全设计 微服务架构进行安全设计的原因主要包括以下几点: 提高数据保护:微服务架构中,服务间通信频繁,涉及到大量敏感数据的交换。安全设计可以确保数据在传输和存储过程中的安全性,防止数据泄露和篡改。…...

vue的图片显示
通过参数 调用方法 进行显示图片 方法一: 方法二:...
深度学习06:线性回归模型
线性回归:从理论到实现 1. 什么是线性回归? 线性回归是一种用于预测因变量(目标值)和自变量(特征值)之间关系的基本模型。它假设目标值(y)是特征值(x)的线性…...
Angular ng-state script 元素的生成机制介绍
ng-state 的生成过程是在 Angular SSR 中非常关键的部分。为了让客户端能够接管服务器渲染的页面状态,Angular 在服务器端需要将应用的当前状态保存下来,并将其嵌入到返回的 HTML 中。这样,客户端在接管时就可以直接使用这些状态,…...

小程序-全局数据共享
目录 1.什么是全局数据共享 2. 小程序中的全局数据共享方案 MboX 1. 安装 MobX 相关的包 2. 创建 MobX 的 Store 实例 3. 将 Store 中的成员绑定到页面中 4. 在页面上使用 Store 中的成员 5. 将 Store 中的成员绑定到组件中 6. 在组件中使用 Store 中的成员 1.什么是全…...

vSAN01:vSAN简介、安装、磁盘组、内部架构与调用关系
目录 传统的共享存储vSAN存储OSA的系统要求vSAN安装vSAN集群vSAN skyline healthvSAN与HA磁盘组混合磁盘架构全闪磁盘架构 vSAN对象vSAN内部架构 传统的共享存储 通过隔离的存储网络使得不同的ESXi主机访问独立的存储设备。需要前期投入较高的资金单独采购存储、网络可以单独规…...
Apache NiFi最全面试题及参考答案
目录 解释什么是Apache NiFi以及它的主要用途。 NiFi 的数据处理流程是怎样的? NiFi 的架构包括哪些组件? 解释 NiFi 的 “FlowFile” 概念及其组成部分。 NiFi 的 “Processor” 是什么?有哪些类型? 如何在 NiFi 中创建一个新的数据流? NiFi 的 “Connection” 有…...
基于Docker部署最新版本SkyWalking【10.1.0版本】
文章目录 前言前置条件一、创建Docker 网络二、部署 SkyWalking OAP 服务器三 部署 SkyWalking UI四 查看日志4.1. 查看 SkyWalking OAP 日志4.2. 查看 SkyWalking UI 日志 五 停止并删除容器结论 前言 由于本地的 JDK 版本与 SkyWalking 对应的 JDK 版本不一致,为…...
如何在 Ubuntu 18.04 上使用 LEMP 安装 WordPress
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。 简介 WordPress 是互联网上最流行的 CMS(内容管理系统)。它允许您在 MySQL 后端和 PHP 处理的基础上轻松设置灵…...
shadcn-vue 快速入门(2)
components.json 关于项目配置 components.json 文件保存了项目的配置信息。 我们使用该文件了解项目的基本设定,并生成定制化的组件以适应项目需求。 注意:components.json 文件是可选的,仅在使用 CLI 向项目添加组件时才需要。如果使用复…...

Oracle数据恢复—异常断电导致Oracle数据库报错的数据恢复案例
Oracle数据库故障: 机房异常断电后,Oracle数据库启库报错:“system01.dbf需要更多的恢复来保持一致性,数据库无法打开”。数据库没有备份,归档日志不连续。用户方提供了Oracle数据库的在线文件,需要恢复zxf…...

数据结构-4.1.特殊矩阵的压缩存储
一.一维数组的存储结构: 1.知道一维数组的起始地址,就可以求出任意下标对应的元素所在的地址; 2.注:如果数组下标从1开始,上述公式的i就要改为i-1; 3.数组里的元素类型相同,因此所占空间也相同…...
Hive数仓操作(十四)
一、Hive的DDL语句 在 Hive 中,DDL(数据定义语言)语句用于数据库和表的创建、修改、删除等操作。以下是一些重要的 DDL 语句: 1. 创建数据库和表 创建数据库 CREATE DATABASE IF NOT EXISTS database_name;创建表 CREATE TABLE …...
SpringBoot技术:实现古典舞在线交流平台的秘诀
摘 要 随着互联网技术的发展,各类网站应运而生,网站具有新颖、展现全面的特点。因此,为了满足用户古典舞在线交流的需求,特开发了本古典舞在线交流平台。 本古典舞在线交流平台应用Java技术,MYSQL数据库存储数据&#…...
RestClient
什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端,它允许HTTP与Elasticsearch 集群通信,而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级ÿ…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...

高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...
【Go】3、Go语言进阶与依赖管理
前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程,它的核心机制是 Goroutine 协程、Channel 通道,并基于CSP(Communicating Sequential Processes࿰…...

第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明
AI 领域的快速发展正在催生一个新时代,智能代理(agents)不再是孤立的个体,而是能够像一个数字团队一样协作。然而,当前 AI 生态系统的碎片化阻碍了这一愿景的实现,导致了“AI 巴别塔问题”——不同代理之间…...

视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...

CVE-2020-17519源码分析与漏洞复现(Flink 任意文件读取)
漏洞概览 漏洞名称:Apache Flink REST API 任意文件读取漏洞CVE编号:CVE-2020-17519CVSS评分:7.5影响版本:Apache Flink 1.11.0、1.11.1、1.11.2修复版本:≥ 1.11.3 或 ≥ 1.12.0漏洞类型:路径遍历&#x…...
Qt 事件处理中 return 的深入解析
Qt 事件处理中 return 的深入解析 在 Qt 事件处理中,return 语句的使用是另一个关键概念,它与 event->accept()/event->ignore() 密切相关但作用不同。让我们详细分析一下它们之间的关系和工作原理。 核心区别:不同层级的事件处理 方…...
《Offer来了:Java面试核心知识点精讲》大纲
文章目录 一、《Offer来了:Java面试核心知识点精讲》的典型大纲框架Java基础并发编程JVM原理数据库与缓存分布式架构系统设计二、《Offer来了:Java面试核心知识点精讲(原理篇)》技术文章大纲核心主题:Java基础原理与面试高频考点Java虚拟机(JVM)原理Java并发编程原理Jav…...