数据管理模块主要负责对于数据库中数据进行统一的增删改查管理,其他模块要对数据操作都必须通过数据管理模块完成。主要分为下面的两大块进行设计:
创建一个 user 表, 用来表示用户信息及积分信息:
也就是说我们的表里面要有下面这些字段:
所以我们创建一个 .sql
文件,创建一个数据库,叫做 gobang
,然后创建一张表,叫做 user
,然后将这些字段放到 user
表中!
create database if not exists gobang;
use gobang;
create table if not exists user(
id int primary key auto_increment,
username varchar(32) unique key not null,
password varchar(128) not null,
score int,
total_count int,
win_count int
);
如果在测试代码的时候需要删掉该数据库中表,那么可以在上述代码最上方加入此行 mysql 指令:
drop database if exists gobang;
然后重新将 db.sql
导入到客户端中即可!
其中 username
我们设为具有唯一性的!
接下来我们保存这个文件内容,然后将该文件内容重定向到 mysql -uroot
指令中去,也就是如下操作:
[root@VM-8-7-centos source]# mysql -uroot < db.sql
[root@VM-8-7-centos source]# mysql -uroot
……
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| gobang |
| mysql |
| performance_schema |
| sys |
+--------------------+
5 rows in set (0.00 sec)
mysql> use gobang;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> show tables;
+------------------+
| Tables_in_gobang |
+------------------+
| stu |
| user |
+------------------+
2 rows in set (0.00 sec)
mysql> desc user;
+-------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| username | varchar(32) | NO | UNI | NULL | |
| password | varchar(128)| NO | | NULL | |
| score | int(11) | YES | | NULL | |
| total_count | int(11) | YES | | NULL | |
| win_count | int(11) | YES | | NULL | |
+-------------+-------------+------+-----+---------+----------------+
6 rows in set (0.00 sec)
数据库中有可能存在很多张表,每张表中管理的数据又有不同,要进行的数据操作也各不相同,因此我们可以为每⼀张表中的数据操作都设计一个类,通过类实例化的对象来管理这张数据库表中的数据,这样的话当我们要访问哪张表的时候,使用哪个类实例化的对象即可。
这里创建一个 user_table
类, 该类的作用是负责通过 MySQL
接⼝管理用户数据。主要提供以下几种方法:
除此之外,因为我们要操作这个数据库表,所以我们**得有 MYSQL* 操作句柄,另外因为有可能两个线程同时在访问或者修改数据库表,那么就会导致数据出现错误,所以必须加锁**!
所以大体的类框架如下:
#ifndef __MY_DB_H__
#define __MY_DB_H__
#include "util.hpp"
#include <mutex>
class user_table
{
private:
MYSQL* _mysql; // 操作句柄
std::mutex _mtx; // 互斥锁保护数据库的操作
public:
user_table()
{}
~user_table()
{}
// 注册函数
bool sign_up(Json::Value& user)
{}
// 登录函数
bool login(Json::Value& user)
{}
// 通过用户名获取用户信息
bool select_by_name(const std::string& name, Json::Value& user)
{}
// 通过id获取用户信息
bool select_by_id(uint64_t id, Json::Value& user)
{}
// 胜利处理函数
bool win(uint64_t id)
{}
// 失败处理函数
bool lose(uint64_t id)
{}
};
#endif
构造函数就是对操作句柄的初始化嘛!
user_table(const std::string& host,
const std::string& user,
const std::string& passwd,
const std::string& dbname,
uint16_t port = 3306)
{
_mysql = mysql_util::mysql_create(host, user, passwd, dbname, port);
assert(_mysql != NULL);
}
析构函数就是释放句柄!
~user_table()
{
mysql_util::mysql_destroy(_mysql);
}
false
即可sql
语句,通过 sprintf
函数格式化放到字符数组 query
中sql
语句啦,执行完判断一下是否成功即可!bool sign_up(Json::Value& user)
{
#define SIGN_UP "insert user values(null, '%s', password('%s'), 1000, 0, 0);"
// 1. 首先判断是否提供了用户名和密码 -- 并且用户名密码不能为空
if(user["username"].isNull() || user["password"].isNull() || user["username"].asString().empty() || user["password"].asString().empty())
{
DLOG("user didn't enter an username or password!");
return false;
}
// 2. 将语句格式化后放到query数组中
char query[4096] = {0};
sprintf(query, SIGN_UP, user["username"].asCString(), user["password"].asCString());
// 3. 执行语句
bool ret = mysql_util::mysql_exec(_mysql, query);
if(ret == false)
{
DLOG("sign up user failed!");
return false;
}
return true;
}
登录函数相对前面来说就比较复杂了,因为我们要执行的是查询语句,查询语句又涉及到了保存结果集到本地,获取结果集等等的操作,那么相对来说会繁琐一些,但是步骤还是比较清晰的!下面是几大步骤:
false
即可query
数组中sql
语句,并且保存结果集到本地 sql
语句,此时要是还没保存就被修改或者删除了,就造成线程安全问题了,所以必须加锁!user
对象中去,作为输出型参数 bool login(Json::Value& user)
{
#define LOGIN_SQL "select id, score, total_count, win_count from user where username='%s' and password=password('%s');"
// 1. 首先判断是否提供了用户名和密码 -- 并且用户名密码不能为空
if(user["username"].isNull() || user["password"].isNull() || user["username"].asString().empty() || user["password"].asString().empty())
{
DLOG("user didn't enter an username or password!");
return false;
}
// 2. 将语句格式化后放到query数组中
char query[4096] = {0};
sprintf(query, LOGIN_SQL, user["username"].asCString(), user["password"].asCString());
// 3. 执行sql语句,因为查询之后需要保存到本地,为了保证保存过程的线程安全,这段代码需要加锁
// 这里不直接使用加锁,而是通过守卫锁来管理锁,更加安全
// 并且因为守卫锁是当前作用域有效,所以只对要加锁的区域放在一个空代码块中当作一个作用域
MYSQL_RES* res = nullptr;
{
std::unique_lock<std::mutex> lock(_mtx); // 相当于加锁了
// 执行sql语句
bool ret = mysql_util::mysql_exec(_mysql, query);
if(ret == false)
{
if(ret == false)
{
DLOG("user login failed!");
return false;
}
}
// 保存查询结果到本地
res = mysql_store_result(_mysql);
if(res == nullptr)
{
DLOG("mysql_store_result!");
return false;
}
}
// 4. 获取结果集条数,得到结果集 -- 这里行数肯定是只有一行,因为在表中我们规定了用户名是唯一的
int row_num = mysql_num_rows(res);
if(row_num == 0)
{
DLOG("the user information is not found!");
return false;
}
else if (row_num != 1)
{
DLOG("the user information queried is not unique!");
return false;
}
MYSQL_ROW row = mysql_fetch_row(res);
// 5. 将数据库中的用户信息填写到user对象中去,作为输出型参数
// 这里有细节,因为结果集中的数据都是字符串,所以转化为整型,最好是长整型
// 但是转化为长整型会报错,所以再强转为json的数据类型,如下面的Json::UInt64
user["id"] = (Json::UInt64)std::stol(row[0]);
user["score"] = (Json::UInt64)std::stol(row[1]);
user["total_count"] = std::stoi(row[2]);
user["win_count"] = std::stoi(row[3]);
// 6. 别忘了释放结果集
mysql_free_result(res);
return true;
}
因为涉及到的依然是查询语句,其实大体的过程和上面的登录函数是类似的,不同的就是 sql
语句改变了、一些日志内容改变、用户信息填写时候多填一个姓名的字段,仅此而已,这里就不多赘述了,具体参考上面登录函数,结合下面的代码注释:
bool select_by_name(const std::string& name, Json::Value& user)
{
#define SELECT_BY_NAME "select id, score, total_count, win_count from user where username='%s';"
// 1. 将语句格式化后放到query数组中
char query[4096] = {0};
sprintf(query, SELECT_BY_NAME, name.c_str());
// 2. 执行语句,并且保存结果集到本地 -- 因为是查询语句,所以还是依然要加锁保证线程安全
MYSQL_RES* res = nullptr;
{
std::unique_lock<std::mutex> lock(_mtx); // 相当于加锁
// 执行语句
bool ret = mysql_util::mysql_exec(_mysql, query);
if(ret == false)
{
DLOG("get user by name failed!!");
return false;
}
// 保存结果集
res = mysql_store_result(_mysql);
if(res == nullptr)
{
DLOG("mysql_store_result failed");
return false;
}
}
// 3. 获取结果集条数,得到结果集
int row_num = mysql_num_rows(res);
if(row_num == 0)
{
DLOG("the user information is not found!");
return false;
}
else if(row_num != 1)
{
DLOG("the user information queried is not unique!!");
return false;
}
MYSQL_ROW row = mysql_fetch_row(res);
// 4. 将数据库中的用户信息填写到user对象中去,作为输出型参数
user["id"] = (Json::UInt64)std::stol(row[0]);
user["username"] = name;
user["score"] = (Json::UInt64)std::stol(row[1]);
user["total_count"] = std::stoi(row[2]);
user["win_count"] = std::stoi(row[3]);
// 5. 释放结果集
mysql_free_result(res);
return true;
}
上面的 select_by_name
函数修改细节就能变成这个函数!
bool select_by_id(uint64_t id, Json::Value& user)
{
#define SELECT_BY_ID "select username, score, total_count, win_count from user where id='%d';"
// 1. 将语句格式化后放到query数组中
char query[4096] = {0};
sprintf(query, SELECT_BY_ID, id);
// 2. 执行语句,并且保存结果集到本地 -- 因为是查询语句,所以还是依然要加锁保证线程安全
MYSQL_RES* res = nullptr;
{
std::unique_lock<std::mutex> lock(_mtx); // 相当于加锁
// 执行语句
bool ret = mysql_util::mysql_exec(_mysql, query);
if(ret == false)
{
DLOG("get user by id failed!!");
return false;
}
// 保存结果集
res = mysql_store_result(_mysql);
if(res == nullptr)
{
DLOG("mysql_store_result failed");
return false;
}
}
// 3. 获取结果集条数,得到结果集
int row_num = mysql_num_rows(res);
if(row_num == 0)
{
DLOG("the user information is not found!");
return false;
}
else if(row_num != 1)
{
DLOG("the user information queried is not unique!!");
return false;
}
MYSQL_ROW row = mysql_fetch_row(res);
// 4. 将数据库中的用户信息填写到user对象中去,作为输出型参数
user["id"] = (Json::UInt64)id;
user["username"] = row[0];
user["score"] = (Json::UInt64)std::stol(row[1]);
user["total_count"] = std::stoi(row[2]);
user["win_count"] = std::stoi(row[3]);
// 5. 释放结果集
mysql_free_result(res);
return true;
}
这函数比较简单了,因为用到的 sql
语句是修改语句,不需要做太多工作,具体看代码:
// 胜利处理函数 -- 胜利时天梯分数增加30分,战斗场次增加1,胜利场次增加1
bool win(uint64_t id)
{
#define WIN "update user set score=score+30, total_count=total_count+1, win_count=win_count+1 where id='%d';"
// 1. 将语句格式化后放到query数组中
char query[4096] = {0};
sprintf(query, WIN, id);
// 2. 执行语句
bool ret = mysql_util::mysql_exec(_mysql, query);
if(ret == false)
{
DLOG("update win user info failed!!\n");
return false;
}
return true;
}
上面的 win
函数修改一下就变成了这里的函数!
// 失败处理函数 -- 失败时天梯分数减少30,战斗场次增加1,其他不变
bool lose(uint64_t id)
{
#define LOSE "update user set score=score-30, total_count=total_count+1 where id='%d';"
// 1. 将语句格式化后放到query数组中
char query[4096] = {0};
sprintf(query, LOSE, id);
// 2. 执行语句
bool ret = mysql_util::mysql_exec(_mysql, query);
if(ret == false)
{
DLOG("update lose user info failed!!\n");
return false;
}
return true;
}
void db_test()
{
user_table ut(HOST, USER, PASSWD, DBNAME, PORT);
Json::Value user;
// user["username"] = "xiaoming";
// user["password"] = "123456";
bool ret = ut.lose(1);
if(ret == false)
{
DLOG("login failed!");
return;
}
std::string body;
json_util::serialize(user, body);
std::cout << body << std::endl;
}
#ifndef __MY_DB_H__
#define __MY_DB_H__
#include "util.hpp"
#include <mutex>
#include <cassert>
class user_table
{
private:
MYSQL* _mysql; // 操作句柄
std::mutex _mtx; // 互斥锁保护数据库的操作
public:
user_table(const std::string& host,
const std::string& user,
const std::string& passwd,
const std::string& dbname,
uint16_t port = 3306)
{
_mysql = mysql_util::mysql_create(host, user, passwd, dbname, port);
assert(_mysql != NULL);
}
~user_table()
{
mysql_util::mysql_destroy(_mysql);
_mysql = NULL;
}
// 注册函数
bool sign_up(Json::Value& user)
{
#define SIGN_UP "insert user values(null, '%s', password('%s'), 1000, 0, 0);"
// 1. 首先判断是否提供了用户名和密码 -- 并且用户名密码不能为空
if(user["username"].isNull() || user["password"].isNull() || user["username"].asString().empty() || user["password"].asString().empty())
{
DLOG("user didn't enter an username or password!");
return false;
}
// 2. 将语句格式化后放到query数组中
char query[4096] = {0};
sprintf(query, SIGN_UP, user["username"].asCString(), user["password"].asCString());
// 3. 执行语句
bool ret = mysql_util::mysql_exec(_mysql, query);
if(ret == false)
{
DLOG("sign up user failed!");
return false;
}
return true;
}
// 登录函数
bool login(Json::Value& user)
{
#define LOGIN_SQL "select id, score, total_count, win_count from user where username='%s' and password=password('%s');"
// 1. 首先判断是否提供了用户名和密码 -- 并且用户名密码不能为空
if(user["username"].isNull() || user["password"].isNull() || user["username"].asString().empty() || user["password"].asString().empty())
{
DLOG("user didn't enter an username or password!");
return false;
}
// 2. 将语句格式化后放到query数组中
char query[4096] = {0};
sprintf(query, LOGIN_SQL, user["username"].asCString(), user["password"].asCString());
// 3. 执行sql语句,因为查询之后需要保存到本地,为了保证保存过程的线程安全,这段代码需要加锁
// 这里不直接使用加锁,而是通过守卫锁来管理锁,更加安全
// 并且因为守卫锁是当前作用域有效,所以只对要加锁的区域放在一个空代码块中当作一个作用域
MYSQL_RES* res = nullptr;
{
std::unique_lock<std::mutex> lock(_mtx); // 相当于加锁了
// 执行sql语句
bool ret = mysql_util::mysql_exec(_mysql, query);
if(ret == false)
{
if(ret == false)
{
DLOG("user login failed!");
return false;
}
}
// 保存查询结果到本地
res = mysql_store_result(_mysql);
if(res == nullptr)
{
DLOG("mysql_store_result!");
return false;
}
}
// 4. 获取结果集条数,得到结果集 -- 这里行数肯定是只有一行,因为在表中我们规定了用户名是唯一的
int row_num = mysql_num_rows(res);
if(row_num == 0)
{
DLOG("the user information is not found!");
return false;
}
else if (row_num != 1)
{
DLOG("the user information queried is not unique!");
return false;
}
MYSQL_ROW row = mysql_fetch_row(res);
// 5. 将数据库中的用户信息填写到user对象中去,作为输出型参数
// 这里有细节,因为结果集中的数据都是字符串,所以转化为整型,最好是长整型
// 但是转化为长整型会报错,所以再强转为json的数据类型,如下面的Json::UInt64
user["id"] = (Json::UInt64)std::stol(row[0]);
user["score"] = (Json::UInt64)std::stol(row[1]);
user["total_count"] = std::stoi(row[2]);
user["win_count"] = std::stoi(row[3]);
// 6. 别忘了释放结果集
mysql_free_result(res);
return true;
}
// 通过用户名获取用户信息
bool select_by_name(const std::string& name, Json::Value& user)
{
#define SELECT_BY_NAME "select id, score, total_count, win_count from user where username='%s';"
// 1. 将语句格式化后放到query数组中
char query[4096] = {0};
sprintf(query, SELECT_BY_NAME, name.c_str());
// 2. 执行语句,并且保存结果集到本地 -- 因为是查询语句,所以还是依然要加锁保证线程安全
MYSQL_RES* res = nullptr;
{
std::unique_lock<std::mutex> lock(_mtx); // 相当于加锁
// 执行语句
bool ret = mysql_util::mysql_exec(_mysql, query);
if(ret == false)
{
DLOG("get user by name failed!!");
return false;
}
// 保存结果集
res = mysql_store_result(_mysql);
if(res == nullptr)
{
DLOG("mysql_store_result failed");
return false;
}
}
// 3. 获取结果集条数,得到结果集
int row_num = mysql_num_rows(res);
if(row_num == 0)
{
DLOG("the user information is not found!");
return false;
}
else if(row_num != 1)
{
DLOG("the user information queried is not unique!!");
return false;
}
MYSQL_ROW row = mysql_fetch_row(res);
// 4. 将数据库中的用户信息填写到user对象中去,作为输出型参数
user["id"] = (Json::UInt64)std::stol(row[0]);
user["username"] = name;
user["score"] = (Json::UInt64)std::stol(row[1]);
user["total_count"] = std::stoi(row[2]);
user["win_count"] = std::stoi(row[3]);
// 5. 释放结果集
mysql_free_result(res);
return true;
}
// 通过id获取用户信息
bool select_by_id(uint64_t id, Json::Value& user)
{
#define SELECT_BY_ID "select username, score, total_count, win_count from user where id='%d';"
// 1. 将语句格式化后放到query数组中
char query[4096] = {0};
sprintf(query, SELECT_BY_ID, id);
// 2. 执行语句,并且保存结果集到本地 -- 因为是查询语句,所以还是依然要加锁保证线程安全
MYSQL_RES* res = nullptr;
{
std::unique_lock<std::mutex> lock(_mtx); // 相当于加锁
// 执行语句
bool ret = mysql_util::mysql_exec(_mysql, query);
if(ret == false)
{
DLOG("get user by id failed!!");
return false;
}
// 保存结果集
res = mysql_store_result(_mysql);
if(res == nullptr)
{
DLOG("mysql_store_result failed");
return false;
}
}
// 3. 获取结果集条数,得到结果集
int row_num = mysql_num_rows(res);
if(row_num == 0)
{
DLOG("the user information is not found!");
return false;
}
else if(row_num != 1)
{
DLOG("the user information queried is not unique!!");
return false;
}
MYSQL_ROW row = mysql_fetch_row(res);
// 4. 将数据库中的用户信息填写到user对象中去,作为输出型参数
user["id"] = (Json::UInt64)id;
user["username"] = row[0];
user["score"] = (Json::UInt64)std::stol(row[1]);
user["total_count"] = std::stoi(row[2]);
user["win_count"] = std::stoi(row[3]);
// 5. 释放结果集
mysql_free_result(res);
return true;
}
// 胜利处理函数 -- 胜利时天梯分数增加30分,战斗场次增加1,胜利场次增加1
bool win(uint64_t id)
{
#define WIN "update user set score=score+30, total_count=total_count+1, win_count=win_count+1 where id='%d';"
// 1. 将语句格式化后放到query数组中
char query[4096] = {0};
sprintf(query, WIN, id);
// 2. 执行语句
bool ret = mysql_util::mysql_exec(_mysql, query);
if(ret == false)
{
DLOG("update win user info failed!!\n");
return false;
}
return true;
}
// 失败处理函数 -- 失败时天梯分数减少30,战斗场次增加1,其他不变
bool lose(uint64_t id)
{
#define LOSE "update user set score=score-30, total_count=total_count+1 where id='%d';"
// 1. 将语句格式化后放到query数组中
char query[4096] = {0};
sprintf(query, LOSE, id);
// 2. 执行语句
bool ret = mysql_util::mysql_exec(_mysql, query);
if(ret == false)
{
DLOG("update lose user info failed!!\n");
return false;
}
return true;
}
};
#endif