C++项目 | 集群聊天服务器 | 添加好友业务

添加好友后,每次用户登录会返回离线消息和好友列表

这里的添加好友功能就是单方面的,我只要通过你的id加上你,就是加上了,不用同意或者拒绝

1.数据库层的封装

friend表:

image-20250131135924724

friendmodel.hpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#ifndef FRIENDMODEL_H
#define FRIENDMODEL_H

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

//维护好友信息的操作接口方法
class FriendModel
{
public:
//添加好友关系
void insert(int userid,int frinedid);

//返回用户好友列表 每次下线更新,每次上线返回给用户
vector<User> query(int userid);
};

#endif

friendmodel.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
#include"friendmodel.hpp"
#include"db.h"

//添加好友关系
void FriendModel::insert(int userid,int frinedid)
{
//1.组装sql语句
char sql[1024]={0};
sprintf(sql,"insert into friend values(%d,%d)",userid,frinedid);

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

//返回用户好友列表 每次下线更新,每次上线返回给用户
vector<User> FriendModel::query(int userid)
{
//根据用户号码查询用户信息
//1.组装sql语句
char sql[1024]={0};
sprintf(sql,"select a.id,a.name,a.state from user a inner join friend b on b.friendid=a.id where b.userid = %d",userid);

vector<User> vec;
MySQL mysql;
//建立连接
if(mysql.connect())
{
//获取结果
MYSQL_RES *res=mysql.query(sql);
if(res!=nullptr)
{
//把所有的离线消息放入vec中返回
MYSQL_ROW row;
while((row=mysql_fetch_row(res))!=nullptr)
{
User user;
user.setId(atoi(row[0]));
user.setName(row[1]);
user.setState(row[2]);
vec.push_back(user);
}
//释放资源
mysql_free_result(res);
return vec;
}
}
//没查询到结果
return vec;
}

sql语句说明

1
select a.id,a.name,a.state from user a inner join friend b on b.friendid=a.id where b.userid = %d

User和Friend两表联合查询,

2.添加数据库操作对象

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
//聊天服务器业务类 单例模式来实现
class ChatService
{
public:
//获取单例对象的接口函数
static ChatService* instance();

//处理登录业务
void login(const TcpConnectionPtr& conn,json &js,Timestamp time);

//处理注册业务
void reg(const TcpConnectionPtr& conn,json &js,Timestamp time);

//一对一聊天业务
void oneChat(const TcpConnectionPtr& conn,json &js,Timestamp time);

//添加好友业务
void addFriend(const TcpConnectionPtr& conn,json &js,Timestamp time);

//获取消息对应的处理器
MsgHandler getHandler(int msgid);

//处理客户端异常退出
void clientCloseExecption(const TcpConnectionPtr &conn);
//服务器异常后业务重置方法
void reset();

private:
ChatService();

//存储消息id和其对应的业务处理方法
unordered_map<int,MsgHandler> _msgHandlerMap;



//定义互斥锁,保证_userConnMap的线程安全
mutex _connMutex;

//存储在线用户的通信连接
unordered_map<int,TcpConnectionPtr> _userConnMap;

//数据操作类对象
UserModel _userModel;
OfflineMsgModel _offlineMsgModel;
FriendModel _friendModel;
};

3.注册绑定相应的处理器handler

1
2
3
4
5
6
7
8
//注册消息以及对应的Handler回调操作
ChatService::ChatService()
{
_msgHandlerMap.insert({LOGIN_MSG,std::bind(&ChatService::login,this,_1,_2,_3)});
_msgHandlerMap.insert({REG_MSG,std::bind(&ChatService::reg,this,_1,_2,_3)});
_msgHandlerMap.insert({ONE_CHAT_MSG,std::bind(&ChatService::oneChat,this,_1,_2,_3)});
_msgHandlerMap.insert({ADD_FRIEND_MSG,std::bind(&ChatService::addFriend,this,_1,_2,_3)});
}

4.返回好友列表

新增部分是45-58行,处理用户登录后进行返回好友列表的操作的

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
//处理登录业务
void ChatService::login(const TcpConnectionPtr& conn,json &js,Timestamp time)
{
int id=js["id"].get<int>();
string pwd=js["password"];
User user=_userModel.query(id);
//id==-1 表示没找着
if(user.getId()==id && user.getPwd()==pwd)
{
if(user.getState()=="online")
{
//该用户已经登录,不允许重复登录
json response;
response["msgid"]=LOGIN_MSG_ACK;
response["errno"]=2;
response["errmsg"]="该用户已经登录,请重新输入新账号";
conn->send(response.dump());
}
else
{
//mysql操作线程安全由mysql server保证
//json都是局部变量,线程的栈是自己的,所以不需要线程安全
//可是都放在锁的作用范围锁的粒度太大了,所以加个大括号,锁出了作用域自动释放资源了
{ //登录成功,记录用户连接信息
lock_guard<mutex> lock(_connMutex);
_userConnMap.insert({id,conn});
}
//登录成功,更新用户状态信息
user.setState("online");
_userModel.updateState(user);

json response;
response["msgid"]=LOGIN_MSG_ACK;
response["errno"]=0;
response["id"]=user.getId();
response["name"]=user.getName();
//查询该用户是否有离线消息 有的话也要封装到response中
vector<string> vec=_offlineMsgModel.query(id);
if(!vec.empty())
{
response["offlinemsg"]=vec;
//读取用户的离线消息后,把该用户的离线消息删除掉
_offlineMsgModel.remove(id);
}
//查询该用户的好友信息并返回
vector<User> userVec=_friendModel.query(id);
if(!userVec.empty())
{
vector<string> vec2;
for(User &user:userVec)
{
json js;
js["id"]=user.getId();
js["name"]=user.getName();
js["state"]=user.getState();
vec2.push_back(js.dump());
}
response["friends"]=vec2;
}
conn->send(response.dump());
}
}
else
{
//登录失败 该用户不存在或者用户存在但密码错误
json response;
response["msgid"]=LOGIN_MSG_ACK;
response["errno"]=1;
response["errmsg"]="用户名或者密码错误";
conn->send(response.dump());
}
}

//处理注册业务 name password
void ChatService::reg(const TcpConnectionPtr& conn,json &js,Timestamp time)
{
string name=js["name"];
string pwd=js["password"];

User user;
user.setName(name);
user.setPwd(pwd);
bool state=_userModel.insert(user);
if(state)
{
//注册成功 成功返回0,失败返回1
json response;
response["msgid"]=REG_MSG_ACK;
response["errno"]=0;
response["id"]=user.getId();
conn->send(response.dump());
}
else
{
//注册失败
json response;
response["msgid"]=REG_MSG_ACK;
response["errno"]=1;
conn->send(response.dump());
}
}
1
2
3
4
5
//用户登录
{"msgid":1,"id":1,"password":"123456"}

//添加好友
{"msgid":6,"id":1,"friendid":2}