基本逻辑
功能实现流程
- 服务注册和发现
- 通过服务注册模块将服务注册到服务中心
- 通过服务发现模块进行服务发现,管理可用的用户子服务
- 消息传递流程
- 客户端通过调用RPC方法,传递请求消息
- 服务根据请求获取用户信息、构建消息对象、查询聊天会话成员
- 消息通过消息队列(RabbitMQ)进行持久化,确保消息的可靠传递
- RPC服务
- 通过实现RPC接口,处理客户端请求并返回结果
- 消息队列
- 消息发布到消息队列中,其他服务可以对该队列中的消息进一步处理
具体实现
数据管理
ODB
#pragma once
#include <string>
#include <cstddef>
#include <odb/core.hxx>
namespace mag {
#pragma db object table("chat_session_member")
class ChatSessionMember {
public:
ChatSessionMember(){}
ChatSessionMember(const std::string &ssid, const std::string &uid):
_session_id(ssid), _user_id(uid){}
~ChatSessionMember(){}
std::string session_id() const { return _session_id; }
void session_id(std::string &ssid) { _session_id = ssid; }
std::string user_id() const { return _user_id; }
void user_id(std::string &uid) { _user_id = uid; }
private:
friend class odb::access;
#pragma db id auto
unsigned long _id;
#pragma db type("varchar(64)") index
std::string _session_id;
#pragma db type("varchar(64)")
std::string _user_id;
};
}
mysql数据库操作,针对于聊天会话成员表
- 新增单个会话成员
- 新增多个会话成员
- 删除单个会话成员
- 删除多个会话成员
- 获取会话成员
#pragma once
#include "mysql.hpp"
#include "chat_session_member.hxx"
#include "chat_session_member-odb.hxx"
namespace mag
{
class ChatSessionMemeberTable{
public:
using ptr = std::shared_ptr<ChatSessionMemeberTable>;
ChatSessionMemeberTable(const std::shared_ptr<odb::core::database> &db):_db(db){}
//新增单个会话成员
bool append(ChatSessionMember &csm) {
try {
odb::transaction trans(_db->begin());
_db->persist(csm);
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("新增单会话成员失败 {}-{}:{}!",
csm.session_id(), csm.user_id(), e.what());
return false;
}
return true;
}
//新增多个会话成员
bool append(std::vector<ChatSessionMember> &csm_lists) {
try {
odb::transaction trans(_db->begin());
for (auto &csm : csm_lists) {
_db->persist(csm);
}
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("新增多会话成员失败 {}-{}:{}!",
csm_lists[0].session_id(), csm_lists.size(), e.what());
return false;
}
return true;
}
//删除会话中指定成员
bool remove(ChatSessionMember &csm) {
try {
odb::transaction trans(_db->begin());
typedef odb::query<ChatSessionMember> query;
typedef odb::result<ChatSessionMember> result;
_db->erase_query<ChatSessionMember>(query::session_id == csm.session_id() &&
query::user_id == csm.user_id());
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("删除单会话成员失败 {}-{}:{}!",
csm.session_id(), csm.user_id(), e.what());
return false;
}
return true;
}
//删除会话中所有成员信息
//删除会话的所有成员信息
bool remove(const std::string &ssid) {
try {
odb::transaction trans(_db->begin());
typedef odb::query<ChatSessionMember> query;
typedef odb::result<ChatSessionMember> result;
_db->erase_query<ChatSessionMember>(query::session_id == ssid);
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("删除会话所有成员失败 {}:{}!", ssid, e.what());
return false;
}
return true;
}
//获取所有成员信息
std::vector<std::string> members(const std::string &ssid) {
std::vector<std::string> res;
try {
odb::transaction trans(_db->begin());
typedef odb::query<ChatSessionMember> query;
//result 模版类,专门用于存储从数据库中返回的多个对象,提供了迭代器方便查询
typedef odb::result<ChatSessionMember> result;
result r(_db->query<ChatSessionMember>(query::session_id == ssid));
for (result::iterator i(r.begin()); i != r.end(); ++i) {
res.push_back(i->user_id());
}
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("获取会话成员失败:{}-{}!", ssid, e.what());
}
return res;
}
private:
std::shared_ptr<odb::core::database> _db;
};
}
业务逻辑实现
- 获取请求信息:从请求中提取关键信息(请求ID、用户ID、聊天会话ID等)
- 用户服务调用:通过RPC调用获取目标用户信息
- 消息构建:构建消息对象并从数据库获取目标用户
- 消息发布:将消息发布到消息队列以便传递给目标用户
- 响应构造:构造响应,包括消息和目标用户信息,并返回给客户端
其他搭建RPC与前面业务逻辑类似
void GetTransmitTarget(google::protobuf::RpcController* controller,
const ::mag::NewMessageReq* request,
::mag::GetTransmitTargetRsp* response,
::google::protobuf::Closure* done) override{
//RPC调用后释放资源
brpc::ClosureGuard rpc_guard(done);
//错误处理
auto err_response = [this, response](const std::string &rid, const std::string &errmsg) -> void {
response->set_request_id(rid);
response->set_success(false);
response->set_errmsg(errmsg);
return;
};
//请求中获取信息
std::string rid = request->request_id();
std::string uid = request->user_id();
std::string chat_ssid = request->chat_session_id();
const MessageContent &content = request->message();
//选择服务通道
auto channel = _mm_channels->choose(_user_service_name);
if (!channel) {
LOG_ERROR("{}-{} 没有可供访问的用户子服务节点!", rid, _user_service_name);
return err_response(rid, "没有可供访问的用户子服务节点!");
}
//RPC调用用户子服务获取用户信息
UserService_Stub stub(channel.get());
GetUserInfoReq req;
GetUserInfoRsp rsp;
req.set_request_id(rid);
req.set_user_id(uid);
brpc::Controller cntl;
stub.GetUserInfo(&cntl, &req, &rsp, nullptr);
if (cntl.Failed() == true || rsp.success() == false) {
LOG_ERROR("{} - 用户子服务调用失败:{}!", request->request_id(), cntl.ErrorText());
return err_response(request->request_id(), "用户子服务调用失败!");
}
//构建消息对象
MessageInfo message;
message.set_message_id(uuid());
message.set_chat_session_id(chat_ssid);
message.set_timestamp(time(nullptr));
message.mutable_sender()->CopyFrom(rsp.user_info());
message.mutable_message()->CopyFrom(content);
//获取消息转发的目标用户列表,从mysql数据库中获取
auto target_list = _mysql_session_member_table->members(chat_ssid);
//发布消息到消息队列
bool ret = _mq_client->publish(_exchange_name, message.SerializeAsString(), _routing_key);
if (ret == false) {
LOG_ERROR("{} - 持久化消息发布失败:{}!", request->request_id(), cntl.ErrorText());
return err_response(request->request_id(), "持久化消息发布失败:!");
}
//组织成功响应
response->set_request_id(rid);
response->set_success(true);
response->mutable_message()->CopyFrom(message);
//将目标用户的ID列表添加到响应中,表示消息需要发送给哪些用户
for (const auto &id : target_list) {
response->add_target_id_list(id);
}
}
功能测试
数据管理测试
数据插入测试
void append_test(mag::ChatSessionMemeberTable &tb) {
mag::ChatSessionMember csm1("会话ID1", "用户ID1");
tb.append(csm1);
mag::ChatSessionMember csm2("会话ID1", "用户ID2");
tb.append(csm2);
mag::ChatSessionMember csm3("会话ID2", "用户ID3");
tb.append(csm3);
}
批量插入测试
void multi_append_test(mag::ChatSessionMemeberTable &tb) {
mag::ChatSessionMember csm1("会话ID3", "用户ID1");
mag::ChatSessionMember csm2("会话ID3", "用户ID2");
mag::ChatSessionMember csm3("会话ID3", "用户ID3");
std::vector<mag::ChatSessionMember> list = {csm1, csm2, csm3};
tb.append(list);
}
移除测试
void remove_test(mag::ChatSessionMemeberTable &tb){
mag::ChatSessionMember csm3("会话ID2", "用户ID3");
tb.remove(csm3);
}
获取指定成员的所有信息
void ss_members(mag::ChatSessionMemeberTable &tb){
auto res = tb.members("会话ID1");
for(auto &id : res){
std::cout<< id <<std::endl;
}
}
移除所有ID3的信息
void remove_all(mag::ChatSessionMemeberTable &tb) {
tb.remove("会话ID3");
}
整体测试
用户客户端增加信息测试
#include "etcd.hpp"
#include "channel.hpp"
#include "utils.hpp"
#include <gflags/gflags.h>
#include <gtest/gtest.h>
#include <thread>
#include "user.pb.h"
#include "base.pb.h"
DEFINE_bool(run_mode, false, "程序的运行模式,false-调试; true-发布;");
DEFINE_string(log_file, "", "发布模式下,用于指定日志的输出文件");
DEFINE_int32(log_level, 0, "发布模式下,用于指定日志输出等级");
DEFINE_string(etcd_host, "http://127.0.0.1:2379", "服务注册中心地址");
DEFINE_string(base_service, "/service", "服务监控根目录");
DEFINE_string(user_service, "/service/user_service", "服务监控根目录");
mag::ServiceManager::ptr user_channels;
void reg_user(const std::string &nickname, const std::string &pswd){
//获取信道
auto channel = user_channels->choose(FLAGS_user_service);
ASSERT_TRUE(channel);
//构建用户注册请求
mag::UserRegisterReq req;
req.set_request_id(mag::uuid());
req.set_nickname(nickname);
req.set_password(pswd);
//发送注册请求并接受响应
mag::UserRegisterRsp rsp;
brpc::Controller cntl;
mag::UserService_Stub stub(channel.get());
stub.UserRegister(&cntl, &req, &rsp, nullptr);
// 断言请求成功
ASSERT_FALSE(cntl.Failed());
ASSERT_TRUE(rsp.success());
}
void set_user_avatar(const std::string &uid, const std::string &avatar){
//获取用户服务通信信道
auto channel = user_channels->choose(FLAGS_user_service);
ASSERT_TRUE(channel);
//构建用户头像请求
mag::SetUserAvatarReq req;
req.set_request_id(mag::uuid());
req.set_user_id(uid);
req.set_session_id("测试登录会话ID");
req.set_avatar(avatar);
//发送设置头像请求并接收响应
mag::SetUserAvatarRsp rsp;
brpc::Controller cntl;
mag::UserService_Stub stub(channel.get());
stub.SetUserAvatar(&cntl, &req, &rsp, nullptr);
// 断言请求成功
ASSERT_FALSE(cntl.Failed());
ASSERT_TRUE(rsp.success());
}
int main(int argc, char *argv[])
{
// 解析命令行参数
google::ParseCommandLineFlags(&argc, &argv, true);
// 初始化日志
init_logger(FLAGS_run_mode, FLAGS_log_file, FLAGS_log_level);
// 创建 ServiceManager 实例并声明用户服务
user_channels = std::make_shared<mag::ServiceManager>();
user_channels->declared(FLAGS_user_service);
// 绑定服务上线和下线的回调
auto put_cb = std::bind(&mag::ServiceManager::onServiceOnline, user_channels.get(), std::placeholders::_1, std::placeholders::_2);
auto del_cb = std::bind(&mag::ServiceManager::onServiceOffline, user_channels.get(), std::placeholders::_1, std::placeholders::_2);
// 构造服务发现客户端
mag::Discovery::ptr dclient = std::make_shared<mag::Discovery>(FLAGS_etcd_host, FLAGS_base_service, put_cb, del_cb);
// 注册用户测试
// reg_user("小猪佩奇", "123456");
// reg_user("小猪乔治", "123456");
// 设置用户头像
set_user_avatar("8d25-14d9ff25-0000", "猪爸爸头像数据");
set_user_avatar("0b24-e341f8f9-0001", "猪妈妈头像数据");
}
发送消息到消息队列
root@hcss-ecs-b4a9:/home/chatServer/chatServer/src/server/transmite/build# ./transmite_client
[default-logger][16:26:31][1242866][debug ][/home/chatServer/chatServer/src/server/transmite/../common/channel.hpp:96] /service/file_service-127.0.0.1:10002 服务上线了,但是当前并不关心!
[default-logger][16:26:31][1242866][debug ][/home/chatServer/chatServer/src/server/transmite/../common/channel.hpp:96] /service-127.0.0.1:9090 服务上线了,但是当前并不关心!
[default-logger][16:26:31][1242866][debug ][/home/chatServer/chatServer/src/server/transmite/../common/channel.hpp:113] /service/transmite_service-127.0.0.1:10004 服务上线新节点,进行添加管理!
[default-logger][16:26:31][1242866][debug ][/home/chatServer/chatServer/src/server/transmite/../common/channel.hpp:96] /service/user_service-127.0.0.1:10003 服务上线了,但是当前并不关心!
[default-logger][16:26:31][1242866][info ][/home/chatServer/chatServer/src/server/transmite/test/transmite_client.cc:25] 尝试发送字符串消息。用户ID: 8d25-14d9ff25-0000, 会话ID: 会话ID1, 消息内容: 吃饭了吗?
[default-logger][16:26:31][1242866][debug ][/home/chatServer/chatServer/src/server/transmite/test/transmite_client.cc:32] 成功获取服务通信通道,服务: /service/transmite_service
[default-logger][16:26:31][1242866][debug ][/home/chatServer/chatServer/src/server/transmite/test/transmite_client.cc:47] 请求已构建:request_id=d42c-31e9e768-0000, user_id=8d25-14d9ff25-0000, chat_session_id=会话ID1, message_type=STRING, content=吃饭了吗?
[default-logger][16:26:32][1242866][info ][/home/chatServer/chatServer/src/server/transmite/test/transmite_client.cc:58] RPC调用成功,响应成功:true
[default-logger][16:26:32][1242866][info ][/home/chatServer/chatServer/src/server/transmite/test/transmite_client.cc:25] 尝试发送字符串消息。用户ID: 0b24-e341f8f9-0001, 会话ID: 会话ID1, 消息内容: 吃的盖浇饭!!
[default-logger][16:26:32][1242866][debug ][/home/chatServer/chatServer/src/server/transmite/test/transmite_client.cc:32] 成功获取服务通信通道,服务: /service/transmite_service
[default-logger][16:26:32][1242866][debug ][/home/chatServer/chatServer/src/server/transmite/test/transmite_client.cc:47] 请求已构建:request_id=a9b2-f32b92b8-0001, user_id=0b24-e341f8f9-0001, chat_session_id=会话ID1, message_type=STRING, content=吃的盖浇饭!!
[default-logger][16:26:32][1242866][info ][/home/chatServer/chatServer/src/server/transmite/test/transmite_client.cc:58] RPC调用成功,响应成功:true
//
///发送字符串消息
//
void string_message(const std::string &uid, const std::string &sid, const std::string &msg)
{
LOG_INFO("尝试发送字符串消息。用户ID: {}, 会话ID: {}, 消息内容: {}", uid, sid, msg);
auto channel = sm->choose(FLAGS_transmite_service);
if (!channel) {
std::cout << "获取通信信道失败!" << std::endl;
return;
}
LOG_DEBUG("成功获取服务通信通道,服务: {}", FLAGS_transmite_service);
//构建RPC stub
mag::MsgTransmitService_Stub stub(channel.get());
//构建请求和接收响应
mag::NewMessageReq req;
mag::GetTransmitTargetRsp rsp;
req.set_request_id(mag::uuid());
req.set_user_id(uid);
req.set_chat_session_id(sid);
req.mutable_message()->set_message_type(mag::MessageType::STRING);
req.mutable_message()->mutable_string_message()->set_content(msg);
LOG_DEBUG("请求已构建:request_id={}, user_id={}, chat_session_id={}, message_type=STRING, content={}",
req.request_id(), req.user_id(), req.chat_session_id(), req.message().string_message().content());
//发送请求并处理响应
brpc::Controller cntl;
stub.GetTransmitTarget(&cntl, &req, &rsp, nullptr);
if (cntl.Failed()) {
LOG_ERROR("RPC调用失败: {}", cntl.ErrorText());
} else {
LOG_INFO("RPC调用成功,响应成功:{}", rsp.success() ? "true" : "false");
}
ASSERT_FALSE(cntl.Failed());
ASSERT_TRUE(rsp.success());
}
发送图片消息
[default-logger][16:50:40][1244626][info ][/home/chatServer/chatServer/src/server/transmite/test/transmite_client.cc:66] 尝试发送图片消息。用户ID: 8d25-14d9ff25-0000, 会话ID: 会话ID1, 图片内容: 可爱表情图片数据
[default-logger][16:50:40][1244626][debug ][/home/chatServer/chatServer/src/server/transmite/test/transmite_client.cc:73] 成功获取服务通信通道,服务: /service/transmite_service
[default-logger][16:50:40][1244626][debug ][/home/chatServer/chatServer/src/server/transmite/test/transmite_client.cc:84] 请求已构建:request_id=67a4-cbc665a7-0002, user_id=8d25-14d9ff25-0000, chat_session_id=会话ID1, message_type=IMAGE, image_content=可爱表情图片数据
[default-logger][16:50:40][1244626][info ][/home/chatServer/chatServer/src/server/transmite/test/transmite_client.cc:93] RPC调用成功,响应成功:true
///
///发送图片消息
//
void image_message(const std::string &uid, const std::string &sid, const std::string &msg) {
LOG_INFO("尝试发送图片消息。用户ID: {}, 会话ID: {}, 图片内容: {}", uid, sid, msg);
auto channel = sm->choose(FLAGS_transmite_service);
if (!channel) {
std::cout << "获取通信信道失败!" << std::endl;
return;
}
LOG_DEBUG("成功获取服务通信通道,服务: {}", FLAGS_transmite_service);
mag::MsgTransmitService_Stub stub(channel.get());
mag::NewMessageReq req;
mag::GetTransmitTargetRsp rsp;
req.set_request_id(mag::uuid());
req.set_user_id(uid);
req.set_chat_session_id(sid);
req.mutable_message()->set_message_type(mag::MessageType::IMAGE);
req.mutable_message()->mutable_image_message()->set_image_content(msg);
LOG_DEBUG("请求已构建:request_id={}, user_id={}, chat_session_id={}, message_type=IMAGE, image_content={}",
req.request_id(), req.user_id(), req.chat_session_id(), req.message().image_message().image_content());
brpc::Controller cntl;
stub.GetTransmitTarget(&cntl, &req, &rsp, nullptr);
if (cntl.Failed()) {
LOG_ERROR("RPC调用失败: {}", cntl.ErrorText());
} else {
LOG_INFO("RPC调用成功,响应成功:{}", rsp.success() ? "true" : "false");
}
ASSERT_FALSE(cntl.Failed());
ASS
发送语音消息
[default-logger][16:50:40][1244626][info ][/home/chatServer/chatServer/src/server/transmite/test/transmite_client.cc:104] 尝试发送语音消息。用户ID: 8d25-14d9ff25-0000, 会话ID: 会话ID1, 语音文件内容: 动听猪叫声数据
[default-logger][16:50:40][1244626][debug ][/home/chatServer/chatServer/src/server/transmite/test/transmite_client.cc:111] 成功获取服务通信通道,服务: /service/transmite_service
[default-logger][16:50:40][1244626][debug ][/home/chatServer/chatServer/src/server/transmite/test/transmite_client.cc:122] 请求已构建:request_id=b704-a3615a54-0003, user_id=8d25-14d9ff25-0000, chat_session_id=会话ID1, message_type=SPEECH, file_contents=动听猪叫声数据
[default-logger][16:50:40][1244626][info ][/home/chatServer/chatServer/src/server/transmite/test/transmite_client.cc:131] RPC调用成功,响应成功:true
///
///发送语音消息
//
void speech_message(const std::string &uid, const std::string &sid, const std::string &msg) {
LOG_INFO("尝试发送语音消息。用户ID: {}, 会话ID: {}, 语音文件内容: {}", uid, sid, msg);
auto channel = sm->choose(FLAGS_transmite_service);
if (!channel) {
std::cout << "获取通信信道失败!" << std::endl;
return;
}
LOG_DEBUG("成功获取服务通信通道,服务: {}", FLAGS_transmite_service);
mag::MsgTransmitService_Stub stub(channel.get());
mag::NewMessageReq req;
mag::GetTransmitTargetRsp rsp;
req.set_request_id(mag::uuid());
req.set_user_id(uid);
req.set_chat_session_id(sid);
req.mutable_message()->set_message_type(mag::MessageType::SPEECH);
req.mutable_message()->mutable_speech_message()->set_file_contents(msg);
LOG_DEBUG("请求已构建:request_id={}, user_id={}, chat_session_id={}, message_type=SPEECH, file_contents={}",
req.request_id(), req.user_id(), req.chat_session_id(), req.message().speech_message().file_contents());
brpc::Controller cntl;
stub.GetTransmitTarget(&cntl, &req, &rsp, nullptr);
if (cntl.Failed()) {
LOG_ERROR("RPC调用失败: {}", cntl.ErrorText());
} else {
LOG_INFO("RPC调用成功,响应成功:{}", rsp.success() ? "true" : "false");
}
ASSERT_FALSE(cntl.Failed());
ASSERT_TRUE(rsp.success());
}
发送文件消息
[default-logger][16:50:40][1244626][info ][/home/chatServer/chatServer/src/server/transmite/test/transmite_client.cc:143] 尝试发送文件消息。用户ID: 8d25-14d9ff25-0000, 会话ID: 98d4-87a1ebfd-0003, 文件名: 猪爸爸的文件名称, 文件大小: 24
[default-logger][16:50:40][1244626][debug ][/home/chatServer/chatServer/src/server/transmite/test/transmite_client.cc:150] 成功获取服务通信通道,服务: /service/transmite_service
[default-logger][16:50:40][1244626][debug ][/home/chatServer/chatServer/src/server/transmite/test/transmite_client.cc:163] 请求已构建:request_id=24f5-4b4e0e57-0004, user_id=8d25-14d9ff25-0000, chat_session_id=98d4-87a1ebfd-0003, message_type=FILE, filename=猪爸爸的文件名称, file_size=24
[default-logger][16:50:40][1244626][info ][/home/chatServer/chatServer/src/server/transmite/test/transmite_client.cc:172] RPC调用成功,响应成功:true
///
///发送文件消息
//
void file_message(const std::string &uid, const std::string &sid,
const std::string &filename, const std::string &content) {
LOG_INFO("尝试发送文件消息。用户ID: {}, 会话ID: {}, 文件名: {}, 文件大小: {}", uid, sid, filename, content.size());
auto channel = sm->choose(FLAGS_transmite_service);
if (!channel) {
std::cout << "获取通信信道失败!" << std::endl;
return;
}
LOG_DEBUG("成功获取服务通信通道,服务: {}", FLAGS_transmite_service);
mag::MsgTransmitService_Stub stub(channel.get());
mag::NewMessageReq req;
mag::GetTransmitTargetRsp rsp;
req.set_request_id(mag::uuid());
req.set_user_id(uid);
req.set_chat_session_id(sid);
req.mutable_message()->set_message_type(mag::MessageType::FILE);
req.mutable_message()->mutable_file_message()->set_file_contents(content);
req.mutable_message()->mutable_file_message()->set_file_name(filename);
req.mutable_message()->mutable_file_message()->set_file_size(content.size());
LOG_DEBUG("请求已构建:request_id={}, user_id={}, chat_session_id={}, message_type=FILE, filename={}, file_size={}",
req.request_id(), req.user_id(), req.chat_session_id(), filename, content.size());
brpc::Controller cntl;
stub.GetTransmitTarget(&cntl, &req, &rsp, nullptr);
if (cntl.Failed()) {
LOG_ERROR("RPC调用失败: {}", cntl.ErrorText());
} else {
LOG_INFO("RPC调用成功,响应成功:{}", rsp.success() ? "true" : "false");
}
ASSERT_FALSE(cntl.Failed());
ASSERT_TRUE(rsp.success());
}