提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
好友管理子服务要管理的数据有两种:好友相关数据,聊天会话相关数据。
而好友的操作涉及到了好友管理表,好友申请表。
聊天会话的操作涉及到了会话管理表,会话成员表。
因此我们需要管理四张数据库表,
同时,该子服务提供了一个用户搜索功能。我们需要管理一个ES用户类。
在获取好友列表时,需要通过用户管理子服务获取用户信息。
在获取会话列表时,需要通过消息存储子服务获取最近一条消息。
好友关系表
好友关系表只需要三个字段,一个自增ID,一个用户ID和一个对端ID。需要给用户ID添加一个索引,方便我们查询好友列表。
#pragma db object table("relation")
class Relation {
//只需要给用户ID添加索引,因为获取自己的好友列表都是通过user_id查询的
#pragma db type("varchar(64)") index
std::string _user_id;
#pragma db type("varchar(64)")
std::string _peer_id;
}
好友申请事件表
好友申请表需要四个字段,其中event_id字段已经被废弃了,在项目设计之初是以服务器主动推送给客户端的叫做一个事件的概念。另外两个字段就是用户ID和对端ID,代表谁申请谁为好友。需要给这个两个字段都加上索引,其中给peer_id加上索引是为了方便获取待处理好友申请列表。
#pragma db object table("friend_apply")
class FriendApply {
#pragma db type("varchar(64)") index unique
std::string _event_id;
#pragma db type("varchar(64)") index
std::string _user_id;
#pragma db type("varchar(64)") index
std::string _peer_id;
}
会话信息表
会话信息表有四个字段,会话ID,会话名称,和会话类型。其中有单聊和群聊两种会话类型
enum class ChatSessionType
{
SINGLE = 1,
GROUP = 2
};
对于单聊来说会话名称就是好友的昵称。
#pragma db object table("chat_session")
class ChatSession {
#pragma db type("varchar(64)") index unique
std::string _chat_session_id;
#pragma db type("varchar(64)")
std::string _chat_session_name;
#pragma db type("tinyint")
ChatSessionType _chat_session_type;
}
在提供的服务中,有一个是获取指定用户ID会话列表。我们需要组织ChatSessionInfo数组响应。在这个ChatSessionInfo中有一个字段。
single_chat_friend_id对于单聊会话需要设置为好友的用户ID,因为在前端我们点击好友Item的时候需要跳转到对于的会话Item,因此就需要给聊天会话Item和好友ID建立映射,这也是需要这个字段的原因。
对于单聊会话获取到好友用户ID后,还需要通过用户子服务获取好友的用户信息,用来填充会话名称和会话头像。
对于群聊会话来说,就不需要填充这几个字段了,只需要填充会话ID,会话名称和最近一条消息。
//聊天会话信息
message ChatSessionInfo {
//群聊会话不需要设置,单聊会话设置为对方用户 ID
optional string single_chat_friend_id = 1;
string chat_session_id = 2; //会话 ID
string chat_session_name = 3;//会话名称 git
//会话上一条消息,新建的会话没有最新消息
optional MessageInfo prev_message = 4;
//会话头像 --群聊会话不需要,直接由前端固定渲染,单聊就是对方的头像
optional bytes avatar = 5;
}
为了获取到好友的用户ID,我们需要多表联查,在这里我们使用odb的视图来简化查询。
需要三张表联合。一个会话信息表和两张会话成员表。而要获取的字段就是会话ID和好友用户ID.
where css.session_type = ChatSessionType::SINGAL and csm1.user_id != csm2.user_id and csm1.user_id == user_id。
#pragma db view object(ChatSession = css)\
object(ChatSessionMember = csm1 : cs::_chat_session_id == csm1::_session_id)\
object(ChatSessionMember = csm2 : cs::_chat_session_id == csm2::_session_id)\
query(?)
struct SingleChatSession {
#pragma db column(css::_chat_session_id)
std::string chat_session_id;
#pragma db column(csm2::_user_id)
std::string friend_id;
};
群聊会话的组织需要的字段和单聊不一样,群聊会话不需要好友的用户ID,只需要群聊名称就行。
这里只需要两张表联查。
where css.session_type = ChatSessionType::GROUP and csm2._user_id == user_Id;
#pragma db view object(ChatSession = css)\
object(ChatSessionMember = csm : cs::_chat_session_id == csm1::_session_id)\
query(?)
struct GroupChatSession {
#pragma db column(css::_chat_session_id)
std::string chat_session_id;
#pragma db column(css::_chat_session_name)
std::string chat_session_name;
};
会话成员表
会话成员表只需要三个字段,会话ID和好友ID和一个自增字段。
其中会话ID需要添加一个索引,方便我们获取会话所有成员。
#pragma db object table("chat_session_member")
class ChatSessionMember {
#pragma db type("varchar(64)") index
std::string _session_id;
#pragma db type("varchar(64)")
std::string _user_id;
}
数据库表操作类
下面我们封装一下四张表的操作类。
好友关系表
新增好友,需要在表中添加{uid,pid} 和{pid,uid}两条记录,代表互为好友。
bool insert(const std::string& uid,const std::string& pid)
{
//新增好友必须添加{1,2} {2,1} 互相为好友
try {
odb::transaction trans(_db->begin());
Relation r1(uid,pid);
Relation r2(pid,uid);
_db->persist(r1);
_db->persist(r2);
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("新增好友关系失败 {}:{} -- {}!",uid,pid,e.what());
return false;
}
return true;
}
移除用户关系,同样的需要移除{uid,pid},{pid,uid}两组记录,我们的删除时双向删除。
bool remove(const std::string& uid,const std::string& pid)
{
//删除好友关系,需要删除{1,2}{2,1}
try{
odb::transaction trans(_db->begin());
typedef odb::query<Relation> query;
typedef odb::result<Relation> result;
_db->erase_query<Relation>(query::user_id == uid && query::peer_id == pid);
_db->erase_query<Relation>(query::user_id == pid && query::peer_id == uid);
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("删除好友关系失败 {}-{}:{}!", uid,pid,e.what());
return false;
}
return true;
}
判断两人是否是好友关系,在添加好友和好友申请事件时,需要判断两人是否是好友,防止重复添加。
bool exists(const std::string& uid,const std::string& pid)
{
bool flage = false;
try {
odb::transaction trans(_db->begin());
typedef odb::query<Relation> query;
typedef odb::result<Relation> result;
result r(_db->query<Relation>(query::user_id == uid && query::peer_id == pid));
for (result::iterator i(r.begin()); i != r.end(); ++i) {
flage = !r.empty();
}
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("判断好友关系是否存在失败:{}-{}:{}!", uid,pid, e.what());
}
return flage;
}
以用户 ID 获取用户的所有好友 ID,这个接口在获取好友列表时使用
std::vector<std::string> friends(const std::string& uid)
{
std::vector<std::string> res;
try {
odb::transaction trans(_db->begin());
typedef odb::query<Relation> query;
typedef odb::result<Relation> result;
result r(_db->query<Relation>(query::user_id == uid));
for (result::iterator i(r.begin()); i != r.end(); ++i) {
res.push_back(i->peer_id());
}
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("获取好友ID失败:{}-{}!", uid, e.what());
}
return res;
}
好友事件通知类
新增好友申请事件:申请的时候新增
bool insert(FriendApply &ev) {
try {
odb::transaction trans(_db->begin());
_db->persist(ev);
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("新增好友申请事件失败 {}-{}:{}!", ev.user_id(), ev.peer_id(), e.what());
return false;
}
return true;
}
删除好友申请事件:处理完毕(同意/拒绝)的时候删除
bool remove(const std::string &uid, const std::string &pid) {
try {
odb::transaction trans(_db->begin());
typedef odb::query<FriendApply> query;
typedef odb::result<FriendApply> result;
_db->erase_query<FriendApply>(query::user_id == uid && query::peer_id == pid);
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("删除好友申请事件失败 {}-{}:{}!", uid, pid, e.what());
return false;
}
return true;
}
判断指定用户申请是否存在,在添加好友和好友事件申请的时候,用于防止重复申请。
bool exists(const std::string &uid, const std::string &pid) {
bool flag = false;
try {
typedef odb::query<FriendApply> query;
typedef odb::result<FriendApply> result;
odb::transaction trans(_db->begin());
result r(_db->query<FriendApply>(query::user_id == uid && query::peer_id == pid));
LOG_DEBUG("{} - {} 好友事件数量:{}", uid, pid, r.size());
flag = !r.empty();
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("获取好友申请事件失败:{}-{}-{}!", uid, pid, e.what());
}
return flag;
}
获取指定用户的好友申请者ID,用于获取好友申请列表。这里获取的是谁申请我为好友。
std::vector<std::string> applyUsers(const std::string &uid){
std::vector<std::string> res;
try {
odb::transaction trans(_db->begin());
typedef odb::query<FriendApply> query;
typedef odb::result<FriendApply> result;
//当前的uid是被申请者的用户ID
result r(_db->query<FriendApply>(query::peer_id == uid));
for (result::iterator i(r.begin()); i != r.end(); ++i) {
res.push_back(i->user_id());
}
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("通过用户{}的好友申请者失败:{}!", uid, e.what());
}
return res;
}
会话成员表
向指定会话中添加单个成员,添加好友成功,需要创建会话,同时添加会话成员
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;
}
通过会话 ID,获取会话的所有成员 ID,这个接口在消息转发自服务用到的,用来确认消息的转发目标。
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;
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;
}
删除会话所有成员:删除好友伴随着删除会话和删除会话成员。
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;
}
会话信息表
向会话表中新增会话信息,添加好友成功和创建群聊的时候调用。
bool insert(ChatSession &cs) {
try {
odb::transaction trans(_db->begin());
_db->persist(cs);
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("新增会话失败 {}:{}!", cs.chat_session_name(), e.what());
return false;
}
return true;
}
单聊会话的删除(通过uesr_id和peer_id删除会话),这个接口非常重要,当删除好友时调用,需要通过user_id和peer_id来删除会话信息和会话成员信息。
删除会话信息和删除会话成员,就需要找到对应的会话ID。
通过多表联查来获取到会话ID,在根据会话ID删除会话信息和会话成员信息。
bool remove(const std::string &uid, const std::string &pid) {
//单聊会话的删除,-- 根据单聊会话的两个成员
//先查找到要删除的会话ID,再根据会话ID删除会话
try {
odb::transaction trans(_db->begin());
typedef odb::query<SingleChatSession> query;
typedef odb::result<SingleChatSession> result;
auto res = _db->query_one<SingleChatSession>(
query::csm1::user_id == uid &&
query::csm2::user_id == pid &&
query::css::chat_session_type == ChatSessionType::SINGLE);
std::string cssid = res->chat_session_id;
typedef odb::query<ChatSession> cquery;
_db->erase_query<ChatSession>(cquery::chat_session_id == cssid);
typedef odb::query<ChatSessionMember> mquery;
_db->erase_query<ChatSessionMember>(mquery::session_id == cssid);
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("删除会话失败 {}-{}:{}!", uid, pid, e.what());
return false;
}
return true;
}
通过用户 ID 获取所有的好友单聊会话(连接会话成员表)
这个接口同样很重要,是用于获取会话列表的。由于单聊会话和群聊会话列表的组织防止不一致,所以我们需要提供两个接口。
在单聊会话这个接口中,需要获取的字段时好友用户ID。通过好友用户ID就可以去用户子服务获取用户信息,用来组织ChatSessionInfo了。一个头像ID一个好友昵称。
std::vector<SingleChatSession> singleChatSession(const std::string &uid) {
std::vector<SingleChatSession> res;
try {
odb::transaction trans(_db->begin());
typedef odb::query<SingleChatSession> query;
typedef odb::result<SingleChatSession> result;
//当前的uid是被申请者的用户ID
result r(_db->query<SingleChatSession>(
query::css::chat_session_type == ChatSessionType::SINGLE &&
query::csm1::user_id == uid &&
query::csm2::user_id != query::csm1::user_id));
for (result::iterator i(r.begin()); i != r.end(); ++i) {
res.push_back(*i);
}
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("获取用户 {} 的单聊会话失败:{}!", uid, e.what());
}
return res;
}
通过用户 ID 获取所有自己的群聊会话(连接会话成员表)
对于群聊会话需要获取的字段就比较简单了,只需要获取一个群聊名称就行
std::vector<GroupChatSession> groupChatSession(const std::string &uid) {
std::vector<GroupChatSession> res;
try {
odb::transaction trans(_db->begin());
typedef odb::query<GroupChatSession> query;
typedef odb::result<GroupChatSession> result;
//当前的uid是被申请者的用户ID
result r(_db->query<GroupChatSession>(
query::css::chat_session_type == ChatSessionType::GROUP &&
query::csm::user_id == uid ));
for (result::iterator i(r.begin()); i != r.end(); ++i) {
res.push_back(*i);
}
trans.commit();
}catch (std::exception &e) {
LOG_ERROR("获取用户 {} 的群聊会话失败:{}!", uid, e.what());
}
return res;
}
业务代码的编写
- 好友列表的获取:当用户登录成功之后,获取自己好友列表进行展示
- 申请好友:搜索用户之后,点击申请好友,向对方发送好友申请
- 待处理申请的获取:当用户登录成功之后,会获取离线的好友申请请求以待处理
- 好友申请的处理:针对收到的好友申请进行同意/拒绝的处理
- 删除好友:删除当前好友列表中的好友
- 用户搜索:可以进行用户的搜索用于申请好友
- 聊天会话列表的获取:每个单人/多人聊天都有一个聊天会话,在登录成功后可以
获取聊天会话,查看历史的消息以及对方的各项信息- 多人聊天会话的创建:单人聊天会话在对方同意好友时创建,而多人会话需要调
用该接口进行手动创建- 聊天成员列表的获取:多人聊天会话中,可以点击查看群成员按钮,查看群成员
信
好友列表的获取
从请求中获取用户ID,在好友关系数据库中查询用户的好友ID数组,通过用户子服务的批量获取多个用户信息,获取到UserInfo数组,组织响应进行返回。
virtual void GetFriendList(::google::protobuf::RpcController* controller,
const ::lkm_im::GetFriendListReq* request,
::lkm_im::GetFriendListRsp* response,
::google::protobuf::Closure* done) {
brpc::ClosureGuard rpc_guard(done);
//1. 定义错误回调
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;
};
//1. 提取请求中的关键要素:用户ID
std::string rid = request->request_id();
std::string uid = request->user_id();
//2. 从数据库中查询获取用户的好友ID
auto friend_id_lists = _mysql_relation->friends(uid);
std::unordered_set<std::string> user_id_lists;
for (auto &id : friend_id_lists) {
user_id_lists.insert(id);
}
//3. 从用户子服务批量获取用户信息
std::unordered_map<std::string, UserInfo> user_list;
bool ret = GetUserInfo(rid, user_id_lists, user_list);
if (ret == false) {
LOG_ERROR("{} - 批量获取用户信息失败!", rid);
return err_response(rid, "批量获取用户信息失败!");
}
//4. 组织响应
response->set_request_id(rid);
response->set_success(true);
for (const auto & user_it : user_list) {
auto user_info = response->add_friend_list();
user_info->CopyFrom(user_it.second);
}
}
删除好友
从请求种提取用户ID和要删除的用户ID,从好友关系数据库中删除好友关系。从会话列表数据库中删除会话信息,从会话成员数据库删除会话成员。
在会话信息类中提供了一个接口可以在删除会话信息的同时删除会话成员。
virtual void FriendRemove(::google::protobuf::RpcController* controller,
const ::lkm_im::FriendRemoveReq* request,
::lkm_im::FriendRemoveRsp* response,
::google::protobuf::Closure* done) {
brpc::ClosureGuard rpc_guard(done);
//1. 定义错误回调
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;
};
//1. 提取关键要素:当前用户ID,要删除的好友ID
std::string rid = request->request_id();
std::string uid = request->user_id();
std::string pid = request->peer_id();
//2. 从好友关系表中删除好友关系信息
bool ret = _mysql_relation->remove(uid, pid);
if (ret == false) {
LOG_ERROR("{} - 从数据库删除好友信息失败!", rid);
return err_response(rid, "从数据库删除好友信息失败!");
}
//3. 从会话信息表中,删除对应的聊天会话 -- 同时删除会话成员表中的成员信息
ret = _mysql_chat_session->remove(uid, pid);
if (ret == false) {
LOG_ERROR("{}- 从数据库删除好友会话信息失败!", rid);
return err_response(rid, "从数据库删除好友会话信息失败!");
}
//4. 组织响应
response->set_request_id(rid);
response->set_success(true);
}
添加好友
提取出申请人ID和被申请人ID,首先需要判断好友申请数据表是否存在该条好友申请记录了,接着去好友关系数据表查看两人是否已经是好友了。然后到好友申请表中调价一条记录。最后组织响应返回。
virtual void FriendAdd(::google::protobuf::RpcController* controller,
const ::lkm_im::FriendAddReq* request,
::lkm_im::FriendAddRsp* response,
::google::protobuf::Closure* done) {
brpc::ClosureGuard rpc_guard(done);
//1. 定义错误回调
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;
};
//1. 提取请求中的关键要素:申请人用户ID; 被申请人用户ID
std::string rid = request->request_id();
std::string uid = request->user_id();
std::string pid = request->respondent_id();
//2. 判断两人是否已经是好友
bool ret = _mysql_relation->exists(uid, pid);
if (ret == true) {
LOG_ERROR("{}- 申请好友失败-两者{}-{}已经是好友关系", rid, uid, pid);
return err_response(rid, "两者已经是好友关系!");
}
//3. 当前是否已经申请过好友
ret = _mysql_apply->exists(uid, pid);
if (ret == true) {
LOG_ERROR("{}- 申请好友失败-已经申请过对方好友!", rid, uid, pid);
return err_response(rid, "已经申请过对方好友!");
}
//4. 向好友申请表中,新增申请信息
std::string eid = uuid();
FriendApply ev(eid, uid, pid);
ret = _mysql_apply->insert(ev);
if (ret == false) {
LOG_ERROR("{} - 向数据库新增好友申请事件失败!", rid);
return err_response(rid, "向数据库新增好友申请事件失败!");
}
//3. 组织响应
response->set_request_id(rid);
response->set_success(true);
response->set_notify_event_id(eid);
}
处理好友申请
提取出请求中的申请人ID和被申请人ID和处理结果。
首先判断好友申请表中是否存在该申请记录。接着删除该条记录,代表该事件处理完毕。
接着判断处理结果,如果时同意,则需要添加好友关系,创建会话,添加会话成员。
如果不同意,则什么也不做。最后组织响应返回。
virtual void FriendAddProcess(::google::protobuf::RpcController* controller,
const ::lkm_im::FriendAddProcessReq* request,
::lkm_im::FriendAddProcessRsp* response,
::google::protobuf::Closure* done) {
brpc::ClosureGuard rpc_guard(done);
//1. 定义错误回调
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;
};
//1. 提取请求中的关键要素:申请人用户ID;被申请人用户ID;处理结果;事件ID
std::string rid = request->request_id();
std::string eid = request->notify_event_id();
std::string uid = request->user_id(); //被申请人
std::string pid = request->apply_user_id();//申请人
bool agree = request->agree();
//2. 判断有没有该申请事件
bool ret = _mysql_apply->exists(pid, uid);
if (ret == false) {
LOG_ERROR("{}- 没有找到{}-{}对应的好友申请事件!", rid, pid, uid);
return err_response(rid, "没有找到对应的好友申请事件!");
}
//3. 如果有: 可以处理; --- 删除申请事件--事件已经处理完毕
ret = _mysql_apply->remove(pid, uid);
if (ret == false) {
LOG_ERROR("{}- 从数据库删除申请事件 {}-{} 失败!", rid, pid, uid);
return err_response(rid, "从数据库删除申请事件失败!");
}
//4. 如果处理结果是同意:向数据库新增好友关系信息;新增单聊会话信息及会话成员
std::string cssid;
if (agree == true) {
ret = _mysql_relation->insert(uid, pid);
if (ret == false) {
LOG_ERROR("{}- 新增好友关系信息-{}-{}!", rid, uid, pid);
return err_response(rid, "新增好友关系信息!");
}
cssid = uuid();
ChatSession cs(cssid, "", ChatSessionType::SINGLE);
ret = _mysql_chat_session->insert(cs);
if (ret == false) {
LOG_ERROR("{}- 新增单聊会话信息-{}!", rid, cssid);
return err_response(rid, "新增单聊会话信息失败!");
}
ChatSessionMember csm1(cssid, uid);
ChatSessionMember csm2(cssid, pid);
std::vector<ChatSessionMember> mlist = {csm1, csm2};
ret = _mysql_chat_session_member->append(mlist);
if (ret == false) {
LOG_ERROR("{}- 没有找到{}-{}对应的好友申请事件!", rid, pid, uid);
return err_response(rid, "没有找到对应的好友申请事件!");
}
}
//5. 组织响应
response->set_request_id(rid);
response->set_success(true);
response->set_new_session_id(cssid);
}
用户搜索
这个功能应该是放在用户子服务的,但是前期接口设计的时候放在了好友子服务。
首先提取出用户ID和搜索关键字。在好友关系表中获取好友ID列表,在通过ESUser进行用户搜索。
我们需要提供一个用户ID,用于进行过滤,这里需要把自己的ID也添加进行。
ES会返回一组User数组。我们通过这个User中的user_id字段去用户自服务批量获取用户信息,进行组织响应。
在ES的用户索引中我们还存储了用户签名和头像ID,其实这两个字段时,没有用到的,我们著用到了用户ID,手机号和昵称。所以这也是设计接口是的一个小问题。
virtual void FriendSearch(::google::protobuf::RpcController* controller,
const ::lkm_im::FriendSearchReq* request,
::lkm_im::FriendSearchRsp* response,
::google::protobuf::Closure* done) {
brpc::ClosureGuard rpc_guard(done);
//1. 定义错误回调
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;
};
//1. 提取请求中的关键要素:搜索关键字(可能是用户ID,可能是手机号,可能是昵称的一部分)
std::string rid = request->request_id();
std::string uid = request->user_id();
std::string skey = request->search_key();
LOG_DEBUG("{} 好友搜索 : {}", uid, skey);
//2. 根据用户ID,获取用户的好友ID列表
auto friend_id_lists = _mysql_relation->friends(uid);
//3. 从ES搜索引擎进行用户信息搜索 --- 过滤掉当前的好友
std::unordered_set<std::string> user_id_lists;
friend_id_lists.push_back(uid);// 把自己也过滤掉
auto search_res = _es_user->search(skey, friend_id_lists);
for (auto &it : search_res) {
user_id_lists.insert(it.user_id());
}
//4. 根据获取到的用户ID, 从用户子服务器进行批量用户信息获取
std::unordered_map<std::string, UserInfo> user_list;
bool ret = GetUserInfo(rid, user_id_lists, user_list);
if (ret == false) {
LOG_ERROR("{} - 批量获取用户信息失败!", rid);
return err_response(rid, "批量获取用户信息失败!");
}
//5. 组织响应
response->set_request_id(rid);
response->set_success(true);
for (const auto & user_it : user_list) {
auto user_info = response->add_user_info();
user_info->CopyFrom(user_it.second);
}
}
获取待处理好友申请列表
提取请求中的用户ID,去好友申请表中查询有多少人申请我为好友,会返回一个id数组,通过这个数组去用户自服务批量获取用户信息。组织响应返回。
virtual void GetPendingFriendEventList(::google::protobuf::RpcController* controller,
const ::lkm_im::GetPendingFriendEventListReq* request,
::lkm_im::GetPendingFriendEventListRsp* response,
::google::protobuf::Closure* done) {
brpc::ClosureGuard rpc_guard(done);
//1. 定义错误回调
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;
};
//1. 提取关键要素:当前用户ID
std::string rid = request->request_id();
std::string uid = request->user_id();
//2. 从数据库获取待处理的申请事件信息 --- 申请人用户ID列表
auto res = _mysql_apply->applyUsers(uid);
std::unordered_set<std::string> user_id_lists;
for (auto &id : res) {
user_id_lists.insert(id);
}
//3. 批量获取申请人用户信息、
std::unordered_map<std::string, UserInfo> user_list;
bool ret = GetUserInfo(rid, user_id_lists, user_list);
if (ret == false) {
LOG_ERROR("{} - 批量获取用户信息失败!", rid);
return err_response(rid, "批量获取用户信息失败!");
}
//4. 组织响应
response->set_request_id(rid);
response->set_success(true);
for (const auto & user_it : user_list) {
auto ev = response->add_event();
ev->mutable_sender()->CopyFrom(user_it.second);
}
}
获取会话列表
提取出请求中的用户ID,去会话表中查询用户的单聊会话列表,他会返回一个SingleChatSession数组,这个数组中就包含了会话ID和好友用户ID,有了这个好友用户ID就可以去用户自服务批量获取到用户信息,。同时我们还需要获取会话的最后一条消息,通过消息存储自服务获取。
有了用户信息和最后一条消息就可以组织单聊会话的chatSessionInfo了。
另外还需要获取群聊会话,群聊会话的组织就比较简单了,只需要获取最新一条消息即可。群聊名称直接通过表查询就可以获取。
virtual void GetChatSessionList(::google::protobuf::RpcController* controller,
const ::lkm_im::GetChatSessionListReq* request,
::lkm_im::GetChatSessionListRsp* response,
::google::protobuf::Closure* done) {
brpc::ClosureGuard rpc_guard(done);
//1. 定义错误回调
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;
};
//获取聊天会话的作用:一个用户登录成功后,能够展示自己的历史聊天信息
//1. 提取请求中的关键要素:当前请求用户ID
std::string rid = request->request_id();
std::string uid = request->user_id();
//2. 从数据库中查询出用户的单聊会话列表
auto sf_list = _mysql_chat_session->singleChatSession(uid);
// 1. 从单聊会话列表中,取出所有的好友ID,从用户子服务获取用户信息
std::unordered_set<std::string> users_id_list;
for (const auto &f : sf_list) {
users_id_list.insert(f.friend_id);
}
std::unordered_map<std::string, UserInfo> user_list;
bool ret = GetUserInfo(rid, users_id_list, user_list);
if (ret == false) {
LOG_ERROR("{} - 批量获取用户信息失败!", rid);
return err_response(rid, "批量获取用户信息失败!");
}
// 2. 设置响应会话信息:会话名称就是好友名称;会话头像就是好友头像
//3. 从数据库中查询出用户的群聊会话列表
auto gc_list = _mysql_chat_session->groupChatSession(uid);
//4. 根据所有的会话ID,从消息存储子服务获取会话最后一条消息
//5. 组织响应
for (const auto &f : sf_list) {
auto chat_session_info = response->add_chat_session_info_list();
chat_session_info->set_single_chat_friend_id(f.friend_id);
chat_session_info->set_chat_session_id(f.chat_session_id);
chat_session_info->set_chat_session_name(user_list[f.friend_id].nickname());
chat_session_info->set_avatar(user_list[f.friend_id].avatar());
MessageInfo msg;
ret = GetRecentMsg(rid, f.chat_session_id, msg);
if (ret == false) {continue;}
chat_session_info->mutable_prev_message()->CopyFrom(msg);
}
for (const auto &f : gc_list) {
auto chat_session_info = response->add_chat_session_info_list();
chat_session_info->set_chat_session_id(f.chat_session_id);
chat_session_info->set_chat_session_name(f.chat_session_name);
MessageInfo msg;
ret = GetRecentMsg(rid, f.chat_session_id, msg);
if (ret == false) { continue; }
chat_session_info->mutable_prev_message()->CopyFrom(msg);
}
response->set_request_id(rid);
response->set_success(true);
}
创建会话
会话创建这块主要是针对群聊会话,单聊会话在添加好友后自动就创建了。
提取出请求中的会话名称,和会话成员ID.
创建一个会话,然后在会话成员表中添加会话成员。组织响应返回。
virtual void ChatSessionCreate(::google::protobuf::RpcController* controller,
const ::lkm_im::ChatSessionCreateReq* request,
::lkm_im::ChatSessionCreateRsp* response,
::google::protobuf::Closure* done) {
brpc::ClosureGuard rpc_guard(done);
//1. 定义错误回调
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;
};
//创建会话,其实针对的是用户要创建一个群聊会话
//1. 提取请求关键要素:会话名称,会话成员
std::string rid = request->request_id();
std::string uid = request->user_id();
std::string cssname = request->chat_session_name();
//2. 生成会话ID,向数据库添加会话信息,添加会话成员信息
std::string cssid = uuid();
ChatSession cs(cssid, cssname, ChatSessionType::GROUP);
bool ret = _mysql_chat_session->insert(cs);
if (ret == false) {
LOG_ERROR("{} - 向数据库添加会话信息失败: {}", rid, cssname);
return err_response(rid, "向数据库添加会话信息失败!");
}
std::vector<ChatSessionMember> member_list;
for (int i = 0; i < request->member_id_list_size(); i++) {
ChatSessionMember csm(cssid, request->member_id_list(i));
member_list.push_back(csm);
}
ret = _mysql_chat_session_member->append(member_list);
if (ret == false) {
LOG_ERROR("{} - 向数据库添加会话成员信息失败: {}", rid, cssname);
return err_response(rid, "向数据库添加会话成员信息失败!");
}
//3. 组织响应---组织会话信息
response->set_request_id(rid);
response->set_success(true);
response->mutable_chat_session_info()->set_chat_session_id(cssid);
response->mutable_chat_session_info()->set_chat_session_name(cssname);
}
获取会话成员列表
这一块主要是群聊会话的详情界面需要显示群聊成员信息
提取出请求中的会话ID,在会话成员表中获取该会话的所有用户ID,去用户子服务批量获取用户信息。
组织响应返回。
virtual void GetChatSessionMember(::google::protobuf::RpcController* controller,
const ::lkm_im::GetChatSessionMemberReq* request,
::lkm_im::GetChatSessionMemberRsp* response,
::google::protobuf::Closure* done) {
brpc::ClosureGuard rpc_guard(done);
//1. 定义错误回调
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;
};
//用于用户查看群聊成员信息的时候:进行成员信息展示
//1. 提取关键要素:聊天会话ID
std::string rid = request->request_id();
std::string uid = request->user_id();
std::string cssid = request->chat_session_id();
//2. 从数据库获取会话成员ID列表
auto member_id_lists = _mysql_chat_session_member->members(cssid);
std::unordered_set<std::string> uid_list;
for (const auto &id : member_id_lists) {
uid_list.insert(id);
}
//3. 从用户子服务批量获取用户信息
std::unordered_map<std::string, UserInfo> user_list;
bool ret = GetUserInfo(rid, uid_list, user_list);
if (ret == false) {
LOG_ERROR("{} - 从用户子服务获取用户信息失败!", rid);
return err_response(rid, "从用户子服务获取用户信息失败!");
}
//4. 组织响应
response->set_request_id(rid);
response->set_success(true);
for (const auto &uit : user_list) {
auto user_info = response->add_member_info_list();
user_info->CopyFrom(uit.second);
}
}