C++项目 | 集群聊天服务器 | 群组功能业务

实现三个功能:

  • 1.创建群

  • 2.加入群

  • 3.群里聊天

1.相关的数据库表

image-20250201123047852

2.消息类型

1
2
3
4
5
6
7
8
9
10
11
12
13
enum EnMsgType
{
LOGIN_MSG=1, //登录消息
LOGIN_MSG_ACK, //登录响应消息
REG_MSG, //注册消息
REG_MSG_ACK, //注册响应消息
ONE_CHAT_MSG, //聊天消息
ADD_FRIEND_MSG,//添加好友消息

CREATE_GROUP_MSG,//创建群组
ADD_GROUP_MSG,//加入群组
GROUP_CHAT_MSG,//群聊天
};

3.groupuser.hpp

用来存储群组成员信息的

对比user多了一个role,表明是管理员还是普通成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef GROUPUSER_H
#define GROUPUSER_H

#include"user.hpp"

//群组用户,多了一个role角色信息,从user类直接继承,复用User的其他信息,id,name,state等
class GroupUser:public User
{
public:
void setRole(string role){this->role=role;}
string getRole(){return this->role;}
private:
string role;
};

#endif

4.group.hpp

封装群的相关信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#ifndef GROUP_H
#define GROUP_H

#include"groupuser.hpp"
#include<string>
#include<vector>
using namespace std;

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;}
vector<GroupUser> &getUsers(){return this->users;}
private:
int id;
string name;
string desc;
vector<GroupUser> users;//存储组的所有成员
};


#endif

5.groupmodel.hpp

对群的操作的封装

主要包含四个操作

1.创建群组

2.加入群组

3.查询用户所在群组信息

4.发送群聊消息,在这个群的所有成员都可以收到,不在线的成员的消息要存储到离线消息表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#ifndef GROUPMODEL_H
#define GROUPMODEL_H

#include"group.hpp"
#include<string>
#include<vector>
using namespace std;

class GroupModel
{
public:
//创建群组
bool createGroup(Group &group);

//加入群组
void addGrooup(int userid,int groupid,string role);

//查询用户所在群组信息
vector<Group> queryGroups(int userid);

//根据指定的groupid查询群组用户id列表,除userid自己,主要用户群聊业务给群组其他成员发送消息
vector<int> queryGroupUsers(int userid,int groupid);
};

#endif

6.groupmodel.cpp

对上面四个方法的具体实现

创建和加入群组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#include"groupmodel.hpp"
#include"db.h"

//创建群组
bool GroupModel::createGroup(Group &group)
{
//1.组装sql语句
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))
{
//返回的是群组id
group.setId(mysql_insert_id(mysql.getConnection()));
return true;
}
}
return false;
}

//加入群组
void GroupModel::addGrooup(int userid,int groupid,string role)
{
//1.组装sql语句
char sql[1024]={0};
sprintf(sql,"insert into groupuservalues(%d,%d,'%s')",
groupid,userid,role.c_str());

MySQL mysql;
if(mysql.connect())
{
mysql.update(sql);
}
}

//查询用户所在群组信息
vector<Group> GroupModel::queryGroups(int userid)
{
/*
1.先根据userid在groupuser表中查询出该用户所属的群组信息
2.再根据群组信息,查询属于该群组的所有用户的userid,并且和user表进行多表联合查询,查出用户的详细信息
*/
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=mysql_fetch_row(res);
if(row!=nullptr)
{
Group group;
//row拿出来的是一行记录
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 a.id=b.userid 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;
}

//根据指定的groupid查询群组用户id列表,除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;
}

7.群组业务代码实现

记得注册绑定到Map上面,不然对应的消息类型发过来不会调用对应的函数的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//创建群组业务
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.addGrooup(userid,group.getId(),"creator");
}
}

//加入群组业务
void ChatService::addGroup(const TcpConnectionPtr& conn,json &js,Timestamp time)
{
int userid=js["id"].get<int>();
int groupid=js["groupid"].get<int>();
_groupModel.addGrooup(userid,groupid,"normal");
}

//群组聊天业务
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())
{
//转发群消息
it->second->send(js.dump());
}
else
{
//存储离线群消息
_offlineMsgModel.insert(id,js.dump());
}
}
}

8.项目工作目录结构

image-20250206165347947

9.CMake改动

工作目录下的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 项目所需cmake的最低版本
cmake_minimum_required(VERSION 3.0)
#项目的名称
project(chat)

#配置编译选项 对应第2项
set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} -g)

#设置可执行文件最终存储的路径 PROJECT_SOURCE_DIR是工程的根目录
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

#配置头文件搜索路径
include_directories(${PROJECT_SOURCE_DIR}/include)
include_directories(${PROJECT_SOURCE_DIR}/include/server)
include_directories(${PROJECT_SOURCE_DIR}/include/server/db)
include_directories(${PROJECT_SOURCE_DIR}/include/server/model)
include_directories(${PROJECT_SOURCE_DIR}/include/client)
include_directories(${PROJECT_SOURCE_DIR}/thirdparty)

#加载子目录
add_subdirectory(src)

./src下的

1
add_subdirectory(server)

./src/server下的

1
2
3
4
5
6
7
8
9
10
11

#定义了一个SRC_LIST变量,包含了该目录下所有的源文件
aux_source_directory(. SRC_LIST)
aux_source_directory(./db DB_LIST)
aux_source_directory(./model MODEL_LIST)

#指定生成可执行文件
add_executable(ChatServer ${SRC_LIST} ${DB_LIST} ${MODEL_LIST})

#指定可执行文件链接时需要依赖的库文件
target_link_libraries(ChatServer muduo_net muduo_base mysqlclient pthread)