【聊天室后端服务器开发】 入口网关开发
功能分析
核心功能
- 作为入口服务器接收客户端的所有请求,入口网关接收到请求后,将请求进行子服务分发,子服务给服务器返回响应,然后服务器再向客户端返回响应
- 网关对客户端进行事件通知,例如好友申请及删除、新消息创建等
服务器需要完成两种通信方式
- HTTP通信,主要用于进行业务处理
- WebSocket通信:主要进行事件通知
主要模块
整体架构分析
- WebSocket 服务器:用于实时通信,如消息推送、好友申请通知等
- HTTP 服务器:用于处理常规的 HTTP 请求,如用户注册、登录、获取用户信息等
- Redis:用于会话管理和状态存储
- 服务发现与信道管理:通过服务发现机制(Etcd)和信道管理器,将请求转发到相应的子服务(如用户服务、好友服务、消息服务等)
- 子服务通信:使用 gRPC 和 Protobuf 与各个子服务进行通信,实现具体的业务逻辑
具体实现
长连接管理
分析功能需求
- 客户端登录成功后与服务器建立长连接,并且发送自己的身份信息,也就是登录ID
- 数据管理
- 通知推送:通过用户ID找到该客户端对应的长连接
- 断开连接:通过连接找到用户ID,然后删除登录会话ID(redis中会话缓存与登录信息缓存)
功能实现
- 哈希表保存用户ID:长连接映射关系
- 映射关系:长连接 - 用户ID与会话ID
具体操作
- 新增数据接口
- 通过用户ID获取长连接接口
- 通过长连接获取用户ID和会话ID接口
具体实现
#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>
#include "logger.hpp"
namespace mag {
// 定义 WebSocket 服务器类型,使用 Asio 配置且不启用 TLS
typedef websocketpp::server<websocketpp::config::asio> server_t;
// 连接的类型定义:server_t::connection_ptr
// 这是一个指向 WebSocket 连接的智能指针
// 用于管理和引用具体的 WebSocket 连接
// 例如:server_t::connection_ptr conn = server.get_connection(...);
class Connection {
public:
// 定义一个内部结构体 Client,用于存储客户端的用户ID和会话ID
struct Client {
// 构造函数,初始化 uid 和 ssid
Client(const std::string &u, const std::string &s) : uid(u), ssid(s) {}
std::string uid; // 用户唯一标识符
std::string ssid; // 会话唯一标识符
};
using ptr = std::shared_ptr<Connection>; // 定义共享指针类型别名,便于管理 Connection 实例
// 默认构造函数
Connection() {}
// 析构函数
~Connection() {}
/**
* @brief 插入一个新的连接,将其与用户ID和会话ID关联
*
* @param conn WebSocket 连接指针
* @param uid 用户唯一标识符
* @param ssid 会话唯一标识符
*/
void insert(const server_t::connection_ptr &conn,
const std::string &uid, const std::string &ssid) {
// 使用 unique_lock 对互斥锁进行加锁,确保线程安全
std::unique_lock<std::mutex> lock(_mutex);
// 将用户ID与连接指针关联,并插入到 _uid_connections 映射中
_uid_connections.insert(std::make_pair(uid, conn));
// 将连接指针与 Client 结构体关联,并插入到 _conn_clients 映射中
_conn_clients.insert(std::make_pair(conn, Client(uid, ssid)));
// 记录调试日志,包含连接地址、用户ID和会话ID
LOG_DEBUG("新增长连接用户信息:{}-{}-{}", (size_t)conn.get(), uid, ssid);
}
/**
* @brief 根据用户ID获取对应的连接指针
*
* @param uid 用户唯一标识符
* @return server_t::connection_ptr 如果找到则返回连接指针,否则返回空指针
*/
server_t::connection_ptr connection(const std::string &uid) {
// 加锁以确保线程安全
std::unique_lock<std::mutex> lock(_mutex);
// 在 _uid_connections 映射中查找用户ID
auto it = _uid_connections.find(uid);
if (it == _uid_connections.end()) {
// 如果未找到,记录错误日志并返回空指针
LOG_ERROR("未找到 {} 客户端的长连接!", uid);
return server_t::connection_ptr();
}
// 如果找到,记录调试日志并返回连接指针
LOG_DEBUG("找到 {} 客户端的长连接!", uid);
return it->second;
}
/**
* @brief 根据连接指针获取对应的用户ID和会话ID
*
* @param conn WebSocket 连接指针
* @param uid 输出参数,返回用户唯一标识符
* @param ssid 输出参数,返回会话唯一标识符
* @return true 如果找到对应的客户端信息
* @return false 如果未找到对应的客户端信息
*/
bool client(const server_t::connection_ptr &conn, std::string &uid, std::string &ssid) {
// 加锁以确保线程安全
std::unique_lock<std::mutex> lock(_mutex);
// 在 _conn_clients 映射中查找连接指针
auto it = _conn_clients.find(conn);
if (it == _conn_clients.end()) {
// 如果未找到,记录错误日志并返回 false
LOG_ERROR("获取-未找到长连接 {} 对应的客户端信息!", (size_t)conn.get());
return false;
}
// 如果找到,将 uid 和 ssid 赋值给输出参数
uid = it->second.uid;
ssid = it->second.ssid;
// 记录调试日志,表示成功获取客户端信息
LOG_DEBUG("获取长连接客户端信息成功!");
return true;
}
/**
* @brief 移除一个连接,将其从管理系统中删除
*
* @param conn WebSocket 连接指针
*/
void remove(const server_t::connection_ptr &conn) {
// 加锁以确保线程安全
std::unique_lock<std::mutex> lock(_mutex);
// 在 _conn_clients 映射中查找连接指针
auto it = _conn_clients.find(conn);
if (it == _conn_clients.end()) {
// 如果未找到,记录错误日志并返回
LOG_ERROR("删除-未找到长连接 {} 对应的客户端信息!", (size_t)conn.get());
return;
}
// 从 _uid_connections 映射中删除对应的用户ID
_uid_connections.erase(it->second.uid);
// 从 _conn_clients 映射中删除连接指针
_conn_clients.erase(it);
// 记录调试日志,表示成功删除连接信息
LOG_DEBUG("删除长连接信息完毕!");
}
private:
std::mutex _mutex; // 互斥锁,确保对映射的线程安全访问
// 用户ID到连接指针的映射,便于通过用户ID快速查找连接
std::unordered_map<std::string, server_t::connection_ptr> _uid_connections;
// 连接指针到客户端信息的映射,便于通过连接查找用户ID和会话ID
std::unordered_map<server_t::connection_ptr, Client> _conn_clients;
};
} // namespace mag
网关初始化
主要逻辑分析
- 注意处理Websocket与HTTP请求是两个不同的线程
- redis会话与状态管理:通过Session和Status类管理用户会话和登录会话状态
- WebSocket服务器初始化
- 绑定事件处理函数,其中包括连接打开、关闭与消息接收
- 绑定指定的WebSocket端口并开始新的连接
- HTTP服务器初始化
- 为每个HTTP请求路径配置对应的处理函数
- 启动一个独立的线程运行HTTP服务器,监听指定的HTTP端口
实现
GatewayServer(
int websocket_port,
int http_port,
const std::shared_ptr<sw::redis::Redis> &redis_client,
const ServiceManager::ptr &channels,
const Discovery::ptr &service_discoverer,
const std::string user_service_name,
const std::string file_service_name,
const std::string speech_service_name,
const std::string message_service_name,
const std::string transmite_service_name,
const std::string friend_service_name)
: _redis_session(std::make_shared<Session>(redis_client)),
_redis_status(std::make_shared<Status>(redis_client)),
_mm_channels(channels),
_service_discoverer(service_discoverer),
_user_service_name(user_service_name),
_file_service_name(file_service_name),
_speech_service_name(speech_service_name),
_message_service_name(message_service_name),
_transmite_service_name(transmite_service_name),
_friend_service_name(friend_service_name),
_connections(std::make_shared<Connection>()) {
// 初始化WebSocket服务器设置
_ws_server.set_access_channels(websocketpp::log::alevel::none); // 关闭日志输出
_ws_server.init_asio(); // 初始化Asio
// 绑定WebSocket连接打开事件处理函数
_ws_server.set_open_handler(std::bind(&GatewayServer::onOpen, this, std::placeholders::_1));
// 绑定WebSocket连接关闭事件处理函数
_ws_server.set_close_handler(std::bind(&GatewayServer::onClose, this, std::placeholders::_1));
// 绑定WebSocket消息接收事件处理函数
auto wscb = std::bind(&GatewayServer::onMessage, this,
std::placeholders::_1, std::placeholders::_2);
_ws_server.set_message_handler(wscb);
_ws_server.set_reuse_addr(true); // 允许重用地址
_ws_server.listen(websocket_port); // 监听指定端口
_ws_server.start_accept(); // 开始接受连接
// 绑定各个HTTP接口的处理函数
_http_server.Post(GET_PHONE_VERIFY_CODE,
(httplib::Server::Handler)std::bind(&GatewayServer::GetPhoneVerifyCode, this, std::placeholders::_1, std::placeholders::_2));
// ...(其他HTTP接口绑定省略)
// 启动HTTP服务器线程
_http_thread = std::thread([this, http_port]() {
_http_server.listen("0.0.0.0", http_port); // 监听所有IP地址
});
_http_thread.detach(); // 分离线程,使其独立运行
}
Websocket事件处理函数
新连接建立
void onOpen(websocketpp::connection_hdl hdl) {
// 获取连接对象并记录日志
LOG_DEBUG("WebSocket 长连接建立成功,连接ID: {}", (size_t)_ws_server.get_con_from_hdl(hdl).get());
}
连接关闭
void onClose(websocketpp::connection_hdl hdl) {
// WebSocket连接断开时的清理工作
// 步骤:
// 1. 获取连接对象
auto conn = _ws_server.get_con_from_hdl(hdl);
std::string uid, ssid;
// 2. 通过连接对象获取对应的用户ID和会话ID
bool ret = _connections->client(conn, uid, ssid);
if (ret == false) {
LOG_WARN("长连接断开,未找到长连接对应的客户端信息!");
return;
}
// 3. 移除登录会话信息
_redis_session->remove(ssid);
// 4. 移除登录状态信息
_redis_status->remove(uid);
// 5. 移除长连接管理数据
_connections->remove(conn);
LOG_DEBUG("会话ID: {} 用户ID: {} 长连接断开,清理缓存数据!", ssid, uid, (size_t)conn.get());
}
接收消息后判会话
- 会话验证:通过会话 ID 在 Redis 中查找对应的用户 ID
- 身份验证:
- 如果会话信息不存在,记录错误并关闭连接
- 如果会话信息存在,将连接添加到连接管理器中,并启动保活机制(定期发送 Ping 消息,保持连接活跃)
void onMessage(websocketpp::connection_hdl hdl, server_t::message_ptr msg) {
// 收到第一条消息后,根据消息中的会话ID进行身份识别,将客户端长连接添加管理
// 步骤:
// 1. 获取连接对象
auto conn = _ws_server.get_con_from_hdl(hdl);
// 2. 反序列化消息内容,提取会话ID
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("新增长连接管理:会话ID: {} 用户ID: {} 连接ID: {}", ssid, *uid, (size_t)conn.get());
// 6. 启动保活机制
keepAlive(conn);
}
HTTP接口处理函数
客户端请求获取验证码
基本逻辑
- 反序列化请求:将 HTTP 请求的正文反序列化为
PhoneVerifyCodeReq
对象 - 请求转发:将请求通过 gRPC 转发到用户子服务 (
UserService
) 的GetPhoneVerifyCode
方法 - 处理响应:将用户子服务的响应序列化后返回给客户端
实现
void GetPhoneVerifyCode(const httplib::Request &request, httplib::Response &response) {
// 1. 反序列化HTTP请求正文
PhoneVerifyCodeReq req;
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("未找到可提供业务处理的用户子服务节点!");
}
mag::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");
}
用户名注册
实现逻辑
- 反序列化请求:将 HTTP 请求正文反序列化为
UserRegisterReq
对象,提取用户名、密码等注册信息 - 请求转发:将注册请求通过 gRPC 转发到用户子服务 (
UserService
) 的UserRegister
方法 - 处理响应:将用户子服务的注册结果(成功或失败)序列化后返回给客户端
实现
void UserRegister(const httplib::Request &request, httplib::Response &response) {
// 1. 反序列化HTTP请求正文
UserRegisterReq req;
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("未找到可提供业务处理的用户子服务节点!");
}
mag::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");
}
用户名密码登录
void UserLogin(const httplib::Request &request, httplib::Response &response) {
// 1. 反序列化HTTP请求正文
UserLoginReq req;
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("未找到可提供业务处理的用户子服务节点!");
}
mag::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");
}
手机号注册
void PhoneRegister(const httplib::Request &request, httplib::Response &response) {
// 1. 反序列化HTTP请求正文
PhoneRegisterReq req;
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("未找到可提供业务处理的用户子服务节点!");
}
mag::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");
}
手机号与验证码登录
void PhoneLogin(const httplib::Request &request, httplib::Response &response) {
// 1. 反序列化HTTP请求正文
PhoneLoginReq req;
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("未找到可提供业务处理的用户子服务节点!");
}
mag::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");
}
获取用户信息
基本逻辑梳理
例如用户A已经登录,需要查看自己的个人信息,那么客户端就会向/service/user/get_user_info发送一个HTTP POST请求,请求正文中包含会话ID,服务器接收到请求后,通过GetUserInfo进行处理
- 解析请求:提取会话 ID
- 验证身份:通过会话 ID 获取用户 ID,确保请求者的身份合法
- 调用子服务:将请求转发给用户子服务,获取用户的详细信息
- 返回结果:将用户信息返回给客户端,供客户端展示
实现
void GetUserInfo(const httplib::Request &request, httplib::Response &response) {
// 1. 反序列化HTTP请求正文
GetUserInfoReq req;
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);
// 3. 将请求转发给用户子服务进行业务处理
auto channel = _mm_channels->choose(_user_service_name);
if (!channel) {
LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());
return err_response("未找到可提供业务处理的用户子服务节点!");
}
mag::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("用户子服务调用失败!");
}
// 4. 序列化子服务响应作为HTTP响应正文
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
设置用户头像
基本逻辑
- 反序列化请求:将 HTTP 请求正文反序列化为SetUserAvatarReq对象,提取会话 ID 和头像 URL 或图片数据
- 身份验证:通过会话 ID 在 Redis 中查找对应的用户 ID
- 请求转发:将设置头像的请求通过 gRPC 转发到用户子服务
- 处理响应:将用户子服务的设置结果(成功或失败)序列化后返回给客户端
实现
void SetUserAvatar(const httplib::Request &request, httplib::Response &response) {
// 1. 反序列化HTTP请求正文
SetUserAvatarReq req;
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);
// 3. 将请求转发给用户子服务进行业务处理
auto channel = _mm_channels->choose(_user_service_name);
if (!channel) {
LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());
return err_response("未找到可提供业务处理的用户子服务节点!");
}
mag::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("用户子服务调用失败!");
}
// 4. 序列化子服务响应作为HTTP响应正文
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
设置用户昵称
void SetUserNickname(const httplib::Request &request, httplib::Response &response) {
// 1. 反序列化HTTP请求正文
SetUserNicknameReq req;
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);
// 3. 将请求转发给用户子服务进行业务处理
auto channel = _mm_channels->choose(_user_service_name);
if (!channel) {
LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());
return err_response("未找到可提供业务处理的用户子服务节点!");
}
mag::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("用户子服务调用失败!");
}
// 4. 序列化子服务响应作为HTTP响应正文
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
设置个性签名
void SetUserDescription(const httplib::Request &request, httplib::Response &response) {
// 1. 反序列化HTTP请求正文
SetUserDescriptionReq req;
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);
// 3. 将请求转发给用户子服务进行业务处理
auto channel = _mm_channels->choose(_user_service_name);
if (!channel) {
LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());
return err_response("未找到可提供业务处理的用户子服务节点!");
}
mag::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("用户子服务调用失败!");
}
// 4. 序列化子服务响应作为HTTP响应正文
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
设置手机号
void SetUserPhoneNumber(const httplib::Request &request, httplib::Response &response) {
// 1. 反序列化HTTP请求正文
SetUserPhoneNumberReq req;
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);
// 3. 将请求转发给用户子服务进行业务处理
auto channel = _mm_channels->choose(_user_service_name);
if (!channel) {
LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());
return err_response("未找到可提供业务处理的用户子服务节点!");
}
mag::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("用户子服务调用失败!");
}
// 4. 序列化子服务响应作为HTTP响应正文
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
获取好友列表
void GetFriendList(const httplib::Request &request, httplib::Response &response) {
// 1. 反序列化HTTP请求正文
GetFriendListReq req;
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);
// 3. 将请求转发给好友子服务进行业务处理
auto channel = _mm_channels->choose(_friend_service_name);
if (!channel) {
LOG_ERROR("{} 未找到可提供业务处理的好友子服务节点!", req.request_id());
return err_response("未找到可提供业务处理的好友子服务节点!");
}
mag::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("好友子服务调用失败!");
}
// 4. 序列化子服务响应作为HTTP响应正文
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
好友申请
逻辑总结
- 反序列化请求:将 HTTP 请求正文反序列化为FriendAddReq对象,提取会话 ID 和被申请者的用户 ID
- 身份验证:通过会话 ID 在 Redis 中查找对应的用户 ID
- 请求转发:将好友申请的请求通过 gRPC 转发到好友子服务
- 通知被申请方:
- 如果好友申请成功,并且被申请方在线(有 WebSocket 连接),则通过 WebSocket 向被申请方发送好友申请通知
- 获取申请人的用户信息,构建并发送通知消息
- 返回结果:将好友申请结果(成功或失败)返回给客户端
实现
void FriendAdd(const httplib::Request &request, httplib::Response &response) {
// 好友申请的业务处理中,好友子服务其实只是在数据库创建了申请事件
// 网关需要做的事情:当好友子服务将业务处理完毕后,如果处理是成功的--需要通知被申请方
// 步骤:
// 1. 反序列化请求,提取登录会话ID
FriendAddReq req;
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("未找到可提供业务处理的好友子服务节点!");
}
mag::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. 如果业务处理成功,并且被申请方在线,则向被申请方发送好友申请通知
if (rsp.success()) {
auto conn = _connections->connection(req.respondent_id());
if (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("获取当前客户端用户信息失败!");
}
// 构建通知消息
NotifyMessage notify;
notify.set_notify_type(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");
}
好友申请同意或者拒绝
逻辑分析
- 反序列化请求:将 HTTP 请求正文反序列化为FriendAddProcessReq对象,提取会话 ID、申请者 ID、处理结果(同意或拒绝)
- 身份验证:通过会话 ID 在 Redis 中查找对应的用户 ID
- 请求转发:将好友申请处理请求通过 gRPC 转发到好友子服务
- 通知申请人:
- 如果处理成功,获取申请人和处理人的用户信息
- 如果申请人在线,通过 WebSocket 向申请人发送好友处理结果通知
- 如果好友申请被同意,创建新的聊天会话,并通过 WebSocket 通知申请人和处理人
实现
void FriendAddProcess(const httplib::Request &request, httplib::Response &response) {
//好友申请的处理-----
FriendAddProcessReq req;
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("未找到可提供业务处理的好友子服务节点!");
}
mag::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("好友子服务调用失败!");
}
// 4. 如果处理成功,通知申请人并可能创建聊天会话
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 (!apply_user_rsp) {
LOG_ERROR("{} 获取用户信息失败!", req.request_id());
return err_response("获取用户信息失败!");
}
// 获取处理人和申请人的WebSocket连接
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("未找到申请人的长连接!");
// 向申请人发送好友处理结果通知
if (apply_conn) {
NotifyMessage notify;
notify.set_notify_type(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("对申请人进行申请处理结果通知!");
}
// 如果同意好友申请,创建单聊会话并通知双方
if (req.agree()) {
// 向申请人发送会话创建通知
if (apply_conn) {
NotifyMessage notify;
notify.set_notify_type(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 (process_conn) {
NotifyMessage notify;
notify.set_notify_type(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("对处理人进行会话创建通知!");
}
}
}
// 5. 向客户端返回响应
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
删除好友
void FriendRemove(const httplib::Request &request, httplib::Response &response) {
// 1. 反序列化请求,提取登录会话ID
FriendRemoveReq req;
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("未找到可提供业务处理的好友子服务节点!");
}
mag::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. 如果业务处理成功,并且被删除方在线,则向被删除方发送好友删除通知
if (rsp.success()) {
auto conn = _connections->connection(req.peer_id());
if (conn) {
LOG_ERROR("对被删除人 {} 进行好友删除通知!", req.peer_id());
NotifyMessage notify;
notify.set_notify_type(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");
}
搜索好友
void FriendSearch(const httplib::Request &request, httplib::Response &response) {
// 1. 反序列化请求,提取会话ID和搜索关键词
FriendSearchReq req;
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("未找到可提供业务处理的好友子服务节点!");
}
mag::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("好友子服务调用失败!");
}
// 4. 序列化子服务响应作为HTTP响应正文
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
获取用户待处理好友申请列表
void GetPendingFriendEventList(const httplib::Request &request, httplib::Response &response) {
// 1. 反序列化请求,提取会话ID
GetPendingFriendEventListReq req;
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("未找到可提供业务处理的好友子服务节点!");
}
mag::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("好友子服务调用失败!");
}
// 4. 序列化子服务响应作为HTTP响应正文
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
获取用户聊天会话列表
void GetChatSessionList(const httplib::Request &request, httplib::Response &response) {
// 1. 反序列化请求,提取会话ID
GetChatSessionListReq req;
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("未找到可提供业务处理的好友子服务节点!");
}
mag::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("好友子服务调用失败!");
}
// 4. 序列化子服务响应作为HTTP响应正文
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
创建新聊天会话
void ChatSessionCreate(const httplib::Request &request, httplib::Response &response) {
// 1. 反序列化请求,提取会话ID和成员列表
ChatSessionCreateReq req;
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("未找到可提供业务处理的好友子服务节点!");
}
mag::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++) {
std::string notify_uid = req.member_id_list(i);
auto conn = _connections->connection(notify_uid);
if (!conn) {
LOG_DEBUG("未找到群聊成员 {} 长连接", notify_uid);
continue;
}
// 构建通知消息
NotifyMessage notify;
notify.set_notify_type(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("对群聊成员 {} 进行会话创建通知", notify_uid);
}
}
// 5. 清理会话信息并向客户端返回响应
rsp.clear_chat_session_info();
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
获取特定聊天会话成员
void GetChatSessionMember(const httplib::Request &request, httplib::Response &response) {
// 1. 反序列化请求,提取会话ID和聊天会话ID
GetChatSessionMemberReq req;
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("未找到可提供业务处理的好友子服务节点!");
}
mag::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("好友子服务调用失败!");
}
// 4. 序列化子服务响应作为HTTP响应正文
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
获取特定时间区间消息
void GetHistoryMsg(const httplib::Request &request, httplib::Response &response) {
// 1. 反序列化请求,提取会话ID和消息区间
GetHistoryMsgReq req;
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("未找到可提供业务处理的消息存储子服务节点!");
}
bite_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("消息存储子服务调用失败!");
}
// 4. 序列化子服务响应作为HTTP响应正文
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
获取最近若干条消息
void GetRecentMsg(const httplib::Request &request, httplib::Response &response) {
// 1. 反序列化请求,提取会话ID和获取最近消息的数量
GetRecentMsgReq req;
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("未找到可提供业务处理的消息存储子服务节点!");
}
bite_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("消息存储子服务调用失败!");
}
// 4. 序列化子服务响应作为HTTP响应正文
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
搜索消息
void MsgSearch(const httplib::Request &request, httplib::Response &response) {
// 1. 反序列化请求,提取会话ID和搜索关键词
MsgSearchReq req;
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("未找到可提供业务处理的消息存储子服务节点!");
}
mag::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("消息存储子服务调用失败!");
}
// 4. 序列化子服务响应作为HTTP响应正文
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
获取用户下载的单个文件
void GetSingleFile(const httplib::Request &request, httplib::Response &response) {
// 1. 反序列化请求,提取会话ID和文件ID
GetSingleFileReq req;
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("未找到可提供业务处理的文件子服务节点!");
}
mag::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("文件存储子服务调用失败!");
}
// 4. 序列化子服务响应作为HTTP响应正文
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
获取用户下载的多个文件
void GetMultiFile(const httplib::Request &request, httplib::Response &response) {
// 1. 反序列化请求,提取会话ID和文件ID列表
GetMultiFileReq req;
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("未找到可提供业务处理的文件子服务节点!");
}
mag::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("文件存储子服务调用失败!");
}
// 4. 序列化子服务响应作为HTTP响应正文
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
用户上传单个文件
void PutSingleFile(const httplib::Request &request, httplib::Response &response) {
// 1. 反序列化请求,提取会话ID和文件数据
PutSingleFileReq req;
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("未找到可提供业务处理的文件子服务节点!");
}
mag::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("文件存储子服务调用失败!");
}
// 4. 序列化子服务响应作为HTTP响应正文
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
用户上传多个文件
void PutMultiFile(const httplib::Request &request, httplib::Response &response) {
// 1. 反序列化请求,提取会话ID和文件数据列表
PutMultiFileReq req;
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("未找到可提供业务处理的文件子服务节点!");
}
mag::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("文件存储子服务调用失败!");
}
// 4. 序列化子服务响应作为HTTP响应正文
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
语音转文字
void SpeechRecognition(const httplib::Request &request, httplib::Response &response) {
LOG_DEBUG("收到语音转文字请求!");
// 1. 反序列化请求,提取会话ID和语音数据
SpeechRecognitionReq req;
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("未找到可提供业务处理的语音子服务节点!");
}
mag::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("语音识别子服务调用失败!");
}
// 4. 序列化子服务响应作为HTTP响应正文
response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}
发送新消息
void NewMessage(const httplib::Request &request, httplib::Response &response) {
// 1. 反序列化请求,提取会话ID和消息内容
NewMessageReq req;
NewMessageRsp rsp; // 这是给客户端的响应
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("未找到可提供业务处理的消息转发子服务节点!");
}
mag::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; } // 目标用户未在线
// 构建通知消息
NotifyMessage notify;
notify.set_notify_type(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. 向客户端返回响应,包含请求ID、成功状态和错误信息
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");
}
获取用户信息
std::shared_ptr<GetUserInfoRsp> _GetUserInfo(const std::string &rid, const std::string &uid) {
GetUserInfoReq req;
auto rsp = std::make_shared<GetUserInfoRsp>();
req.set_request_id(rid);
req.set_user_id(uid);
// 将请求转发给用户子服务进行业务处理
auto channel = _mm_channels->choose(_user_service_name);
if (!channel) {
LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());
return std::shared_ptr<GetUserInfoRsp>();
}
mag::UserService_Stub stub(channel.get());
brpc::Controller cntl;
stub.GetUserInfo(&cntl, &req, rsp.get(), nullptr);
if (cntl.Failed()) {
LOG_ERROR("{} 用户子服务调用失败!", req.request_id());
return std::shared_ptr<GetUserInfoRsp>();
}
return rsp;
}