目錄
日志打印工具
實用 Helper 工具
sqlite 基礎操作類
?字符串操作類
?UUID 生成器類
?文件基礎操作
文件是否存在判斷
文件大小獲取
讀文件
寫文件
文件重命名
文件創建/刪除
?父級目錄的獲取
目錄創建/刪除
附錄(完整代碼)
日志打印工具
????????為了便于編寫項目中能夠快速定位程序的錯誤位置,因此編寫一個日志輸出宏工具,進行簡單的日志打印。
- 定義DEBUG,INFO,ERROR三個日志等級。
- 日志格式:[日志等級][時間][文件][行號]? ? 日志信息。
#pragma once#include <iostream>
#include <ctime>
#include <string>namespace jiuqi
{
#define DEBUG_LEVEL 0
#define INFO_LEVEL 1
#define ERROR_LEVEL 2#define DEFAULT_LEVEL DEBUG_LEVEL#define LOG(level, fmt, ...) \{ \if (level >= DEFAULT_LEVEL) \{ \std::string level_str; \if (level == DEBUG_LEVEL) \level_str = "DEBUG"; \else if (level == INFO_LEVEL) \level_str = "INFO"; \else \level_str = "ERROR"; \time_t t = time(nullptr); \struct tm *tm = localtime(&t); \char time[32]; \strftime(time, 31, "%H:%M:%S", tm); \printf("[%s][%s][%s:%d]\t" fmt "\n", level_str.c_str(), time, __FILE__, __LINE__, ##__VA_ARGS__);\} \}#define DEBUG(fmt, ...) LOG(DEBUG_LEVEL, fmt, ##__VA_ARGS__)
#define INFO(fmt, ...) LOG(INFO_LEVEL, fmt, ##__VA_ARGS__)
#define ERROR(fmt, ...) LOG(ERROR_LEVEL, fmt, ##__VA_ARGS__)
}
實用 Helper 工具
????????Helper 工具類中要完成的是項目中需要的一些輔助零碎的功能代碼實現,其中包括文
件的基礎操作,字符串的額外操作等在項目中用到的零碎功能。
sqlite 基礎操作類
? ? ? ? 使用我們之前demo中編寫過的sqlite類即可,將輸出替換為日志打印。
class SqliteHelper{public:typedef int (*sqliteCallback)(void *, int, char **, char **);SqliteHelper(const std::string &dbfile): _dbfile(dbfile), _handler(nullptr) {}bool open(int safe_level = SQLITE_OPEN_FULLMUTEX){int ret = sqlite3_open_v2(_dbfile.c_str(), &_handler, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | safe_level, nullptr);if (ret != SQLITE_OK){// std::cerr << "打開/創建數據庫失敗: " << sqlite3_errmsg(_handler) << std::endl;ERROR("%s:%s", "打開/創建數據庫失敗", sqlite3_errmsg(_handler));return false;}return true;}bool exec(const std::string &sql, sqliteCallback callback, void *arg){int ret = sqlite3_exec(_handler, sql.c_str(), callback, arg, nullptr);if (ret != SQLITE_OK){// std::cerr << sql << std::endl;// std::cerr << "執行語句失敗: " << sqlite3_errmsg(_handler) << std::endl;ERROR("%s\n%s:%s", sql.c_str(), "執行語句失敗: ", sqlite3_errmsg(_handler));return false;}return true;}void close(){if (_handler)sqlite3_close_v2(_handler);}private:std::string _dbfile;sqlite3 *_handler;};
?字符串操作類
? ? ? ? 這里先實現字符串分割功能,主要是用于后續消息類型與隊列類型,即綁定信息匹配的問題。
class StrHelper{public:static size_t split(const std::string &str, const std::string &sep, std::vector<std::string> &result){size_t end = 0, start = 0;while (start < str.size()){end = str.find(sep, start);if (end == std::string::npos){result.push_back(str.substr(start));break;}if (end != start){result.push_back(str.substr(start, end - start));}start = end + sep.size();}return result.size();}};
說明:
- 以sep作為分割符將str分割,結果存放在result中。
- 返回分割子串的數量。
- 注意,如果出現連續的分割符,我們必須跳過,即不允許插入空串。
?UUID 生成器類
????????UUID(Universally Unique Identifier), 也叫通用唯一識別碼,通常由 32 位 16 進制數字字符組成。
????????在這里,uuid 生成,我們采用生成 8 個隨機數字,加上 8 字節序號,共 16 字節數組生成 32 位 16 進制字符的組合形式來確保全局唯一的同時能夠根據序號來分辨數據。
? ? ? ? 為了獲得隨機數與穩定遞增的序號(需支持高并發),我們介紹一下幾個對象:
- 隨機數:
-
std::random_device對象:生成機器隨機數,真隨機數,但是效率較低。
-
std::mt19937_64對象:64位梅森旋轉算法(Mersenne Twister)生成偽隨機數,可以使用std::random_device對象生成的隨機數作為種子。
-
std::uniform_int_distribution<int>對象:將隨機數引擎的輸出轉換為均勻分布的整數。
-
- 序號:
- 使用size_t的原子計數器,使用fetch_add接口可以獲得穩定遞增的序號。
class UUIDHelper{public:static std::string uuid(){std::random_device rd;std::mt19937_64 gernator(rd());std::uniform_int_distribution<int> distribution(0, 255);std::stringstream ss;for (int i = 1; i <= 8; i++){ss << std::setw(2) << std::setfill('0') << std::hex << distribution(gernator);if (i % 2 == 0)ss << '-';}static std::atomic<size_t> sep(1);size_t num = sep.fetch_add(1);for (int i = 7; i >= 0; i--){ss << std::setw(2) << std::setfill('0') << std::hex << ((num >> (i * 8)) & 0xff);if (i == 4)ss << '-';}return ss.str();}};
?文件基礎操作
? ? ? ? 我們將提供一系列文件操作,每個功能實現靜態和非靜態操作兩種。
文件是否存在判斷
bool exists(){struct stat st;return (stat(_filename.c_str(), &st) == 0);}static bool exists(const std::string &filename){struct stat st;return (stat(filename.c_str(), &st) == 0);}
????????使用stat接口可跨平臺。
文件大小獲取
size_t size(){struct stat st;int ret = stat(_filename.c_str(), &st);if (ret < 0)return 0;return st.st_size;}static size_t size(const std::string &filename){struct stat st;int ret = stat(filename.c_str(), &st);if (ret < 0)return 0;return st.st_size;}
? ? ? ? 同樣使用stat接口,文件大小會被存放在結構體st中。
讀文件
bool read(std::string &body){size_t fsize = this->size();return read(body, 0, fsize);}bool read(std::string &body, size_t offset, size_t len){// 打開文件std::ifstream ifs(_filename, std::ios::binary | std::ios::in);if (!ifs.is_open()){ERROR("%s:文件打開失敗", _filename.c_str());return false;}// 移動文件讀取位置ifs.seekg(offset, std::ios::beg);// 讀取文件body.resize(len);ifs.read(&body[0], len);if (!ifs.good()){ERROR("%s:文件讀取失敗", _filename.c_str());ifs.close();return false;}// 關閉文件返回ifs.close();return true;}static bool read(const std::string &filename, std::string &body){size_t fsize = size(filename);return read(filename, body, 0, fsize);}static bool read(const std::string &filename, std::string &body, size_t offset, size_t len){// 打開文件std::ifstream ifs(filename, std::ios::binary | std::ios::in);if (!ifs.is_open()){ERROR("%s:文件打開失敗", filename.c_str());return false;}// 移動文件讀取位置ifs.seekg(offset, std::ios::beg);// 讀取文件body.resize(len);ifs.read(&body[0], len);if (!ifs.good()){ERROR("%s:文件讀取失敗", filename.c_str());ifs.close();return false;}// 關閉文件返回ifs.close();return true;}
? ? ? ? 我們提供讀取文件全部內容與讀取文件特定位置特定長度內容的接口,有兩點需要注意:
- 每次讀取需要把讀取緩沖區(即body)resize,否則會導致讀取失敗。
- ifstream對象的read接口第一個參數是char*類型的,不能使用string對象的c_str接口(因為返回的是const char*),解決辦法是傳入body第一個字符的地址。
寫文件
bool write(const std::string &body){size_t fsize = this->size();return write(body.c_str(), fsize, body.size());}bool write(const char *body, size_t offset, size_t len){// 打開文件std::fstream fs(_filename, std::ios::binary | std::ios::out | std::ios::in);if (!fs.is_open()){ERROR("%s:文件打開失敗", _filename.c_str());return false;}// 移動文件寫入位置fs.seekp(offset, std::ios::beg);// 寫入文件fs.write(body, len);if (!fs.good()){ERROR("%s:文件讀取失敗", _filename.c_str());fs.close();return false;}fs.close();return true;}static bool write(const std::string &filename, const std::string &body){size_t fsize = size(filename);return write(filename, body.c_str(), fsize, body.size());}static bool write(const std::string &filename, const char *body, size_t offset, size_t len){// 打開文件std::fstream fs(filename, std::ios::binary | std::ios::out | std::ios::in);if (!fs.is_open()){ERROR("%s:文件打開失敗", filename.c_str());return false;}// 移動文件寫入位置fs.seekp(offset, std::ios::beg);// 寫入文件fs.write(body, len);if (!fs.good()){ERROR("%s:文件讀取失敗", filename.c_str());fs.close();return false;}fs.close();return true;}
? ? ? ? ?這里實現了追加寫入與在特定位置寫入的功能,但是在特定位置寫入會覆蓋原本的內容,暫時未實現真正的插入功能。
? ? ? ? 需要注意,因為要移動文件寫位置,必須以讀寫方式打開文件。
文件重命名
bool rename(const std::string new_name){return ::rename(_filename.c_str(), new_name.c_str());}static bool rename(const std::string old_name, const std::string new_name){return ::rename(old_name.c_str(), new_name.c_str());}
? ? ? ? 使用庫中的rename函數即可,一定要前加兩個冒號,聲明作用域。
文件創建/刪除
bool createFile(){return createFile(_filename);}static bool createFile(const std::string &filename){// 打開文件并立即關閉,創建空文件std::ofstream ofs(filename, std::ios::out);if (!ofs.is_open()){ERROR("%s:文件創建失敗", filename.c_str());return false;}ofs.close();return true;}bool removeFile(){return removeFile(_filename);}static bool removeFile(const std::string &filename){return (::remove(filename.c_str()) == 0);}
- 文件創建:ofstrea的out打開模式:如果文件不存在就會創建。
- 文件刪除,調用庫中的remove接口即可。
?父級目錄的獲取
std::string parentDirectory(){return parentDirectory(_filename);}static std::string parentDirectory(const std::string &filename){size_t pos = filename.find_last_of("/\\");if (pos == std::string::npos)return ".";std::string path = filename.substr(0, pos);return path;}
? ? ? ? 尋找最后一個文件目錄分割符即可,找不到就返回當前目錄。
目錄創建/刪除
bool createDirectory(){return createDirectory(parentDirectory(_filename));}static bool createDirectory(const std::string &path){size_t start = 0, end = 0;while (start < path.size()){end = path.find_first_of("/\\", start);if (end == std::string::npos){
#ifdef _WIN32int ret = _mkdir(path.c_str());
#elseint ret = mkdir(path.c_str(), 0775);
#endifif (ret != 0 && errno != EEXIST){ERROR("%s:目錄創建失敗,錯誤碼: %d", path.c_str(), errno);return false;}break;}if (end == start){start += 1;continue;}std::string subpath = path.substr(0, end);if (exists(path)){start = end + 1;continue;}if (!subpath.empty()){
#ifdef _WIN32int ret = _mkdir(subpath.c_str());
#elseint ret = mkdir(subpath.c_str(), 0775);
#endifif (ret != 0 && errno != EEXIST){ERROR("%s:目錄創建失敗,錯誤碼: %d", subpath.c_str(), errno);return false;}}start = end + 1;}return true;}bool removeDirectory(){return removeDirectory(parentDirectory(_filename));}static bool removeDirectory(const std::string &path){std::string cmd = "rm -rf " + path;return (system(cmd.c_str()) != -1);}
- 目錄的創建:需要先創建父級目錄才可以創建當前目錄,注意如果遇到連續的文件目錄分割符需要跳過。
- 目錄的刪除:調用系統命令即可。
附錄(完整代碼)
#pragma once#include <sqlite3.h>
#include <iostream>
#include <string>
#include <vector>
#include <random>
#include <sstream>
#include <fstream>
#include <atomic>
#include <sys/stat.h>
#include <iomanip>#ifdef _WIN32#include <direct.h>
#endif#include "log.hpp"namespace jiuqi
{class SqliteHelper{public:typedef int (*sqliteCallback)(void *, int, char **, char **);SqliteHelper(const std::string &dbfile): _dbfile(dbfile), _handler(nullptr) {}bool open(int safe_level = SQLITE_OPEN_FULLMUTEX){int ret = sqlite3_open_v2(_dbfile.c_str(), &_handler, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | safe_level, nullptr);if (ret != SQLITE_OK){// std::cerr << "打開/創建數據庫失敗: " << sqlite3_errmsg(_handler) << std::endl;ERROR("%s:%s", "打開/創建數據庫失敗", sqlite3_errmsg(_handler));return false;}return true;}bool exec(const std::string &sql, sqliteCallback callback, void *arg){int ret = sqlite3_exec(_handler, sql.c_str(), callback, arg, nullptr);if (ret != SQLITE_OK){// std::cerr << sql << std::endl;// std::cerr << "執行語句失敗: " << sqlite3_errmsg(_handler) << std::endl;ERROR("%s\n%s:%s", sql.c_str(), "執行語句失敗: ", sqlite3_errmsg(_handler));return false;}return true;}void close(){if (_handler)sqlite3_close_v2(_handler);}private:std::string _dbfile;sqlite3 *_handler;};class StrHelper{public:static size_t split(const std::string &str, const std::string &sep, std::vector<std::string> &result){size_t end = 0, start = 0;while (start < str.size()){end = str.find(sep, start);if (end == std::string::npos){result.push_back(str.substr(start));break;}if (end != start){result.push_back(str.substr(start, end - start));}start = end + sep.size();}return result.size();}};class UUIDHelper{public:static std::string uuid(){std::random_device rd;std::mt19937_64 gernator(rd());std::uniform_int_distribution<int> distribution(0, 255);std::stringstream ss;for (int i = 1; i <= 8; i++){ss << std::setw(2) << std::setfill('0') << std::hex << distribution(gernator);if (i % 2 == 0)ss << '-';}static std::atomic<size_t> sep(1);size_t num = sep.fetch_add(1);for (int i = 7; i >= 0; i--){ss << std::setw(2) << std::setfill('0') << std::hex << ((num >> (i * 8)) & 0xff);if (i == 4)ss << '-';}return ss.str();}};class FileHelper{public:FileHelper(const std::string &filename) : _filename(filename) {}bool exists(){struct stat st;return (stat(_filename.c_str(), &st) == 0);}static bool exists(const std::string &filename){struct stat st;return (stat(filename.c_str(), &st) == 0);}size_t size(){struct stat st;int ret = stat(_filename.c_str(), &st);if (ret < 0)return 0;return st.st_size;}static size_t size(const std::string &filename){struct stat st;int ret = stat(filename.c_str(), &st);if (ret < 0)return 0;return st.st_size;}bool read(std::string &body){size_t fsize = this->size();return read(body, 0, fsize);}bool read(std::string &body, size_t offset, size_t len){// 打開文件std::ifstream ifs(_filename, std::ios::binary | std::ios::in);if (!ifs.is_open()){ERROR("%s:文件打開失敗", _filename.c_str());return false;}// 移動文件讀取位置ifs.seekg(offset, std::ios::beg);// 讀取文件body.resize(len);ifs.read(&body[0], len);if (!ifs.good()){ERROR("%s:文件讀取失敗", _filename.c_str());ifs.close();return false;}// 關閉文件返回ifs.close();return true;}static bool read(const std::string &filename, std::string &body){size_t fsize = size(filename);return read(filename, body, 0, fsize);}static bool read(const std::string &filename, std::string &body, size_t offset, size_t len){// 打開文件std::ifstream ifs(filename, std::ios::binary | std::ios::in);if (!ifs.is_open()){ERROR("%s:文件打開失敗", filename.c_str());return false;}// 移動文件讀取位置ifs.seekg(offset, std::ios::beg);// 讀取文件body.resize(len);ifs.read(&body[0], len);if (!ifs.good()){ERROR("%s:文件讀取失敗", filename.c_str());ifs.close();return false;}// 關閉文件返回ifs.close();return true;}bool write(const std::string &body){size_t fsize = this->size();return write(body.c_str(), fsize, body.size());}bool write(const char *body, size_t offset, size_t len){// 打開文件std::fstream fs(_filename, std::ios::binary | std::ios::out | std::ios::in);if (!fs.is_open()){ERROR("%s:文件打開失敗", _filename.c_str());return false;}// 移動文件寫入位置fs.seekp(offset, std::ios::beg);// 寫入文件fs.write(body, len);if (!fs.good()){ERROR("%s:文件讀取失敗", _filename.c_str());fs.close();return false;}fs.close();return true;}static bool write(const std::string &filename, const std::string &body){size_t fsize = size(filename);return write(filename, body.c_str(), fsize, body.size());}static bool write(const std::string &filename, const char *body, size_t offset, size_t len){// 打開文件std::fstream fs(filename, std::ios::binary | std::ios::out | std::ios::in);if (!fs.is_open()){ERROR("%s:文件打開失敗", filename.c_str());return false;}// 移動文件寫入位置fs.seekp(offset, std::ios::beg);// 寫入文件fs.write(body, len);if (!fs.good()){ERROR("%s:文件讀取失敗", filename.c_str());fs.close();return false;}fs.close();return true;}bool rename(const std::string new_name){return ::rename(_filename.c_str(), new_name.c_str());}static bool rename(const std::string old_name, const std::string new_name){return ::rename(old_name.c_str(), new_name.c_str());}bool createFile(){return createFile(_filename);}static bool createFile(const std::string &filename){// 打開文件并立即關閉,創建空文件std::ofstream ofs(filename, std::ios::out);if (!ofs.is_open()){ERROR("%s:文件創建失敗", filename.c_str());return false;}ofs.close();return true;}bool removeFile(){return removeFile(_filename);}static bool removeFile(const std::string &filename){return (::remove(filename.c_str()) == 0);}bool createDirectory(){return createDirectory(parentDirectory(_filename));}static bool createDirectory(const std::string &path){size_t start = 0, end = 0;while (start < path.size()){end = path.find_first_of("/\\", start);if (end == std::string::npos){
#ifdef _WIN32int ret = _mkdir(path.c_str());
#elseint ret = mkdir(path.c_str(), 0775);
#endifif (ret != 0 && errno != EEXIST){ERROR("%s:目錄創建失敗,錯誤碼: %d", path.c_str(), errno);return false;}break;}if (end == start){start += 1;continue;}std::string subpath = path.substr(0, end);if (exists(path)){start = end + 1;continue;}if (!subpath.empty()){
#ifdef _WIN32int ret = _mkdir(subpath.c_str());
#elseint ret = mkdir(subpath.c_str(), 0775);
#endifif (ret != 0 && errno != EEXIST){ERROR("%s:目錄創建失敗,錯誤碼: %d", subpath.c_str(), errno);return false;}}start = end + 1;}return true;}bool removeDirectory(){return removeDirectory(parentDirectory(_filename));}static bool removeDirectory(const std::string &path){std::string cmd = "rm -rf " + path;return (system(cmd.c_str()) != -1);}std::string parentDirectory(){return parentDirectory(_filename);}static std::string parentDirectory(const std::string &filename){size_t pos = filename.find_last_of("/\\");if (pos == std::string::npos)return ".";std::string path = filename.substr(0, pos);return path;}private:std::string _filename;};
}