目錄
功能設計
模塊劃分
業務接口/功能示意圖
服務實現流程
服務代碼實現
封裝文件操作模塊(utils.hpp)
獲取唯一標識ID
文件讀操作
文件寫操作
編寫proto文件
文件元信息
文件管理proto
單文件上傳
多文件上傳
單文件下載
多文件下載
RPC調用
服務端創建子類(FileManageServiceImpl)完成RPC服務調用函數重寫
SingleFileUp(單文件上傳)
MultiFileUp(多文件上傳)
SingleFileDown(單文件下載)
MultiFileDown(多文件下載)
RPC服務端代碼(總)
服務端完成文件管理子服務類(FileManageServer)
注意
實例化服務類對象,啟動服務
工程系統構建配置文件(CMakeLists.txt)
服務測試
本章節,主要對項目中文件管理子服務模塊進行分析、開發與測試。
功能設計
文件管理子服務,主要提供兩個功能:文件的上傳和文件的下載,因此,文件管理子服務主要提供4個功能性接口:
1、單個文件的上傳:主要用于后臺,將收到的文件消息進行存儲。
2、多個文件的上傳:主要用于后臺,將收到的文件消息進行存儲。
3、單個文件的下載:在后臺用于獲取頭像文件數據,以及客戶端用于獲取文件數。
4、多個文件的下載:在后臺用于大批量獲取頭像文件數據,以及前端的批量文件下載。
模塊劃分
參數/配置文件解析模塊 | 基于gflags框架直接使用,進行參數/配置文件的解析。 |
日志模塊 | 基于spdlog封裝的logger 直接進行日志輸出。 |
服務注冊模塊 | 基于etcd框架封裝的注冊模塊 直接進行文件管理子服務模塊的服務注冊。 |
RPC服務模塊 | 基于brpc框架 搭建文件管理子服務的RPC服務器。 |
文件操作模塊 | 基于標準庫的文件流操作實現文件的讀寫封裝,用于文件操作。 |
業務接口/功能示意圖
文件上傳:
文件下載/獲取:
服務實現流程
1、實現文件操作模塊的封裝(utils.hpp),其中包括 文件讀操作、文件寫操作,外加一個獲取唯一標識ID的操作(用于用戶ID、文件ID等)。 |
2、編寫服務所需的proto文件,利用protoc工具生成RPC服務器所需的.pb.h 和 .pb.cc 項目文件。 |
3、服務端 創建子類,繼承于proto文件中RPC調用類,并進行功能性接口函數重寫。 |
4、服務端 完成文件管理子服務類。 |
5、實例化 服務類對象,啟動服務。 |
服務代碼實現
封裝文件操作模塊(utils.hpp)
獲取唯一標識ID
在代碼中,文件ID、用戶ID 或者是 會話ID 都由此處操作來獲取。
這里使用16個隨機的字符串 組成這個唯一的標識ID。
實現思想:
1、先生成6個 0 ~ 255 內的隨機數字,而1 個 字節,為 8位。再將這8位,分成4 4 位,每4位轉換成1個16進制數字,從而 1個隨機數字 轉換成 2個 16位數字。至此,得到12 位 隨機16進制字符。
2、再通過一個 靜態變量,生成一個2 字節的 編號數字,同樣 轉換成 4 個 16位數字。至此,得到4位 隨機16進制字符。
3、將1和2進行拼接,得到16個隨機的字符串。
utils.hpp:
// 生成一個唯一標識IDstd::string uuid(){// 1. 生成12位16進制字符std::random_device rd; // 實例化設備隨機數對象, 用于生成設備隨機數(唯一性更強)std::mt19937 generator(rd()); // 以設備隨機數為種子, 實例化隨機數對象(mt19937:一種生成隨機數的方式)std::uniform_int_distribution<int> distribution(0, 255); // 限定生成隨機數的范圍std::stringstream ss;for (int i = 0; i < 6; ++i){if (i == 2)ss << "-"; // 添加-, 最終形式為: xxxx-yyyy-zzzz-ddddss << std::setw(2) << std::setfill('0') << std::hex << distribution(generator);// distribution(generator) : 生成一個 0-255的隨機數// std::hex + std::setw(2) : 轉換為 2個 16進制數// std::setfill('0'): 不足的,前面用0填充}// 2. 通過靜態變量生成 4位 16進制字符ss << "-";static std::atomic<short> idx(0);short tmp = idx.fetch_add(1);ss << std::setw(4) << std::setfill('0') << std::hex << tmp;return ss.str();}
文件讀操作
通過傳入文件名 和 承接文件內容的string,用來獲取文件內容。
實現思想:
1、根據文件名打開文件。
2、跳轉文件內部指針,獲取文件指針偏移量(文件大小)。
3、再將文件內部指針跳轉開頭,進行讀取文件內容。
4、關閉文件。
utils.hpp:
// 讀取文件bool ReadFile(const std::string &file_name, std::string &body){std::ifstream ifs(file_name, std::ios::in | std::ios::binary);if (ifs.is_open() == false){LOG_ERROR("打開文件失敗, file_name: {}", file_name);return false;}ifs.seekg(0, std::ios::end);size_t file_size = ifs.tellg();ifs.seekg(0, std::ios::beg);body.resize(file_size);ifs.read(&body[0], file_size);if (ifs.good() == false){LOG_ERROR("讀取文件失敗, file_name: {}", file_name);ifs.close();return false;}ifs.close();return true;}
文件寫操作
通過傳入文件名 和 想要寫入的內容,用來向文件寫入數據。
實現思想:
1、根據文件名打開文件。
2、寫入數據。
3、關閉文件。
utils.hpp:
// 寫入文件bool WriteFile(const std::string &file_name, const std::string &body){std::ofstream ofs(file_name, std::ios::out | std::ios::binary | std::ios::trunc); // 覆蓋式寫入if (ofs.is_open() == false){LOG_ERROR("打開文件失敗, file_name: {}", file_name);return false;}ofs.write(body.c_str(), body.size());if (ofs.good() == false){LOG_ERROR("寫入文件失敗, file_name: {}", file_name);ofs.close();return false;}ofs.close();return true;}
編寫proto文件
文件元信息
首先對于文件來說,不光需要編寫文件的上傳/下載的proto文件,文件還需要有它的元信息(文件ID、文件名稱、文件大小、文件內容),并且后續用戶發送的消息里面,也可能是文件,需要我們進行識別,所以將文件的元信息,單獨放在一個proto文件里面(后續用戶元信息、會話元信息、圖像元信息、語音元信息、字符串消息元信息都放在里面)。統稱為 base.proto
文件元信息(FileInfo)成員:
1、file_id:文件ID。
2、file_size:文件大小。
3、file_name:文件名稱。
4、file_content:文件內容。
// ------文件元信息------
message FileInfo
{optional string file_id = 1;optional int64 file_size = 2;optional string file_name = 3;optional bytes file_content = 4;
};
考慮到多文件上傳/下載需要repeated的相同信息,所以將文件的上傳和下載所需要的信息也放進來。
// ------文件元信息 + 文件上傳/下載信息------
message FileInfo
{optional string file_id = 1;optional int64 file_size = 2;optional string file_name = 3;optional bytes file_content = 4;
};
message FileUpInfo
{string file_name = 1;int64 file_size = 2;bytes file_content = 3;
};
message FileDownInfo
{string file_id = 1;bytes file_content = 2;
};
文件管理proto
既然文件管理模塊有4個功能性接口,那么就有4個對應的請求與響應結構,以及最終的PRC調用(fileManage.proto)。
單文件上傳
SingleFileUpReq包含成員:
1、請求ID:標識請求的唯一性。
2、文件上傳信息:存儲文件上傳所需信息(文件名、文件大小、文件內容)。
3、用戶ID(optional):標明來自哪個用戶。
4、會話ID(optional):標明來自哪個會話。
SingleFileUpResp包含成員:
1、請求ID:對應請求中的請求ID,標識請求唯一性。
2、成功標識:標識該次請求的處理結果。
3、錯誤信息(optional):如果處理出錯,記錄出錯信息。
4、文件元信息:存儲文件元信息(文件ID、文件大小、文件名、文件內容)。
// ------單文件上傳------
message SingleFileUpReq
{string req_id = 1;FileUpInfo file_up_info = 2;optional string user_id = 3;optional string session_id = 4;
};
message SingleFileUpResp
{string req_id = 1;bool success = 2;optional string err_msg = 3;optional FileInfo file_info = 4;
};
多文件上傳
多文件上傳和單文件上傳沒啥不同的,就是里面的文件東西,由列表來構成。
// ------多文件上傳------
message MultiFileUpReq
{string req_id = 1;repeated FileUpInfo file_up_info_list = 2;optional string user_id = 3;optional string session_id = 4;
};
message MultiFileUpResp
{string req_id = 1;bool success = 2;optional string err_msg = 3;repeated FileInfo file_info_list = 4;
};
單文件下載
SingleFileDownReq包含成員:
1、請求ID:標識請求的唯一性。
2、文件ID:根據文件ID才能找到文件。
3、用戶ID(optional):標明來自哪個用戶。
4、會話ID(optional):標明來自哪個會話。
SingleFileDownResp包含成員:
1、請求ID:對應請求中的請求ID,標識請求唯一性。
2、成功標識:標識該次請求的處理結果。
3、錯誤信息(optional):如果處理出錯,記錄出錯信息。
4、文件下載信息:存儲文