接着上文关于[集群聊天服务器]----(七)业务模块之一对一聊天、添加好友函数、好友类以及离线消息类的剖析。本章将对创建群组,加入群组以及群组聊天业务进行剖析。
群类
类似于User类,构建了Group类
#ifndef GROUP_H
#define GROUP_H
#include "groupuser.hpp"
#include <string>
#include <vector>
using namespace std;
//User表的ORM类
class Group
{
public:
Group(int id = -1, string name = "", string desc = "")
{
this->id = id;
this->name = name;
this->desc = desc;
}
void setId(int id) {this->id=id;}
void setName(string name) {this->name=name;}
void setDesc(string desc) {this->desc=desc;}
int getId() {return this->id;}
string getName() {return this->name;}
string getDesc() {return this->desc=desc;} //组功能描述
vector<GroupUser> &getUsers() {return this->users;}
private:
int id;
string name;
string desc;
vector<GroupUser> users; //组成员
};
#endif
- 群的id初始默认为-1,设置/获取群id,群名称以及群用途函数
GroupUser类
GroupUser为群组用户,继承了User类,多了一个role角色信息,从User类直接继承,复用User的其他信息
void setRole(string role) {this->role = role;}
string getRole() {return this->role;}
//派生类的特殊变量 role角色
string role;
群组信息的操作接口
// 创建群组
bool createGroup(Group &group);
// 加入群组
void addGroup(int userid, int groupid, string role);
// 查询用户所在群组信息
vector<Group> queryGroups(int userid);
// 根据指定的groupid查询群组用户id列表,除userid自己,主要用户群聊业务给群组其它成员群发消息
vector<int> queryGroupUsers(int userid, int groupid);
创建群组函数
bool GroupModel::createGroup(Group &group)
{
char sql[1024] = {0};
sprintf(sql, "insert into allgroup(groupname, groupdesc) values('%s', '%s')",
group.getName().c_str(), group.getDesc().c_str());
MySQL mysql;
// 连接数据库
if (mysql.connect())
{
// 更新数据库语句
if (mysql.update(sql))
{
group.setId(mysql_insert_id(mysql.getConnection()));
return true;
}
}
return false;
}
- 组装sql语句,根据群名称,群作用在allgroup表插入相关消息;
- 连接数据库,并进行更新,并返回群id
加入群组函数
void GroupModel::addGroup(int userid, int groupid, string role)
{
char sql[1024] = {0};
sprintf(sql, "insert into groupuser values(%d,%d,'%s')",
groupid,userid,role.c_str());
MySQL mysql;
// 连接数据库
if (mysql.connect())
{
// 更新数据库语句
mysql.update(sql);
}
}
- 组装sql语句,根据群id,用户id,以及群内角色在allgroup表插入相关消息;
- 连接数据库,并进行更新,并返回群id
查询用户所在群组信息
vector<Group> GroupModel::queryGroups(int userid)
{
char sql[1024] = {0};
sprintf(sql,"select a.id,a.groupname,a.groupdesc from allgroup a inner join \
groupuser b on a.id = b.groupid where b.userid=%d",userid);
vector<Group> groupVec;
MySQL mysql;
if (mysql.connect())
{
// 更新数据库语句
MYSQL_RES *res = mysql.query(sql); // 指针 内部动态内存开辟 需要释放资源
if (res != nullptr)
{
// 获取行 根据主键查
MYSQL_ROW row ;
while((row = mysql_fetch_row(res)) != nullptr)
{
Group group;
group.setId(atoi(row[0]));
group.setName(row[1]);
group.setDesc(row[2]);
groupVec.push_back(group);
}
mysql_free_result(res);
}
}
for(Group &group: groupVec)
{
sprintf(sql,"select a.id,a.name,a.state,b.grouprole from user a \
inner join groupuser b on b.userid = a.id where b.groupid=%d", group.getId());
MYSQL_RES *res = mysql.query(sql);
if (res != nullptr)
{
// 获取行 根据主键查
MYSQL_ROW row ;
while((row = mysql_fetch_row(res)) != nullptr)
{
GroupUser user;
user.setId(atoi(row[0]));
user.setName(row[1]);
user.setState(row[2]);
user.setRole(row[3]);
group.getUsers().push_back(user);
}
mysql_free_result(res);
}
}
return groupVec;
}
- 组装sql语句,根据用户id在allgroup表和groupuser表中进联合查找,寻找用户所在的组;
- 根据sql语句,调用MySQL::query()语句进行查找好友,然后调用mysql_fetch_row()函数,查找对应的行,row是MYSQL_ROW类型,可以根据下标找到对应的值,并把用户对应的组Group类放入groupVec中;
- 根据groupVec中,对组内的成员姓名,id以及状态和群内角色进行查找,对应的GroupUser类进行返回
- 注意释放资源
查找群内除userid以外的用户
vector<int> GroupModel::queryGroupUsers(int userid, int groupid)
{
char sql[1024] = {0};
sprintf(sql,"select userid from groupuser where groupid = %d and userid != %d",groupid,userid);
vector<int> idVec;
MySQL mysql;
if (mysql.connect())
{
// 更新数据库语句
MYSQL_RES *res = mysql.query(sql); // 指针 内部动态内存开辟 需要释放资源
if (res != nullptr)
{
// 获取行 根据主键查
MYSQL_ROW row ;
while((row = mysql_fetch_row(res)) != nullptr)
{
idVec.push_back(atoi(row[0]));
}
mysql_free_result(res);
}
}
return idVec;
}
- 组装sql语句,根据userid在groupuser表查找其余用户id;
- 根据sql语句,调用MySQL::query()语句进行查找好友,然后调用mysql_fetch_row()函数,查找对应的行,row是MYSQL_ROW类型,可以根据下标找到对应的值,并把用户放入idVec中返回;
- 注意释放资源
创建群组
当客户端输入msgid=CREATE_GROUP_MSG时,ChatService::ChatService()
中会回调ChatService::createGroup()
函数进行处理:
void ChatService::createGroup(const TcpConnectionPtr &conn, json &js, Timestamp time)
{
int userid = js["id"].get<int>();
string name = js["groupname"];
string desc = js["groupdesc"];
// 存储新创建的群组信息
Group group(-1, name, desc);
if (_groupModel.createGroup(group))
{
// 存储群组创建人信息
_groupModel.addGroup(userid, group.getId(), "creator");
}
}
- 通过 JSON 对象反序列结果,寻找用户id userid对应的值,groupname以及groupdesc
- 创建Group类,并调用
GroupModel::createGroup()
创建群,角色为creator; - 根据创建的群组,设置群id
加入群组
当客户端输入msgid=ADD_GROUP_MSG时,ChatService::ChatService()
中会回调ChatService::addGroup()
函数进行处理:
void ChatService::addGroup(const TcpConnectionPtr &conn, json &js, Timestamp time)
{
int userid = js["id"].get<int>();
int groupid = js["groupid"].get<int>();
_groupModel.addGroup(userid, groupid, "normal");
}
- 根据userid以及要加入的groupid,调用
GroupModel::addGroup()
函数加入群,角色默认为normal;
群组聊天
当客户端输入msgid=GROUP_CHAT_MSG时,ChatService::ChatService()
中会回调ChatService::groupChat()
函数进行处理:
void ChatService::groupChat(const TcpConnectionPtr &conn, json &js, Timestamp time)
{
int userid = js["id"].get<int>();
int groupid = js["groupid"].get<int>();
vector<int> useridVec = _groupModel.queryGroupUsers(userid,groupid);
lock_guard<mutex> lock(_connMutex); // 线程安全
for(int id : useridVec)
{
auto it = _userConnMap.find(id);
if (it != _userConnMap.end())
{
// id 在线 转发消息 服务器主动推送消息给toid用户
it->second->send(js.dump());
}
else
{
User user = _userModel.query(id);
//在另一台电脑上
if (user.getState() == "online")
{
_redis.publish(id, js.dump());
}
else
{
// id 不在线 存储离线消息
_offlineMsgModel.insert(id, js.dump());
}
}
}
}
- 通过 JSON 对象反序列结果,寻找用户id以及群id对应的值,找到用户想要对话的群,并在
_groupModel
中进行查找群内其余成员,并放入useridVec
中; - 在
_userConnMap
查找用户是否存在; - 如果用户在一台主机并且处于在线状态,就发送想要发送的消息;
- 如果不在同一台主机,根据id调用
_userModel
在user表中进行查看对方是否在线,如果在线就通过redis发布消息 - 不在线就存储其离线消息