Qt项目:基于Qt实现的网络聊天室---注册模块

news2024/11/25 20:18:47

文章目录

  • 基本页面设计
    • 创建登录界面
    • 创建注册界面
    • 优化样式
    • 完善注册类界面
  • 客户端逻辑完善
    • 客户端增加post逻辑
    • 客户端配置管理
  • 邮箱注册服务
    • 认证服务
    • 读取配置
    • 邮箱验证服务联调
    • 设置验证码过期
    • 封装redis操作类
    • 封装redis连接池
    • 注册功能
    • Server端接受注册请求
    • 封装mysql连接池
    • 封装DAO操作层
    • 数据库管理者
    • 逻辑层调用

本篇是基于搭建好的Beast库实现的,主要是进行一些注册模块业务逻辑实现

基本页面设计

创建登录界面

右键项目,选择创建,点击设计师界面类

创建的名字就叫做LoginDialog。

将LoginDialog.ui修改为如下布局

在这里插入图片描述

在mainwindow.h中添加LoginDialog指针成员,然后在构造函数将LoginDialog设置为中心部件

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    //创建一个CentralWidget, 并将其设置为MainWindow的中心部件
    _login_dlg = new LoginDialog();
    setCentralWidget(_login_dlg);
    _login_dlg->show();
}

创建注册界面

注册界面创建方式和登录界面类似,我们创建的界面如下:

在这里插入图片描述

创建好界面后接下来在LoginDialog类声明里添加信号切换注册界面

signals:
    void switchRegister();

在LoginDialog的构造函数里连接按钮点击事件

connect(ui->reg_btn, &QPushButton::clicked, this, &LoginDialog::switchRegister);

按钮点击后LoginDialog发出switchRegister信号,该信号发送给MainWindow用来切换界面。

我们在MainWindow里声明注册类变量

private:
    RegisterDialog* _reg_dlg;

在其构造函数中添加注册类对象的初始化以及连接switchRegister信号

    //创建和注册消息的链接
    connect(_login_dlg, &LoginDialog::switchRegister,
            this, &MainWindow::SlotSwitchReg);
    _reg_dlg = new RegisterDialog();

接下来实现槽函数SlotSwitchReg

void MainWindow::SlotSwitchReg(){
    setCentralWidget(_reg_dlg);
    _login_dlg->hide();
    _reg_dlg->show();
}

这样启动程序主界面优先显示登录界面,点击注册后跳转到注册界面

优化样式

我们在项目根目录下创建style文件夹,在文件夹里创建stylesheet.qss文件,然后在qt项目中的rc.qrc右键添加现有文件,选择stylesheet.qss,这样qss就被导入到项目中了

在主程序启动后加载qss

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QFile qss(":/style/stylesheet.qss");

    if( qss.open(QFile::ReadOnly))
    {
        qDebug("open success");
        QString style = QLatin1String(qss.readAll());
        a.setStyleSheet(style);
        qss.close();
    }else{
         qDebug("Open failed");
     }

    MainWindow w;
    w.show();

    return a.exec();
}

然后我们写qss样式美化界面

QDialog#LoginDialog{
background-color:rgb(255,255,255)
}

完善注册类界面

先在注册类构造函数里添加lineEdit的模式为密码模式

ui->lineEdit_Passwd->setEchoMode(QLineEdit::Password);
ui->lineEdit_Confirm->setEchoMode(QLineEdit::Password);

在注册界面的ui里添加一个widget,widget内部包含一个tip居中显示,用来提示错误。设置label的显示为文字居中。

在qss里添加err_tip样式,根据不同的状态做字体显示

#err_tip[state='normal']{
   color: green;
}

#err_tip[state='err']{
   color: red;
}

接下来项目中添加global.h和global.cpp文件,global.h声明repolish函数,global.cpp用来定义这个函数。

.h中的声明

#ifndef GLOBAL_H
#define GLOBAL_H
#include <QWidget>
#include <functional>
#include "QStyle"
extern std::function<void(QWidget*)> repolish;

#endif // GLOBAL_H

.cpp中的定义

#include "global.h"

std::function<void(QWidget*)> repolish =[](QWidget *w){
    w->style()->unpolish(w);
    w->style()->polish(w);
};

在Register的构造函数中添加样式设置。

ui->err_tip->setProperty("state","normal");
repolish(ui->err_tip);

接下来实现获取验证码的逻辑,ui里关联get_code按钮的槽事件,并实现槽函数

void RegisterDialog::on_get_code_clicked()
{
    //验证邮箱的地址正则表达式
    auto email = ui->email_edit->text();
    // 邮箱地址的正则表达式
    QRegularExpression regex(R"((\w+)(\.|_)?(\w*)@(\w+)(\.(\w+))+)");
    bool match = regex.match(email).hasMatch(); // 执行正则表达式匹配
    if(match){
        //发送http请求获取验证码
    }else{
        //提示邮箱不正确
        showTip(tr("邮箱地址不正确"));
    }
}

在RegisterDialog中添加showTip函数

void RegisterDialog::showTip(QString str)
{
    ui->err_tip->setText(str);
    ui->err_tip->setProperty("state","err");
    repolish(ui->err_tip);
}

客户端逻辑完善

客户端增加post逻辑

之前在客户端实现了httpmgr的post请求,在点击获取验证码的槽函数里添加发送http的post请求即可

void RegisterDialog::on_get_code_clicked()
{
    //验证邮箱的地址正则表达式
    auto email = ui->email_edit->text();
    // 邮箱地址的正则表达式
    QRegularExpression regex(R"((\w+)(\.|_)?(\w*)@(\w+)(\.(\w+))+)");
    bool match = regex.match(email).hasMatch(); // 执行正则表达式匹配
    if(match){
        //发送http请求获取验证码
        QJsonObject json_obj;
        json_obj["email"] = email;
        HttpMgr::GetInstance()->PostHttpReq(QUrl("http://localhost:8080/get_varifycode"),
                     json_obj, ReqId::ID_GET_VARIFY_CODE,Modules::REGISTERMOD);

    }else{
        //提示邮箱不正确
        showTip(tr("邮箱地址不正确"),false);
    }
}

当服务器不启动,客户端输入邮箱,点击获取验证码,客户端会收到网络连接失败的提示

启动服务器后,再次获取验证码,就显示正确提示了

客户端配置管理

我们发现客户端代码中很多参数都是写死的,最好通过配置文件管理,我们在代码所在目录中新建一个config.ini文件, 内部添加配置

[GateServer]
host=localhost
port=8080

接着右键项目添加现有文件config.ini即可加入项目中

global.h中添加声明

extern QString gate_url_prefix;

在cpp中添加定义

QString gate_url_prefix = "";

在main函数中添加解析配置的逻辑

// 获取当前应用程序的路径
QString app_path = QCoreApplication::applicationDirPath();
// 拼接文件名
QString fileName = "config.ini";
QString config_path = QDir::toNativeSeparators(app_path +
                        QDir::separator() + fileName);

QSettings settings(config_path, QSettings::IniFormat);
QString gate_host = settings.value("GateServer/host").toString();
QString gate_port = settings.value("GateServer/port").toString();
gate_url_prefix = "http://"+gate_host+":"+gate_port;

将RegisterDialog发送post请求修改为

 HttpMgr::GetInstance()->PostHttpReq(QUrl(gate_url_prefix+"/get_varifycode"),
                     json_obj, ReqId::ID_GET_VARIFY_CODE,Modules::REGISTERMOD);

再次测试仍旧可以收到服务器回馈的http包

这么做的好处就是客户端增加了配置,而且以后修改参数也方便

邮箱注册服务

认证服务

认证服务要给邮箱发送验证码,所以用nodejs较为合适,nodejs是一门IO效率很高而且生态完善的语言,用到发送邮件的库也方便。

新建VarifyServer文件夹,在文件夹内部初始化server要用到的nodejs库的配置文件

npm init

根据提示同意会创建一个package.json文件

接着安装proto-loader用来动态解析proto文件

npm install @grpc/proto-loader

再安装email处理的库

npm install nodemailer

将proto文件放入VarifyServer文件夹,并且新建一个proto.js用来解析proto文件

const path = require('path')
const grpc = require('@grpc/grpc-js')
const protoLoader = require('@grpc/proto-loader')

const PROTO_PATH = path.join(__dirname, 'message.proto')
const packageDefinition = protoLoader.loadSync(PROTO_PATH, { keepCase: true, longs: String, enums: String, defaults: true, oneofs: true })
const protoDescriptor = grpc.loadPackageDefinition(packageDefinition)

const message_proto = protoDescriptor.message

module.exports = message_proto

keepCase: 如果为 true,则保留字段名的原始大小写。如果为 false,则将所有字段名转换为驼峰命名法。

longs: 控制如何表示 Protocol Buffers 中的 long 类型。如果设置为 String,则长整数会被转换为字符串,以避免 JavaScript 中的整数溢出问题。

enums: 控制如何表示 Protocol Buffers 中的枚举类型。如果设置为 String,则枚举值会被转换为字符串。

defaults: 如果为 true,则为未明确设置的字段提供默认值。

oneofs: 如果为 true,则支持 Protocol Buffers 中的 oneof 特性。

在写代码发送邮件之前,我们先去邮箱开启smtp服务。我用的163邮箱,在邮箱设置中查找smtp服务器地址,需要开启smtp服务。这个是固定的,不需要修改。

网易163邮箱的 SMTP 服务器地址为: smtp.163.com

发送邮件,建议使用授权码(有的邮箱叫 独立密码),确保邮箱密码的安全性。授权码在邮箱设置中进行设置。如果开启了授权码,发送邮件的时候,必须使用授权码

读取配置

因为我们要实现参数可配置,所以要读取配置,先在文件夹内创建一个config.json文件

{
    "email": {
      "user": "xxxxxxx@163.com",
      "pass": ""
    },
}

user是我们得邮箱地址,pass是邮箱得授权码,只有有了授权码才能用代码发邮件。大家记得把授权码改为你们自己的,否则用我的无法发送成功。

另外我们也要用到一些常量和全局得变量,所以定义一个const.js

let code_prefix = "code_";

const Errors = {
    Success : 0,
    RedisErr : 1,
    Exception : 2,
};


module.exports = {code_prefix,Errors}

新建config.js用来读取配置

const fs = require('fs');

let config = JSON.parse(fs.readFileSync('config.json', 'utf8'));
let email_user = config.email.user;
let email_pass = config.email.pass;
let mysql_host = config.mysql.host;
let mysql_port = config.mysql.port;
let redis_host = config.redis.host;
let redis_port = config.redis.port;
let redis_passwd = config.redis.passwd;
let code_prefix = "code_";


module.exports = {email_pass, email_user, mysql_host, mysql_port,redis_host, redis_port, redis_passwd, code_prefix}

接下来封装发邮件的模块,新建一个email.js文件

const nodemailer = require('nodemailer');
const config_module = require("./config")

/**
 * 创建发送邮件的代理
 */
let transport = nodemailer.createTransport({
    host: 'smtp.163.com',
    port: 465,
    secure: true,
    auth: {
        user: config_module.email_user, // 发送方邮箱地址
        pass: config_module.email_pass // 邮箱授权码或者密码
    }
});

接下来实现发邮件函数

/**
 * 发送邮件的函数
 * @param {*} mailOptions_ 发送邮件的参数
 * @returns 
 */
function SendMail(mailOptions_){
    return new Promise(function(resolve, reject){
        transport.sendMail(mailOptions_, function(error, info){
            if (error) {
                console.log(error);
                reject(error);
            } else {
                console.log('邮件已成功发送:' + info.response);
                resolve(info.response)
            }
        });
    })
   
}

module.exports.SendMail = SendMail

因为transport.SendMail相当于一个异步函数,调用该函数后发送的结果是通过回调函数通知的,所以我们没办法同步使用,需要用Promise封装这个调用,抛出Promise给外部,那么外部就可以通过await或者then catch的方式处理了

新建server.js,用来启动grpc server

async function GetVarifyCode(call, callback) {
    console.log("email is ", call.request.email)
    try{
        uniqueId = uuidv4();
        console.log("uniqueId is ", uniqueId)
        let text_str =  '您的验证码为'+ uniqueId +'请三分钟内完成注册'
        //发送邮件
        let mailOptions = {
            from: 'secondtonone1@163.com',
            to: call.request.email,
            subject: '验证码',
            text: text_str,
        };
    
        let send_res = await emailModule.SendMail(mailOptions);
        console.log("send res is ", send_res)

        callback(null, { email:  call.request.email,
            error:const_module.Errors.Success
        }); 
        
 
    }catch(error){
        console.log("catch error is ", error)

        callback(null, { email:  call.request.email,
            error:const_module.Errors.Exception
        }); 
    }
     
}

function main() {
    var server = new grpc.Server()
    server.addService(message_proto.VarifyService.service, { GetVarifyCode: GetVarifyCode })
    server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), () => {
        server.start()
        console.log('grpc server started')        
    })
}

main()

GetVarifyCode声明为async是为了能在内部调用await

邮箱验证服务联调

启动GateServer和VarifyServer

GateServer收到Client发送的请求后,会调用grpc 服务 访问VarifyServer,VarifyServer会随机生成验证码,并且调用邮箱模块发送邮件给指定邮箱。而且把发送的结果给GateServer,GateServer再将消息回传给客户端

设置验证码过期

我们的验证码是要设置过期的,可以用redis管理过期的验证码自动删除,key为邮箱,value为验证码,过期时间为3min

封装redis操作类

因为hredis提供的操作太别扭了,所以需要手动封装redis操作类,简化调用流程

class RedisMgr: public Singleton<RedisMgr>, 
	public std::enable_shared_from_this<RedisMgr>
{
	friend class Singleton<RedisMgr>;
public:
	~RedisMgr();
	bool Connect(const std::string& host, int port);
	bool Get(const std::string &key, std::string& value);
	bool Set(const std::string &key, const std::string &value);
	bool Auth(const std::string &password);
	bool LPush(const std::string &key, const std::string &value);
	bool LPop(const std::string &key, std::string& value);
	bool RPush(const std::string& key, const std::string& value);
	bool RPop(const std::string& key, std::string& value);
	bool HSet(const std::string &key, const std::string  &hkey, const std::string &value);
	bool HSet(const char* key, const char* hkey, const char* hvalue, size_t hvaluelen);
	std::string HGet(const std::string &key, const std::string &hkey);
	bool Del(const std::string &key);
	bool ExistsKey(const std::string &key);
	void Close();
private:
	RedisMgr();

	redisContext* _connect;
	redisReply* _reply;
};

封装redis连接池

class RedisConPool {
public:
	RedisConPool(size_t poolSize, const char* host, int port, const char* pwd)
		: poolSize_(poolSize), host_(host), port_(port), b_stop_(false){
		for (size_t i = 0; i < poolSize_; ++i) {
			auto* context = redisConnect(host, port);
			if (context == nullptr || context->err != 0) {
				if (context != nullptr) {
					redisFree(context);
				}
				continue;
			}

			auto reply = (redisReply*)redisCommand(context, "AUTH %s", pwd);
			if (reply->type == REDIS_REPLY_ERROR) {
				std::cout << "认证失败" << std::endl;
				//执行成功 释放redisCommand执行后返回的redisReply所占用的内存
				freeReplyObject(reply);
				continue;
			}

			//执行成功 释放redisCommand执行后返回的redisReply所占用的内存
			freeReplyObject(reply);
			std::cout << "认证成功" << std::endl;
			connections_.push(context);
		}

	}

	~RedisConPool() {
		std::lock_guard<std::mutex> lock(mutex_);
		while (!connections_.empty()) {
			connections_.pop();
		}
	}

	redisContext* getConnection() {
		std::unique_lock<std::mutex> lock(mutex_);
		cond_.wait(lock, [this] { 
			if (b_stop_) {
				return true;
			}
			return !connections_.empty(); 
			});
		//如果停止则直接返回空指针
		if (b_stop_) {
			return  nullptr;
		}
		auto* context = connections_.front();
		connections_.pop();
		return context;
	}

	void returnConnection(redisContext* context) {
		std::lock_guard<std::mutex> lock(mutex_);
		if (b_stop_) {
			return;
		}
		connections_.push(context);
		cond_.notify_one();
	}

	void Close() {
		b_stop_ = true;
		cond_.notify_all();
	}

private:
	atomic<bool> b_stop_;
	size_t poolSize_;
	const char* host_;
	int port_;
	std::queue<redisContext*> connections_;
	std::mutex mutex_;
	std::condition_variable cond_;
};

RedisMgr构造函数中初始化pool连接池

RedisMgr::RedisMgr() {
	auto& gCfgMgr = ConfigMgr::Inst();
	auto host = gCfgMgr["Redis"]["Host"];
	auto port = gCfgMgr["Redis"]["Port"];
	auto pwd = gCfgMgr["Redis"]["Passwd"];
	_con_pool.reset(new RedisConPool(5, host.c_str(), atoi(port.c_str()), pwd.c_str()));
}

在析构函数中回收资源

RedisMgr::~RedisMgr() {
	Close();
}

void RedisMgr::Close() {
	_con_pool->Close();
}

在使用的时候改为从Pool中获取链接

bool RedisMgr::Get(const std::string& key, std::string& value)
{
	auto connect = _con_pool->getConnection();
	if (connect == nullptr) {
		return false;
	}
	 auto reply = (redisReply*)redisCommand(connect, "GET %s", key.c_str());
	 if (reply == NULL) {
		 std::cout << "[ GET  " << key << " ] failed" << std::endl;
		 freeReplyObject(reply);
		 _con_pool->returnConnection(connect);
		  return false;
	}

	 if (reply->type != REDIS_REPLY_STRING) {
		 std::cout << "[ GET  " << key << " ] failed" << std::endl;
		 freeReplyObject(reply);
		 _con_pool->returnConnection(connect);
		 return false;
	}

	 value = reply->str;
	 freeReplyObject(reply);

	 std::cout << "Succeed to execute command [ GET " << key << "  ]" << std::endl;
	 _con_pool->returnConnection(connect);
	 return true;
}

注册功能

实现注册功能,先实现客户端发送post请求, 将注册ui中确定按钮改为sure_btn,并为其添加click槽函数

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;
    }

    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连接池

Mysql Connector C++

尽管Mysql提供了访问数据库的接口,但是都是基于C风格的,为了便于面向对象设计,我们使用Mysql Connector C++ 这个库来访问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;
	}
}

数据库管理者

我们需要建立一个数据库管理者用来实现服务层,对接逻辑层的调用

#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;
    });

再次启动客户端测试,可以注册成功

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1897973.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

传统视觉Transformer的替代者:交叉注意力Transformer(CAT)

传统视觉Transformer的替代者:交叉注意力Transformer(CAT) 在深度学习的世界里,Transformer架构以其在自然语言处理(NLP)领域的卓越表现而闻名。然而,当它进入计算机视觉(CV)领域时,却面临着计算成本高昂和推理速度慢的双重挑战。现在,一项革命性的创新——交叉注意…

【Linux】—VMware安装Centos7步骤

文章目录 前言一、虚拟机准备二、CentOS7操作系统安装 前言 本文介绍VMware安装Centos7步骤。 软件准备 软件&#xff1a;VMware Workstation Pro&#xff0c;直接官网安装。镜像&#xff1a;CentOS7&#xff0c;镜像官网下载链接&#xff1a;https://vault.centos.org/&#x…

[SAP ABAP] 子例程

子例程 示例1 主程序(Z437_TEST_2024) INCLUDE文件(Z437_TEST_2024_F01) 输出结果如下所示 示例2 主程序(Z437_TEST_2024) INCLUDE文件(Z437_TEST_2024_F01) 输出结果如下所示 补充扩展练习 主程序(Z437_TEST_2024) INCLUDE文件(Z437_TEST_2024_F01) 输出结果如下所示 提示…

使用Rough.js库在画布上绘制一只毛毛虫

本文由ScriptEcho平台提供技术支持 项目地址&#xff1a;传送门 代码应用场景介绍 本代码使用Rough.js库在画布上绘制一只毛毛虫。 代码基本功能介绍 初始化画布&#xff1a; 使用Rough.js库创建画布&#xff0c;并设置画布尺寸。 绘制毛毛虫身体&#xff1a; 使用椭圆形和…

【C++】 解决 C++ 语言报错:未定义行为(Undefined Behavior)

文章目录 引言 未定义行为&#xff08;Undefined Behavior, UB&#xff09;是 C 编程中非常危险且难以调试的错误之一。未定义行为发生时&#xff0c;程序可能表现出不可预测的行为&#xff0c;导致程序崩溃、安全漏洞甚至硬件损坏。本文将深入探讨未定义行为的成因、检测方法…

上千套源码分享免费(师哥师姐毕设分享)

项目管理平台详解 项目管理平台是专为项目管理者设计的系统软件&#xff0c;旨在在有限的资源约束下&#xff0c;通过系统的观点、方法和理论&#xff0c;对项目涉及的全部工作进行有效管理。这种平台不仅有助于项目从投资决策到结束的全程计划、组织、指挥、协调、控制和评价…

html纯原生网页引入vue3版本的quill editor

效果图 版本 vueup/vue-quill v1.2.0vue3.3.8Element Plus v2.4.2 引入流程 找一个vue3的项目, 然后安装插件vue版本的quill: vue-quill npm install vueup/vue-quill --save官方地址&#xff1a;https://vueup.github.io/vue-quill/ 安装完成之后&#xff0c;把vue-quil插件下…

[单master节点k8s部署]18.监控系统构建(三)Grafana安装

Grafana是一个跨平台的开源的度量分析和可视化工具。支持多种数据源&#xff0c;比如OpenTSDB&#xff0c;Prometheus&#xff0c;ElasticResearch&#xff0c;Cloudwatch等。 Grafana安装 通过yaml配置grafana的pod和service&#xff0c;grafana工作在kube-system的命名空间…

小学英语语法

目录 a和an的用法名词的单复数be动词和人称代词&#xff08;主格&#xff09;指示代词形容词物主代词名词所有格双重所有格方位介词some&#xff0c;any和no的用法How many和How much的用法情态动词can的用法祈使句人称代词&#xff08;宾格&#xff09;常见实义动词的用法一般…

也说字母U:房子到底是什么?

​ 不记得是第几期了&#xff0c;湖南卫视有档很火的音乐节目叫《歌手》&#xff0c;那一期是最终是韩磊夺得了冠军&#xff0c;他有一杀手锏&#xff0c;叫《向天再借五百年》&#xff0c;他要不夺冠&#xff0c;好像大家也对不起对这首歌的印象&#xff0c;因为他是多少人的记…

【网址】Andorid Studio历史版本下载

Andorid Studio官网是最新版本的下载&#xff0c;历史版本的下载地址 &#xff1a;https://developer.android.google.cn/studio/archive 1.如果是空白页面的话&#xff0c;切换语言【中文切换成English】 2.滑倒最后点击同意 3.历史版本浏览 4.下载安装包 遇到问题1&#xf…

【HICE】基于用户认证的虚拟服务搭建

1.创建特定的内容 --账号与密码&#xff08;需要认证访问&#xff09;【里面】 2.编辑配置1.conf的内容&#xff0c;更新httpd 3.编辑hehe网页&#xff08;外部公开&#xff09; cd /www/ echo hehe > hehe/index.html 4.更改本地hosts和window下的解析 5.浏览器下验证内…

Linux搭建hive手册

一、将hive安装包上传到NameNode节点并解压 1、删除安装MySQL时的.rpm文件 cd /opt/install_packages/ rm -rf *.rpm 2、将安装包拖进/install_packages目录 3、解压安装包 tar -zxvf apache-hive-3.1.2-bin.tar.gz -C /opt/softs/ 4、修改包名 cd /opt/softs mv apache-…

[单master节点k8s部署]19.监控系统构建(四)kube-state-metrics

kube-state-metrics 是一个Kubernetes的附加组件&#xff0c;它通过监听 Kubernetes API 服务器来收集和生成关于 Kubernetes 对象&#xff08;如部署、节点和Pod等&#xff09;的状态的指标。这些指标可供 Prometheus 进行抓取和存储&#xff0c;从而使你能够监控和分析Kubern…

Python从0到100(三十八):json字符串的数据提取

JSON的数据提取 1.学习目标 掌握JSON相关的方法&#xff08;load, loads, dump, dumps&#xff09;了解JSONPath的使用&#xff08;提取JSON中的数据&#xff09; 2 复习什么是JSON JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式&#xff0c;它使得人们很容…

并发编程(多线程)带来了哪些问题?

前面我们了解到多线程技术有很多好处,比如说多线程可以充分利用多核 CPU 的计算能力,那多线程难道就没有一点缺点吗? 有。 多线程很难掌握,稍不注意,就容易使程序崩溃。我们以在路上开车为例: 在一个单向行驶的道路上,每辆汽车都遵守交通规则,这时候整体通行是正常的…

【吊打面试官系列-MyBatis面试题】Xml 映射文件中,除了常见的 select|insert|updae|delete标签之外,还有哪些标签?

大家好&#xff0c;我是锋哥。今天分享关于 【Xml 映射文件中&#xff0c;除了常见的 select|insert|updae|delete标签之外&#xff0c;还有哪些标签&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; Xml 映射文件中&#xff0c;除了常见的 select|insert|updae|…

链串算法库构建

学习贺利坚老师链串算法库 数据结构之自建算法库——链串_串数据结构-CSDN博客 本人详细解析博客 串的链式存储及其基本操作实现_串链式存储的操作-CSDN博客 版本更新日志 V1.0 : 结合顺序串操作, 使用链串进行优化, 此次链串, 空间将不受限制, 只写了最基本的操作, 相当于 单链…

HTML【详解】超链接 a 标签的四大功能(页面跳转、页内滚动【锚点】、页面刷新、文件下载)

超链接 a 标签主要有以下功能&#xff1a; 跳转到其他页面 <a href"https://www.baidu.com/" target"_blank" >百度</a>href&#xff1a;目标页面的 url 地址或同网站的其他页面地址&#xff0c;如 detail.htmltarget&#xff1a;打开目标页面…

kylin arm xcb版本异常问题解决

源码编译qt 未生成xcb库&#xff0c;查看源码xcb readme.txt 提示 版本要求 下载 [ANNOUNCE] libxcb 1.14 [ANNOUNCE] xcb-proto 1.14 解压源码编译, 先编译xcb-proto sudo ./configure --prefix/usr/local/xcb-proto make make install 在编译xcb export PKG_CONFIG_PATH…