提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、搭建思想
- 二、服务器搭建
- 1.继承fileService类,重写业务方法
- 服务器类设计
- 建造者类
前言
文件管理子服务,主要用于管理用户的头像,以及消息中的文件存储,因此需要提供
以下接口:
- 文件的上传
a. 单个文件的上传:这个接口基本用于后台部分,收到文件消息后将文件数据
转发给文件子服务进行存储
b. 多个文件的上传:这个接口基本用于后台部分,收到文件消息后将文件数据
转发给文件子服务进行存储 - 文件的下载
a. 单个文件的下载:在后台用于获取用户头像文件数据,以及客户端用于获取
文件/语音/图片消息的文件数据
b. 多个文件的下载:在后台用于大批量获取用户头像数据(比如获取用户列表
的时候),以及前端的批量文件下载
//获取单个文件请求
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;optional 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;optional string errmsg = 3;map<string, FileDownloadData> file_data = 4;
}message PutSingleFileReq {string request_id = 1;optional string user_id = 2;optional string session_id = 3;FileUploadData file_data = 4;
}message PutSingleFileRsp {string request_id = 1;bool success = 2;optional string errmsg = 3;optional 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;
}rpc服务
service FileService {rpc GetSingleFile(GetSingleFileReq) returns (GetSingleFileRsp);rpc GetMultiFile(GetMultiFileReq) returns (GetMultiFileRsp);rpc PutSingleFile(PutSingleFileReq) returns (PutSingleFileRsp);rpc PutMultiFile(PutMultiFileReq) returns (PutMultiFileRsp);
}
一、搭建思想
文件管理子服务和语音管理子服务基本一致,他们都有服务注册,rpc服务器.
唯一不同的是文件管理子服务中的rpc服务是进行文件的读写。
我们通过标准库的文件读写类进行一个文件的读写。
针对文件的上传,我们需要给文件生成一个uuid,这个uuid就作为文件的名称在服务器进行文件的写入。然后在resp中设置进生成的uuid。
针对文件的下载,调用端只需要提供一个文件id,也就是我们生成的uuid,服务端就可以找到对应的文件了。
二、服务器搭建
1.继承fileService类,重写业务方法
文件管理子服务提供了四个功能。单个文件的上传下载,多个文件的上传下载。
针对上传:首先为该文件生成一个uuid,然后进行进行文件内容的写入。最后返回文件Id(uuid)。
针对下载:首先获取文件ID,然后找到文件进行文件内容的读取,最后返回文件内容。
只有一个地方需要注意,就是该类有一个成员变量std::string _storage_path。
我们的文件都是存贮在这个路径下的,所以当我们进行文件的写入和读取时,都需要先在文件id的前面拼接上这个路径。
//1.继承文件服务类,重写业务方法class FileServiceImpl : public FileService{public:FileServiceImpl(const std::string& path):_storage_path(path){umask(0);if(_storage_path.back() != '/') _storage_path += "/";mkdir(_storage_path.c_str(), 0775);}~FileServiceImpl(){}//获取单个文件void GetSingleFile(google::protobuf::RpcController* controller,const ::lkm_im::GetSingleFileReq* request,::lkm_im::GetSingleFileRsp* response,::google::protobuf::Closure* done){brpc::ClosureGuard rpc_guard(done);response->set_request_id(request->request_id());//获取文件ID std::string file_id = request->file_id();std::string file_name = _storage_path + file_id;//读取文件内容std::string body;bool ret = readFile(file_name,body);if(!ret){LOG_ERROR("{},获取单个文件失败",request->request_id());response->set_success(false);response->set_errmsg("读取文件数据失败");return;}//组织响应response->set_success(true);response->mutable_file_data()->set_file_id(request->file_id());response->mutable_file_data()->set_file_content(body);}//获取多个文件void GetMultiFile(google::protobuf::RpcController* controller,const ::lkm_im::GetMultiFileReq* request,::lkm_im::GetMultiFileRsp* response,::google::protobuf::Closure* done){brpc::ClosureGuard rpc_guard(done);response->set_request_id(request->request_id());//循环处理多个文件for (int i = 0; i < request->file_id_list_size(); i++){//获取文件ID (文件名)std::string file_id = request->file_id_list(i);std::string file_name = _storage_path + file_id;//读取文件内容std::string body;bool ret = readFile(file_name,body);if(!ret){LOG_ERROR("{},获取多个文件失败",request->request_id());response->set_success(false);response->set_errmsg("读取文件数据失败");return;}//组织响应FileDownloadData fileDownloadData;fileDownloadData.set_file_id(file_id);fileDownloadData.set_file_content(body);response->mutable_file_data()->insert({file_id,fileDownloadData});}response->set_success(true);}//上传单个文件void PutSingleFile(google::protobuf::RpcController* controller,const ::lkm_im::PutSingleFileReq* request,::lkm_im::PutSingleFileRsp* response,::google::protobuf::Closure* done){brpc::ClosureGuard rpc_guard(done);response->set_request_id(request->request_id());//为文件生成一个uuidstd::string fid = uuid();std::string file_name = _storage_path + fid; //文件实际存储路径//获取文件内容std::string file_content = request->file_data().file_content();//将内容写入文件int ret = writeFile(file_name,file_content);if(!ret){LOG_ERROR("{},上传单个文件失败",request->request_id());response->set_success(false);response->set_errmsg("写入文件数据失败");return;}//组织响应 给客户端返回我们生成的文件ID就行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());response->set_success(true);}//上传多个文件void PutMultiFile(google::protobuf::RpcController* controller,const ::lkm_im::PutMultiFileReq* request,::lkm_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++){//为文件生成一个uuidstd::string fid = uuid();std::string file_name = _storage_path + fid;//获取文件内容std::string file_content = request->file_data(i).file_content();//将内容写入文件int ret = writeFile(file_name,file_content);if(!ret){LOG_ERROR("{},上传多个文件失败",request->request_id());response->set_success(false);response->set_errmsg("写入文件数据失败");return;}//组织响应 给客户端返回我们生成的文件ID就行lkm_im::FileMessageInfo * fileMessageInfo = response->add_file_info();fileMessageInfo->set_file_id(fid);fileMessageInfo->set_file_name(request->file_data(i).file_name());fileMessageInfo->set_file_size(request->file_data(i).file_size());}response->set_success(true);}private:std::string _storage_path; //存储文件路径};
服务器类设计
和语音识别子服务一样,这里也采用了建造者模式。但是我们的文件管理子服务只有两个对象,一个rpc服务器,一个服务注册对象。
//2.文件存储管理子服务服务器编写class FileServer{public:using ptr = std::shared_ptr<FileServer>;FileServer(const Registry::ptr& registry,const std::shared_ptr<brpc::Server>& server):_registry(registry),_server(server){}~FileServer(){}//启动rpc服务器void start(){_server->RunUntilAskedToQuit();}private:Registry::ptr _registry; //服务注册对象std::shared_ptr<brpc::Server> _server; //rpc服务器};
建造者类
在构造rpc服务器时需要传入一个路径,因为这里需要为rpc服务器添加服务,需要构造fileServiceImpl对象,这个对象需要传入一个path存储路径。
//3.文件存储管理子服务构造者class FileServerBuild{public:void make_registry(const std::string& etcd_host,const std::string& service_name,const std::string& service_host){_registry = std::make_shared<Registry>(etcd_host);//进行服务注册_registry->registry(service_name,service_host);}void make_brpc(uint16_t port,uint32_t timeout = -1,uint8_t num_threads = 1,const std::string& path = "./data/"){//创建brpc服务器对象_server = std::make_shared<brpc::Server>();//添加服务FileServiceImpl *FileService = new FileServiceImpl(path); //把这个对象的交给_server释放int ret = _server->AddService(FileService,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 = _server->Start(port,&options);if(ret == -1){LOG_ERROR("rpc服务器启动失败");abort();}}FileServer::ptr build(){if(!_registry){LOG_ERROR("服务注册客户端对象未构造");abort();}if(!_server){LOG_ERROR("rpc服务器对象未构造");abort();}FileServer::ptr FileServer(new lkm_im::FileServer(_registry,_server));return FileServer;}private:Registry::ptr _registry; //服务注册对象std::shared_ptr<brpc::Server> _server; //rpc服务器};