MySql数据库封装
注意:业务层代码千万不要直接写数据库,相当于把网络模块代码和业务模块代码通过事件回调完全拆分开,现在来到业务层我们也是相当希望把数据层和业务层代码逻辑区分开,不要让俩者代码掺杂在一起。将来数据存储模块不想在MySql上存储了,全部存放在Redis上那么业务模块代码需要大量改动,所有我们一般ORM框架 object realtion map 对象关系映射,帮我们解决了业务层操作的都是对象,我们看不到任何sql语句。数据层才是有数据库操作。
include/server/db/db.h
include_directories(${PROJECT_SOURCE_DIR}/include/server/db)
CMakeLists.txt里头文件搜索目录记得添加一条
可执行文件依赖的库文件也需要更新下
target_link_libraries(ChatServer muduo_net muduo_base mysqlclient pthread)
src/server/CMakeLists.txt添加搜索db下源文件并
aux_source_directory(./db DB_LIST)
更新#指定生成可执行文件
add_executable(ChatServer ${SRC_LIST} ${DB_LIST})
#ifndef DB_H
#define DB_H
#include <mysql/mysql.h>
#include <string>
using namespace std;
// 数据库操作类
class MySQL
{
public:
// 初始化数据库连接
MySQL();
// 释放数据库连接资源
~MySQL();
// 连接数据库
bool connect();
// 更新操作
bool update(string sql);
// 查询操作
MYSQL_RES *query(string sql);
// 获取连接
MYSQL* getConnection();
private:
MYSQL *_conn;
};
#endif
src/server/db/db.cpp
#include "db.h"
#include <muduo/base/Logging.h>
// 数据库配置信息
static string server = "ip";
static string user = "root";
static string password = "密码";
static string dbname = "chat";
// 初始化数据库连接
MySQL::MySQL()
{
_conn = mysql_init(nullptr);
}
// 释放数据库连接资源
MySQL::~MySQL()
{
if (_conn != nullptr)
mysql_close(_conn);
}
// 连接数据库
bool MySQL::connect()
{
MYSQL *p = mysql_real_connect(_conn, server.c_str(), user.c_str(),
password.c_str(), dbname.c_str(), 3306, nullptr, 0);
if (p != nullptr)
{
// C和C++代码默认的编码字符是ASCII,如果不设置,从MySQL上拉下来的中文显示?
mysql_query(_conn, "set names gbk");
LOG_INFO << "connect mysql success!";
}
else
{
LOG_INFO << "connect mysql fail!";
}
return p;
}
// 更新操作
bool MySQL::update(string sql)
{
if (mysql_query(_conn, sql.c_str()))
{
LOG_INFO << __FILE__ << ":" << __LINE__ << ":"
<< sql << "更新失败!";
return false;
}
return true;
}
// 查询操作
MYSQL_RES *MySQL::query(string sql)
{
if (mysql_query(_conn, sql.c_str()))
{
LOG_INFO << __FILE__ << ":" << __LINE__ << ":"
<< sql << "查询失败!";
return nullptr;
}
return mysql_use_result(_conn);
}
// 获取连接
MYSQL* MySQL::getConnection()
{
return _conn;
}
include/server/user.hpp
在数据层操作数据库语句,需要定义相关的类和数据库里的表一一对应,这样我们才能把数据库里边读出来的字段合成一个对象提交给业务方去使用。
#ifndef USER_H
#define USER_H
#include <string>
using namespace std;
// User表的ORM类
class User
{
public:
User(int id = -1, string name = "", string pwd = "", string state = "offline")
{
this->id = id;
this->name = name;
this->password = pwd;
this->state = state;
}
void setId(int id) { this->id = id; }
void setName(string name) { this->name = name; }
void setPwd(string pwd) { this->password = pwd; }
void setState(string state) { this->state = state; }
int getId() { return this->id; }
string getName() { return this->name; }
string getPwd() { return this->password; }
string getState() { return this->state; }
protected:
int id;
string name;
string password;
string state;
};
#endif
include/server/usermodel.hpp
#ifndef USERMODEL_H
#define USERMODEL_H
#include "user.hpp"
// User表的数据操作类
class UserModel {
public:
// User表的增加方法
bool insert(User &user);
//根据用户号码查询用户信息
User query(int id);
//更新用户的状态信息
bool updateState(User user);
};
#endif
id是主键自增键,mysql自动生成。
src/server/usermodel.cpp
#include "usermodel.hpp"
#include "db.h"
#include <iostream>
using namespace std;
// User表的增加方法
bool UserModel::insert(User &user)
{
// 1.组装sql语句
char sql[1024] = {0};
sprintf(sql, "insert into user(name, password, state) values('%s', '%s', '%s')",
user.getName().c_str(), user.getPwd().c_str(), user.getState().c_str());
MySQL mysql;
if (mysql.connect())
{
if (mysql.update(sql))
{
// 获取插入成功的用户数据生成的主键id
user.setId(mysql_insert_id(mysql.getConnection()));
return true;
}
}
return false;
}
//根据用户号码查询用户信息
User UserModel::query(int id)
{
// 1.组装sql语句
char sql[1024] = {0};
sprintf(sql, "select * from user where id = %d", id);
MySQL mysql;
if (mysql.connect())
{
MYSQL_RES *res = mysql.query(sql);
if (res != nullptr)
{
MYSQL_ROW row = mysql_fetch_row(res);
if (row != nullptr)
{
User user;
user.setId(atoi(row[0]));
user.setName(row[1]);
user.setPwd(row[2]);
user.setState(row[3]);
mysql_free_result(res);
return user;
}
}
}
return User();
}
// 更新用户的状态信息
bool UserModel::updateState(User user)
{
// 1.组装sql语句
char sql[1024] = {0};
sprintf(sql, " update user set state = '%s' where id =%d",user.getState().c_str(),user.getId());
MySQL mysql;
if (mysql.connect())
{
if(mysql.update(sql))
{
return true;
}
}
return false;
}
这里不管具体业务,业务是业务层该管的,这里只负责数据库的增删改查就好。
UserModel数据类把数据库相关sql,数据库相应API方法调用都写再这里,对于业务层到时候直接访问Model提供的方法,方法里全部都是对象,跟数据库相关的sql 方法根本看不到。
插入先组装sql语句,根据用户传进来的sql语句,然后这里边链接数据库,链接数据库以后发送这个updeta更新数据库的语句,要插入一条数据,把新插入的用户生成的主键ID拿出来 return true否则的话return false 表示注册失败了 。
查询先组装sql语句,query有返回值的,类型是MYSQL_RES *,接着从res里读数据,不为空,就是查成功了,mysql_fetch_row,用主键查把资源的一行全拿出来。row里边有数据的话,创建一个user对象,MYSQL-ROW对象可以直接用[ ]。这里边row的0 1 2 3 相当于user表的四个字段。这里边res是需要释放资源的,返回指针内部肯定通过动态内存开辟申请资源了。如果没找到的话,直接返回User 它的默认id是-1,name pwd都是空。我们可以通过返回值看如果是-1的话,就出错了
include/server/chatservice.hpp
添加usermodel.hpp头文件 在 添加数据操作类对象、
#include "usermodel.hpp" //数据操作类对象 UserModel _userModel;
服务这里只是依赖model类 不做数据库相关操作,数据库相关操纵操作都封装在model里,
而且model给业务层提供的都是对象 而不是数据库的字段。
src/server/chatservice.cpp
注册消息过来以后,网络模块把这个消息进行反序列化生成json的数据对象,上报到注册业务里边了,注册我们在这里边比较简单 填一个 name password 就可以了,因为注册么 不需要添加id,id是注册成功给返回的。
先获取js的name 和password,这样我们就可以创建一个user对象了,再用userModel里的insert方法进行一个新用户的插入。注册成功或失败返回相应的json字符串给对端的客户端。在public.hpp里添加枚举类型 REG_MSG_ACK 注册消息的响应。最后组装好json,通过网络给客户端发送回去
// 处理登录业务 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;
responce["errmsg"]="该账号已经登录,请重新输入新账号"
conn->send(response.dump());
}
else
{
// 登陆成功,更新用户状态信息 state offline-》online
user.setState("online");
_userModel.updateState(user);
json response;
response["msgid"] = LOGIN_MSG_ACK;
response["errno"] = 0;
response["id"] = user.getId();
response["name"] = user.getName();
conn->send(response.dump());
}
}
else
{
// 该用户不存在登录失败
json response;
response["msgid"] = LOGIN_MSG_ACK;
response["errno"] = 1;
responce["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)
{
// 注册成功
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());
}
}
登录业务 需要id号 pwd秘密就可以,id对应的密码是否正确,跟注册逻辑一样。 user用query传入id值 用主键查返回这个id主键对应的数据到user,到了以后我们将俩个pwd比较。登录成功以后需要更新用户状态信息
include/public.hpp
#ifndef PUBLIC_H
#define PUBLIC_H
/*
server和client的公共文件
*/
enum EnMsgType
{
LOGIN_MSG = 1, // 登录消息
LOGIN_MSG_ACK,//登陆响应消息
REG_MSG,// 注册消息
REG_MSG_ACK//注册相应消息
};
#endif
测试:
telnet ip 端口 链接进入
发送一个注册消息{"msgid":2,"name":"zhang san","password":"123456"}
errno:0 id是13 msgid 3是注册相应消息,
检查数据库 这个也是没有问题的,注册的业务就验证成功。这相当于我们把整个业务全部打通了,客户端发过来注册业务, 先从最开始的网络,再通过事件分发到业务层的handler处理注册,然后再访问底层的model。
登录业务
{"msgid":1,"id":15,"password":"123456"} 登录失败
登录出错因为我们是13号id
{"msgid":1,"id":13,"password":"123456"} 登录成功
登录以后数据库中也是online 状态