IM项目------网关子服务

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录


前言

入口网关子服务主要负责三个方面:1.接收客户端的请求,并把请求转发给的对应的字服务进行业务处理。2.对客户端的身份进行识别和鉴权。3.直接与客户端进行通信,主动推送事件通知。


所需模块

1.httplib模块,用来搭建http服务区接收客户端的请求。

2.websocket模块,用来搭建websocket服务器向客户端主动推送通知

3.redis数据管理,用来用户会话管理和登录状态管理。

4.服务发现和信道管理,发现子服务进行业务调用

5.客户端长连接管理模块,客户端登录成功后进行长连接管理。

长连接管理模块

我们需要主动给客户端推送通知,所以就需要建立客户端的用户ID和长连接的映射关系。

另外当客户端离线后也就是连接断开,我们需要清除redis缓存中的会话信息和登录信息。需要建立长连接和用户的会话id用户id的映射关系。

cpp 复制代码
std::mutex _mutex;
std::unordered_map<std::string,server_t::connection_ptr> _uid_connections;  //用户Id和连接的映射
std::unordered_map<server_t::connection_ptr,Client> _conn_clients;          //连接和用户id会话ID的映射

新增连接管理

当客户端上线后,将客户端的用户ID和用户ID与连接进行管理。

cpp 复制代码
void insert(const server_t::connection_ptr &conn, 
    const std::string &uid, const std::string &ssid){
    std::unique_lock<std::mutex> lock(_mutex);
    _uid_connections.insert(std::make_pair(uid, conn));
    _conn_clients.insert(std::make_pair(conn, Client(uid, ssid)));
    LOG_DEBUG("新增长连接用户信息:{}-{}-{}", (size_t)conn.get(), uid, ssid);
}

通过用户ID获取长连接

当我们需要主动推送通知时,需要根据用户ID获取到对于客户端的长连接。

cpp 复制代码
server_t::connection_ptr connection(const std::string &uid) {
    std::unique_lock<std::mutex> lock(_mutex);
    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;
}

通过长连接获取用户ID和会话ID

当客户端下线也就是连接断开时,我们需要清除redis中的会话和登录状态的缓存。

cpp 复制代码
 bool client(const server_t::connection_ptr &conn, std::string &uid, std::string &ssid){
	 std::unique_lock<std::mutex> lock(_mutex);
	 auto it = _conn_clients.find(conn);
	 if (it == _conn_clients.end()) {
	     LOG_ERROR("获取-未找到长连接 {} 对应的客户端信息!", (size_t)conn.get());
	     return false;
	 }
	 uid = it->second.uid;
	 ssid = it->second.ssid;
	 LOG_DEBUG("获取长连接客户端信息成功!");
	 return true;
	}

删除连接管理

用户下线后,删除长连接管理。

通过连接先找到用户的ID和登录ID的映射,在通过用户ID删除对应连接。

cpp 复制代码
 void remove(const server_t::connection_ptr &conn){
     std::unique_lock<std::mutex> lock(_mutex);
     auto it = _conn_clients.find(conn);
     if (it == _conn_clients.end()) {
         LOG_ERROR("删除-未找到长连接 {} 对应的客户端信息!", (size_t)conn.get());
         return;
     }
     _uid_connections.erase(it->second.uid);
     _conn_clients.erase(it);
     LOG_DEBUG("删除长连接信息完毕!");
 }

服务器搭建

这里也是一个构造者类来进行网关服务器对象的构建。

服务器对象中需要搭建两个服务器,一个http服务器,一个websocket服务器。这俩个服务器都是组设运行的,因此这里创建一个线程,在子线程中启动http服务器。

cpp 复制代码
 _http_thread = std::thread([this, http_port](){
	_http_server.listen("0.0.0.0", http_port);
});
_http_thread.detach();

在启动服务器之前需要注册功能请求,客户端和网关的通信我们约定好的使用post请求。

cpp 复制代码
_http_server.Post(GET_PHONE_VERIFY_CODE  , (httplib::Server::Handler)std::bind(&GatewayServer::GetPhoneVerifyCode         , this, std::placeholders::_1, std::placeholders::_2));
_http_server.Post(USERNAME_REGISTER      , (httplib::Server::Handler)std::bind(&GatewayServer::UserRegister               , this, std::placeholders::_1, std::placeholders::_2));
_http_server.Post(USERNAME_LOGIN         , (httplib::Server::Handler)std::bind(&GatewayServer::UserLogin                  , this, std::placeholders::_1, std::placeholders::_2));
_http_server.Post(PHONE_REGISTER         , (httplib::Server::Handler)std::bind(&GatewayServer::PhoneRegister              , this, std::placeholders::_1, std::placeholders::_2));
_http_server.Post(PHONE_LOGIN            , (httplib::Server::Handler)std::bind(&GatewayServer::PhoneLogin                 , this, std::placeholders::_1, std::placeholders::_2));
_http_server.Post(GET_USERINFO           , (httplib::Server::Handler)std::bind(&GatewayServer::GetUserInfo                , this, std::placeholders::_1, std::placeholders::_2));
_http_server.Post(SET_USER_AVATAR        , (httplib::Server::Handler)std::bind(&GatewayServer::SetUserAvatar              , this, std::placeholders::_1, std::placeholders::_2));
_http_server.Post(SET_USER_NICKNAME      , (httplib::Server::Handler)std::bind(&GatewayServer::SetUserNickname            , this, std::placeholders::_1, std::placeholders::_2));
_http_server.Post(SET_USER_DESC          , (httplib::Server::Handler)std::bind(&GatewayServer::SetUserDescription         , this, std::placeholders::_1, std::placeholders::_2));
_http_server.Post(SET_USER_PHONE         , (httplib::Server::Handler)std::bind(&GatewayServer::SetUserPhoneNumber         , this, std::placeholders::_1, std::placeholders::_2));
_http_server.Post(FRIEND_GET_LIST        , (httplib::Server::Handler)std::bind(&GatewayServer::GetFriendList              , this, std::placeholders::_1, std::placeholders::_2));
_http_server.Post(FRIEND_APPLY           , (httplib::Server::Handler)std::bind(&GatewayServer::FriendAdd                  , this, std::placeholders::_1, std::placeholders::_2));
_http_server.Post(FRIEND_APPLY_PROCESS   , (httplib::Server::Handler)std::bind(&GatewayServer::FriendAddProcess           , this, std::placeholders::_1, std::placeholders::_2));
_http_server.Post(FRIEND_REMOVE          , (httplib::Server::Handler)std::bind(&GatewayServer::FriendRemove               , this, std::placeholders::_1, std::placeholders::_2));
_http_server.Post(FRIEND_SEARCH          , (httplib::Server::Handler)std::bind(&GatewayServer::FriendSearch               , this, std::placeholders::_1, std::placeholders::_2));
_http_server.Post(FRIEND_GET_PENDING_EV  , (httplib::Server::Handler)std::bind(&GatewayServer::GetPendingFriendEventList  , this, std::placeholders::_1, std::placeholders::_2));
_http_server.Post(CSS_GET_LIST           , (httplib::Server::Handler)std::bind(&GatewayServer::GetChatSessionList         , this, std::placeholders::_1, std::placeholders::_2));
_http_server.Post(CSS_CREATE             , (httplib::Server::Handler)std::bind(&GatewayServer::ChatSessionCreate          , this, std::placeholders::_1, std::placeholders::_2));
_http_server.Post(CSS_GET_MEMBER         , (httplib::Server::Handler)std::bind(&GatewayServer::GetChatSessionMember       , this, std::placeholders::_1, std::placeholders::_2));
_http_server.Post(MSG_GET_RANGE          , (httplib::Server::Handler)std::bind(&GatewayServer::GetHistoryMsg              , this, std::placeholders::_1, std::placeholders::_2));
_http_server.Post(MSG_GET_RECENT         , (httplib::Server::Handler)std::bind(&GatewayServer::GetRecentMsg               , this, std::placeholders::_1, std::placeholders::_2));
_http_server.Post(MSG_KEY_SEARCH         , (httplib::Server::Handler)std::bind(&GatewayServer::MsgSearch                  , this, std::placeholders::_1, std::placeholders::_2));
_http_server.Post(NEW_MESSAGE            , (httplib::Server::Handler)std::bind(&GatewayServer::NewMessage                 , this, std::placeholders::_1, std::placeholders::_2));
_http_server.Post(FILE_GET_SINGLE        , (httplib::Server::Handler)std::bind(&GatewayServer::GetSingleFile              , this, std::placeholders::_1, std::placeholders::_2));
_http_server.Post(FILE_GET_MULTI         , (httplib::Server::Handler)std::bind(&GatewayServer::GetMultiFile               , this, std::placeholders::_1, std::placeholders::_2));
_http_server.Post(FILE_PUT_SINGLE        , (httplib::Server::Handler)std::bind(&GatewayServer::PutSingleFile              , this, std::placeholders::_1, std::placeholders::_2));
_http_server.Post(FILE_PUT_MULTI         , (httplib::Server::Handler)std::bind(&GatewayServer::PutMultiFile               , this, std::placeholders::_1, std::placeholders::_2));
_http_server.Post(SPEECH_RECOGNITION     , (httplib::Server::Handler)std::bind(&GatewayServer::SpeechRecognition          , this, std::placeholders::_1, std::placeholders::_2));

向外部提供一个接口,用来启动websocket服务器

cpp 复制代码
 void start() {
    _ws_server.run();
}

websocket服务器需要绑定三个回调函数,分别是新连接建立,连接断开,收到新消息。

这里我们只需要关心连接断开和收到新消息。在客户端实现时,我们规定了在于网关建立连接后,需要主动发送一个身份验证消息。其中需要包含自己的登录会话ID。

于是我们的websocket服务器就需要通过这个登录会话ID,从redis来获取到用户ID,进行一个身份验证。

同时,有了会话ID和用户ID,就可以对这个长连接进行管理。

cpp 复制代码
 void onMessage(websocketpp::connection_hdl hdl, server_t::message_ptr msg) {
    //收到第一条消息后,根据消息中的会话ID进行身份识别,将客户端长连接添加管理
    //1. 取出长连接对应的连接对象
    auto conn = _ws_server.get_con_from_hdl(hdl);
    //2. 针对消息内容进行反序列化 -- ClientAuthenticationReq -- 提取登录会话ID
    ClientAuthenticationReq request;
    bool ret = request.ParseFromString(msg->get_payload());
    if (ret == false) {
        LOG_ERROR("长连接身份识别失败:正文反序列化失败!");
        _ws_server.close(hdl, websocketpp::close::status::unsupported_data, "正文反序列化失败!");
        return;
    }
    //3. 在会话信息缓存中,查找会话信息 
    std::string ssid = request.session_id();
    auto uid = _redis_session->uid(ssid);
    //4. 会话信息不存在则关闭连接
    if (!uid) {
        LOG_ERROR("长连接身份识别失败:未找到会话信息 {}!", ssid);
        _ws_server.close(hdl, websocketpp::close::status::unsupported_data, "未找到会话信息!");
        return;
    }
    //5. 会话信息存在,则添加长连接管理
    _connections->insert(conn, *uid, ssid);
    LOG_DEBUG("新增长连接管理:{}-{}-{}", ssid, *uid, (size_t)conn.get());
    keepAlive(conn);
}

在连接断开时,我们需要清除redis中的缓存信息,同时移除长连接管理

cpp 复制代码
void onClose(websocketpp::connection_hdl hdl) {
//长连接断开时做的清理工作
   //0. 通过连接对象,获取对应的用户ID与登录会话ID
   auto conn = _ws_server.get_con_from_hdl(hdl);
   std::string uid, ssid;
   bool ret = _connections->client(conn, uid, ssid);
   if (ret == false) {
       LOG_WARN("长连接断开,未找到长连接对应的客户端信息!");
       return ;
   }
   //1. 移除登录会话信息
   _redis_session->remove(ssid);
   //2. 移除登录状态信息
   _redis_status->remove(uid);
   //3. 移除长连接管理数据
   _connections->remove(conn);
   LOG_DEBUG("{} {} {} 长连接断开,清理缓存数据!", ssid, uid, (size_t)conn.get());
}

为了防止长时间不进行连接相关的动作而断开连接,这里需要做一个心跳包保活,设置一个60s定时器,给客户端发送一个空字符串。

cpp 复制代码
void keepAlive(server_t::connection_ptr conn) {
     if (!conn || conn->get_state() != websocketpp::session::state::value::open) {
         LOG_DEBUG("非正常连接状态,结束连接保活");
         return;
     }
     conn->ping("");
     _ws_server.set_timer(60000, std::bind(&GatewayServer::keepAlive, this, conn));
 }

业务代码编写

这里大致流程就是:

取出http请求正文进行反序列化,调用对应子服务进行业务处理,得到子服务的响应后,将响应序列化后作为http响应的响应正文。

获取手机号验证码/登录/注册/这几个接口都不需要会话ID,所以不需要进行身份鉴权。

cpp 复制代码
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("未找到可提供业务处理的用户子服务节点!");
   }
   bite_im::UserService_Stub stub(channel.get());
   brpc::Controller cntl;
   stub.GetPhoneVerifyCode(&cntl, &req, &rsp, nullptr);
   if (cntl.Failed()) {
       LOG_ERROR("{} 用户子服务调用失败!", req.request_id());
       return err_response("用户子服务调用失败!");
   }
   //3. 得到用户子服务的响应后,将响应内容进行序列化作为http响应正文
   response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}

登录成功后,用户就有了登录会话ID,所以后续的操作都需要对用户的会话ID进行身份鉴权。

也就是从redis中判断键值对存不存在。存在则获取用户ID填入到请求中。在这里插入代码片

cpp 复制代码
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);
    //2. 将请求转发给用户子服务进行业务处理
    auto channel = _mm_channels->choose(_user_service_name);
    if (!channel) {
        LOG_ERROR("{} 未找到可提供业务处理的用户子服务节点!", req.request_id());
        return err_response("未找到可提供业务处理的用户子服务节点!");
    }
    bite_im::UserService_Stub stub(channel.get());
    brpc::Controller cntl;
    stub.GetUserInfo(&cntl, &req, &rsp, nullptr);
    if (cntl.Failed()) {
        LOG_ERROR("{} 用户子服务调用失败!", req.request_id());
        return err_response("用户子服务调用失败!");
    }
    //3. 得到用户子服务的响应后,将响应内容进行序列化作为http响应正文
    response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}

添加好友这里需要给被申请人主动推送一个通知。

通过被申请人的用户ID获取到对应的长连接。当业务处理成功后才进行推送。

另外还需要给申请人返回响应。

cpp 复制代码
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("未找到可提供业务处理的用户子服务节点!");
      }
      bite_im::FriendService_Stub stub(channel.get());
      brpc::Controller cntl;
      stub.FriendAdd(&cntl, &req, &rsp, nullptr);
      if (cntl.Failed()) {
          LOG_ERROR("{} 好友子服务调用失败!", req.request_id());
          return err_response("好友子服务调用失败!");
      }
      // 4. 若业务处理成功 --- 且获取被申请方长连接成功,则向被申请放进行好友申请事件通知
      auto conn = _connections->connection(req.respondent_id());
      if (rsp.success() && conn) {
          LOG_DEBUG("找到被申请人 {} 长连接,对其进行好友申请通知", req.respondent_id());
          auto user_rsp = _GetUserInfo(req.request_id(), *uid);
          if (!user_rsp) {
              LOG_ERROR("{} 获取当前客户端用户信息失败!", req.request_id());
              return err_response("获取当前客户端用户信息失败!");
          }
          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");
  }

好友申请的处理,这一块比较复杂,因为好友申请的处理如果是同意的话,涉及到了聊天会话的创建。而聊天会话也是需要进行事件通知的。

获取申请人和被申请人的用户ID.然后通过用户子服务获取到用户信息。因为我们要推送会话创建通知,需要传入一个会话信息,这是一个单聊会话。会话信息中需要有好友用户ID和好友昵称。

当业务处理成功且获取到长连接后,且处理结果是同意的情况才进行一个会话创建的推送。

最后需要对客户端返回响应,也就是被申请人。

cpp 复制代码
 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("未找到可提供业务处理的用户子服务节点!");
   }
   bite_im::FriendService_Stub stub(channel.get());
   brpc::Controller cntl;
   stub.FriendAddProcess(&cntl, &req, &rsp, nullptr);
   if (cntl.Failed()) {
       LOG_ERROR("{} 好友子服务调用失败!", req.request_id());
       return err_response("好友子服务调用失败!");
   }
   
   if (rsp.success()) {
       auto process_user_rsp = _GetUserInfo(req.request_id(), *uid);
       if (!process_user_rsp) {
           LOG_ERROR("{} 获取用户信息失败!", req.request_id());
           return err_response("获取用户信息失败!");
       }
       auto apply_user_rsp = _GetUserInfo(req.request_id(), req.apply_user_id());
       if (!process_user_rsp) {
           LOG_ERROR("{} 获取用户信息失败!", req.request_id());
           return err_response("获取用户信息失败!");
       }
       auto process_conn = _connections->connection(*uid);
       if (process_conn) LOG_DEBUG("找到处理人的长连接!");
       else LOG_DEBUG("未找到处理人的长连接!");
       auto apply_conn = _connections->connection(req.apply_user_id());
       if (apply_conn) LOG_DEBUG("找到申请人的长连接!");
       else LOG_DEBUG("未找到申请人的长连接!");
       //4. 将处理结果给申请人进行通知
       if (apply_conn) {
           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("对申请人进行申请处理结果通知!");
       }
       //5. 若处理结果是同意 --- 会伴随着单聊会话的创建 -- 因此需要对双方进行会话创建的通知
       if (req.agree() && 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 (req.agree() && 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("对处理人进行会话创建通知!");
       }
   }
   //6. 对客户端进行响应
   response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}

好友删除,也是需要进行事件通知的。当业务处理成功且被删除好友的长连接存在才去进行一个通知。

cpp 复制代码
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("未找到可提供业务处理的用户子服务节点!");
   }
   bite_im::FriendService_Stub stub(channel.get());
   brpc::Controller cntl;
   stub.FriendRemove(&cntl, &req, &rsp, nullptr);
   if (cntl.Failed()) {
       LOG_ERROR("{} 好友子服务调用失败!", req.request_id());
       return err_response("好友子服务调用失败!");
   }
   // 4. 若业务处理成功 --- 且获取被申请方长连接成功,则向被申请放进行好友申请事件通知
   auto conn = _connections->connection(req.peer_id());
   if (rsp.success() && conn) {
       LOG_ERROR("对被删除人 {} 进行好友删除通知!", req.peer_id());
       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");
}

会话的创建,这一块主要是群聊会话,单聊会话在好友申请处理同意时会自动创建。

我们需要获取到用户ID列表,循环这个列表,进行一个事件推送,当有对应的用户ID的长连接存在才进行一个推送。这里的推送也需要一个chatSessionINfo,但是这个信息在好友子服务已经帮我们创建好了,我们只需要把好友子服务响应中的结构拷贝就行。

和单聊不同,单聊会话的创建是在好友申请处理同时由好友子服务进行一个数据库表的操作。后续进行事件通知需要网关自己去构造一个chatSessionINfo.

cpp 复制代码
void ChatSessionCreate(const httplib::Request &request, httplib::Response &response) {
    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("未找到可提供业务处理的用户子服务节点!");
    }
    bite_im::FriendService_Stub stub(channel.get());
    brpc::Controller cntl;
    stub.ChatSessionCreate(&cntl, &req, &rsp, nullptr);
    if (cntl.Failed()) {
        LOG_ERROR("{} 好友子服务调用失败!", req.request_id());
        return err_response("好友子服务调用失败!");
    }
    // 4. 若业务处理成功 --- 且获取被申请方长连接成功,则向被申请放进行好友申请事件通知
    if (rsp.success()){
        for (int i = 0; i < req.member_id_list_size(); i++) {
            auto conn = _connections->connection(req.member_id_list(i));
            if (!conn) { 
                LOG_DEBUG("未找到群聊成员 {} 长连接", req.member_id_list(i));
                continue;
            }
            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("对群聊成员 {} 进行会话创建通知", req.member_id_list(i));
        }
    }
    // 5. 向客户端进行响应
    rsp.clear_chat_session_info();
    response.set_content(rsp.SerializeAsString(), "application/x-protbuf");
}

获取新消息,也是需要进行推送的,只需要从消息转发子服务的响应中获取到用户ID列表进行一个遍历,长连接存在则进行事件推送,但这个ID列表中是通过查询会话成员表获取的,我们也在这个表中,因此我们的id也在这个列表中,我们不需要对自己进行一个推送。在客户端已经进行了消息发送的一个界面渲染。

cpp 复制代码
void NewMessage(const httplib::Request &request, httplib::Response &response) {
    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("未找到可提供业务处理的用户子服务节点!");
    }
    bite_im::MsgTransmitService_Stub stub(channel.get());
    brpc::Controller cntl;
    stub.GetTransmitTarget(&cntl, &req, &target_rsp, nullptr);
    if (cntl.Failed()) {
        LOG_ERROR("{} 消息转发子服务调用失败!", req.request_id());
        return err_response("消息转发子服务调用失败!");
    }
    // 4. 若业务处理成功 --- 且获取被申请方长连接成功,则向被申请放进行好友申请事件通知
    if (target_rsp.success()){
        for (int i = 0; i < target_rsp.target_id_list_size(); i++) {
            std::string notify_uid = target_rsp.target_id_list(i);
            if (notify_uid == *uid) continue; //不通知自己
            auto conn = _connections->connection(notify_uid);
            if (!conn) { continue;}
            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. 向客户端进行响应
    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");
}

剩下的接口都是不需要进行消息推送的,流程都是一样的。获取http请求正文内容,反序列化后调用对应的子服务进行业务处理,得到子服务的响应后序列化作为http响应的正文。返回给客户端。

相关推荐
Yvemil71 小时前
《开启微服务之旅:Spring Boot Web开发》(二)
前端·spring boot·微服务
维李设论1 小时前
Node.js的Web服务在Nacos中的实践
前端·spring cloud·微服务·eureka·nacos·node.js·express
jwolf24 小时前
基于K8S的微服务:一、服务发现,负载均衡测试(附calico网络问题解决)
微服务·kubernetes·服务发现
Yvemil76 小时前
《开启微服务之旅:Spring Boot Web开发举例》(二)
前端·spring boot·微服务
一个儒雅随和的男子6 小时前
微服务详细教程之nacos和sentinel实战
微服务·架构·sentinel
Yvemil78 小时前
《开启微服务之旅:Spring Boot Web开发》(三)
前端·spring boot·微服务
Java程序之猿9 小时前
微服务分布式(二、注册中心Consul)
分布式·微服务·consul
Hello Dam9 小时前
面向微服务的Spring Cloud Gateway的集成解决方案:用户登录认证与访问控制
spring cloud·微服务·云原生·架构·gateway·登录验证·单点登录
小笨猪-19 小时前
统⼀服务⼊⼝-Gateway
java·spring cloud·微服务·gateway
橘子在努力2 天前
【橘子微服务】spring cloud function的编程模型
spring cloud·微服务·架构