提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
文件管理子服务,主要用于管理用户的头像,以及消息中的文件存储,因此需要提供
以下接口:
- 文件的上传
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());
//为文件生成一个uuid
std::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++)
{
//为文件生成一个uuid
std::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服务器
};