首页/新闻资讯/正文详情

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

发布时间:2026/9/24 17:15:41 来源:云帆数科 栏目:资讯中心
【在线五子棋对战】数据管理模块实现
目录表结构RegisterLoginSelectByNameSelectByIdSelectOneWInLose数据管理模块用于对登录与注册用户进行管理这里采用以表为单位每个可操作的表为一个类构建该表的接口表结构用于存储用户的表为gomoku数据库下的user表结构如下mysql show create table user\G *************************** 1. row *************************** Table: user Create Table: CREATE TABLE user ( id int unsigned NOT NULL AUTO_INCREMENT COMMENT 编号, username varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_as_cs NOT NULL COMMENT 昵称, password_hash char(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT SHA-256密码哈希值32字节64位十六进制字符, ladder_points int NOT NULL DEFAULT 1000 COMMENT 天梯积分, number_of_sessions int unsigned NOT NULL DEFAULT 0 COMMENT 场数, win_count int unsigned NOT NULL DEFAULT 0 COMMENT 胜利场次, registration_date timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 注册时间, salt char(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT 16字节随机盐的十六进制表示, PRIMARY KEY (id) USING BTREE, UNIQUE KEY uk_username (username) ) ENGINEInnoDB AUTO_INCREMENT9 DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci mysql desc user; ----------------------------------------------------------------------------------- | Field | Type | Null | Key | Default | Extra | ----------------------------------------------------------------------------------- | id | int unsigned | NO | PRI | NULL | auto_increment | | username | varchar(32) | NO | UNI | NULL | | | password_hash | char(64) | NO | | NULL | | | ladder_points | int | NO | | 1000 | | | number_of_sessions | int unsigned | NO | | 0 | | | win_count | int unsigned | NO | | 0 | | | registration_date | timestamp | NO | | CURRENT_TIMESTAMP | DEFAULT_GENERATED | | salt | char(32) | NO | | NULL | | -----------------------------------------------------------------------------------在实用工具模块--密码模块的实现中采用SHA-256 随机盐的方法其中SHA-256结果为32字节(256BIT)盐设定的是16字节// 生成随机盐 bool Password::GenerateSalt(std::string salt) { constexpr size_t SALT_LEN 16; // 编译期常量确认随机盐的长度 16 // ...... }如果用字符数组存储单位长度为1字节SHA-256占32长度盐占16位长度但在密码模块的实现中最后将SHA-256和盐为了传输方便与直观转换成了十六进制字符串2位十六进制长度1字节因此32字节需要64长度password_hash16字节需要32长度salt因此代码要做的工作就是对该表操作的接口封装起来class User_Table{}/* -------------------------------- db.hpp |数据管理模块| MySQL的客户端用于对表进行操作 每个表的操作为一个类 -------------------------------- */ #pragma once #include Util.hpp #include Password.hpp #include mutex #include cassert #include memory #include mutex class User_Table { public: User_Table(const std::string host, const std::string user, const std::string passwd, const std::string db, uint16_t port 3306) { // 初始化句柄 _mysql MySQL_Util::MySQL_Init(host, user, passwd, db, port); assert(_mysql ! nullptr); } ~User_Table() { if (_mysql ! nullptr) { MySQL_Util::MySQL_Destroy(_mysql); _mysql nullptr; } } bool Register(const Json::Value user); // 注册(新增)用户 bool Login(Json::Value user); // 登录验证user返回用户信息 bool SelectByName(const std::string username, Json::Value user); // 通过用户名获取用户信息 bool SelectById(const int id, Json::Value user); // 通过编号获取用户信息 bool Win(const int id); // 胜利时操作 bool Lose(const int id); // 失败时操作 bool SelectOne(const std::string sql, Json::Value user); // 执行只会返回一条记录的Select语句 private: MYSQL *_mysql; // 句柄 std::mutex _mutex; // 互斥锁保证每个接口都是线程安全的 // 虽然mysql的原生接口都是线程安全但封装之后是多个接口一起.... };这里需要采用互斥锁std::mutex确保线程安全保证同时只有一套流程在调用_mysql句柄因为同一个MYSQL*连接句柄被多个业务线程共享。例如现在有两个进程同时调用mysql_query()并获取结果集mysql_store_result()如果顺序是这样的A: query(SQL_A) B: query(SQL_B) A: store_result() B: store_result()就会导致A和B的结果集都是SQL_B的而加锁后就可以确保顺序是A先查询获取结果集B再查询获取结果集A: query A: store_result B: query B: store_resultRegister当有新用户注册时需要用Register接口将字段写入数据库bool User_Table::Register(const Json::Value user) // 注册(新增)用户 { Json::Value root; if (SelectByName(user[username].asString(), root)) { LOG(LogLv::ERROR, 用户名[%s]重复请重新输入, user[username].asCString()); return false; } constexpr char INSERT_SQL[] INSERT INTO user(username, password_hash, salt) VALUES (%s, %s,%s); bool success false; // 如果在后面的do while中没有设置成true就是false do { // 生成随机盐 std::string salt; if (Password::GenerateSalt(salt) false) break; // 生成哈希摘要 std::string hash; if (Password::Hash(user[password].asString(), salt, hash) false) break; // 为数据表user新增用户 std::string sql; // 要插入的sql sql.resize(4096); // 预留好空间防止段错误 // 将用户名、密码哈希、盐写入sql [SQL注入问题] int len snprintf(sql.data(), sql.size(), INSERT_SQL, user[username].asCString(), hash.c_str(), salt.c_str()); if (len 0 || static_castsize_t(len) sql.size()) { LOG(LogLv::ERROR, 格式化打印发生错误或截断); break; } { std::lock_guardstd::mutex lock(_mutex); // 防止多个线程同时调用mysql_query() (操作_mysql句柄) // 执行SQL语句 if (MySQL_Util::MySQL_Query(_mysql, sql.c_str()) false) break; } success true; } while (false); return success; }这里的MySQL_Query就需要加锁防止多个线程同时调用查询、同时对_mysql句柄进行操作LoginLogin接口用于对登录的用户名和密码进行验证bool User_Table::Login(Json::Value user) // 登录验证user返回用户信息 { // 输入检查 if (!(user.isMember(username) user.isMember(password))) { LOG(LogLv::ERROR, 提交参数不全([username][password])); return false; } Json::Value baseinfo; // 用于存数据库内的信息 if (SelectByName(user[username].asString(), baseinfo) false) // 查找数据库内的该用户信息 { LOG(LogLv::ERROR, 没有[%s]用户请先注册, user[username].asCString()); return false; } // 验证用户输入的密码哈希与数据库内哈希是否一致 if (Password::Verify(user[password].asString(), baseinfo[salt].asString(), baseinfo[password_hash].asString()) true) { user baseinfo; // 不要将密码和盐传给前端 user.removeMember(password_hash); user.removeMember(salt); return true; } else { LOG(LogLv::WARNING, 用户[%s]密码错误, user[username].asCString()); return false; } }SelectByName通过username返回该用户其他信息bool User_Table::SelectByName(const std::string username, Json::Value user) // 通过用户名获取用户信息 { constexpr char SELECT_SQL[] SELECT * FROM user WHERE username%s; bool success false; std::string sql; // 要执行的sql sql.resize(4096); // 预留好空间防止段错误 int len snprintf(sql.data(), sql.size(), SELECT_SQL, username.c_str()); if (len 0 || static_castsize_t(len) sql.size()) { LOG(LogLv::ERROR, 格式化打印发生错误或截断); return false; } return SelectOne(sql, user); }SelectById通过id返回用户其他信息bool User_Table::SelectById(const int id, Json::Value user) // 通过编号获取用户信息 { constexpr char SELECT_SQL[] SELECT * FROM user WHERE id%d; std::string sql; // 要执行的sql sql.resize(4096); // 预留好空间防止段错误 int len snprintf(sql.data(), sql.size(), SELECT_SQL, id); if (len 0 || static_castsize_t(len) sql.size()) { LOG(LogLv::ERROR, 格式化打印发生错误或截断); return false; } return SelectOne(sql, user); }SelectOne上面这两个接口都依赖于SelectOne该接口用于通过传入的SQL返回一条记录Json格式这里通过智能指针unique_ptr管理MYSQL_RES*对象防止内存泄漏由于该对象的释放必须通过mysql_free_result()而unique_ptr默认调用delete因此需要自定义删除器MYSQL_RES_Deleternamespace // 匿名命名空间仅供db.cpp使用 { struct MYSQL_RES_Deleter // MYSQL_RES删除器 { void operator()(MYSQL_RES *res) const { if (res ! nullptr) mysql_free_result(res); } }; }通过指定unique_ptrMYSQL_RES, MYSQL_RES_Deleter来指定删除器bool User_Table::SelectOne(const std::string sql, Json::Value user) // 通过sql返回一条记录 { std::unique_ptrMYSQL_RES, MYSQL_RES_Deleter res; { std::lock_guardstd::mutex lock(_mutex); // 对_mysql句柄加锁 // 查询记录 if (!MySQL_Util::MySQL_Query(_mysql, sql.c_str())) return false; // SQL执行成功获取查询结果 // 存储结果集 res.reset(mysql_store_result(_mysql)); if (res nullptr) { LOG(LogLv::ERROR, 存储结果集失败错误码%d错误描述%s, mysql_errno(_mysql), mysql_error(_mysql)); return false; } } // 获取行列数 my_ulonglong rows mysql_num_rows(res.get()); // 正常情况用户不存在比如注册时查重或者输错用户名登录 if (rows 0) { LOG(LogLv::DEBUG, 查询结果为0条); return false; } // 异常情况 if (rows 1) { LOG(LogLv::ERROR, 查询结果记录数异常共[%llu]条, rows); return false; } my_ulonglong fields mysql_num_fields(res.get()); // 获取该记录 MYSQL_ROW row mysql_fetch_row(res.get()); MYSQL_FIELD *field mysql_fetch_fields(res.get()); // 获取该记录所有字段名方便后面进行类型赋值 if (row nullptr || field nullptr) { LOG(LogLv::ERROR, 获取查询结果失败); return false; } user.clear(); // 清除user以前的数据 // 通过类型判断将字段传入user for (my_ulonglong i 0; i fields; i) { if(row[i] nullptr) { user[field[i].name] Json::nullValue; continue; } switch (field[i].type) { case MYSQL_TYPE_TINY: case MYSQL_TYPE_SHORT: case MYSQL_TYPE_LONG: case MYSQL_TYPE_INT24: // 整型字段传整数 user[field[i].name] std::stoi(row[i]); break; case MYSQL_TYPE_LONGLONG: // 长整型字段传长整数 user[field[i].name] Json::Int64(std::stoll(row[i])); break; case MYSQL_TYPE_FLOAT: // 浮点型字段传浮点数 user[field[i].name] std::stof(row[i]); break; case MYSQL_TYPE_DOUBLE: // double字段传double user[field[i].name] std::stod(row[i]); break; default: // 字符串数据 user[field[i].name] row[i]; break; } } return true; }这里将MySQL_Query()与mysql_store_result()加锁保护起来防止在执行完查询后还没拿到结果集就去执行其他_mysql句柄操作该接口为 MySQL C API 常用的类型做了选择器传入正确的JSON字段WIn当用户对战胜利时对数据库的修改操作例如增加天梯积分、战斗场次、胜利场次bool User_Table::Win(const int id) // 胜利时操作 { // 增加天梯积分(30)、战斗场次增加、胜利场次增加 constexpr char UPDATE_SQL[] UPDATE user SET ladder_points ladder_points30, number_of_sessions number_of_sessions 1, win_count win_count 1 WHERE id %d; std::string sql; // 要执行的sql sql.resize(4096); // 预留空间防止空间不足段错误因为下面用的是原始指针不会自己扩容 bool success false; // 若走到最后success则为true do { int len snprintf(sql.data(), sql.size(), UPDATE_SQL, id); if (len 0 || static_castsize_t(len) sql.size()) { LOG(LogLv::ERROR, 格式化打印发生错误或截断); break; } { std::lock_guardstd::mutex lock(_mutex); // 防止多个线程同时调用mysql_query() (操作_mysql句柄) // 执行查询 if (MySQL_Util::MySQL_Query(_mysql, sql.c_str()) false) break; } success true; } while (false); return success; }Lose当用户对战失败时对数据库的修改操作例如减少天梯积分增加战斗场次bool User_Table::Lose(const int id) // 失败时操作 { // 减少天梯积分(-30)、战斗场次增加 constexpr char UPDATE_SQL[] UPDATE user SET ladder_points ladder_points-30, number_of_sessions number_of_sessions 1 WHERE id %d; std::string sql; // 要执行的sql sql.resize(4096); // 预留空间防止空间不足段错误因为下面用的是原始指针不会自己扩容 bool success false; // 若走到最后success则为true do { int len snprintf(sql.data(), sql.size(), UPDATE_SQL, id); if (len 0 || static_castsize_t(len) sql.size()) { LOG(LogLv::ERROR, 格式化打印发生错误或截断); break; } { std::lock_guardstd::mutex lock(_mutex); // 防止多个线程同时调用mysql_query() (操作_mysql句柄) // 执行查询 if (MySQL_Util::MySQL_Query(_mysql, sql.c_str()) false) break; } success true; } while (false); return success; }

相关推荐

【轻量 Web 架构三维 GIS 平台】
【轻量 Web 架构三维 GIS 平台】

有人说:一个人从1岁活到80岁很平凡,但如果从80岁倒着活,那么一半以上的人都可能不凡。 生活没有捷径,我们踩过的坑都成为了生活的经验,这些经验越早知道,你要走的弯路就会越少。 · 2026/9/24 17:15:35

为什么会有 .axf、.out、.elf、.hex、.bin?——嵌入式程序文件格式详解
为什么会有 .axf、.out、.elf、.hex、.bin?——嵌入式程序文件格式详解

为什么会有 .axf、.out、.elf、.hex、.bin?——嵌入式程序文件格式详解 我们在做嵌入式开发时,经常会遇到编译工具输出各种格式的文件,比如 .axf、.out、.elf、.hex、.bin。 心里是不是难免会嘀咕: “为什么会有这么多种文件?它… · 2026/9/24 17:15:35

Kornia 颜色空间系列:CIE XYZ 与 RGB 互转(rgb_to_xyz / xyz_to_rgb)原理与实践指南
Kornia 颜色空间系列:CIE XYZ 与 RGB 互转(rgb_to_xyz / xyz_to_rgb)原理与实践指南

计算机视觉人工智能深度学习图像处理 【免费下载链接】kornia 🐍 Geometric Computer Vision Library for Spatial AI 项目地址: https://gitcode.com/gh_mirrors/ko/kornia 点击查看 免费下载 本文以 docs/source/color.xyz.rst 为骨架,结合… · 2026/9/24 17:15:35

TCP协议栈管理、文件符表映射机制与TCP资源关闭流程介绍
TCP协议栈管理、文件符表映射机制与TCP资源关闭流程介绍

文章目录 一、进程与内核 1.用户态的Java程序进程 2.内核态的操作系统内核 2.1操作系统 2.1.1内核 二、文件描述符与表引用 1.文件描述符 2.文件描述符表 3.引用比例 三、TCP资源管理与连接维护 1.TCP协议栈 1.1TCB 1.1.1端点 1.1.1.1TCP连接状态 四、Socket引用… · 2026/9/24 17:52:44

基于 Java Spring Boot 的幼儿早教微信小程序设计与实现
基于 Java Spring Boot 的幼儿早教微信小程序设计与实现

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 1. 引言 随着移动互联网的普及和微信生态的快速发展,微信小程序凭借其即用即走、无需下载安装、传播便捷等优势,已成为教育服务领域的重要载体。… · 2026/9/24 17:52:44

视频素材格式转换:多款视频转换工具能力客观记录
视频素材格式转换:多款视频转换工具能力客观记录

自媒体素材整理、课件转码、监控视频归档时,经常遇到视频格式不兼容、平台上传受限的问题。批量转格式、压缩体积、提取音频、转 GIF,不同工具支持的格式种类、批量上限、编码自定义范围差别较大。下文客观记录多款视频转换工具基础能力与使用边界&#… · 2026/9/24 17:52:38

教学反思怎么写才不只是感想:四步各留一处能回查的痕迹
教学反思怎么写才不只是感想:四步各留一处能回查的痕迹

一节课上完,随笔写下几行,往往只是当堂的感受;真正立得住的反思,是每一步都留下了一处日后能翻回来核对的凭据。知学术AIPaperGPT 把这条线看得比字数更重。先要一个框架,让免费智能大纲接手;图表这类素材&… · 2026/9/24 17:52:38

独立站建站平台有哪些?Shopify、WooCommerce和外贸建站方案盘点
独立站建站平台有哪些?Shopify、WooCommerce和外贸建站方案盘点

搜索“独立站建站平台有哪些”的企业,往往已经决定不只依赖第三方平台或社媒入口,而是希望拥有自己的官网、产品展示、询盘或交易阵地。但独立站并不是单一工具,它包括B2B外贸询盘站、跨境电商交易站、品牌展示站、内容型网站和复杂定制系统&… · 2026/9/24 17:52:19

Large Bin Attack
Large Bin Attack

学习 Large Bin Attack,最重要的一点是不要被它的名字吓倒。虽然它属于高级的堆利用技巧,但它的本质其实非常简单:利用 glibc 在维护“有序双向链表”时,缺乏足够的安全检查,从而让我们能在一个任意的内存地址里&#… · 2026/9/24 17:52:19

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13

1D-CNN时间序列建模实战:从Conv1d原理到工业落地
1D-CNN时间序列建模实战:从Conv1d原理到工业落地

简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26

柔软的L:汉语语流中被忽视的舌肌张力控制
柔软的L:汉语语流中被忽视的舌肌张力控制

1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码