文章目录
- 前言
-
- [1. 前言](#1. 前言)
- [2. 往期内容](#2. 往期内容)
- [3. 本篇简介](#3. 本篇简介)
- 功能设计
-
- [1. 模块划分:](#1. 模块划分:)
- [2. 模块功能示意图:](#2. 模块功能示意图:)
- 接口实现流程:
-
- [1. 用户名注册](#1. 用户名注册)
- [2. 用户名登录](#2. 用户名登录)
- [3. 短信验证码获取](#3. 短信验证码获取)
- [4. 手机号码注册](#4. 手机号码注册)
- [5. 手机号码登录](#5. 手机号码登录)
- [6. 用户信息获取](#6. 用户信息获取)
- [7. 修改用户头像](#7. 修改用户头像)
- [8. 修改用户签名](#8. 修改用户签名)
- [9. 修改用户昵称](#9. 修改用户昵称)
- [10. 修改用户绑定手机号](#10. 修改用户绑定手机号)
- [11. 获取好友列表](#11. 获取好友列表)
- [12. 发送好友申请](#12. 发送好友申请)
- [13. 获取待处理好友申请](#13. 获取待处理好友申请)
- [14. 好友申请处理](#14. 好友申请处理)
- [15. 删除好友](#15. 删除好友)
- [16. 搜索用户](#16. 搜索用户)
- [17. 获取用户聊天会话列表](#17. 获取用户聊天会话列表)
- [18. 创建多人聊天会话](#18. 创建多人聊天会话)
- [19. 获取消息会话成员列表](#19. 获取消息会话成员列表)
- [20. 发送新消息](#20. 发送新消息)
- [21. 获取指定时间段消息列表](#21. 获取指定时间段消息列表)
- [22. 获取最近 N 条消息列表](#22. 获取最近 N 条消息列表)
- [23. 搜索关键字历史消息](#23. 搜索关键字历史消息)
- [24. 单个文件数据获取](#24. 单个文件数据获取)
- [25. 多个文件数据获取](#25. 多个文件数据获取)
- [26. 单个文件数据上传](#26. 单个文件数据上传)
- [27. 多个文件数据上传](#27. 多个文件数据上传)
- [28. 语音转文字](#28. 语音转文字)
- 其余函数
前言
1. 前言
本篇是我介绍Chat-Im(仿微信的即时通讯项目)中的一篇,大家可以有兴趣的可以参照我的gitee代码和博客自己动手试一试。
gitee:https://gitee.com/qi-haozhe/chat-im
点我直接转到gitee

大家可以点击右上角的红框,这里会显示我提交的记录,在这里面你可以看到我做这个项目的顺序,以及每次完成了什么模块,每一次提交了什么文件,再结合我博客对于该项目的一些解析,相信有点基础的同学都可以独立完成这个项目的。
2. 往期内容
都已设置超链接,点击跳转,且按照学习顺序排序,不建议跳着来看。博客还未跟新完,持续更新中!
3. 本篇简介
本模块主要介绍即时通讯项目的网关部分,其它微服务完成以后,网关部分主要处理各种服务的请求,网关层根据请求服务的不同,去分别调用对应的微服务进行处理,然后给客户端返回结果就行。
功能设计
网关服务器在设计中,最重要的两个功能:
- 作为入口服务器接收客户端的所有请求,进行请求的子服务分发,得到响应后进行响应
- 对客户端进行事件通知(好友申请和处理及删除,单聊/群聊会话创建,新消息)
基于以上的两个功能,因此网关服务器包含两项通信: - HTTP 通信:进行业务处理
- WEBSOCKET 通信:进行事件通知
1. 模块划分:
- 参数/配置文件解析模块:基于 gflags 框架直接使用进行参数/配置文件解析。
- 日志模块:基于 spdlog 框架封装的模块直接使用进行日志输出。
- rpc 服务发现与调用模块:基于 etcd 框架与 brpc 框架封装的服务发现与调用模块
- 因为要分发处理所有请求,因此所有的子服务都需要进行服务发现。
- redis 客户端模块:基于 redis++封装的客户端进行内存数据库数据操作
- 根据用户子服务添加的会话信息进行用户连接身份识别与鉴权
- HTTP 通信服务器模块:基于 cpp-httplib 库搭建 HTTP 服务器,接收 HTTP 请求进行业务处理。
- WEBSOCKET 服务器模块:基于 Websocketpp 库,搭建 websocket 服务器,进行事件通知。
- 客户端长连接管理模块:建议用户 ID 与长连接句柄映射关系,便于后续根据用户ID 找到连接进行事件通知
2. 模块功能示意图:

接口实现流程:
1. 用户名注册
- 取出 HTTP 请求正文,进行 ProtoBuf 反序列化
- 查找用户子服务
- 调用子服务对应接口进行业务处理
- 将处理结果响应给客户端。
cpp
void UserRegister(const httplib::Request &request, httplib::Response &response) {
//1. 取出http请求正文,将正文进行反序列化
ymm_im::UserRegisterReq req;
ymm_im::UserRegisterRsp rsp;
auto err_response = [&req, &rsp, &response](const std::string &errmsg) -> void {
rsp.set_success(false);
rsp.set_errmsg(errmsg);
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
};
bool ret = req.ParseFromString(request.body);
if (ret == false) {
LOG_ERROR("用户名注册请求正文反序列化失败!");
return err_response("用户名注册请求正文反序列化失败!");
}
//2. 将请求转发给用户子服务进行业务处理
auto channel = _mm_channels->choose(_user_service_name);
if (!channel) {
LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());
return err_response("未找到可提供业务处理的用户子服务节点!");
}
ymm_im::UserService_Stub stub(channel.get());
brpc::Controller cntl;
stub.UserRegister(&cntl, &req, &rsp, nullptr);
if (cntl.Failed()) {
LOG_ERROR("{} 用户子服务调用失败!", req.request_id());
return err_response("用户子服务调用失败!");
}
//3. 得到用户子服务的响应后,将响应内容进行序列化作为http响应正文
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
2. 用户名登录
- 取出 HTTP 请求正文,进行 ProtoBuf 反序列化
- 查找用户子服务
- 调用子服务对应接口进行业务处理
- 将处理结果响应给客户端。
cpp
void UserLogin(const httplib::Request &request, httplib::Response &response) {
//1. 取出http请求正文,将正文进行反序列化
ymm_im::UserLoginReq req;
ymm_im::UserLoginRsp rsp;
auto err_response = [&req, &rsp, &response](const std::string &errmsg) -> void {
rsp.set_success(false);
rsp.set_errmsg(errmsg);
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
};
bool ret = req.ParseFromString(request.body);
if (ret == false) {
LOG_ERROR("用户登录请求正文反序列化失败!");
return err_response("用户登录请求正文反序列化失败!");
}
//2. 将请求转发给用户子服务进行业务处理
auto channel = _mm_channels->choose(_user_service_name);
if (!channel) {
LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());
return err_response("未找到可提供业务处理的用户子服务节点!");
}
ymm_im::UserService_Stub stub(channel.get());
brpc::Controller cntl;
stub.UserLogin(&cntl, &req, &rsp, nullptr);
if (cntl.Failed()) {
LOG_ERROR("{} 用户子服务调用失败!", req.request_id());
return err_response("用户子服务调用失败!");
}
//3. 得到用户子服务的响应后,将响应内容进行序列化作为http响应正文
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
3. 短信验证码获取
- 取出 HTTP 请求正文,进行 ProtoBuf 反序列化
- 查找用户子服务
- 调用子服务对应接口进行业务处理
- 将处理结果响应给客户端。
cpp
void GetPhoneVerifyCode(const httplib::Request &request, httplib::Response &response) {
//1. 取出http请求正文,将正文进行反序列化
ymm_im::PhoneVerifyCodeReq req;
ymm_im::PhoneVerifyCodeRsp rsp;
auto err_response = [&req, &rsp, &response](const std::string &errmsg) -> void {
rsp.set_success(false);
rsp.set_errmsg(errmsg);
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
};
bool ret = req.ParseFromString(request.body);
if (ret == false) {
LOG_ERROR("获取短信验证码请求正文反序列化失败!");
return err_response("获取短信验证码请求正文反序列化失败!");
}
//2. 将请求转发给用户子服务进行业务处理
auto channel = _mm_channels->choose(_user_service_name);
if (!channel) {
LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());
return err_response("未找到可提供业务处理的用户子服务节点!");
}
ymm_im::UserService_Stub stub(channel.get());
brpc::Controller cntl;
stub.GetPhoneVerifyCode(&cntl, &req, &rsp, nullptr);
if (cntl.Failed()) {
LOG_ERROR("{} 用户子服务调用失败!", req.request_id());
return err_response("用户子服务调用失败!");
}
//3. 得到用户子服务的响应后,将响应内容进行序列化作为http响应正文
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
4. 手机号码注册
- 取出 HTTP 请求正文,进行 ProtoBuf 反序列化
- 查找用户子服务
- 调用子服务对应接口进行业务处理
- 将处理结果响应给客户端。
cpp
void PhoneRegister(const httplib::Request &request, httplib::Response &response) {
//1. 取出http请求正文,将正文进行反序列化
ymm_im::PhoneRegisterReq req;
ymm_im::PhoneRegisterRsp rsp;
auto err_response = [&req, &rsp, &response](const std::string &errmsg) -> void {
rsp.set_success(false);
rsp.set_errmsg(errmsg);
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
};
bool ret = req.ParseFromString(request.body);
if (ret == false) {
LOG_ERROR("手机号注册请求正文反序列化失败!");
return err_response("手机号注册请求正文反序列化失败!");
}
//2. 将请求转发给用户子服务进行业务处理
auto channel = _mm_channels->choose(_user_service_name);
if (!channel) {
LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());
return err_response("未找到可提供业务处理的用户子服务节点!");
}
ymm_im::UserService_Stub stub(channel.get());
brpc::Controller cntl;
stub.PhoneRegister(&cntl, &req, &rsp, nullptr);
if (cntl.Failed()) {
LOG_ERROR("{} 用户子服务调用失败!", req.request_id());
return err_response("用户子服务调用失败!");
}
//3. 得到用户子服务的响应后,将响应内容进行序列化作为http响应正文
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
5. 手机号码登录
- 取出 HTTP 请求正文,进行 ProtoBuf 反序列化
- 查找用户子服务
- 调用子服务对应接口进行业务处理
- 将处理结果响应给客户端。
cpp
void PhoneLogin(const httplib::Request &request, httplib::Response &response) {
//1. 取出http请求正文,将正文进行反序列化
ymm_im::PhoneLoginReq req;
ymm_im::PhoneLoginRsp rsp;
auto err_response = [&req, &rsp, &response](const std::string &errmsg) -> void {
rsp.set_success(false);
rsp.set_errmsg(errmsg);
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
};
bool ret = req.ParseFromString(request.body);
if (ret == false) {
LOG_ERROR("手机号登录请求正文反序列化失败!");
return err_response("手机号登录请求正文反序列化失败!");
}
//2. 将请求转发给用户子服务进行业务处理
auto channel = _mm_channels->choose(_user_service_name);
if (!channel) {
LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());
return err_response("未找到可提供业务处理的用户子服务节点!");
}
ymm_im::UserService_Stub stub(channel.get());
brpc::Controller cntl;
stub.PhoneLogin(&cntl, &req, &rsp, nullptr);
if (cntl.Failed()) {
LOG_ERROR("{} 用户子服务调用失败!", req.request_id());
return err_response("用户子服务调用失败!");
}
//3. 得到用户子服务的响应后,将响应内容进行序列化作为http响应正文
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
6. 用户信息获取
- 取出 HTTP 请求正文,进行 ProtoBuf 反序列化
- 根据请求中的会话 ID 进行鉴权,并获取用户 ID,向请求中设置用户 ID
- 查找用户子服务
- 调用子服务对应接口进行业务处理
- 将处理结果响应给客户端。
cpp
void GetUserInfo(const httplib::Request &request, httplib::Response &response) {
//1. 取出http请求正文,将正文进行反序列化
ymm_im::GetUserInfoReq req;
ymm_im::GetUserInfoRsp rsp;
auto err_response = [&req, &rsp, &response](const std::string &errmsg) -> void {
rsp.set_success(false);
rsp.set_errmsg(errmsg);
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
};
bool ret = req.ParseFromString(request.body);
if (ret == false) {
LOG_ERROR("获取用户信息请求正文反序列化失败!");
return err_response("获取用户信息请求正文反序列化失败!");
}
//2. 客户端身份识别与鉴权
std::string ssid = req.session_id();
auto uid = _redis_session->uid(ssid);
if (!uid) {
LOG_ERROR("{} 获取登录会话关联用户信息失败!", ssid);
return err_response("获取登录会话关联用户信息失败!");
}
req.set_user_id(*uid);
//2. 将请求转发给用户子服务进行业务处理
auto channel = _mm_channels->choose(_user_service_name);
if (!channel) {
LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());
return err_response("未找到可提供业务处理的用户子服务节点!");
}
ymm_im::UserService_Stub stub(channel.get());
brpc::Controller cntl;
stub.GetUserInfo(&cntl, &req, &rsp, nullptr);
if (cntl.Failed()) {
LOG_ERROR("{} 用户子服务调用失败!", req.request_id());
return err_response("用户子服务调用失败!");
}
//3. 得到用户子服务的响应后,将响应内容进行序列化作为http响应正文
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
7. 修改用户头像
- 取出 HTTP 请求正文,进行 ProtoBuf 反序列化
- 根据请求中的会话 ID 进行鉴权,并获取用户 ID,向请求中设置用户 ID
- 查找用户子服务
- 调用子服务对应接口进行业务处理
- 将处理结果响应给客户端。
cpp
void SetUserAvatar(const httplib::Request &request, httplib::Response &response) {
//1. 取出http请求正文,将正文进行反序列化
ymm_im::SetUserAvatarReq req;
ymm_im::SetUserAvatarRsp rsp;
auto err_response = [&req, &rsp, &response](const std::string &errmsg) -> void {
rsp.set_success(false);
rsp.set_errmsg(errmsg);
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
};
bool ret = req.ParseFromString(request.body);
if (ret == false) {
LOG_ERROR("用户头像设置请求正文反序列化失败!");
return err_response("用户头像设置请求正文反序列化失败!");
}
//2. 客户端身份识别与鉴权
std::string ssid = req.session_id();
auto uid = _redis_session->uid(ssid);
if (!uid) {
LOG_ERROR("{} 获取登录会话关联用户信息失败!", ssid);
return err_response("获取登录会话关联用户信息失败!");
}
req.set_user_id(*uid);
//2. 将请求转发给用户子服务进行业务处理
auto channel = _mm_channels->choose(_user_service_name);
if (!channel) {
LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());
return err_response("未找到可提供业务处理的用户子服务节点!");
}
ymm_im::UserService_Stub stub(channel.get());
brpc::Controller cntl;
stub.SetUserAvatar(&cntl, &req, &rsp, nullptr);
if (cntl.Failed()) {
LOG_ERROR("{} 用户子服务调用失败!", req.request_id());
return err_response("用户子服务调用失败!");
}
//3. 得到用户子服务的响应后,将响应内容进行序列化作为http响应正文
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
8. 修改用户签名
- 取出 HTTP 请求正文,进行 ProtoBuf 反序列化
- 根据请求中的会话 ID 进行鉴权,并获取用户 ID,向请求中设置用户 ID
- 查找用户子服务
- 调用子服务对应接口进行业务处理
- 将处理结果响应给客户端。
cpp
void SetUserDescription(const httplib::Request &request, httplib::Response &response) {
//1. 取出http请求正文,将正文进行反序列化
ymm_im::SetUserDescriptionReq req;
ymm_im::SetUserDescriptionRsp rsp;
auto err_response = [&req, &rsp, &response](const std::string &errmsg) -> void {
rsp.set_success(false);
rsp.set_errmsg(errmsg);
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
};
bool ret = req.ParseFromString(request.body);
if (ret == false) {
LOG_ERROR("用户签名设置请求正文反序列化失败!");
return err_response("用户签名设置请求正文反序列化失败!");
}
//2. 客户端身份识别与鉴权
std::string ssid = req.session_id();
auto uid = _redis_session->uid(ssid);
if (!uid) {
LOG_ERROR("{} 获取登录会话关联用户信息失败!", ssid);
return err_response("获取登录会话关联用户信息失败!");
}
req.set_user_id(*uid);
//2. 将请求转发给用户子服务进行业务处理
auto channel = _mm_channels->choose(_user_service_name);
if (!channel) {
LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());
return err_response("未找到可提供业务处理的用户子服务节点!");
}
ymm_im::UserService_Stub stub(channel.get());
brpc::Controller cntl;
stub.SetUserDescription(&cntl, &req, &rsp, nullptr);
if (cntl.Failed()) {
LOG_ERROR("{} 用户子服务调用失败!", req.request_id());
return err_response("用户子服务调用失败!");
}
//3. 得到用户子服务的响应后,将响应内容进行序列化作为http响应正文
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
9. 修改用户昵称
- 取出 HTTP 请求正文,进行 ProtoBuf 反序列化
- 根据请求中的会话 ID 进行鉴权,并获取用户 ID,向请求中设置用户 ID
- 查找用户子服务
- 调用子服务对应接口进行业务处理
- 将处理结果响应给客户端。
cpp
void SetUserNickname(const httplib::Request &request, httplib::Response &response) {
//1. 取出http请求正文,将正文进行反序列化
ymm_im::SetUserNicknameReq req;
ymm_im::SetUserNicknameRsp rsp;
auto err_response = [&req, &rsp, &response](const std::string &errmsg) -> void {
rsp.set_success(false);
rsp.set_errmsg(errmsg);
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
};
bool ret = req.ParseFromString(request.body);
if (ret == false) {
LOG_ERROR("用户昵称设置请求正文反序列化失败!");
return err_response("用户昵称设置请求正文反序列化失败!");
}
//2. 客户端身份识别与鉴权
std::string ssid = req.session_id();
auto uid = _redis_session->uid(ssid);
if (!uid) {
LOG_ERROR("{} 获取登录会话关联用户信息失败!", ssid);
return err_response("获取登录会话关联用户信息失败!");
}
req.set_user_id(*uid);
//2. 将请求转发给用户子服务进行业务处理
auto channel = _mm_channels->choose(_user_service_name);
if (!channel) {
LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());
return err_response("未找到可提供业务处理的用户子服务节点!");
}
ymm_im::UserService_Stub stub(channel.get());
brpc::Controller cntl;
stub.SetUserNickname(&cntl, &req, &rsp, nullptr);
if (cntl.Failed()) {
LOG_ERROR("{} 用户子服务调用失败!", req.request_id());
return err_response("用户子服务调用失败!");
}
//3. 得到用户子服务的响应后,将响应内容进行序列化作为http响应正文
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
10. 修改用户绑定手机号
- 取出 HTTP 请求正文,进行 ProtoBuf 反序列化
- 根据请求中的会话 ID 进行鉴权,并获取用户 ID,向请求中设置用户 ID
- 查找用户子服务
- 调用子服务对应接口进行业务处理
- 将处理结果响应给客户端。
cpp
void SetUserPhoneNumber(const httplib::Request &request, httplib::Response &response) {
//1. 取出http请求正文,将正文进行反序列化
ymm_im::SetUserPhoneNumberReq req;
ymm_im::SetUserPhoneNumberRsp rsp;
auto err_response = [&req, &rsp, &response](const std::string &errmsg) -> void {
rsp.set_success(false);
rsp.set_errmsg(errmsg);
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
};
bool ret = req.ParseFromString(request.body);
if (ret == false) {
LOG_ERROR("用户手机号设置请求正文反序列化失败!");
return err_response("用户手机号设置请求正文反序列化失败!");
}
//2. 客户端身份识别与鉴权
std::string ssid = req.session_id();
auto uid = _redis_session->uid(ssid);
if (!uid) {
LOG_ERROR("{} 获取登录会话关联用户信息失败!", ssid);
return err_response("获取登录会话关联用户信息失败!");
}
req.set_user_id(*uid);
//2. 将请求转发给用户子服务进行业务处理
auto channel = _mm_channels->choose(_user_service_name);
if (!channel) {
LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());
return err_response("未找到可提供业务处理的用户子服务节点!");
}
ymm_im::UserService_Stub stub(channel.get());
brpc::Controller cntl;
stub.SetUserPhoneNumber(&cntl, &req, &rsp, nullptr);
if (cntl.Failed()) {
LOG_ERROR("{} 用户子服务调用失败!", req.request_id());
return err_response("用户子服务调用失败!");
}
//3. 得到用户子服务的响应后,将响应内容进行序列化作为http响应正文
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
11. 获取好友列表
- 取出 HTTP 请求正文,进行 ProtoBuf 反序列化
- 根据请求中的会话 ID 进行鉴权,并获取用户 ID,向请求中设置用户 ID
- 查找好友子服务
- 调用子服务对应接口进行业务处理
- 将处理结果响应给客户端。
cpp
void GetFriendList(const httplib::Request &request, httplib::Response &response) {
//1. 取出http请求正文,将正文进行反序列化
ymm_im::GetFriendListReq req;
ymm_im::GetFriendListRsp rsp;
auto err_response = [&req, &rsp, &response](const std::string &errmsg) -> void {
rsp.set_success(false);
rsp.set_errmsg(errmsg);
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
};
bool ret = req.ParseFromString(request.body);
if (ret == false) {
LOG_ERROR("获取好友列表请求正文反序列化失败!");
return err_response("获取好友列表请求正文反序列化失败!");
}
//2. 客户端身份识别与鉴权
std::string ssid = req.session_id();
auto uid = _redis_session->uid(ssid);
if (!uid) {
LOG_ERROR("{} 获取登录会话关联用户信息失败!", ssid);
return err_response("获取登录会话关联用户信息失败!");
}
req.set_user_id(*uid);
//2. 将请求转发给好友子服务进行业务处理
auto channel = _mm_channels->choose(_friend_service_name);
if (!channel) {
LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());
return err_response("未找到可提供业务处理的用户子服务节点!");
}
ymm_im::FriendService_Stub stub(channel.get());
brpc::Controller cntl;
stub.GetFriendList(&cntl, &req, &rsp, nullptr);
if (cntl.Failed()) {
LOG_ERROR("{} 好友子服务调用失败!", req.request_id());
return err_response("好友子服务调用失败!");
}
//3. 得到用户子服务的响应后,将响应内容进行序列化作为http响应正文
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
12. 发送好友申请
- 取出 HTTP 请求正文,进行 ProtoBuf 反序列化
- 根据请求中的会话 ID 进行鉴权,并获取用户 ID,向请求中设置用户 ID
- 查找用户子服务
- 根据请求中的用户 ID,调用用户子服务,获取用户的详细信息
- 查找好友子服务
- 调用子服务对应接口进行业务处理
- 若处理成功,则通过被申请人 ID,查找对方长连接
a. 若长连接存在(对方在线),则组织好友申请通知进行事件通知 - 将处理结果响应给客户端。
cpp
void FriendAdd(const httplib::Request &request, httplib::Response &response) {
// 好友申请的业务处理中,好友子服务其实只是在数据库创建了申请事件
// 网关需要做的事情:当好友子服务将业务处理完毕后,如果处理是成功的--需要通知被申请方
// 1. 正文的反序列化,提取关键要素:登录会话ID
ymm_im::FriendAddReq req;
ymm_im::FriendAddRsp rsp;
auto err_response = [&req, &rsp, &response](const std::string &errmsg) -> void {
rsp.set_success(false);
rsp.set_errmsg(errmsg);
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
};
bool ret = req.ParseFromString(request.body);
if (ret == false) {
LOG_ERROR("申请好友请求正文反序列化失败!");
return err_response("申请好友请求正文反序列化失败!");
}
// 2. 客户端身份识别与鉴权
std::string ssid = req.session_id();
auto uid = _redis_session->uid(ssid);
if (!uid) {
LOG_ERROR("{} 获取登录会话关联用户信息失败!", ssid);
return err_response("获取登录会话关联用户信息失败!");
}
req.set_user_id(*uid);
// 3. 将请求转发给好友子服务进行业务处理
auto channel = _mm_channels->choose(_friend_service_name);
if (!channel) {
LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());
return err_response("未找到可提供业务处理的用户子服务节点!");
}
ymm_im::FriendService_Stub stub(channel.get());
brpc::Controller cntl;
stub.FriendAdd(&cntl, &req, &rsp, nullptr);
if (cntl.Failed()) {
LOG_ERROR("{} 好友子服务调用失败!", req.request_id());
return err_response("好友子服务调用失败!");
}
// 4. 若业务处理成功 --- 且获取被申请方长连接成功,则向被申请放进行好友申请事件通知
auto conn = _connections->connection(req.respondent_id());
if (rsp.success() && conn) {
LOG_DEBUG("找到被申请人 {} 长连接,对其进行好友申请通知", req.respondent_id());
auto user_rsp = _GetUserInfo(req.request_id(), *uid);
if (!user_rsp) {
LOG_ERROR("{} 获取当前客户端用户信息失败!", req.request_id());
return err_response("获取当前客户端用户信息失败!");
}
ymm_im::NotifyMessage notify;
notify.set_notify_type(ymm_im::NotifyType::FRIEND_ADD_APPLY_NOTIFY);
notify.mutable_friend_add_apply()->mutable_user_info()->CopyFrom(user_rsp->user_info());
conn->send(notify.SerializeAsString(), websocketpp::frame::opcode::value::binary);
}
// 5. 向客户端进行响应
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
13. 获取待处理好友申请
- 取出 HTTP 请求正文,进行 ProtoBuf 反序列化
- 根据请求中的会话 ID 进行鉴权,并获取用户 ID,向请求中设置用户 ID
- 查找好友子服务
- 调用子服务对应接口进行业务处理
- 将处理结果响应给客户端。
cpp
void GetPendingFriendEventList(const httplib::Request &request, httplib::Response &response) {
ymm_im::GetPendingFriendEventListReq req;
ymm_im::GetPendingFriendEventListRsp rsp;
auto err_response = [&req, &rsp, &response](const std::string &errmsg) -> void {
rsp.set_success(false);
rsp.set_errmsg(errmsg);
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
};
bool ret = req.ParseFromString(request.body);
if (ret == false) {
LOG_ERROR("获取待处理好友申请请求正文反序列化失败!");
return err_response("获取待处理好友申请请求正文反序列化失败!");
}
// 2. 客户端身份识别与鉴权
std::string ssid = req.session_id();
auto uid = _redis_session->uid(ssid);
if (!uid) {
LOG_ERROR("{} 获取登录会话关联用户信息失败!", ssid);
return err_response("获取登录会话关联用户信息失败!");
}
req.set_user_id(*uid);
// 3. 将请求转发给好友子服务进行业务处理
auto channel = _mm_channels->choose(_friend_service_name);
if (!channel) {
LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());
return err_response("未找到可提供业务处理的用户子服务节点!");
}
ymm_im::FriendService_Stub stub(channel.get());
brpc::Controller cntl;
stub.GetPendingFriendEventList(&cntl, &req, &rsp, nullptr);
if (cntl.Failed()) {
LOG_ERROR("{} 好友子服务调用失败!", req.request_id());
return err_response("好友子服务调用失败!");
}
// 5. 向客户端进行响应
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
14. 好友申请处理
- 取出 HTTP 请求正文,进行 ProtoBuf 反序列化
- 根据请求中的会话 ID 进行鉴权,并获取用户 ID,向请求中设置用户 ID
- 查找用户子服务
- 根据请求中的用户 ID,调用用户子服务,获取申请人与被申请人的详细信息
- 查找好友子服务
- 调用子服务对应接口进行业务处理
- 若处理成功,则通过申请人 ID,查找申请人长连接,进行申请处理结果的通知
- 若处理结果是同意,则意味着新聊天会话的创建,则对申请人继续进行聊天会话创建通知
- 从处理结果中取出会话 ID,使用对方的昵称作为会话名称,对方的头像作为会话头像组织会话信息
- 若处理结果是同意,则对当前处理者用户 ID 查找长连接,进行聊天会话创建的通知
- 从处理结果中取出会话 ID,使用对方的昵称作为会话名称,对方的头像作为会话头像组织会话信息
- 清理响应中的会话 ID 信息,
- 若处理结果是同意,则意味着新聊天会话的创建,则对申请人继续进行聊天会话创建通知
- 将处理结果响应给客户端
cpp
void FriendAddProcess(const httplib::Request &request, httplib::Response &response) {
//好友申请的处理-----
ymm_im::FriendAddProcessReq req;
ymm_im::FriendAddProcessRsp rsp;
auto err_response = [&req, &rsp, &response](const std::string &errmsg) -> void {
rsp.set_success(false);
rsp.set_errmsg(errmsg);
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
};
bool ret = req.ParseFromString(request.body);
if (ret == false) {
LOG_ERROR("好友申请处理请求正文反序列化失败!");
return err_response("好友申请处理请求正文反序列化失败!");
}
// 2. 客户端身份识别与鉴权
std::string ssid = req.session_id();
auto uid = _redis_session->uid(ssid);
if (!uid) {
LOG_ERROR("{} 获取登录会话关联用户信息失败!", ssid);
return err_response("获取登录会话关联用户信息失败!");
}
req.set_user_id(*uid);
// 3. 将请求转发给好友子服务进行业务处理
auto channel = _mm_channels->choose(_friend_service_name);
if (!channel) {
LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());
return err_response("未找到可提供业务处理的用户子服务节点!");
}
ymm_im::FriendService_Stub stub(channel.get());
brpc::Controller cntl;
stub.FriendAddProcess(&cntl, &req, &rsp, nullptr);
if (cntl.Failed()) {
LOG_ERROR("{} 好友子服务调用失败!", req.request_id());
return err_response("好友子服务调用失败!");
}
if (rsp.success()) {
auto process_user_rsp = _GetUserInfo(req.request_id(), *uid);
if (!process_user_rsp) {
LOG_ERROR("{} 获取用户信息失败!", req.request_id());
return err_response("获取用户信息失败!");
}
auto apply_user_rsp = _GetUserInfo(req.request_id(), req.apply_user_id());
if (!process_user_rsp) {
LOG_ERROR("{} 获取用户信息失败!", req.request_id());
return err_response("获取用户信息失败!");
}
auto process_conn = _connections->connection(*uid);
if (process_conn) LOG_DEBUG("找到处理人的长连接!");
else LOG_DEBUG("未找到处理人的长连接!");
auto apply_conn = _connections->connection(req.apply_user_id());
if (apply_conn) LOG_DEBUG("找到申请人的长连接!");
else LOG_DEBUG("未找到申请人的长连接!");
//4. 将处理结果给申请人进行通知
if (apply_conn) {
ymm_im::NotifyMessage notify;
notify.set_notify_type(ymm_im::NotifyType::FRIEND_ADD_PROCESS_NOTIFY);
auto process_result = notify.mutable_friend_process_result();
process_result->mutable_user_info()->CopyFrom(process_user_rsp->user_info());
process_result->set_agree(req.agree());
apply_conn->send(notify.SerializeAsString(),
websocketpp::frame::opcode::value::binary);
LOG_DEBUG("对申请人进行申请处理结果通知!");
}
//5. 若处理结果是同意 --- 会伴随着单聊会话的创建 -- 因此需要对双方进行会话创建的通知
if (req.agree() && apply_conn) { //对申请人的通知---会话信息就是处理人信息
ymm_im::NotifyMessage notify;
notify.set_notify_type(ymm_im::NotifyType::CHAT_SESSION_CREATE_NOTIFY);
auto chat_session = notify.mutable_new_chat_session_info();
chat_session->mutable_chat_session_info()->set_single_chat_friend_id(*uid);
chat_session->mutable_chat_session_info()->set_chat_session_id(rsp.new_session_id());
chat_session->mutable_chat_session_info()->set_chat_session_name(process_user_rsp->user_info().nickname());
chat_session->mutable_chat_session_info()->set_avatar(process_user_rsp->user_info().avatar());
apply_conn->send(notify.SerializeAsString(), websocketpp::frame::opcode::value::binary);
LOG_DEBUG("对申请人进行会话创建通知!");
}
if (req.agree() && process_conn) { //对处理人的通知 --- 会话信息就是申请人信息
ymm_im::NotifyMessage notify;
notify.set_notify_type(ymm_im::NotifyType::CHAT_SESSION_CREATE_NOTIFY);
auto chat_session = notify.mutable_new_chat_session_info();
chat_session->mutable_chat_session_info()->set_single_chat_friend_id(req.apply_user_id());
chat_session->mutable_chat_session_info()->set_chat_session_id(rsp.new_session_id());
chat_session->mutable_chat_session_info()->set_chat_session_name(apply_user_rsp->user_info().nickname());
chat_session->mutable_chat_session_info()->set_avatar(apply_user_rsp->user_info().avatar());
process_conn->send(notify.SerializeAsString(), websocketpp::frame::opcode::value::binary);
LOG_DEBUG("对处理人进行会话创建通知!");
}
}
//6. 对客户端进行响应
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
15. 删除好友
- 取出 HTTP 请求正文,进行 ProtoBuf 反序列化
- 根据请求中的会话 ID 进行鉴权,并获取用户 ID,向请求中设置用户 ID
- 查找好友子服务
- 调用子服务对应接口进行业务处理
- 若处理成功,则通过被删除者用户 ID,查找对方长连接
a. 若长连接存在(对方在线),则组织好友删除通知进行事件通知 - 将处理结果响应给客户端。
cpp
void FriendRemove(const httplib::Request &request, httplib::Response &response) {
// 1. 正文的反序列化,提取关键要素:登录会话ID
ymm_im::FriendRemoveReq req;
ymm_im::FriendRemoveRsp rsp;
auto err_response = [&req, &rsp, &response](const std::string &errmsg) -> void {
rsp.set_success(false);
rsp.set_errmsg(errmsg);
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
};
bool ret = req.ParseFromString(request.body);
if (ret == false) {
LOG_ERROR("删除好友请求正文反序列化失败!");
return err_response("删除好友请求正文反序列化失败!");
}
// 2. 客户端身份识别与鉴权
std::string ssid = req.session_id();
auto uid = _redis_session->uid(ssid);
if (!uid) {
LOG_ERROR("{} 获取登录会话关联用户信息失败!", ssid);
return err_response("获取登录会话关联用户信息失败!");
}
req.set_user_id(*uid);
// 3. 将请求转发给好友子服务进行业务处理
auto channel = _mm_channels->choose(_friend_service_name);
if (!channel) {
LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());
return err_response("未找到可提供业务处理的用户子服务节点!");
}
ymm_im::FriendService_Stub stub(channel.get());
brpc::Controller cntl;
stub.FriendRemove(&cntl, &req, &rsp, nullptr);
if (cntl.Failed()) {
LOG_ERROR("{} 好友子服务调用失败!", req.request_id());
return err_response("好友子服务调用失败!");
}
// 4. 若业务处理成功 --- 且获取被申请方长连接成功,则向被申请放进行好友申请事件通知
auto conn = _connections->connection(req.peer_id());
if (rsp.success() && conn) {
LOG_ERROR("对被删除人 {} 进行好友删除通知!", req.peer_id());
ymm_im::NotifyMessage notify;
notify.set_notify_type(ymm_im::NotifyType::FRIEND_REMOVE_NOTIFY);
notify.mutable_friend_remove()->set_user_id(*uid);
conn->send(notify.SerializeAsString(), websocketpp::frame::opcode::value::binary);
}
// 5. 向客户端进行响应
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
16. 搜索用户
- 取出 HTTP 请求正文,进行 ProtoBuf 反序列化
- 根据请求中的会话 ID 进行鉴权,并获取用户 ID,向请求中设置用户 ID
- 查找好友子服务
- 调用子服务对应接口进行业务处理
- 将处理结果响应给客户端。
cpp
void FriendSearch(const httplib::Request &request, httplib::Response &response) {
ymm_im::FriendSearchReq req;
ymm_im::FriendSearchRsp rsp;
auto err_response = [&req, &rsp, &response](const std::string &errmsg) -> void {
rsp.set_success(false);
rsp.set_errmsg(errmsg);
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
};
bool ret = req.ParseFromString(request.body);
if (ret == false) {
LOG_ERROR("用户搜索请求正文反序列化失败!");
return err_response("用户搜索请求正文反序列化失败!");
}
// 2. 客户端身份识别与鉴权
std::string ssid = req.session_id();
auto uid = _redis_session->uid(ssid);
if (!uid) {
LOG_ERROR("{} 获取登录会话关联用户信息失败!", ssid);
return err_response("获取登录会话关联用户信息失败!");
}
req.set_user_id(*uid);
// 3. 将请求转发给好友子服务进行业务处理
auto channel = _mm_channels->choose(_friend_service_name);
if (!channel) {
LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());
return err_response("未找到可提供业务处理的用户子服务节点!");
}
ymm_im::FriendService_Stub stub(channel.get());
brpc::Controller cntl;
stub.FriendSearch(&cntl, &req, &rsp, nullptr);
if (cntl.Failed()) {
LOG_ERROR("{} 好友子服务调用失败!", req.request_id());
return err_response("好友子服务调用失败!");
}
// 5. 向客户端进行响应
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
17. 获取用户聊天会话列表
- 取出 HTTP 请求正文,进行 ProtoBuf 反序列化
- 根据请求中的会话 ID 进行鉴权,并获取用户 ID,向请求中设置用户 ID
- 查找好友子服务
- 调用子服务对应接口进行业务处理
- 将处理结果响应给客户端。
cpp
void GetChatSessionList(const httplib::Request &request, httplib::Response &response) {
ymm_im::GetChatSessionListReq req;
ymm_im::GetChatSessionListRsp rsp;
auto err_response = [&req, &rsp, &response](const std::string &errmsg) -> void {
rsp.set_success(false);
rsp.set_errmsg(errmsg);
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
};
bool ret = req.ParseFromString(request.body);
if (ret == false) {
LOG_ERROR("获取聊天会话列表请求正文反序列化失败!");
return err_response("获取聊天会话列表请求正文反序列化失败!");
}
// 2. 客户端身份识别与鉴权
std::string ssid = req.session_id();
auto uid = _redis_session->uid(ssid);
if (!uid) {
LOG_ERROR("{} 获取登录会话关联用户信息失败!", ssid);
return err_response("获取登录会话关联用户信息失败!");
}
req.set_user_id(*uid);
// 3. 将请求转发给好友子服务进行业务处理
auto channel = _mm_channels->choose(_friend_service_name);
if (!channel) {
LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());
return err_response("未找到可提供业务处理的用户子服务节点!");
}
ymm_im::FriendService_Stub stub(channel.get());
brpc::Controller cntl;
stub.GetChatSessionList(&cntl, &req, &rsp, nullptr);
if (cntl.Failed()) {
LOG_ERROR("{} 好友子服务调用失败!", req.request_id());
return err_response("好友子服务调用失败!");
}
// 5. 向客户端进行响应
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
18. 创建多人聊天会话
- 取出 HTTP 请求正文,进行 ProtoBuf 反序列化
- 根据请求中的会话 ID 进行鉴权,并获取用户 ID,向请求中设置用户 ID
- 查找好友子服务
- 调用子服务对应接口进行业务处理
- 若处理成功,循环根据会话成员的 ID 找到他们的长连接,
a. 根据响应中的会话信息,逐个进行会话创建的通知
b. 清理响应中的会话信息 - 将处理结果响应给客户端。
cpp
void ChatSessionCreate(const httplib::Request &request, httplib::Response &response) {
ymm_im::ChatSessionCreateReq req;
ymm_im::ChatSessionCreateRsp rsp;
auto err_response = [&req, &rsp, &response](const std::string &errmsg) -> void {
rsp.set_success(false);
rsp.set_errmsg(errmsg);
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
};
bool ret = req.ParseFromString(request.body);
if (ret == false) {
LOG_ERROR("创建聊天会话请求正文反序列化失败!");
return err_response("创建聊天会话请求正文反序列化失败!");
}
// 2. 客户端身份识别与鉴权
std::string ssid = req.session_id();
auto uid = _redis_session->uid(ssid);
if (!uid) {
LOG_ERROR("{} 获取登录会话关联用户信息失败!", ssid);
return err_response("获取登录会话关联用户信息失败!");
}
req.set_user_id(*uid);
// 3. 将请求转发给好友子服务进行业务处理
auto channel = _mm_channels->choose(_friend_service_name);
if (!channel) {
LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());
return err_response("未找到可提供业务处理的用户子服务节点!");
}
ymm_im::FriendService_Stub stub(channel.get());
brpc::Controller cntl;
stub.ChatSessionCreate(&cntl, &req, &rsp, nullptr);
if (cntl.Failed()) {
LOG_ERROR("{} 好友子服务调用失败!", req.request_id());
return err_response("好友子服务调用失败!");
}
// 4. 若业务处理成功 --- 且获取被申请方长连接成功,则向被申请放进行好友申请事件通知
if (rsp.success()){
for (int i = 0; i < req.member_id_list_size(); i++) {
auto conn = _connections->connection(req.member_id_list(i));
if (!conn) {
LOG_DEBUG("未找到群聊成员 {} 长连接", req.member_id_list(i));
continue;
}
ymm_im::NotifyMessage notify;
notify.set_notify_type(ymm_im::NotifyType::CHAT_SESSION_CREATE_NOTIFY);
auto chat_session = notify.mutable_new_chat_session_info();
chat_session->mutable_chat_session_info()->CopyFrom(rsp.chat_session_info());
conn->send(notify.SerializeAsString(), websocketpp::frame::opcode::value::binary);
LOG_DEBUG("对群聊成员 {} 进行会话创建通知", req.member_id_list(i));
}
}
// 5. 向客户端进行响应
rsp.clear_chat_session_info();
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
19. 获取消息会话成员列表
- 取出 HTTP 请求正文,进行 ProtoBuf 反序列化
- 根据请求中的会话 ID 进行鉴权,并获取用户 ID,向请求中设置用户 ID
- 查找好友子服务
- 调用子服务对应接口进行业务处理
- 将处理结果响应给客户端。
cpp
void GetChatSessionMember(const httplib::Request &request, httplib::Response &response) {
ymm_im::GetChatSessionMemberReq req;
ymm_im::GetChatSessionMemberRsp rsp;
auto err_response = [&req, &rsp, &response](const std::string &errmsg) -> void {
rsp.set_success(false);
rsp.set_errmsg(errmsg);
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
};
bool ret = req.ParseFromString(request.body);
if (ret == false) {
LOG_ERROR("获取聊天会话成员请求正文反序列化失败!");
return err_response("获取聊天会话成员请求正文反序列化失败!");
}
// 2. 客户端身份识别与鉴权
std::string ssid = req.session_id();
auto uid = _redis_session->uid(ssid);
if (!uid) {
LOG_ERROR("{} 获取登录会话关联用户信息失败!", ssid);
return err_response("获取登录会话关联用户信息失败!");
}
req.set_user_id(*uid);
// 3. 将请求转发给好友子服务进行业务处理
auto channel = _mm_channels->choose(_friend_service_name);
if (!channel) {
LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());
return err_response("未找到可提供业务处理的用户子服务节点!");
}
ymm_im::FriendService_Stub stub(channel.get());
brpc::Controller cntl;
stub.GetChatSessionMember(&cntl, &req, &rsp, nullptr);
if (cntl.Failed()) {
LOG_ERROR("{} 好友子服务调用失败!", req.request_id());
return err_response("好友子服务调用失败!");
}
// 5. 向客户端进行响应
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
20. 发送新消息
- 取出 HTTP 请求正文,进行 ProtoBuf 反序列化
- 根据请求中的会话 ID 进行鉴权,并获取用户 ID,向请求中设置用户 ID
- 查找消息转发子服务
- 调用子服务对应接口进行业务处理
- 若处理成功,则根据处理结果中的用户 ID 列表,循环找到目标长连接,根据处理结果中的消息字段组织新消息通知,逐个对目标进行新消息通知。
- 若处理失败,则根据处理结果中的错误提示信息,设置响应内容
- 将处理结果响应给客户端。
cpp
void NewMessage(const httplib::Request &request, httplib::Response &response) {
ymm_im::NewMessageReq req;
ymm_im::NewMessageRsp rsp;//这是给客户端的响应
ymm_im::GetTransmitTargetRsp target_rsp;//这是请求子服务的响应
auto err_response = [&req, &rsp, &response](const std::string &errmsg) -> void {
rsp.set_success(false);
rsp.set_errmsg(errmsg);
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
};
bool ret = req.ParseFromString(request.body);
if (ret == false) {
LOG_ERROR("新消息请求正文反序列化失败!");
return err_response("新消息请求正文反序列化失败!");
}
// 2. 客户端身份识别与鉴权
std::string ssid = req.session_id();
auto uid = _redis_session->uid(ssid);
if (!uid) {
LOG_ERROR("{} 获取登录会话关联用户信息失败!", ssid);
return err_response("获取登录会话关联用户信息失败!");
}
req.set_user_id(*uid);
// 3. 将请求转发给好友子服务进行业务处理
auto channel = _mm_channels->choose(_transmite_service_name);
if (!channel) {
LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());
return err_response("未找到可提供业务处理的用户子服务节点!");
}
ymm_im::MsgTransmitService_Stub stub(channel.get());
brpc::Controller cntl;
stub.GetTransmitTarget(&cntl, &req, &target_rsp, nullptr);
if (cntl.Failed()) {
LOG_ERROR("{} 消息转发子服务调用失败!", req.request_id());
return err_response("消息转发子服务调用失败!");
}
// 4. 若业务处理成功 --- 且获取被申请方长连接成功,则向被申请放进行好友申请事件通知
if (target_rsp.success()){
for (int i = 0; i < target_rsp.target_id_list_size(); i++) {
std::string notify_uid = target_rsp.target_id_list(i);
if (notify_uid == *uid) continue; //不通知自己
auto conn = _connections->connection(notify_uid);
if (!conn) { continue;}
ymm_im::NotifyMessage notify;
notify.set_notify_type(ymm_im::NotifyType::CHAT_MESSAGE_NOTIFY);
auto msg_info = notify.mutable_new_message_info();
msg_info->mutable_message_info()->CopyFrom(target_rsp.message());
conn->send(notify.SerializeAsString(), websocketpp::frame::opcode::value::binary);
}
}
// 5. 向客户端进行响应
rsp.set_request_id(req.request_id());
rsp.set_success(target_rsp.success());
rsp.set_errmsg(target_rsp.errmsg());
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
21. 获取指定时间段消息列表
- 取出 HTTP 请求正文,进行 ProtoBuf 反序列化
- 根据请求中的会话 ID 进行鉴权,并获取用户 ID,向请求中设置用户 ID
- 查找消息存储子服务
- 调用子服务对应接口进行业务处理
- 将处理结果响应给客户端。
cpp
void GetHistoryMsg(const httplib::Request &request, httplib::Response &response) {
ymm_im::GetHistoryMsgReq req;
ymm_im::GetHistoryMsgRsp rsp;
auto err_response = [&req, &rsp, &response](const std::string &errmsg) -> void {
rsp.set_success(false);
rsp.set_errmsg(errmsg);
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
};
bool ret = req.ParseFromString(request.body);
if (ret == false) {
LOG_ERROR("获取区间消息请求正文反序列化失败!");
return err_response("获取区间消息请求正文反序列化失败!");
}
// 2. 客户端身份识别与鉴权
std::string ssid = req.session_id();
auto uid = _redis_session->uid(ssid);
if (!uid) {
LOG_ERROR("{} 获取登录会话关联用户信息失败!", ssid);
return err_response("获取登录会话关联用户信息失败!");
}
req.set_user_id(*uid);
// 3. 将请求转发给好友子服务进行业务处理
auto channel = _mm_channels->choose(_message_service_name);
if (!channel) {
LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());
return err_response("未找到可提供业务处理的用户子服务节点!");
}
ymm_im::MsgStorageService_Stub stub(channel.get());
brpc::Controller cntl;
stub.GetHistoryMsg(&cntl, &req, &rsp, nullptr);
if (cntl.Failed()) {
LOG_ERROR("{} 消息存储子服务调用失败!", req.request_id());
return err_response("消息存储子服务调用失败!");
}
// 5. 向客户端进行响应
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
22. 获取最近 N 条消息列表
- 取出 HTTP 请求正文,进行 ProtoBuf 反序列化
- 根据请求中的会话 ID 进行鉴权,并获取用户 ID,向请求中设置用户 ID
- 查找消息存储子服务
- 调用子服务对应接口进行业务处理
- 将处理结果响应给客户端。
cpp
void GetRecentMsg(const httplib::Request &request, httplib::Response &response) {
ymm_im::GetRecentMsgReq req;
ymm_im::GetRecentMsgRsp rsp;
auto err_response = [&req, &rsp, &response](const std::string &errmsg) -> void {
rsp.set_success(false);
rsp.set_errmsg(errmsg);
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
};
bool ret = req.ParseFromString(request.body);
if (ret == false) {
LOG_ERROR("获取最近消息请求正文反序列化失败!");
return err_response("获取最近消息请求正文反序列化失败!");
}
// 2. 客户端身份识别与鉴权
std::string ssid = req.session_id();
auto uid = _redis_session->uid(ssid);
if (!uid) {
LOG_ERROR("{} 获取登录会话关联用户信息失败!", ssid);
return err_response("获取登录会话关联用户信息失败!");
}
req.set_user_id(*uid);
// 3. 将请求转发给好友子服务进行业务处理
auto channel = _mm_channels->choose(_message_service_name);
if (!channel) {
LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());
return err_response("未找到可提供业务处理的用户子服务节点!");
}
ymm_im::MsgStorageService_Stub stub(channel.get());
brpc::Controller cntl;
stub.GetRecentMsg(&cntl, &req, &rsp, nullptr);
if (cntl.Failed()) {
LOG_ERROR("{} 消息存储子服务调用失败!", req.request_id());
return err_response("消息存储子服务调用失败!");
}
// 5. 向客户端进行响应
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
23. 搜索关键字历史消息
- 取出 HTTP 请求正文,进行 ProtoBuf 反序列化
- 根据请求中的会话 ID 进行鉴权,并获取用户 ID,向请求中设置用户 ID
- 查找消息存储子服务
- 调用子服务对应接口进行业务处理
- 将处理结果响应给客户端。
cpp
void MsgSearch(const httplib::Request &request, httplib::Response &response) {
ymm_im::MsgSearchReq req;
ymm_im::MsgSearchRsp rsp;
auto err_response = [&req, &rsp, &response](const std::string &errmsg) -> void {
rsp.set_success(false);
rsp.set_errmsg(errmsg);
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
};
bool ret = req.ParseFromString(request.body);
if (ret == false) {
LOG_ERROR("消息搜索请求正文反序列化失败!");
return err_response("消息搜索请求正文反序列化失败!");
}
// 2. 客户端身份识别与鉴权
std::string ssid = req.session_id();
auto uid = _redis_session->uid(ssid);
if (!uid) {
LOG_ERROR("{} 获取登录会话关联用户信息失败!", ssid);
return err_response("获取登录会话关联用户信息失败!");
}
req.set_user_id(*uid);
// 3. 将请求转发给好友子服务进行业务处理
auto channel = _mm_channels->choose(_message_service_name);
if (!channel) {
LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());
return err_response("未找到可提供业务处理的用户子服务节点!");
}
ymm_im::MsgStorageService_Stub stub(channel.get());
brpc::Controller cntl;
stub.MsgSearch(&cntl, &req, &rsp, nullptr);
if (cntl.Failed()) {
LOG_ERROR("{} 消息存储子服务调用失败!", req.request_id());
return err_response("消息存储子服务调用失败!");
}
// 5. 向客户端进行响应
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
24. 单个文件数据获取
- 取出 HTTP 请求正文,进行 ProtoBuf 反序列化
- 根据请求中的会话 ID 进行鉴权,并获取用户 ID,向请求中设置用户 ID
- 查找文件子服务
- 调用子服务对应接口进行业务处理
- 将处理结果响应给客户端。
cpp
void GetSingleFile(const httplib::Request &request, httplib::Response &response) {
ymm_im::GetSingleFileReq req;
ymm_im::GetSingleFileRsp rsp;
auto err_response = [&req, &rsp, &response](const std::string &errmsg) -> void {
rsp.set_success(false);
rsp.set_errmsg(errmsg);
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
};
bool ret = req.ParseFromString(request.body);
if (ret == false) {
LOG_ERROR("单文件下载请求正文反序列化失败!");
return err_response("单文件下载请求正文反序列化失败!");
}
// 2. 客户端身份识别与鉴权
std::string ssid = req.session_id();
auto uid = _redis_session->uid(ssid);
if (!uid) {
LOG_ERROR("{} 获取登录会话关联用户信息失败!", ssid);
return err_response("获取登录会话关联用户信息失败!");
}
req.set_user_id(*uid);
// 3. 将请求转发给好友子服务进行业务处理
auto channel = _mm_channels->choose(_file_service_name);
if (!channel) {
LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());
return err_response("未找到可提供业务处理的用户子服务节点!");
}
ymm_im::FileService_Stub stub(channel.get());
brpc::Controller cntl;
stub.GetSingleFile(&cntl, &req, &rsp, nullptr);
if (cntl.Failed()) {
LOG_ERROR("{} 文件存储子服务调用失败!", req.request_id());
return err_response("文件存储子服务调用失败!");
}
// 5. 向客户端进行响应
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
25. 多个文件数据获取
- 取出 HTTP 请求正文,进行 ProtoBuf 反序列化
- 根据请求中的会话 ID 进行鉴权,并获取用户 ID,向请求中设置用户 ID
- 查找文件子服务
- 调用子服务对应接口进行业务处理
- 将处理结果响应给客户端。
cpp
void GetMultiFile(const httplib::Request &request, httplib::Response &response) {
ymm_im::GetMultiFileReq req;
ymm_im::GetMultiFileRsp rsp;
auto err_response = [&req, &rsp, &response](const std::string &errmsg) -> void {
rsp.set_success(false);
rsp.set_errmsg(errmsg);
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
};
bool ret = req.ParseFromString(request.body);
if (ret == false) {
LOG_ERROR("单文件下载请求正文反序列化失败!");
return err_response("单文件下载请求正文反序列化失败!");
}
// 2. 客户端身份识别与鉴权
std::string ssid = req.session_id();
auto uid = _redis_session->uid(ssid);
if (!uid) {
LOG_ERROR("{} 获取登录会话关联用户信息失败!", ssid);
return err_response("获取登录会话关联用户信息失败!");
}
req.set_user_id(*uid);
// 3. 将请求转发给好友子服务进行业务处理
auto channel = _mm_channels->choose(_file_service_name);
if (!channel) {
LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());
return err_response("未找到可提供业务处理的用户子服务节点!");
}
ymm_im::FileService_Stub stub(channel.get());
brpc::Controller cntl;
stub.GetMultiFile(&cntl, &req, &rsp, nullptr);
if (cntl.Failed()) {
LOG_ERROR("{} 文件存储子服务调用失败!", req.request_id());
return err_response("文件存储子服务调用失败!");
}
// 5. 向客户端进行响应
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
26. 单个文件数据上传
- 取出 HTTP 请求正文,进行 ProtoBuf 反序列化
- 根据请求中的会话 ID 进行鉴权,并获取用户 ID,向请求中设置用户 ID
- 查找文件子服务
- 调用子服务对应接口进行业务处理
- 将处理结果响应给客户端。
cpp
void PutSingleFile(const httplib::Request &request, httplib::Response &response) {
ymm_im::PutSingleFileReq req;
ymm_im::PutSingleFileRsp rsp;
auto err_response = [&req, &rsp, &response](const std::string &errmsg) -> void {
rsp.set_success(false);
rsp.set_errmsg(errmsg);
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
};
bool ret = req.ParseFromString(request.body);
if (ret == false) {
LOG_ERROR("单文件上传请求正文反序列化失败!");
return err_response("单文件上传请求正文反序列化失败!");
}
// 2. 客户端身份识别与鉴权
std::string ssid = req.session_id();
auto uid = _redis_session->uid(ssid);
if (!uid) {
LOG_ERROR("{} 获取登录会话关联用户信息失败!", ssid);
return err_response("获取登录会话关联用户信息失败!");
}
req.set_user_id(*uid);
// 3. 将请求转发给好友子服务进行业务处理
auto channel = _mm_channels->choose(_file_service_name);
if (!channel) {
LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());
return err_response("未找到可提供业务处理的用户子服务节点!");
}
ymm_im::FileService_Stub stub(channel.get());
brpc::Controller cntl;
stub.PutSingleFile(&cntl, &req, &rsp, nullptr);
if (cntl.Failed()) {
LOG_ERROR("{} 文件存储子服务调用失败!", req.request_id());
return err_response("文件存储子服务调用失败!");
}
// 5. 向客户端进行响应
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
27. 多个文件数据上传
- 取出 HTTP 请求正文,进行 ProtoBuf 反序列化
- 根据请求中的会话 ID 进行鉴权,并获取用户 ID,向请求中设置用户 ID
- 查找文件子服务
- 调用子服务对应接口进行业务处理
- 将处理结果响应给客户端。
cpp
void PutMultiFile(const httplib::Request &request, httplib::Response &response) {
ymm_im::PutMultiFileReq req;
ymm_im::PutMultiFileRsp rsp;
auto err_response = [&req, &rsp, &response](const std::string &errmsg) -> void {
rsp.set_success(false);
rsp.set_errmsg(errmsg);
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
};
bool ret = req.ParseFromString(request.body);
if (ret == false) {
LOG_ERROR("批量文件上传请求正文反序列化失败!");
return err_response("批量文件上传请求正文反序列化失败!");
}
// 2. 客户端身份识别与鉴权
std::string ssid = req.session_id();
auto uid = _redis_session->uid(ssid);
if (!uid) {
LOG_ERROR("{} 获取登录会话关联用户信息失败!", ssid);
return err_response("获取登录会话关联用户信息失败!");
}
req.set_user_id(*uid);
// 3. 将请求转发给好友子服务进行业务处理
auto channel = _mm_channels->choose(_file_service_name);
if (!channel) {
LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());
return err_response("未找到可提供业务处理的用户子服务节点!");
}
ymm_im::FileService_Stub stub(channel.get());
brpc::Controller cntl;
stub.PutMultiFile(&cntl, &req, &rsp, nullptr);
if (cntl.Failed()) {
LOG_ERROR("{} 文件存储子服务调用失败!", req.request_id());
return err_response("文件存储子服务调用失败!");
}
// 5. 向客户端进行响应
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
28. 语音转文字
- 取出 HTTP 请求正文,进行 ProtoBuf 反序列化
- 根据请求中的会话 ID 进行鉴权,并获取用户 ID,向请求中设置用户 ID
- 查找语音子服务
- 调用子服务对应接口进行业务处理
- 将处理结果响应给客户端。
cpp
void SpeechRecognition(const httplib::Request &request, httplib::Response &response) {
LOG_DEBUG("收到语音转文字请求!");
ymm_im::SpeechRecognitionReq req;
ymm_im::SpeechRecognitionRsp rsp;
auto err_response = [&req, &rsp, &response](const std::string &errmsg) -> void {
rsp.set_success(false);
rsp.set_errmsg(errmsg);
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
};
bool ret = req.ParseFromString(request.body);
if (ret == false) {
LOG_ERROR("语音识别请求正文反序列化失败!");
return err_response("语音识别请求正文反序列化失败!");
}
// 2. 客户端身份识别与鉴权
std::string ssid = req.session_id();
auto uid = _redis_session->uid(ssid);
if (!uid) {
LOG_ERROR("{} 获取登录会话关联用户信息失败!", ssid);
return err_response("获取登录会话关联用户信息失败!");
}
req.set_user_id(*uid);
// 3. 将请求转发给好友子服务进行业务处理
auto channel = _mm_channels->choose(_speech_service_name);
if (!channel) {
LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());
return err_response("未找到可提供业务处理的用户子服务节点!");
}
ymm_im::SpeechService_Stub stub(channel.get());
brpc::Controller cntl;
stub.SpeechRecognition(&cntl, &req, &rsp, nullptr);
if (cntl.Failed()) {
LOG_ERROR("{} 语音识别子服务调用失败!", req.request_id());
return err_response("语音识别子服务调用失败!");
}
// 5. 向客户端进行响应
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
其余函数
onopen、onclose、onmessage都是要设置到websocket里的回调函数
- onopen表示连接建立成功时的回调
- onclose表示连接断开之后要执行的回调函数,执行一些清理工作
- 收到第一条消息后,根据消息中的会话ID进行身份识别,将客户端长连接添加管理
cpp
void onOpen(websocketpp::connection_hdl hdl) {
LOG_DEBUG("websocket长连接建立成功 {}", (size_t)_ws_server.get_con_from_hdl(hdl).get());
}
void onClose(websocketpp::connection_hdl hdl) {
//长连接断开时做的清理工作
//0. 通过连接对象,获取对应的用户ID与登录会话ID
auto conn = _ws_server.get_con_from_hdl(hdl);
std::string uid, ssid;
bool ret = _connections->client(conn, uid, ssid);
if (ret == false) {
LOG_WARN("长连接断开,未找到长连接对应的客户端信息!");
return ;
}
//1. 移除登录会话信息
_redis_session->remove(ssid);
//2. 移除登录状态信息
_redis_status->remove(uid);
//3. 移除长连接管理数据
_connections->remove(conn);
LOG_DEBUG("{} {} {} 长连接断开,清理缓存数据!", ssid, uid, (size_t)conn.get());
}
void onMessage(websocketpp::connection_hdl hdl, server_t::message_ptr msg) {
//收到第一条消息后,根据消息中的会话ID进行身份识别,将客户端长连接添加管理
//1. 取出长连接对应的连接对象
auto conn = _ws_server.get_con_from_hdl(hdl);
//2. 针对消息内容进行反序列化 -- ClientAuthenticationReq -- 提取登录会话ID
ymm_im::ClientAuthenticationReq request;
bool ret = request.ParseFromString(msg->get_payload());
if (ret == false) {
LOG_ERROR("长连接身份识别失败:正文反序列化失败!");
_ws_server.close(hdl, websocketpp::close::status::unsupported_data, "正文反序列化失败!");
return;
}
//3. 在会话信息缓存中,查找会话信息
std::string ssid = request.session_id();
auto uid = _redis_session->uid(ssid);
//4. 会话信息不存在则关闭连接
if (!uid) {
LOG_ERROR("长连接身份识别失败:未找到会话信息 {}!", ssid);
_ws_server.close(hdl, websocketpp::close::status::unsupported_data, "未找到会话信息!");
return;
}
//5. 会话信息存在,则添加长连接管理
_connections->insert(conn, *uid, ssid);
LOG_DEBUG("新增长连接管理:{}-{}-{}", ssid, *uid, (size_t)conn.get());
keepAlive(conn);
}
cpp
void keepAlive(server_t::connection_ptr conn) {
if (!conn || conn->get_state() != websocketpp::session::state::value::open) {
LOG_DEBUG("非正常连接状态,结束连接保活");
return;
}
conn->ping("");
_ws_server.set_timer(60000, std::bind(&GatewayServer::keepAlive, this, conn));
}