微服务即时通信系统---(七)文件管理子服务
- 开源代码
- 2025-09-21 02:03:01

目录
功能设计
模块划分
业务接口/功能示意图
服务实现流程
服务代码实现
封装文件操作模块(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:
// 生成一个唯一标识ID std::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-dddd ss << 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、文件下载信息:存储文
微服务即时通信系统---(七)文件管理子服务由讯客互联开源代码栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“微服务即时通信系统---(七)文件管理子服务”