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

【聊天室后端服务器开发】 入口网关开发

功能分析

核心功能

  • 作为入口服务器接收客户端的所有请求,入口网关接收到请求后,将请求进行子服务分发,子服务给服务器返回响应,然后服务器再向客户端返回响应
  • 网关对客户端进行事件通知,例如好友申请及删除、新消息创建等

服务器需要完成两种通信方式

  • 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;
}

http://www.kler.cn/a/450398.html

相关文章:

  • 深入解读数据资产化实践指南(2024年)
  • 石岩基督教福音堂
  • react中使用ResizeObserver来观察元素的size变化
  • 《Java 与 OpenAI 协同:开启智能编程新范式》
  • AI可信论坛亮点:合合信息分享视觉内容安全技术前沿
  • LLaMA-Factory(二)界面解析
  • Hadoop组成概述
  • 循环和迭代
  • 合同尾款产生纠纷该如何处理
  • 京东科技基于 Apache SeaTunnel 复杂场景适配 #数据集成
  • 深度分析 es multi_match 中most_fields、best_fields、cross_fields区别
  • 用于管理Unity中UGUI的工具系统UISystem
  • Bootstrap 5 加载效果
  • python学opencv读取图像(十四)BGR图像和HSV图像通道拆分
  • Vision Pro开发实现系统UI风格 毛玻璃效果
  • |-牛式-|
  • WebRTC学习二:WebRTC音视频数据采集
  • ChatGPT与Postman协作完成接口测试(二)
  • 1 SpringBoot——项目搭建
  • Web 第一次作业 初探html 使用VSCode工具开发
  • 后端-redis
  • Git远程仓库的使用
  • 【唐叔学算法】第21天:超越比较-计数排序、桶排序与基数排序的Java实践及性能剖析
  • 探索数据可视化的利器:Matplotlib
  • 【云原生】kubeadm搭建的kubernetes1.28集群上自建ingress-nginx服务
  • 【Qt】了解和HelloWorld