接着上文关于[集群聊天服务器]----(七)业务模块之一对一聊天、添加好友函数、好友类以及离线消息类的剖析。本章将对创建群组,加入群组以及群组聊天业务进行剖析。
群类
类似于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发布消息 - 不在线就存储其离线消息