在第(7)节中,已经实现用户注册模块,本节来实现用户登录模块
项目流程
1、项目环境搭建
C++项目——集群聊天服务器项目(一)项目介绍、环境搭建、Boost库安装、Muduo库安装、Linux与vscode配置_c++集群聊天服务器-CSDN博客
2、Json第三方库介绍
C++项目——集群聊天服务器项目(二)Json第三方库-CSDN博客
3、muduo网络库介绍
C++项目——集群聊天服务器项目(三)muduo网络库-CSDN博客
4、MySQL数据库创建
C++项目——集群聊天服务器项目(四)MySQL数据库-CSDN博客
5、网络模块与业务模块代码编写
C++项目——集群聊天服务器项目(五)网络模块与业务模块-CSDN博客
6、MySQL模块编写
C++项目——集群聊天服务器项目(六)MySQL模块-CSDN博客
7、Model层设计、注册业务实现
C++项目——集群聊天服务器项目(七)Model层设计、注册业务实现-CSDN博客
一、用户登录业务实现步骤
用户登录业务步骤:
(1)从json对象中获取id和password,用于登录
注:json获取到的是字符串,user表中id为int类型,要将json获取的id转为int类型
(2)调用UserModel类对象中的查询函数,输入id,返回User对象,如果用户不存在返回空对象
(3)判断返回用户id与json对象获取id、返回密码与json获取密码是否一致,分为以下两种情况
1)若一致,则证明数据库中存在该用户,又分为用户在线/不在线两种状态
<1>若用户在线,则不允许重复登录,返回登录响应信息与错误提示信息
<2>若用户不在线,则用户登录成功!
这里会在chatservice.hpp中定义一个哈希表存储用户id及在线用户的通信连接,由于随时都可能有新用户连接与连接删除,而STL容器不保证线程安全,因此这里需要定义一个互斥锁,保证用户通信连接哈希表的线程安全。且仅需在插入和删除哈希表保证线程安全即可。
private:
//存储用户的通信连接
unordered_map<int,TcpConnectionPtr> _userConnMap;
//定义互斥锁,保证_userConnMap的线程安全
mutex _connMutex;
如果用户登录成功,向哈希表中插入用户登录id及通信连接,使用互斥锁保证线程安全,即
{
// 登陆成功,记录用户连接
lock_guard<mutex> lock(_connMutex);
_userConnMap.insert(make_pair(id, conn));
}
插入在线连接后,更新用户为在线状态,定义json对象返回登录响应信息及日志信息,并返回登录用户的姓名和id
注:其实此时应该判断用户是否有离线消息,但本节不进行实现,后续对其实现。
2)若不一致,则证明数据库中没有该用户,即用户名与密码错误
定义json对象,返回登录响应信息与错误提示信息
二、用户登录业务具体实现
在include/server/chatservice.hpp中,定义互斥锁和通信连接哈希表
#ifndef CHATSERVICE_H
#define CHATSERVICE_H
#include <muduo/net/TcpConnection.h>
#include <unordered_map>//一个消息ID映射一个事件处理
#include <functional>
#include <mutex>
using namespace std;
using namespace muduo;
using namespace muduo::net;
#include "usermodel.hpp"
#include "json.hpp"
using json = nlohmann::json;
//表示处理消息的事件回调方法类型,事件处理器,派发3个东西
using MsgHandler = std::function<void(const TcpConnectionPtr &conn, json &js, Timestamp)>;
//聊天服务器业务类
class ChatService
{
public:
//获取单例对象的接口函数
static ChatService *instance();
//处理登录业务
void login(const TcpConnectionPtr &conn, json &js, Timestamp time);
//处理注册业务
void reg(const TcpConnectionPtr &conn, json &js, Timestamp time);
//获取消息对应的处理器
MsgHandler getHandler(int msgid);
private:
ChatService();//单例
//存储消息id和其对应的业务处理方法,消息处理器的一个表,写消息id对应的处理操作
unordered_map<int, MsgHandler> _msgHandlerMap;
//存储用户的通信连接
unordered_map<int,TcpConnectionPtr> _userConnMap;
//定义互斥锁,保证_userConnMap的线程安全
mutex _connMutex;
//数据操作类对象
UserModel _userModel;
};
#endif
在public.hpp中定义登录响应消息
#ifndef PUBLIC_H
#define PUBLIC_H
/*
server和client的公共文件
*/
enum EnMsgType
{
LOGIN_MSG = 1, //登录消息
LOGIN_MSG_ACK, //登录响应消息
REG_MSG, //注册消息
REG_MSG_ACK, //注册响应消息
};
#endif
在src/server/chatservice.cpp中进行登录功能实现
// 处理登录业务 id pwd pwd
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);
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()); // 回调 ,返回json字符串
}
else
{
{
// 登陆成功,记录用户连接
lock_guard<mutex> lock(_connMutex);
_userConnMap.insert(make_pair(id, conn));
}
// 登录成功,更新用户状态信息
user.setState("online");
_userModel.updateState(user);
json response;
response["msgid"] = LOGIN_MSG_ACK;
response["errno"] = 0;
response["errmsg"] = "登录成功!";
response["id"] = user.getId();
response["name"] = user.getName();
}
}
else
{
// 用户名或者密码错误
json response;
response["msgid"] = LOGIN_MSG_ACK;
response["errno"] = 1;
response["errmsg"] = "用户名或者密码错误";
conn->send(response.dump()); // 回调 ,返回json字符串
}
}
三、登录业务验证
查表可以获取当前用户信息
我们使用张三的账号来登录,输入
{"msgid":1,"id":13,"password":"123456"}
登陆成功,回显用户id和姓名
张三在线的情况下,我们再次登录,错误日志显示该账号已经登录
接下来我们使用李四的id输入,但密码错误,验证功能是否正常运转
可以看到用户名或者密码错误,登录失败!
登录业务验证成功!
一起期待下一次项目的更新吧~