C++ - 仿 RabbitMQ 實現消息隊列--服務端核心模塊實現(三)

目錄

隊列數據管理

代碼實現

測試代碼

綁定信息(交換機-隊列)管理

代碼實現

測試代碼


隊列數據管理

當前隊列數據的管理,本質上是隊列描述信息的管理,描述當前服務器上有哪些隊列。

  • 定義隊列描述數據類
  1. 隊列名稱
  2. 是否持久化標志
  3. 是否獨占標志
  4. 是否自動刪除標志
  5. 其他參數
  • 定義隊列數據持久化類(數據持久化的 sqlite3 數據庫中)
  1. 創建/刪除隊列數據表
  2. 新增隊列數據
  3. 移除隊列數據
  4. 查詢所有隊列數據
  • 定義隊列數據管理類
  1. 創建隊列,并添加管理(存在則 OK,不存在則創建)
  2. 刪除隊列
  3. 獲取指定隊列
  4. 獲取所有隊列
  5. 判斷指定隊列是否存在
  6. 獲取隊列數量
  7. 銷毀所有隊列數據

代碼實現

與交換機數據管理的實現非常相似,只需要修改表結構即可

#pragma once
#include "../mqcommon/helper.hpp"
#include "../mqcommon/log.hpp"
#include "../mqcommon/msg.pb.h"
#include <unordered_map>
#include <memory>namespace jiuqi
{struct MsgQueue{using ptr = std::shared_ptr<MsgQueue>;std::string name;bool durable;bool exclusive;bool auto_delete;std::unordered_map<std::string, std::string> args;MsgQueue() {}MsgQueue(const std::string &qname,bool qdurable,bool qexclusive,bool qauto_delete,const std::unordered_map<std::string, std::string> qargs): name(qname), durable(qdurable), exclusive(qexclusive), auto_delete(qauto_delete), args(qargs) {}void setArgs(const std::string &str_args){// key=val&key=val.....std::vector<std::string> sub_args;StrHelper::split(str_args, "&", sub_args);for (auto &arg : sub_args){size_t pos = arg.find("=");std::string key = arg.substr(0, pos);std::string val = arg.substr(pos + 1);args.insert(std::make_pair(key, val));}}std::string getArgs(){if (args.empty())return "";std::string result;for (auto &arg : args){result += arg.first + "=" + arg.second + "&";}result.pop_back();return result;}};using QueueMap = std::unordered_map<std::string, MsgQueue::ptr>;class QueueMapper{public:QueueMapper(const std::string &dbfile) : _sql_helper(dbfile){std::string path = FileHelper::parentDirectory(dbfile);FileHelper::createDirectory(path);            assert(_sql_helper.open());createTable();}void createTable(){std::stringstream ss;ss << "create table if not exists queue_table("<< "name varchar(32) primary key, "<< "durable int, "<< "exclusive int, "<< "auto_delete int, "<< "args varchar(128));";std::string sql = ss.str();bool ret = _sql_helper.exec(sql, nullptr, nullptr);if (!ret){ERROR("創建隊列數據庫表失敗");abort();}}void removeTable(){std::string sql = "drop table if exists queue_table;";bool ret = _sql_helper.exec(sql, nullptr, nullptr);if (!ret){ERROR("刪除交換機數據庫表失敗");abort();}}bool insert(MsgQueue::ptr &queue){std::stringstream ss;ss << "insert into queue_table values('"<< queue->name << "', "<< queue->durable << ", "<< queue->exclusive << ", "<< queue->auto_delete << ", '"<< queue->getArgs() << "');";std::string sql = ss.str();bool ret = _sql_helper.exec(sql, nullptr, nullptr);return ret;}bool remove(const std::string &name){std::stringstream ss;ss << "delete from queue_table where "<< "name = " << "'" << name << "';";std::string sql = ss.str();bool ret = _sql_helper.exec(sql, nullptr, nullptr);return ret;}QueueMap recovery(){QueueMap result;std::string sql = "select name, durable, exclusive, auto_delete, args from queue_table";_sql_helper.exec(sql, selectCallback, &result);return result;}private:static int selectCallback(void *arg, int numcol, char **row, char **fields){QueueMap *result = (QueueMap *)arg;MsgQueue::ptr mqp = std::make_shared<MsgQueue>();mqp->name = row[0];mqp->durable = (bool)std::stoi(row[1]);mqp->exclusive = (bool)std::stoi(row[2]);mqp->auto_delete = (bool)std::stoi(row[3]);if (row[4])mqp->setArgs(row[4]);result->insert(std::make_pair(mqp->name, mqp));return 0;}private:SqliteHelper _sql_helper;};class QueueManager{public:using ptr = std::shared_ptr<QueueManager>;QueueManager(const std::string &dbfile) : _mapper(dbfile){_queues = _mapper.recovery();}void declareQueue(const std::string &name,bool durable,bool exclusive,bool auto_delete,std::unordered_map<std::string, std::string> &args){std::unique_lock<std::mutex> lock(_mutex);auto it = _queues.find(name);if (it != _queues.end())return;auto queue = std::make_shared<MsgQueue>(name, durable, exclusive, auto_delete, args);_queues.insert(std::make_pair(name, queue));if (durable)_mapper.insert(queue);}void deleteQueue(const std::string &name){std::unique_lock<std::mutex> lock(_mutex);auto it = _queues.find(name);if (it == _queues.end())return;if (it->second->durable)_mapper.remove(name);_queues.erase(name);}MsgQueue::ptr selectQueue(const std::string &name){std::unique_lock<std::mutex> lock(_mutex);auto it = _queues.find(name);if (it == _queues.end())return nullptr;return it->second;}QueueMap allQueue(){std::unique_lock<std::mutex> lock(_mutex);return _queues;}bool exists(const std::string &name){std::unique_lock<std::mutex> lock(_mutex);auto it = _queues.find(name);if (it == _queues.end())return false;return true;            }size_t size(){std::unique_lock<std::mutex> lock(_mutex);return _queues.size();}void clear(){std::unique_lock<std::mutex> lock(_mutex);_mapper.removeTable();_queues.clear();}private:std::mutex _mutex;QueueMapper _mapper;QueueMap _queues;};
}

測試代碼

#include "../mqserver/queue.hpp"
#include <gtest/gtest.h>jiuqi::QueueManager::ptr qmp;class ExchangeTest : public testing::Environment
{
public:virtual void SetUp() override{qmp = std::make_shared<jiuqi::QueueManager>("./data/queue.db");}virtual void TearDown() override{qmp->clear();}
};TEST(ExchangeTest, insert_test)
{std::unordered_map<std::string, std::string> map = {{"k1", "v1"}, {"k2", "v2"}, {"k3", "v3"}};std::unordered_map<std::string, std::string> map_empty;qmp->declareQueue("queue1", true, false, false, map);qmp->declareQueue("queue2", true, false, false, map);qmp->declareQueue("queue3", true, false, false, map);qmp->declareQueue("queue4", true, false, false,map_empty);qmp->declareQueue("queue5", true, false, false,map_empty);qmp->declareQueue("queue6", true, false, false,map_empty);ASSERT_EQ(qmp->size(), 6);
}TEST(ExchangeTest, select_test)
{jiuqi::MsgQueue::ptr mqp = qmp->selectQueue("queue3");ASSERT_EQ(mqp->name, "queue3");ASSERT_EQ(mqp->durable, true);ASSERT_EQ(mqp->exclusive, false);ASSERT_EQ(mqp->auto_delete, false);ASSERT_EQ(mqp->getArgs(), std::string("k1=v1&k2=v2&k3=v3"));
}TEST(ExchangeTest, delete_test)
{qmp->deleteQueue("queue1");jiuqi::MsgQueue::ptr mqp = qmp->selectQueue("queue1");ASSERT_EQ(mqp.get(), nullptr);ASSERT_EQ(qmp->exists("queue1"), false);
}int main(int argc, char *argv[])
{testing::InitGoogleTest(&argc, argv);testing::AddGlobalTestEnvironment(new ExchangeTest);return RUN_ALL_TESTS();
}

綁定信息(交換機-隊列)管理

綁定信息,本質上就是一個交換機關聯了哪些隊列的描述。

  • 定義綁定信息類
  1. 交換機名稱
  2. 隊列名稱
  3. binding_key(分發匹配規則-決定了哪些數據能被交換機放入隊列)
  • 定義綁定信息數據持久化類(數據持久化的 sqlite3 數據庫中)
  1. 創建/刪除綁定信息數據表
  2. 新增綁定信息數據
  3. 移除指定綁定信息數據
  4. 移除指定交換機相關綁定信息數據:移除交換機的時候會被調用
  5. 移除指定隊列相關綁定信息數據:移除隊列的時候會被調用f. 查詢所有綁定信息數據:用于重啟服務器時進行歷史數據恢復
  • 定義綁定信息數據管理類
  1. 創建綁定信息,并添加管理(存在則 OK,不存在則創建)
  2. 解除指定的綁定信息
  3. 刪除指定隊列的所有綁定信息
  4. 刪除交換機相關的所有綁定信息
  5. 獲取交換機相關的所有綁定信息:交換機收到消息后,需要分發給自己關聯的隊列
  6. 判斷指定綁定信息是否存在
  7. 獲取當前綁定信息數量
  8. 銷毀所有綁定信息數據

代碼實現

同樣與上述類的實現類似

?

#pragma once
#include "../mqcommon/helper.hpp"
#include "../mqcommon/log.hpp"
#include "../mqcommon/msg.pb.h"
#include <unordered_map>
#include <memory>namespace jiuqi
{struct Binding{using ptr = std::shared_ptr<Binding>;std::string exchangeName;std::string queueName;std::string bindingKey;Binding() {}Binding(const std::string &ename, const std::string &qname, const std::string &key): exchangeName(ename), queueName(qname), bindingKey(key){}};using QueueBindingMap = std::unordered_map<std::string, Binding::ptr>;using BindingMap = std::unordered_map<std::string, QueueBindingMap>;class BindingMapper{public:BindingMapper(const std::string &dbfile) : _sql_helper(dbfile){std::string path = FileHelper::parentDirectory(dbfile);FileHelper::createDirectory(path);_sql_helper.open();createTable();}void createTable(){std::string sql = "create table if not exists binding_table(exchangeName varchar(32), queueName varchar(32), bindingKey varchar(128), PRIMARY KEY (exchangeName, queueName));";bool ret = _sql_helper.exec(sql, nullptr, nullptr);if (!ret){ERROR("創建綁定信息數據庫表失敗");abort();}}void removeTable(){std::string sql = "drop table if exists binding_table;";bool ret = _sql_helper.exec(sql, nullptr, nullptr);if (!ret){ERROR("刪除綁定信息數據庫表失敗");abort();}}bool insert(Binding::ptr &binding){std::stringstream ss;ss << "insert into binding_table values('"<< binding->exchangeName << "', '"<< binding->queueName << "', '"<< binding->bindingKey << "');";std::string sql = ss.str();if (!_sql_helper.exec(sql, nullptr, nullptr)){ERROR("插入綁定記錄失敗");return false;}return true;}bool remove(const std::string &ename, const std::string &qname){std::stringstream ss;ss << "delete from binding_table where "<< "exchangeName = '" << ename << "' "<< "and queueName = '" << qname << "';";std::string sql = ss.str();bool ret = _sql_helper.exec(sql, nullptr, nullptr);return ret;}bool removeExchangeBindings(const std::string &ename){std::stringstream ss;ss << "delete from binding_table where "<< "exchangeName = '" << ename << "';";std::string sql = ss.str();bool ret = _sql_helper.exec(sql, nullptr, nullptr);return ret;}bool removeQueueBindings(const std::string &qname){std::stringstream ss;ss << "delete from binding_table where "<< "queueName = '" << qname << "';";std::string sql = ss.str();bool ret = _sql_helper.exec(sql, nullptr, nullptr);return ret;}BindingMap recovery(){BindingMap result;std::string sql = "select exchangeName, queueName, bindingKey from binding_table;";_sql_helper.exec(sql, selectCallback, &result);return result;}private:static int selectCallback(void *arg, int numcol, char **row, char **fields){BindingMap *result = (BindingMap *)arg;Binding::ptr bp = std::make_shared<Binding>(row[0], row[1], row[2]);// 為了防止交換機相關綁定信息已經存在,不能直接創建隊列映射// 因此要先獲取交換機對應的映射對象// 使用引用的好處, 如果不存在就會創建QueueBindingMap &qbm = (*result)[bp->exchangeName];qbm.insert(std::make_pair(bp->queueName, bp));return 0;}private:SqliteHelper _sql_helper;};class BindingManager{public:using ptr = std::shared_ptr<BindingManager>;BindingManager(const std::string &dbfile) : _mapper(dbfile){_bindings = _mapper.recovery();}bool bind(const std::string &ename, const std::string &qname, const std::string &key, bool durable){std::unique_lock<std::mutex> lock(_mutex);auto eit = _bindings.find(ename);if (eit != _bindings.end() && eit->second.find(qname) != eit->second.end())return true;Binding::ptr bp = std::make_shared<Binding>(ename, qname, key);if (durable){if(!_mapper.insert(bp))return false;}QueueBindingMap &qbm = _bindings[ename];qbm.insert(std::make_pair(qname, bp));return true;}void unbind(const std::string &ename, const std::string &qname){std::unique_lock<std::mutex> lock(_mutex);auto eit = _bindings.find(ename);if (eit == _bindings.end())return;auto qit = eit->second.find(qname);if (qit == eit->second.end())return;_bindings[ename].erase(qname);if (eit->second.empty())_bindings.erase(ename);_mapper.remove(ename, qname);}void removeExchangeBinding(const std::string &ename){std::unique_lock<std::mutex> lock(_mutex);auto eit = _bindings.find(ename);if (eit == _bindings.end())return;_mapper.removeExchangeBindings(ename);_bindings.erase(ename);}void removeQueueBinding(const std::string &qname){std::unique_lock<std::mutex> lock(_mutex);_mapper.removeQueueBindings(qname);// 遍歷所有交換機的綁定for (auto it = _bindings.begin(); it != _bindings.end();){// 刪除該隊列在當前交換機的綁定it->second.erase(qname);// 如果當前交換機的綁定為空,則刪除該交換機的條目if (it->second.empty()){it = _bindings.erase(it); // erase返回下一個有效的迭代器}else{++it;}}}QueueBindingMap getExchangeBindings(const std::string &ename){std::unique_lock<std::mutex> lock(_mutex);auto it = _bindings.find(ename);if (it == _bindings.end())return QueueBindingMap();return _bindings[ename];}Binding::ptr getBinding(const std::string &ename, const std::string &qname){std::unique_lock<std::mutex> lock(_mutex);auto eit = _bindings.find(ename);if (eit == _bindings.end())return nullptr;auto qit = eit->second.find(qname);if (qit == eit->second.end())return nullptr;return qit->second;}bool exists(const std::string &ename, const std::string &qname){std::unique_lock<std::mutex> lock(_mutex);auto eit = _bindings.find(ename);if (eit == _bindings.end())return false;auto qit = eit->second.find(qname);if (qit == eit->second.end())return false;return true;}size_t size(){std::unique_lock<std::mutex> lock(_mutex);size_t size = 0;for (auto start = _bindings.begin(); start != _bindings.end(); start++)size += start->second.size();return size;}void clear(){std::unique_lock<std::mutex> lock(_mutex);_mapper.removeTable();_bindings.clear();}private:std::mutex _mutex;BindingMapper _mapper;BindingMap _bindings;};
}

????????值得注意的是:在使用unordered_map保存綁定信息的時候,插入和刪除的方式與之前不同,具體在注釋中給出了解釋。?

測試代碼

#include "../mqserver/binding.hpp"
#include <gtest/gtest.h>jiuqi::BindingManager::ptr bmp;class BindingTest : public testing::Environment
{
public:virtual void SetUp() override{bmp = std::make_shared<jiuqi::BindingManager>("./data/binding.db");}virtual void TearDown() override{bmp->clear();}
};TEST(ExchangeTest, insert_test)
{bmp->bind("exchange1", "queue1", "651", 1);bmp->bind("exchange1", "queue2", "651", 1);bmp->bind("exchange1", "queue3", "651", 1);bmp->bind("exchange2", "queue1", "651", 1);bmp->bind("exchange2", "queue2", "651", 1);bmp->bind("exchange2", "queue3", "651", 1);ASSERT_EQ(bmp->size(), 6);
}TEST(ExchangeTest, select_test)
{jiuqi::Binding::ptr bp = bmp->getBinding("exchange2", "queue1");ASSERT_EQ(bp->exchangeName, "exchange2");ASSERT_EQ(bp->queueName, "queue1");ASSERT_EQ(bp->bindingKey, "651");
}TEST(ExchangeTest, delete_test)
{bmp->unbind("exchange1", "queue3");jiuqi::Binding::ptr bp = bmp->getBinding("exchange1", "queue3");ASSERT_EQ(bp.get(), nullptr);ASSERT_EQ(bmp->exists("exchange1", "queue3"), false);
}int main(int argc, char *argv[])
{testing::InitGoogleTest(&argc, argv);testing::AddGlobalTestEnvironment(new BindingTest);return RUN_ALL_TESTS();
}

? ? ? ? 在測試的過程中,發現了一種錯誤,就是創建數據庫表時發生了out of memory錯誤,開始還以為是系統內存不足,后來發現在構造mapper時忘記了打開數據庫,所以得知如果沒有打開數據庫就創建表就會發生out of memory錯誤。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/90961.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/90961.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/90961.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

51c自動駕駛~合集9

自己的原文哦~ https://blog.51cto.com/whaosoft/11627386 #端到端1 說起端到端&#xff0c;每個從業者可能都覺得會是下一代自動駕駛量產方案繞不開的點&#xff01;特斯拉率先吹響了方案更新的號角&#xff0c;無論是完全端到端&#xff0c;還是專注于planner的模…

時間長了忘記jupyter的環境是哪個了

有這些但是忘記是哪個了jupyter kernelspec list查看內核路徑&#xff0c;這個內核是用來告訴jupyter 去哪找內核配置的到這個路徑下打開json文件查看使用的python環境從而確定是哪個conda環境為jupyter使用的python環境jupyter的工作原理&#xff1a;在創建conda環境后會安裝j…

PYTHON從入門到實踐-15數據可視化

數據可視化是數據分析中不可或缺的一環&#xff0c;它能夠將抽象的數據轉化為直觀的圖形&#xff0c;幫助我們更好地理解數據特征和發現潛在規律。本文將介紹如何使用Python中的Matplotlib和Plotly庫進行數據可視化&#xff0c;并通過擲骰子的概率模擬案例展示可視化的實際應用…

Spring IOC 容器 **默認注冊 Bean** 的 8 條規則

Spring IOC 容器 默認注冊 Bean 的 8 條規則 &#xff08;Spring Framework 6.x 源碼級總結&#xff09;閱讀提示&#xff1a;把下面 8 條規則背下來&#xff0c;再讀 Spring 源碼時&#xff0c;你會在任何一行代碼里立刻知道「這個 BeanDefinition 是從哪兒來的」。1?? 環境…

29.【.NET8 實戰--孢子記賬--從單體到微服務--轉向微服務】--單體轉微服務--用戶配置服務

用戶配置服務是孢子記賬中最簡單的部分。簡單說&#xff0c;用戶配置服務就是用戶自定義的配置項存儲服務&#xff0c;用于我們的APP根據用戶的配置實現指定的功能。它提供了一個簡單的接口&#xff0c;允許用戶存儲和檢索他們的配置數據。就目前來說&#xff0c;用戶配置只有一…

Python實現PDF按頁分割:靈活拆分文檔的技術指南

Python實現PDF按頁分割&#xff1a;靈活拆分文檔的技術指南 PDF文件處理是日常工作中的常見需求&#xff0c;特別是當我們需要將大型PDF文檔拆分為多個部分時。本文將介紹如何使用Python創建一個靈活的PDF分割工具&#xff0c;能夠根據用戶指定的頁數范圍任意分割文檔。 需求分…

「iOS」——GCD其他方法詳解

GCD學習GCD其他方法dispatch_semaphore &#xff08;信號量&#xff09;**什么是信號量**dispatch_semaphore主要作用dispatch_semaphore主要作用異步轉同步設置一個最大開辟的線程數加鎖機制dispatch_time_t 兩種形式GCD一次性代碼(只執行一次)dispatch_barrier_async/sync柵欄…

【圖像處理基石】如何實現一個車輛檢測算法?

基于AI的車牌檢測和識別算法 問題描述、應用場景與難點 問題描述 車牌檢測和識別是計算機視覺領域的一個特定任務&#xff0c;主要包含兩個核心步驟&#xff1a; 車牌檢測&#xff1a;從圖像中準確定位車牌的位置和區域車牌識別&#xff1a;對檢測到的車牌區域進行字符識別&…

計算機學報 2025年 區塊鏈論文 錄用匯總 附pdf下載

計算機學報 Year&#xff1a;2025 2024請看 1 Title: 基于區塊鏈的動態多云多副本數據完整性審計方法研究 Authors: Key words: 區塊鏈&#xff1b;云存儲&#xff1b;多云多副本存儲&#xff1b;數據完整性審計 Abstract: 隨著云計算技術的快速發展和云存儲服務的日益…

計算機網絡-UDP協議

UDP&#xff08;用戶數據報協議&#xff09;是傳輸層的一種無連接、不可靠、輕量級的協議&#xff0c;適用于對實時性要求高、能容忍少量數據丟失的場景&#xff08;如視頻流、DNS查詢等&#xff09;。以下是UDP的詳細解析&#xff1a;1. UDP的核心特點特性說明無連接通信前無需…

子域名收集和c段查詢

子域名收集方法一、sitesite&#xff1a; 要查詢的域名可以查到相關網站二、oneforall &#xff08;子域名查找工具&#xff09;下載后解壓的文件夾在當前文件夾打開終端然后運行命令 python oneforall.py --target xxxxxxxx&#xff08;這里放你要查的網址&#xff09; run最…

計網-TCP擁塞控制

TCP的擁塞控制&#xff08;Congestion Control&#xff09;是核心機制之一&#xff0c;用于動態調整發送方的數據傳輸速率&#xff0c;避免網絡因過載而出現性能急劇下降&#xff08;如丟包、延遲激增&#xff09;。其核心思想是探測網絡可用帶寬&#xff0c;并在擁塞發生時主動…

依賴倒置原則 Dependency Inversion Principle - DIP

基本知識 1.依賴倒置原則&#xff08;DIP&#xff09;是面向對象設計&#xff08;OOD&#xff09;中的五個基本原則之一&#xff0c;通常被稱為 SOLID 原則中的 D 2.核心思想&#xff1a; 高層模塊不應該依賴低層模塊&#xff0c;兩者都應該依賴抽象。 (High-level modules sho…

原生input添加刪除圖標類似vue里面移入顯示刪除[jquery]

<input type"text" id"servicer-search" class"form-control" autocomplete"off" />上面是剛開始的input <div class"servicer-search-box"><input type"text" id"servicer-search" cla…

整理分享 | Photoshop 2025 (v26.5) 安裝記錄

導語&#xff1a; 最近整理資源時&#xff0c;發現有朋友在找新版 Photoshop。正好手邊有 Photoshop 2025年7月的版本&#xff08;v26.5&#xff09;&#xff0c;就記錄下來分享給大家&#xff0c;供有需要的朋友參考。關于這個版本&#xff1a;這個 Photoshop v26.5 安裝包&am…

【Redis】Redis 數據存儲原理和結構

一、Redis 存儲結構 1.1 KV結構 Redis 本質上是一個 Key-Value&#xff08;鍵值對&#xff0c;KV&#xff09;數據庫&#xff0c;在它豐富多樣的數據結構底層&#xff0c;都基于一種統一的鍵值對存儲結構來進行數據的管理和操作 Redis 使用一個全局的哈希表來管理所有的鍵值對…

【RAG優化】深度剖析OCR錯誤,從根源修復RAG應用的識別問題

1. 引言:OCR——RAG系統中的關鍵問題 當我們將一個包含掃描頁面的PDF或一張報告截圖扔給RAG系統時,我們期望它能“讀懂”里面的內容。這個“讀懂”的第一步,就是OCR。然而,OCR過程并非100%準確,它受到圖像質量、文字布局、字體、語言等多種因素的影響。 一個看似微不足道…

【第六節】方法與事件處理器

方法與事件處理器 方法處理器 可以用 v-on 指令監聽 DOM 事件: <div id="example"> <button v-on:click="greet">Greet</button></div>綁定一個單擊事件處理器到一個方法 greet 。下面在 Vue 實例中定義這個方法 var vm=new V…

大語言模型Claude 4簡介

Anthropic公司成立于2021年&#xff0c;由一群OpenAI前員工組成。他們最新發布的大語言模型(Large Language Model, LLM) Claude 4系列包括兩個版本&#xff1a;Claude Opus 4和Claude Sonnet 4&#xff1a;(1).Claude Sonnet 4&#xff1a;是Claude Sonnet 3.7的升級&#xff…

國產化PDF處理控件Spire.PDF教程:Python 將 PDF 轉換為 Markdown (含批量轉換示例)

PDF 是數字文檔管理的普遍格式&#xff0c;但其固定布局特性限制了在需要靈活編輯、更新或現代工作流集成場景下的應用。相比之下&#xff0c;Markdown&#xff08;.md&#xff09;語法輕量、易讀&#xff0c;非常適合網頁發布、文檔編寫和版本控制。 E-iceblue旗下Spire系列產…