注册功能
实现注册功能,先实现客户端发送post请求, 将注册ui中确定按钮改为sure_btn,并为其添加click槽函数
//day11 添加确认槽函数
void RegisterDialog::on_sure_btn_clicked()
{
if(ui->user_edit->text() == ""){
showTip(tr("用户名不能为空"), false);
return;
}
if(ui->email_edit->text() == ""){
showTip(tr("邮箱不能为空"), false);
return;
}
if(ui->pass_edit->text() == ""){
showTip(tr("密码不能为空"), false);
return;
}
if(ui->confirm_edit->text() == ""){
showTip(tr("确认密码不能为空"), false);
return;
}
if(ui->confirm_edit->text() != ui->pass_edit->text()){
showTip(tr("密码和确认密码不匹配"), false);
return;
}
if(ui->varify_edit->text() == ""){
showTip(tr("验证码不能为空"), false);
return;
}
//day11 发送http请求注册用户
QJsonObject json_obj;
json_obj["user"] = ui->user_edit->text();
json_obj["email"] = ui->email_edit->text();
json_obj["passwd"] = ui->pass_edit->text();
json_obj["confirm"] = ui->confirm_edit->text();
json_obj["varifycode"] = ui->varify_edit->text();
HttpMgr::GetInstance()->PostHttpReq(QUrl(gate_url_prefix+"/user_register"),
json_obj, ReqId::ID_REG_USER,Modules::REGISTERMOD);
}
再添加http请求回复后收到处理流程
void RegisterDialog::initHttpHandlers()
{
//...省略
//注册注册用户回包逻辑
_handlers.insert(ReqId::ID_REG_USER, [this](QJsonObject jsonObj){
int error = jsonObj["error"].toInt();
if(error != ErrorCodes::SUCCESS){
showTip(tr("参数错误"),false);
return;
}
auto email = jsonObj["email"].toString();
showTip(tr("用户注册成功"), true);
qDebug()<< "email is " << email ;
});
}
Server端接受注册请求
Server注册user_register逻辑
RegPost("/user_register", [](std::shared_ptr<HttpConnection> connection) {
auto body_str = boost::beast::buffers_to_string(connection->_request.body().data());
std::cout << "receive body is " << body_str << std::endl;
connection->_response.set(http::field::content_type, "text/json");
Json::Value root;
Json::Reader reader;
Json::Value src_root;
bool parse_success = reader.parse(body_str, src_root);
if (!parse_success) {
std::cout << "Failed to parse JSON data!" << std::endl;
root["error"] = ErrorCodes::Error_Json;
std::string jsonstr = root.toStyledString();
beast::ostream(connection->_response.body()) << jsonstr;
return true;
}
//先查找redis中email对应的验证码是否合理
std::string varify_code;
bool b_get_varify = RedisMgr::GetInstance()->Get(src_root["email"].asString(), varify_code);
if (!b_get_varify) {
std::cout << " get varify code expired" << std::endl;
root["error"] = ErrorCodes::VarifyExpired;
std::string jsonstr = root.toStyledString();
beast::ostream(connection->_response.body()) << jsonstr;
return true;
}
if (varify_code != src_root["varifycode"].asString()) {
std::cout << " varify code error" << std::endl;
root["error"] = ErrorCodes::VarifyCodeErr;
std::string jsonstr = root.toStyledString();
beast::ostream(connection->_response.body()) << jsonstr;
return true;
}
//访问redis查找
bool b_usr_exist = RedisMgr::GetInstance()->ExistsKey(src_root["user"].asString());
if (b_usr_exist) {
std::cout << " user exist" << std::endl;
root["error"] = ErrorCodes::UserExist;
std::string jsonstr = root.toStyledString();
beast::ostream(connection->_response.body()) << jsonstr;
return true;
}
//查找数据库判断用户是否存在
root["error"] = 0;
root["email"] = src_root["email"];
root ["user"]= src_root["user"].asString();
root["passwd"] = src_root["passwd"].asString();
root["confirm"] = src_root["confirm"].asString();
root["varifycode"] = src_root["varifycode"].asString();
std::string jsonstr = root.toStyledString();
beast::ostream(connection->_response.body()) << jsonstr;
return true;
});
安装Mysql
先介绍Windows环境下安装mysql
点击mysql安装包下载链接:https://dev.mysql.com/downloads/mysql
选择window版本,点击下载按钮,如下所示
不用登录直接下载
下载好mysql安装包后,将其解压到指定目录,并记下解压的目录,后续用于环境变量配置
在bin目录同级下创建一个文件,命名为my.ini 编辑my.ini文件
[mysqld]
# 设置3308端口
port=3308
# 设置mysql的安装目录 ---这里输入你安装的文件路径----
basedir=D:\cppsoft\mysql
# 设置mysql数据库的数据的存放目录
datadir=D:\mysql\data
# 允许最大连接数
max_connections=200
# 允许连接失败的次数。
max_connect_errors=10
# 服务端使用的字符集默认为utf8
character-set-server=utf8
# 创建新表时将使用的默认存储引擎
default-storage-engine=INNODB
# 默认使用“mysql_native_password”插件认证
#mysql_native_password
default_authentication_plugin=mysql_native_password
[mysql]
# 设置mysql客户端默认字符集
default-character-set=utf8
[client]
# 设置mysql客户端连接服务端时默认使用的端口
port=3308
default-character-set=utf8
有两点需要注意修改的:
A、basedir这里输入的是mysql解压存放的文件路径
B、datadir这里设置mysql数据库的数据存放目录
打开cmd进入mysql的bin文件下
依次执行命令
第一个命令为:
//安装mysql 安装完成后Mysql会有一个随机密码
.\mysqld.exe --initialize --console
如下图,随机密码要记住,以后我们改密码会用到
接下来在cmd执行第二条命令
//安装mysql服务并启动
.\mysqld.exe --install mysql
如果出现以下情况,说明cmd不是以管理员形式执行的,改用为管理员权限执行即可。
成功如下
目前为止安装完毕,大家如果mysql官网下载缓慢,可以去我的网盘下载
https://pan.baidu.com/s/1BTMZB31FWFUq4mZZdzcA9g?pwd=6xlz
提取码:6xlz
修改mysql密码
1 在本机启动mysql服务:
点击桌面我的电脑,右键选择管理进去:
点击后选择服务
点击服务后可查看当前计算机启动的所有服务,找到mysql,然后右键点击设为启动,同时也可设置其为自动启动和手动启动
继续在cmd上执行以下命令
mysql -uroot -p
回车后输入上面安装时保存的初始密码,进入mysql里面:
在mysql里面继续执行以下命令:
//修改密码为123mysql
ALTER USER 'root'@'localhost' IDENTIFIED BY '123456';
回车按照指引执行完后,代表密码修改成功,再输入exit;退出即可
配置环境变量
为了方便使用mysql命令,可以将mysql目录配置在环境变量里
新建系统变量:
变量名:MYSQL_HOME
变量值:msql目录
修改系统的path变量
编辑path,进去后添加 %MYSQL_HOME%\bin
测试连接
为了方便测试,大家可以使用navicat等桌面工具测试连接。以后增删改查也方便。
可以去官网下载
或者我得网盘下载
https://pan.baidu.com/s/10jApYUrwaI19j345dpPGNA?pwd=77m2
验证码: 77m2
效果如下:
Docker环境配置mysql
拉取mysql镜像
docker pull mysql:8.0
先启动一个测试版本,然后把他的配置文件拷贝出来
docker run --name mysqltest \
-p 3307:3306 -e MYSQL_ROOT_PASSWORD=root \
-d mysql
创建三个目录,我得目录是
mkdir -p /home/zack/llfc/mysql/config
mkdir -p /home/zack/llfc/mysql/data
mkdir -p /home/zack/llfc/mysql/logs
进入docker中
docker exec -it mysqltest bash
之后可以通过搜寻找到配置在/etc/mysql/my.cnf
所以接下来退出容器,执行拷贝命令
docker cp mysqltest:/etc/mysql/my.cnf /home/zack/llfc/mysql/config
然后删除测试用的mysql docker
docker rm -f mysqltest
然后启动我们的容器
docker run --restart=on-failure:3 -d \
-v /home/zack/llfc/mysql/config/my.cnf:/etc/mysql/my.cnf \
-v /home/zack/llfc/mysql/data/:/var/lib/mysql \
-v /home/zack/llfc/mysql/logs:/logs -p 3308:3306 \
--name llfcmysql -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0
设置远程访问
进入docker
docker exec -it llfcmysql bash
登录mysql
mysql -u root -p
设置允许远程访问,我不设置也能访问的,这里介绍一下。
use mysql
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
flush privileges;
再次用navicat连接,是可以连接上了。
完善GateServer配置
添加Redis和Mysql配置
[Mysql]
Host = 81.68.86.146
Port = 3308
Passwd = 123456
[Redis]
Host = 81.68.86.146
Port = 6380
Passwd = 123456
Mysql Connector C++
尽管Mysql提供了访问数据库的接口,但是都是基于C风格的,为了便于面向对象设计,我们使用Mysql Connector C++ 这个库来访问mysql。
我们先安装这个库,因为我们windows环境代码是debug版本,所以下载connector的debug版本,如果你的开发编译用的release版本,那么就要下载releas版本,否则会报错
terminate called after throwing an instance of 'std::bad_alloc'
.
因为我在windows只做debug调试后期会将项目移植到Linux端,所以这里只下载debug版
下载地址
https://dev.mysql.com/downloads/connector/cpp/
如果下载缓慢可以去我的网盘下载 https://pan.baidu.com/s/1XAVhPAAzZpZahsyITua2oQ?pwd=9c1w
提取码:9c1w
下载后将文件夹解压放在一个自己常用的目录,我放在D:\cppsoft\mysql_connector
接下来去visual studio中配置项目
VC++ 包含目录添加D:\cppsoft\mysql_connector\include
库目录包含D:\cppsoft\mysql_connector\lib64\vs14
然后将D:\cppsoft\mysql_connector\lib64\debug
下的mysqlcppconn8-2-vs14.dll和mysqlcppconn9-vs14.dll分别拷贝到项目中
为了让项目自动将dll拷贝到运行目录,可以在生成事件->生成后事件中添加xcopy命令
xcopy $(ProjectDir)config.ini $(SolutionDir)$(Platform)\$(Configuration)\ /y
xcopy $(ProjectDir)*.dll $(SolutionDir)$(Platform)\$(Configuration)\ /y
封装mysql连接池
class MySqlPool {
public:
MySqlPool(const std::string& url, const std::string& user, const std::string& pass, const std::string& schema, int poolSize)
: url_(url), user_(user), pass_(pass), schema_(schema), poolSize_(poolSize), b_stop_(false){
try {
for (int i = 0; i < poolSize_; ++i) {
sql::mysql::MySQL_Driver* driver = sql::mysql::get_mysql_driver_instance();
std::unique_ptr<sql::Connection> con(driver->connect(url_, user_, pass_));
con->setSchema(schema_);
pool_.push(std::move(con));
}
}
catch (sql::SQLException& e) {
// 处理异常
std::cout << "mysql pool init failed" << std::endl;
}
}
std::unique_ptr<sql::Connection> getConnection() {
std::unique_lock<std::mutex> lock(mutex_);
cond_.wait(lock, [this] {
if (b_stop_) {
return true;
}
return !pool_.empty(); });
if (b_stop_) {
return nullptr;
}
std::unique_ptr<sql::Connection> con(std::move(pool_.front()));
pool_.pop();
return con;
}
void returnConnection(std::unique_ptr<sql::Connection> con) {
std::unique_lock<std::mutex> lock(mutex_);
if (b_stop_) {
return;
}
pool_.push(std::move(con));
cond_.notify_one();
}
void Close() {
b_stop_ = true;
cond_.notify_all();
}
~MySqlPool() {
std::unique_lock<std::mutex> lock(mutex_);
while (!pool_.empty()) {
pool_.pop();
}
}
private:
std::string url_;
std::string user_;
std::string pass_;
std::string schema_;
int poolSize_;
std::queue<std::unique_ptr<sql::Connection>> pool_;
std::mutex mutex_;
std::condition_variable cond_;
std::atomic<bool> b_stop_;
};
封装DAO操作层
类的声明
class MysqlDao
{
public:
MysqlDao();
~MysqlDao();
int RegUser(const std::string& name, const std::string& email, const std::string& pwd);
private:
std::unique_ptr<MySqlPool> pool_;
};
实现
MysqlDao::MysqlDao()
{
auto & cfg = ConfigMgr::Inst();
const auto& host = cfg["Mysql"]["Host"];
const auto& port = cfg["Mysql"]["Port"];
const auto& pwd = cfg["Mysql"]["Passwd"];
const auto& schema = cfg["Mysql"]["Schema"];
const auto& user = cfg["Mysql"]["User"];
pool_.reset(new MySqlPool(host+":"+port, user, pwd,schema, 5));
}
MysqlDao::~MysqlDao(){
pool_->Close();
}
int MysqlDao::RegUser(const std::string& name, const std::string& email, const std::string& pwd)
{
auto con = pool_->getConnection();
try {
if (con == nullptr) {
pool_->returnConnection(std::move(con));
return false;
}
// 准备调用存储过程
unique_ptr < sql::PreparedStatement > stmt(con->prepareStatement("CALL reg_user(?,?,?,@result)"));
// 设置输入参数
stmt->setString(1, name);
stmt->setString(2, email);
stmt->setString(3, pwd);
// 由于PreparedStatement不直接支持注册输出参数,我们需要使用会话变量或其他方法来获取输出参数的值
// 执行存储过程
stmt->execute();
// 如果存储过程设置了会话变量或有其他方式获取输出参数的值,你可以在这里执行SELECT查询来获取它们
// 例如,如果存储过程设置了一个会话变量@result来存储输出结果,可以这样获取:
unique_ptr<sql::Statement> stmtResult(con->createStatement());
unique_ptr<sql::ResultSet> res(stmtResult->executeQuery("SELECT @result AS result"));
if (res->next()) {
int result = res->getInt("result");
cout << "Result: " << result << endl;
pool_->returnConnection(std::move(con));
return result;
}
pool_->returnConnection(std::move(con));
return -1;
}
catch (sql::SQLException& e) {
pool_->returnConnection(std::move(con));
std::cerr << "SQLException: " << e.what();
std::cerr << " (MySQL error code: " << e.getErrorCode();
std::cerr << ", SQLState: " << e.getSQLState() << " )" << std::endl;
return -1;
}
}
新建数据库llfc, llfc数据库添加user表和user_id表
user_id就一行数据,用来记录用户id
这里id用简单计数表示,不考虑以后合服务器和分表分库,如果考虑大家可以采取不同的策略,雪花算法等。
新建存储过程
CREATE DEFINER=`root`@`%` PROCEDURE `reg_user`(
IN `new_name` VARCHAR(255),
IN `new_email` VARCHAR(255),
IN `new_pwd` VARCHAR(255),
OUT `result` INT)
BEGIN
-- 如果在执行过程中遇到任何错误,则回滚事务
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
-- 回滚事务
ROLLBACK;
-- 设置返回值为-1,表示错误
SET result = -1;
END;
-- 开始事务
START TRANSACTION;
-- 检查用户名是否已存在
IF EXISTS (SELECT 1 FROM `user` WHERE `name` = new_name) THEN
SET result = 0; -- 用户名已存在
COMMIT;
ELSE
-- 用户名不存在,检查email是否已存在
IF EXISTS (SELECT 1 FROM `user` WHERE `email` = new_email) THEN
SET result = 0; -- email已存在
COMMIT;
ELSE
-- email也不存在,更新user_id表
UPDATE `user_id` SET `id` = `id` + 1;
-- 获取更新后的id
SELECT `id` INTO @new_id FROM `user_id`;
-- 在user表中插入新记录
INSERT INTO `user` (`uid`, `name`, `email`, `pwd`) VALUES (@new_id, new_name, new_email, new_pwd);
-- 设置result为新插入的uid
SET result = @new_id; -- 插入成功,返回新的uid
COMMIT;
END IF;
END IF;
END
数据库管理者
我们需要建立一个数据库管理者用来实现服务层,对接逻辑层的调用
#include "const.h"
#include "MysqlDao.h"
class MysqlMgr: public Singleton<MysqlMgr>
{
friend class Singleton<MysqlMgr>;
public:
~MysqlMgr();
int RegUser(const std::string& name, const std::string& email, const std::string& pwd);
private:
MysqlMgr();
MysqlDao _dao;
};
实现
#include "MysqlMgr.h"
MysqlMgr::~MysqlMgr() {
}
int MysqlMgr::RegUser(const std::string& name, const std::string& email, const std::string& pwd)
{
return _dao.RegUser(name, email, pwd);
}
MysqlMgr::MysqlMgr() {
}
逻辑层调用
在逻辑层注册消息处理。
RegPost("/user_register", [](std::shared_ptr<HttpConnection> connection) {
auto body_str = boost::beast::buffers_to_string(connection->_request.body().data());
std::cout << "receive body is " << body_str << std::endl;
connection->_response.set(http::field::content_type, "text/json");
Json::Value root;
Json::Reader reader;
Json::Value src_root;
bool parse_success = reader.parse(body_str, src_root);
if (!parse_success) {
std::cout << "Failed to parse JSON data!" << std::endl;
root["error"] = ErrorCodes::Error_Json;
std::string jsonstr = root.toStyledString();
beast::ostream(connection->_response.body()) << jsonstr;
return true;
}
auto email = src_root["email"].asString();
auto name = src_root["user"].asString();
auto pwd = src_root["passwd"].asString();
auto confirm = src_root["confirm"].asString();
if (pwd != confirm) {
std::cout << "password err " << std::endl;
root["error"] = ErrorCodes::PasswdErr;
std::string jsonstr = root.toStyledString();
beast::ostream(connection->_response.body()) << jsonstr;
return true;
}
//先查找redis中email对应的验证码是否合理
std::string varify_code;
bool b_get_varify = RedisMgr::GetInstance()->Get(CODEPREFIX+src_root["email"].asString(), varify_code);
if (!b_get_varify) {
std::cout << " get varify code expired" << std::endl;
root["error"] = ErrorCodes::VarifyExpired;
std::string jsonstr = root.toStyledString();
beast::ostream(connection->_response.body()) << jsonstr;
return true;
}
if (varify_code != src_root["varifycode"].asString()) {
std::cout << " varify code error" << std::endl;
root["error"] = ErrorCodes::VarifyCodeErr;
std::string jsonstr = root.toStyledString();
beast::ostream(connection->_response.body()) << jsonstr;
return true;
}
//查找数据库判断用户是否存在
int uid = MysqlMgr::GetInstance()->RegUser(name, email, pwd);
if (uid == 0 || uid == -1) {
std::cout << " user or email exist" << std::endl;
root["error"] = ErrorCodes::UserExist;
std::string jsonstr = root.toStyledString();
beast::ostream(connection->_response.body()) << jsonstr;
return true;
}
root["error"] = 0;
root["uid"] = uid;
root["email"] = email;
root ["user"]= name;
root["passwd"] = pwd;
root["confirm"] = confirm;
root["varifycode"] = src_root["varifycode"].asString();
std::string jsonstr = root.toStyledString();
beast::ostream(connection->_response.body()) << jsonstr;
return true;
});
再次启动客户端测试,可以注册成功