当前位置: 首页 > news >正文

【在线五子棋对战】七、数据管理模块实现

文章目录

  • 前言
  • Ⅰ. 数据库表的设计
  • Ⅱ. 数据管理模块的封装和实现
      • 构造函数
      • 析构函数
      • sign_up注册函数
      • login登录函数
      • select_by_name获取信息函数
      • select_by_id获取信息函数
      • win胜利处理函数
      • lose失败处理函数
  • 测试代码
  • 完整代码

在这里插入图片描述

前言

数据管理模块主要负责对于数据库中数据进行统一的增删改查管理,其他模块要对数据操作都必须通过数据管理模块完成。主要分为下面的两大块进行设计:

  • 数据库表的设计
  • 数据管理模块的封装和实现

Ⅰ. 数据库表的设计

创建一个 user 表, 用来表示用户信息及积分信息:

  • 用户信息:用来实现登录、注册、游戏对战数据管理等功能
  • 积分信息:用来实现匹配功能

也就是说我们的表里面要有下面这些字段:

  • id:作为主键,用于表示用户的唯一性
  • username:用户名称
  • password:密码
  • score:用户的积分
  • total_count:用户总的比赛场次
  • win_count:用户总的胜利比赛场次

​ 所以我们创建一个 .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 -ADatabase 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 接⼝管理用户数据。主要提供以下几种方法:

  • sign_up():新增用户,实现注册功能
  • login():登录验证,并获取完整的用户信息
  • select_by_name():根据用户名查找用户信息
  • select_by_id():根据 id 查找用户信息
  • win():胜利时天梯分数增加,战斗场次增加,胜利场次增加
  • lose():失败时天梯分数减少,战斗场次增加,其它不变

​ 除此之外,因为我们要操作这个数据库表,所以我们**得有 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);
}

sign_up注册函数

  1. 首先肯定是要判断用户是否提供了用户名和密码。如果连用户名和密码都没有的话,那么直接返回 false 即可
  2. 接着就是将我们宏定义的 sql 语句,通过 sprintf 函数格式化放到字符数组 query
  3. 最后就是执行 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;
}

login登录函数

​ 登录函数相对前面来说就比较复杂了,因为我们要执行的是查询语句,查询语句又涉及到了保存结果集到本地,获取结果集等等的操作,那么相对来说会繁琐一些,但是步骤还是比较清晰的!下面是几大步骤:

  1. 首先判断是否提供了用户名和密码。如果连用户名和密码都没有的话,那么直接返回 false 即可
  2. 将语句格式化后放到 query 数组中
  3. 执行 sql 语句,并且保存结果集到本地
    • 为什么将这两个操作放到一步呢,因为我们要对这两个操作进行加锁,为什么要加锁呢❓❓❓
    • 因为查询完之后我们下一步就是将其结果集保存到本地,有可能此时还有其它的线程在执行 sql 语句,此时要是还没保存就被修改或者删除了,就造成线程安全问题了,所以必须加锁!
  4. 获取结果集条数,得到结果集
    • 这里行数肯定是只有一行,因为在表中我们规定了用户名是唯一的!
  5. 将数据库中的用户信息填写到 user 对象中去,作为输出型参数
    • 这里所需注意的就是类型问题,具体看代码
  6. 释放结果集
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::UInt64user["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;
}

select_by_name获取信息函数

​ 因为涉及到的依然是查询语句,其实大体的过程和上面的登录函数是类似的,不同的就是 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_id获取信息函数

​ 上面的 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;
}

win胜利处理函数

​ 这函数比较简单了,因为用到的 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;
}

lose失败处理函数

​ 上面的 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::UInt64user["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,胜利场次增加1bool 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

在这里插入图片描述

相关文章:

  • 依赖已导入,已下载,无法使用问题
  • 【MySQL基础】表的功能实现:增删查改详细讲解
  • 基于大模型的急性梗阻性化脓性胆管炎风险预测与治疗方案研究报告
  • 走进Coinate|迪拜第二大交易平台如何构建极速金融引擎
  • 直线拟合 - 最小二乘法与 RANSAC 算法
  • LeetCode 算 法 实 战 - - - 有 效 的 括 号、用 队 列 实 现 栈、用 栈 实 现 队 列 和 设 计 循 环 队 列
  • 佰力博科技与您探讨铁电分析仪适用场景
  • 物联网传输网关、RTU、DTU及SCADA系统的技术难点与未来开发方向
  • 【数据库】大模型时代的数据库新范式:从平替到智能演进
  • node.js使用websockify代理VNC代理使用NoVNC进行远程桌面实现方案
  • uniapp 对接deepseek
  • element ui el-table嵌套el-table,实现checkbox联动效果
  • 如何在 MX Linux 上安装 AnyDesk
  • 数据库新选择?KingbaseES在线体验详解
  • 【产线烧录太慢】爱普特APT WDO2烧录器!高速MCU+Flash全协议 离线烧录
  • AI 双轮驱动:工具革新与编程进化如何重塑技术生态
  • 【开源工具】Windows屏幕控制大师:息屏+亮度调节+快捷键一体化解决方案
  • 金仓数据库在线体验平台:开启国产数据库云端探索之旅
  • Excel数据导出小记
  • Uniapp跨端兼容性全方位解决方案
  • 四川省城乡住房与建设厅网站首页/推推蛙网站诊断
  • 建个网站公司/seo快速优化文章排名
  • 色流网站怎么做/免费seo刷排名
  • 山西太原网站建设/我国网络营销现状分析
  • 公司网站域名续费/网站优化seo方案
  • 网站 用什么语言/360网站推广客服电话