当前位置: 首页 > news >正文

ChatIm项目文件上传与获取

文章目录

  • 前言
  • 一、实现
    • 1.准备工作
    • 2. 获取单个文件
    • 3. 获取多个文件
    • 4. 写入单个文件
    • 5. 写入多个文件
  • 二、proto文件
    • 1. base.proto
    • 2. file.proto
  • 三、完整代码
    • 1. .cc
    • 2. .hpp


前言

这是一个ChatIM的项目,有兴趣的伙伴可以照着我的博客和gitee进行完成
gitee链接:https://gitee.com/qi-haozhe/chat-im

一、实现

文件上传和获取服务,比如客户端要设置自己的头像的话就需要上传一张图片,然后服务器就需要把这个图片文件保存起来,即文件的上传(保存)。当用户登录的时候首先要把头像这个文件从服务器传到客户端然后进行获取(加载),即文件的获取。

所以file部分主要就俩功能,文件的上传和获取。其中再细分一下就是分为单文件、多文件的上传和获取,共四个功能。体现在file.proto的四个远程调用函数。

1.准备工作

先继承一波protobuf生成的类ymm_im::FileService实现对应的四个远程调用函数。
有一个成员函数是std::string _storage_path;是文件路径,决定把文件保存到哪里,也决定去哪个路径下寻找文件。
构造函数里设置个文件掩码,然后创建个文件夹。

class FileServiceImpl : public ymm_im::FileService
{
public:FileServiceImpl(const std::string &storage_path): _storage_path(storage_path){umask(0);mkdir(storage_path.c_str(), 0775);if (_storage_path.back() != '/')_storage_path.push_back('/');}
private:std::string _storage_path;
};

2. 获取单个文件

首先先把request中的请求id设置到response中去response->set_request_id(request->request_id());,然后request中会有文件的id,获取文件id之后,根据文件的id去指定的目录读取文件读到body中,把body设置到response中就算是获取完毕了。

message GetSingleFileReq {string request_id = 1;string file_id = 2;optional string user_id = 3;optional string session_id = 4;
}
message GetSingleFileRsp {string request_id = 1;bool success = 2;string errmsg = 3; optional FileDownloadData file_data = 4;
}

FileDownloadData定义如下,就是个文件id和bytes类型的文件内容。

message FileDownloadData {string file_id = 1;bytes file_content = 2;
}
// 读取单个文件
void GetSingleFile(google::protobuf::RpcController *controller,const ::ymm_im::GetSingleFileReq *request,::ymm_im::GetSingleFileRsp *response,::google::protobuf::Closure *done)
{brpc::ClosureGuard rpc_guard(done);response->set_request_id(request->request_id());// 1. 取出请求中的文件ID(起始就是文件名)std::string fid = request->file_id();std::string filename = _storage_path + fid;// 2. 将文件ID作为文件名,读取文件数据std::string body;bool ret = readFile(filename, body);if (ret == false){response->set_success(false);response->set_errmsg("读取文件数据失败!");LOG_ERROR("{} 读取文件数据失败!", request->request_id());return;}// 3. 组织响应response->set_success(true);response->mutable_file_data()->set_file_id(fid);response->mutable_file_data()->set_file_content(body);
}

readFile在util.hpp中,实现如下:

bool readFile(const std::string &filename, std::string &body){//实现读取一个文件的所有数据,放入body中std::ifstream ifs(filename, std::ios::binary | std::ios::in);if (ifs.is_open() == false) {LOG_ERROR("打开文件 {} 失败!", filename);return false;}ifs.seekg(0, std::ios::end);//跳转到文件末尾size_t flen = ifs.tellg();  //获取当前偏移量-- 文件大小ifs.seekg(0, std::ios::beg);//跳转到文件起始body.resize(flen);ifs.read(&body[0], flen);if (ifs.good() == false) {LOG_ERROR("读取文件 {} 数据失败!", filename);ifs.close();return false;}ifs.close();return true;
}
bool writeFile(const std::string &filename, const std::string &body){//实现将body中的数据,写入filename对应的文件中std::ofstream ofs(filename, std::ios::out | std::ios::binary | std::ios::trunc);if (ofs.is_open() == false) {LOG_ERROR("打开文件 {} 失败!", filename);return false;}ofs.write(body.c_str(), body.size());if (ofs.good() == false) {LOG_ERROR("读取文件 {} 数据失败!", filename);ofs.close();return false;}ofs.close();return true;
}

3. 获取多个文件

老样子,先把请求id设置到response中去。然后请求req中有个repeated string file_id_list = 4;,这是个数组,里面有获取所有文件所需要的文件名。然后一个for循环按获取单个文件的套路依次把文件读取到body中,然后设置到response中的file_data就好了,这是个map, map<string, FileDownloadData> file_data = 4;存的是文件id,和对应的内容。
FileDownloadData定义如下,就是个文件id和bytes类型的文件内容。

message FileDownloadData {string file_id = 1;bytes file_content = 2;
}
message GetMultiFileReq {string request_id = 1;optional string user_id = 2;optional string session_id = 3;repeated string file_id_list = 4;
}
message GetMultiFileRsp {string request_id = 1;bool success = 2;string errmsg = 3; map<string, FileDownloadData> file_data = 4;//文件ID与文件数据的映射map
}
// 读取多个文件
void GetMultiFile(google::protobuf::RpcController *controller,const ::ymm_im::GetMultiFileReq *request,::ymm_im::GetMultiFileRsp *response,::google::protobuf::Closure *done)
{brpc::ClosureGuard rpc_guard(done);response->set_request_id(request->request_id());// 循环取出请求中的文件ID,读取文件数据进行填充for (int i = 0; i < request->file_id_list_size(); i++){std::string fid = request->file_id_list(i);std::string filename = _storage_path + fid;std::string body;bool ret = readFile(filename, body);if (ret == false){response->set_success(false);response->set_errmsg("读取文件数据失败!");LOG_ERROR("{} 读取文件数据失败!", request->request_id());return;}ymm_im::FileDownloadData data;data.set_file_id(fid);data.set_file_content(body);response->mutable_file_data()->insert({fid, data});}response->set_success(true);
}

4. 写入单个文件

先把请求id设置了,然后生成一个uuid作为这个文件的名字,设置好保存路径即filename。比如filename可以是./data/文件名,表示把这个文件保存到./data/路径下。

然后把req中的FileUploadData file_data = 4;中的file_data中的content数据写入到指定位置。
然后把response中的一些字段设置好就ok。

message PutSingleFileReq {string request_id = 1; //请求ID,作为处理流程唯一标识optional string user_id = 2;optional string session_id = 3;FileUploadData file_data = 4;
}
message PutSingleFileRsp {string request_id = 1;bool success = 2;string errmsg = 3;FileMessageInfo file_info = 4; //返回了文件组织的元信息
}
message FileUploadData {string file_name = 1;   //文件名称int64 file_size = 2;    //文件大小bytes file_content = 3; //文件数据
}
message FileMessageInfo {optional string file_id = 1;//文件id,客户端发送的时候不用设置optional int64 file_size = 2;//文件大小optional string file_name = 3;//文件名称//文件数据,在ES中存储消息的时候只要id和元信息,不要文件数据, 服务端转发的时候也不需要填充optional bytes file_contents = 4;
}
// 写入单个文件
void PutSingleFile(google::protobuf::RpcController *controller,const ::ymm_im::PutSingleFileReq *request,::ymm_im::PutSingleFileRsp *response,::google::protobuf::Closure *done)
{brpc::ClosureGuard rpc_guard(done);response->set_request_id(request->request_id());// 1. 为文件生成一个唯一uudi作为文件名 以及 文件IDstd::string fid = uuid();std::string filename = _storage_path + fid;// 2. 取出请求中的文件数据,进行文件数据写入bool ret = writeFile(filename, request->file_data().file_content());if (ret == false){response->set_success(false);response->set_errmsg("读取文件数据失败!");LOG_ERROR("{} 写入文件数据失败!", request->request_id());return;}// 3. 组织响应response->set_success(true);response->mutable_file_info()->set_file_id(fid);response->mutable_file_info()->set_file_size(request->file_data().file_size());response->mutable_file_info()->set_file_name(request->file_data().file_name());
}

5. 写入多个文件

也是来个for循环多次执行写入单个文件的流程就好了。

 // 写入多个文件
void PutMultiFile(google::protobuf::RpcController *controller,const ::ymm_im::PutMultiFileReq *request,::ymm_im::PutMultiFileRsp *response,::google::protobuf::Closure *done)
{brpc::ClosureGuard rpc_guard(done);response->set_request_id(request->request_id());for (int i = 0; i < request->file_data_size(); i++){std::string fid = uuid();std::string filename = _storage_path + fid;bool ret = writeFile(filename, request->file_data(i).file_content());if (ret == false){response->set_success(false);response->set_errmsg("读取文件数据失败!");LOG_ERROR("{} 写入文件数据失败!", request->request_id());return;}ymm_im::FileMessageInfo *info = response->add_file_info();info->set_file_id(fid);info->set_file_size(request->file_data(i).file_size());info->set_file_name(request->file_data(i).file_name());}response->set_success(true);
}

二、proto文件

1. base.proto

syntax = "proto3";
package ymm_im;
option cc_generic_services = true;//用户信息结构
message UserInfo {string user_id = 1;//用户IDstring nickname = 2;//昵称string description = 3;//个人签名/描述string phone = 4; //绑定手机号bytes  avatar = 5;//头像照片,文件内容使用二进制
}//聊天会话信息
message ChatSessionInfo {//群聊会话不需要设置,单聊会话设置为对方用户IDoptional string single_chat_friend_id = 1;string chat_session_id = 2; //会话IDstring chat_session_name = 3;//会话名称git //会话上一条消息,新建的会话没有最新消息optional MessageInfo prev_message = 4;//会话头像 --群聊会话不需要,直接由前端固定渲染,单聊就是对方的头像optional bytes avatar = 5;
}//消息类型
enum MessageType {STRING = 0;IMAGE = 1;FILE = 2;SPEECH = 3;
}
message StringMessageInfo {string content = 1;//文字聊天内容
}
message ImageMessageInfo {//图片文件id,客户端发送的时候不用设置,由transmit服务器进行设置后交给storage的时候设置optional string file_id = 1;//图片数据,在ES中存储消息的时候只要id不要文件数据, 服务端转发的时候需要原样转发optional bytes image_content = 2;
}
message FileMessageInfo {optional string file_id = 1;//文件id,客户端发送的时候不用设置optional int64 file_size = 2;//文件大小optional string file_name = 3;//文件名称//文件数据,在ES中存储消息的时候只要id和元信息,不要文件数据, 服务端转发的时候也不需要填充optional bytes file_contents = 4;
}
message SpeechMessageInfo {//语音文件id,客户端发送的时候不用设置optional string file_id = 1;//文件数据,在ES中存储消息的时候只要id不要文件数据, 服务端转发的时候也不需要填充optional bytes file_contents = 2;
}
message MessageContent {MessageType message_type = 1; //消息类型oneof msg_content {StringMessageInfo string_message = 2;//文字消息FileMessageInfo file_message = 3;//文件消息SpeechMessageInfo speech_message = 4;//语音消息ImageMessageInfo image_message = 5;//图片消息};
}
//消息结构
message MessageInfo {string message_id = 1;//消息IDstring chat_session_id = 2;//消息所属聊天会话IDint64 timestamp = 3;//消息产生时间UserInfo sender = 4;//消息发送者信息MessageContent message = 5;
}message FileDownloadData {string file_id = 1;bytes file_content = 2;
}message FileUploadData {string file_name = 1;   //文件名称int64 file_size = 2;    //文件大小bytes file_content = 3; //文件数据
}

2. file.proto

syntax = "proto3";
package ymm_im;
import "base.proto";option cc_generic_services = true;message GetSingleFileReq {string request_id = 1;string file_id = 2;optional string user_id = 3;optional string session_id = 4;
}
message GetSingleFileRsp {string request_id = 1;bool success = 2;string errmsg = 3; optional FileDownloadData file_data = 4;
}message GetMultiFileReq {string request_id = 1;optional string user_id = 2;optional string session_id = 3;repeated string file_id_list = 4;
}
message GetMultiFileRsp {string request_id = 1;bool success = 2;string errmsg = 3; map<string, FileDownloadData> file_data = 4;//文件ID与文件数据的映射map
}message PutSingleFileReq {string request_id = 1; //请求ID,作为处理流程唯一标识optional string user_id = 2;optional string session_id = 3;FileUploadData file_data = 4;
}
message PutSingleFileRsp {string request_id = 1;bool success = 2;string errmsg = 3;FileMessageInfo file_info = 4; //返回了文件组织的元信息
}message PutMultiFileReq {string request_id = 1;optional string user_id = 2;optional string session_id = 3;repeated FileUploadData file_data = 4;
}
message PutMultiFileRsp {string request_id = 1;bool success = 2;string errmsg = 3; repeated FileMessageInfo file_info = 4;
}service FileService {rpc GetSingleFile(GetSingleFileReq) returns (GetSingleFileRsp);rpc GetMultiFile(GetMultiFileReq) returns (GetMultiFileRsp);rpc PutSingleFile(PutSingleFileReq) returns (PutSingleFileRsp);rpc PutMultiFile(PutMultiFileReq) returns (PutMultiFileRsp);
}

三、完整代码

1. .cc

/*** @file file_server.cc* @brief 1. 参数解析 2. 日志初始化 3. 构造服务器对象,启动服务器* @author qhz (2695432062@qq.com)*/
#include "file_server.hpp"DEFINE_bool(run_mode, false, "程序的运行模式,false-调试; true-发布;");
DEFINE_string(log_file, "", "发布模式下,用于指定日志的输出文件");
DEFINE_int32(log_level, 0, "发布模式下,用于指定日志输出等级");DEFINE_string(registry_host, "http://127.0.0.1:2379", "服务注册中心地址");
DEFINE_string(base_service, "/service", "服务监控根目录");
DEFINE_string(instance_name, "/file_service/instance", "当前实例名称");
DEFINE_string(access_host, "127.0.0.1:10002", "当前实例的外部访问地址");DEFINE_string(storage_path, "./data/", "当前实例的外部访问地址");DEFINE_int32(listen_port, 10002, "Rpc服务器监听端口");
DEFINE_int32(rpc_timeout, -1, "Rpc调用超时时间");
DEFINE_int32(rpc_threads, 1, "Rpc的IO线程数量");int main(int argc, char *argv[])
{google::ParseCommandLineFlags(&argc, &argv, true);im::init_logger(FLAGS_run_mode, FLAGS_log_file, FLAGS_log_level);im::FileServerBuilder fsb;fsb.make_rpc_server(FLAGS_listen_port, FLAGS_rpc_timeout, FLAGS_rpc_threads, FLAGS_storage_path);fsb.make_reg_object(FLAGS_registry_host, FLAGS_base_service + FLAGS_instance_name, FLAGS_access_host);auto server = fsb.build();server->start();return 0;
}

2. .hpp

/*** @file file_server.hpp* @brief 构造文件服务的服务器,依赖于建造者模式* @author qhz (2695432062@qq.com)*/
#include <brpc/server.h>
#include <butil/logging.h>#include "etcd.hpp"   // 服务注册模块封装
#include "logger.hpp" // 日志模块封装
#include "utils.hpp"
#include "base.pb.h"
#include "file.pb.h"namespace im
{class FileServiceImpl : public ymm_im::FileService{public:FileServiceImpl(const std::string &storage_path): _storage_path(storage_path){umask(0);mkdir(storage_path.c_str(), 0775);if (_storage_path.back() != '/')_storage_path.push_back('/');}~FileServiceImpl(){}// 读取单个文件void GetSingleFile(google::protobuf::RpcController *controller,const ::ymm_im::GetSingleFileReq *request,::ymm_im::GetSingleFileRsp *response,::google::protobuf::Closure *done){brpc::ClosureGuard rpc_guard(done);response->set_request_id(request->request_id());// 1. 取出请求中的文件ID(起始就是文件名)std::string fid = request->file_id();std::string filename = _storage_path + fid;// 2. 将文件ID作为文件名,读取文件数据std::string body;bool ret = readFile(filename, body);if (ret == false){response->set_success(false);response->set_errmsg("读取文件数据失败!");LOG_ERROR("{} 读取文件数据失败!", request->request_id());return;}// 3. 组织响应response->set_success(true);response->mutable_file_data()->set_file_id(fid);response->mutable_file_data()->set_file_content(body);}// 读取多个文件void GetMultiFile(google::protobuf::RpcController *controller,const ::ymm_im::GetMultiFileReq *request,::ymm_im::GetMultiFileRsp *response,::google::protobuf::Closure *done){brpc::ClosureGuard rpc_guard(done);response->set_request_id(request->request_id());// 循环取出请求中的文件ID,读取文件数据进行填充for (int i = 0; i < request->file_id_list_size(); i++){std::string fid = request->file_id_list(i);std::string filename = _storage_path + fid;std::string body;bool ret = readFile(filename, body);if (ret == false){response->set_success(false);response->set_errmsg("读取文件数据失败!");LOG_ERROR("{} 读取文件数据失败!", request->request_id());return;}ymm_im::FileDownloadData data;data.set_file_id(fid);data.set_file_content(body);response->mutable_file_data()->insert({fid, data});}response->set_success(true);}// 写入单个文件void PutSingleFile(google::protobuf::RpcController *controller,const ::ymm_im::PutSingleFileReq *request,::ymm_im::PutSingleFileRsp *response,::google::protobuf::Closure *done){brpc::ClosureGuard rpc_guard(done);response->set_request_id(request->request_id());// 1. 为文件生成一个唯一uudi作为文件名 以及 文件IDstd::string fid = uuid();std::string filename = _storage_path + fid;// 2. 取出请求中的文件数据,进行文件数据写入bool ret = writeFile(filename, request->file_data().file_content());if (ret == false){response->set_success(false);response->set_errmsg("读取文件数据失败!");LOG_ERROR("{} 写入文件数据失败!", request->request_id());return;}// 3. 组织响应response->set_success(true);response->mutable_file_info()->set_file_id(fid);response->mutable_file_info()->set_file_size(request->file_data().file_size());response->mutable_file_info()->set_file_name(request->file_data().file_name());}// 写入多个文件void PutMultiFile(google::protobuf::RpcController *controller,const ::ymm_im::PutMultiFileReq *request,::ymm_im::PutMultiFileRsp *response,::google::protobuf::Closure *done){brpc::ClosureGuard rpc_guard(done);response->set_request_id(request->request_id());for (int i = 0; i < request->file_data_size(); i++){std::string fid = uuid();std::string filename = _storage_path + fid;bool ret = writeFile(filename, request->file_data(i).file_content());if (ret == false){response->set_success(false);response->set_errmsg("读取文件数据失败!");LOG_ERROR("{} 写入文件数据失败!", request->request_id());return;}ymm_im::FileMessageInfo *info = response->add_file_info();info->set_file_id(fid);info->set_file_size(request->file_data(i).file_size());info->set_file_name(request->file_data(i).file_name());}response->set_success(true);}private:std::string _storage_path;};// 文件服务器class FileServer{public:using ptr = std::shared_ptr<FileServer>;FileServer(const Registry::ptr &reg_client,const std::shared_ptr<brpc::Server> &server) : _reg_client(reg_client),_rpc_server(server){}~FileServer(){}// 搭建RPC服务器,并启动服务器void start(){_rpc_server->RunUntilAskedToQuit();}private:Registry::ptr _reg_client;std::shared_ptr<brpc::Server> _rpc_server;};// 建造文件服务器class FileServerBuilder{public:// 用于构造服务注册客户端对象void make_reg_object(const std::string &reg_host,const std::string &service_name,const std::string &access_host){_reg_client = std::make_shared<Registry>(reg_host);_reg_client->registry(service_name, access_host);}// 构造RPC服务器对象void make_rpc_server(uint16_t port, int32_t timeout,uint8_t num_threads, const std::string &path = "./data/"){_rpc_server = std::make_shared<brpc::Server>();FileServiceImpl *file_service = new FileServiceImpl(path);int ret = _rpc_server->AddService(file_service,brpc::ServiceOwnership::SERVER_OWNS_SERVICE);if (ret == -1){LOG_ERROR("添加Rpc服务失败!");abort();}brpc::ServerOptions options;options.idle_timeout_sec = timeout;options.num_threads = num_threads;ret = _rpc_server->Start(port, &options);if (ret == -1){LOG_ERROR("服务启动失败!");abort();}}FileServer::ptr build(){if (!_reg_client){LOG_ERROR("还未初始化服务注册模块!");abort();}if (!_rpc_server){LOG_ERROR("还未初始化RPC服务器模块!");abort();}FileServer::ptr server = std::make_shared<FileServer>(_reg_client, _rpc_server);return server;}private:Registry::ptr _reg_client;std::shared_ptr<brpc::Server> _rpc_server;};
}
http://www.dtcms.com/a/300263.html

相关文章:

  • 配置nodejs
  • 面试150 数据流的中位数
  • 6.数组和字符串
  • 从稀疏数据(CSV)创建非常大的 GeoTIFF(和 WMS)
  • 【时时三省】(C语言基础)返回指针值的函数
  • TRIM功能
  • 《代码随想录》刷题记录
  • 速通python加密之MD5加密
  • Datawhale AI 夏令营:让AI理解列车排期表 Notebook(Baseline拆解)
  • JVM常见工具
  • Java 对象秒变 Map:字段自由伸缩的优雅实现
  • KTO:基于行为经济学的大模型对齐新范式——原理、应用与性能突破
  • 2025测绘程序设计国赛实战 | 基于统计滤波算法的点云去噪
  • 使用binutils工具分析目标文件(贰)
  • U514565 连通块中点的数量
  • 缓存一致性:从单核到异构多核的演进之路
  • HarmonyOS中的PX、 VP、 FP 、LPX、Percentage、Resource 详细区别是什么
  • HCIP--MGRE实验
  • CT、IT、ICT 和 DICT区别
  • Windows卷影复制的增量备份
  • 在VS Code中运行Python:基于Anaconda环境或Python官方环境
  • 人大金仓 kingbase 连接数太多, 清理数据库连接数
  • Go的内存管理和垃圾回收
  • “Datawhale AI夏令营”「结构化数据的用户意图理解和知识问答挑战赛」1
  • 使用Clion开发STM32(Dap调试)
  • 基于华为ENSP的OSPF数据报文保姆级别详解(3)
  • LeetCode——1695. 删除子数组的最大得分
  • TI MSPM0蓝牙串口通信数据包制作
  • C++11 -- emplace、包装器
  • 标准库开发和寄存器开发的区别