第二章-续:辅助功能
目录
第一节:UUIDHelper
第二节:StrHelper
第三节:单元测试
3-1.日志宏测试
3-2.SqliteHelper测试
3-2-1.创建表测试
3-2-2.添加数据测试
3-2-3.修改数据测试
3-2-4.删除数据测试
3-2-5.获取数据测试
3-3.FileHelper测试
3-3-1.创建/打开文件测试
3-3-2.写数据测试
3-3-3.读取数据测试
3-4.UUIDHelper测试
3-5.StrHelper测试
下期预告:
第一节:UUIDHelper
UUIDHelper就是随机id生成器。
仍然是在mq_helper.hpp文件中,定义UUIDHelpr类:
class UUIDHelper {};
它只对外提供生成随机id的函数:
static std::string uuid() { // std::random_device 生成机器随机数,效率低 std::random_device rd; // std::mt19937_64 生成伪随机数,以rd为种子,效率高 std::mt19937_64 rand(rd()); // 生成0~255之间的随机数 std::uniform_int_distribution<int> distr(0,255); // 生成前缀随机数 std::stringstream ss; for(int i = 0;i < 8;i++) { ss << std::setw(2) << std::setfill('0') << std::hex << distr(rand); if(i == 3 || i == 5 || i == 7) ss<<"-"; } // 生成序列号 static std::atomic<size_t> sep(1); size_t number = sep.fetch_add(1); // 先获取sep当前值,sep再自增1 for(int i = 7;i >= 0;i--) { ss << std::setw(2) << std::setfill('0') << std::hex << ((number>>(i*8)) & 0xff); if(i == 6) ss << "-"; } return ss.str(); }
前缀随机数由8个int类型的随机数组合而成,共32字节,因为最大的随机数是255,转成十六进制后最多两位,一位又以"0"填充,这样让每个前缀都是16位。
序列号由一个静态原子变量维护,number是一个4字节(32比特)的数据,每次将序列号的8个比特位转成两位的十六进制,处理8次,就可以正好处理了32比特。只要程序不关闭,每次的序列号肯定是不同的,这样即使前缀相同,它们的识别码也不相同。
第二节:StrHelper
StrHelper也只提供一个对外的接口,它的作用是分割字符串,这在交换机的主题模式的消息审核中十分重要:
class StrHelper { public: // 截取sep之间、开头与sep、sep与结尾之间的字符串 static size_t split(const std::string& str,const std::string& sep,std::vector<std::string>& result) { // news.music.#.pop // 截取到news、music、#、pop size_t pos = 0; // 每次查找到的子串起始位置 size_t idx = 0; // 每次查找的起始位置 while(idx < str.size()) { pos = str.find(sep,idx); if(pos == std::string::npos) // 没找到sep { // 从查找位置起始截取到末尾 result.push_back(str.substr(idx)); // 返回找到的子串数量 return result.size(); } else { if(pos == idx) // 空串不用截取 { idx=pos+sep.size(); continue; } result.push_back(str.substr(idx,pos-idx)); idx=pos+sep.size(); } } return result.size(); } };
第三节:单元测试
所有的单元测试都在mqtest目录下进行。
3-1.日志宏测试
创建一个名为mq_log_test.cc的文件,并包含mq_logger.hpp文件:
#include "../mqcommon/mq_logger.hpp"
再包含<string>作为可变参数:
#include <string>
最后完成main函数:
#include "../mqcommon/mq_logger.hpp" #include <string> int main() { std::string msg = "Hello!"; LOG("日志宏测试:%s",msg.c_str()); return 0; }
再创建makefile文件进行编译:
mq_log_test:mq_log_test.cc g++ -std=c++14 $^ -o $@
运行结果:
时间正确、文件正确、行号正确、打印内容正确。
日志宏的功能正常。
3-2.SqliteHelper测试
创建一个名为mq_sqlite_test.cc的文件,打开并包含头文件:
#include "../mqcommon/mq_helper.hpp"
3-2-1.创建表测试
先测试创建表的功能:
int main() { // 创建sqlite数据库文件的管理句柄 zd::SqliteHelper shelper("./meta.bd"); // 使用句柄创建文件 shelper.open(); // 使用句柄创建一张名为 student 的数据表 // 1.编辑创建表的指令 // sn:学号 name:姓名 age:年龄 // primary key:表示这个数据作为键,一个表内不允许出现相同键的数据 char Create_Table[] = "create table if not exists student(sn int primary key ,name varchar(32),age int);"; // 2.使用exec接口执行这个指令 shelper.exec(Create_Table,nullptr,nullptr); return 0; }
将上述代码进行编译,因为使用了sqlite库,所以还要链接该库:
mq_sqlite_test:mq_sqlite_test.cc g++ -std=c++14 $^ -o $@ -lsqlite3
执行后应当在当前目录出现meta.bd文件:
然后使用sqlite指令打开meta.bd文件:
sqlite3 meta.bd
会出现输入指令的提示:
输入 .table 会出现一个名为 student 的表,这就是创建的表:
此时表中是没有数据的,我们再完善代码。
3-2-2.添加数据测试
先把 meta.bd 删除,因为只要之前的代码不变,它还是会被创建出来。
然后在main函数中继续添加如下代码:
// 使用句柄向表 student 添加数据 // 1.构建插入数据的指令 char Insert_Data[] = "insert into student values(1,'李华',18),(2,'吴凡',17),(3,'Eric',19);"; // 2.使用exec接口执行这个指令 shelper.exec(Insert_Data,nullptr,nullptr);
重新编译并执行,执行完毕后重复上述步骤,使用sqlite指令打开meta.bd文件后,输入指令:select * from student;
在屏幕上打印表中所有数据:
可以看见之前的数据都添加成功了。
3-2-3.修改数据测试
还是先删除meta.bd文件,在mian中添加如下代码:
// 使用句柄向表 student 修改数据 // 1.构建修改数据的指令:吴凡 改名 吴不凡 char Modify_Data[] = "update student set name='吴不凡' where sn=2;"; // 2.使用exec接口执行这个指令 shelper.exec(Modify_Data,nullptr,nullptr);
编译并执行后打印表的内容:
吴凡 同学就变成 吴不凡 同学了。
3-2-4.删除数据测试
删除meta.bd文件,在main中添加如下代码:
// 使用句柄从表 student 删除数据 char Delete_Data[] = "delete from student where sn=3;"; shelper.exec(Delete_Data,nullptr,nullptr);
编译、执行、查看表内容:
毕业的 Eric 同学就从数据库中删除了。
3-2-5.获取数据测试
获取表中的数据时,它是以数组的形式返回,每个元素就是一条数据的一个属性,所以先创建一个std::vecotr<std::string>类型的变量,再设置一个回调函数,回调函数的作用是把数据的属性存储到变量中:
// 回调函数 int select_stu_callback(void* arg,int col_count,char** result,char** files_name) { std::vector<std::string> *arry = (std::vector<std::string>*)arg; std::string col_msg = result[0]; // 一条数据的sn col_msg+=result[1]; // 一条数据的name col_msg+= result[2]; // 一条数据的age arry->push_back(col_msg); return 0; // 返回0表示成功了 }
// 使用句柄从表 student 获取数据 std::vector<std::string> datas; char Select_Data[] = "select sn,name,age from student;"; shelper.exec(Select_Data,select_stu_callback,&datas); // 打印获取到的数据 for(auto& data:datas) { std::cout << data << std::endl; }
执行结果:
![]()
回调函数只处理了一条数据,那么为什么打印出来的是两条数据的内容呢?因为每有一条数据,回调函数就会被调用一次,它会一个一个的处理每条数据。
至此,SqliteHelper的测试就完毕了。
3-3.FileHelper测试
创建一个名为mq_file_test.cc的文件,打开并包含头文件:
#include "../mqcommon/mq_helper.hpp"
3-3-1.创建/打开文件测试
#include "../mqcommon/mq_helper.hpp" int main() { // 创建文件管理句柄 zd::FileHelper fhelper("./data/file"); // 使用句柄创建/打开文件 // 1.创建文件之前,先创建文件所需目录 // 1.1.获取目录 std::string dir = fhelper.parentDirectory(); // 1.2.创建目录 zd::FileHelper::createDirectory(dir); // 2.目录创建完成才能创建文件 fhelper.createFile(); return 0; }
编译上述代码并执行:
mq_file_test:mq_file_test.cc g++ -std=c++14 $^ -o $@
执行后当前目录应该出现data目录,data下有一个名为file的文件:
3-3-2.写数据测试
main函数新增以下代码:
// 使用句柄向文件写入数据 std::string data = "helloworld"; fhelper.write(data);
编译并执行后,打开file文件,里面已经有了写入的内容:
然后再测试一下向文件中间写入数据,因为每次打开/创建文件时,文件内容都会被清空,所以不需要每次都删除文件了。
新增的代码如下:
// 使用句柄向文件中间写入数据 std::string data1 = "12345"; fhelper.write(data1.c_str(),5,data1.size());
编译执行后,world正好5个字符就被覆盖了:
3-3-3.读取数据测试
main函数新增代码:
// 使用句柄从文件读取数据 std::string data2; fhelper.read(data2); // 使用句柄从文件中间读取数据 std::string data3; data3.resize(5); fhelper.read(&data3[0],5,5); // 从第六个字符开始读取,读取5个 // 打印读取到的数据 LOG("起始读取:%s",data2.c_str()); LOG("中间读取:%s",data3.c_str());
data2应该读取到整个文件的内容,而data3应该读取到文件最后的"12345",执行结果:
FileHelper的其他功能函数可以自己去测试,这里不再演示了,唯一需要注意的是重命名函数的参数需要带上路径,不然重命名后的文件就在mqtest路径下了。
3-4.UUIDHelper测试
UUIDHelper很好测试,只需要调用函数,打印出得到的随机id即可:
#include "../mqcommon/mq_helper.hpp" int main() { for(int i = 0;i < 100;i++) { LOG("获得的uuid: %s",zd::UUIDHelper::uuid().c_str()); } return 0; }
执行结果:
序列号也是正常的。
3-5.StrHelper测试
它的测试也很简单:
#include "../mqcommon/mq_helper.hpp" void Print(std::vector<std::string> words) { for(const auto& word:words) { std::cout << word << " "; } std::cout << std::endl; } int main() { std::vector<std::string> v1; std::vector<std::string> v2; std::vector<std::string> v3; std::vector<std::string> v4; zd::StrHelper::split("news.music.pop",".",v1); zd::StrHelper::split("news.music...pop",".",v2); zd::StrHelper::split("news.music.pop...",".",v3); zd::StrHelper::split("..news.music.pop",".",v4); Print(v1); Print(v2); Print(v3); Print(v4); return 0; }
结果也是符合预期的:
至此,五个辅助代码都测试完毕了。
下期预告:
下一章将完成最后一个辅助类——工作线程池。