重置密码label
当我们在登录忘记密码的时候可以支持重置密码,重置密码label也要实现浮动和点击效果,以及未点击效果。所以我们复用之前的ClickedLabel,
在登录界面中升级forget_label为ClickedLabel。
LoginDialog::LoginDialog(QWidget *parent) :QDialog(parent),ui(new Ui::LoginDialog){ui->setupUi(this);connect(ui->reg_btn, &QPushButton::clicked, this, &LoginDialog::switchRegister);ui->forget_label->SetState("normal","hover","","selected","selected_hover","");ui->forget_label->setCursor(Qt::PointingHandCursor);connect(ui->forget_label, &ClickedLabel::clicked, this, &LoginDialog::slot_forget_pwd);}
点击忘记密码发送对应的信号
void LoginDialog::slot_forget_pwd(){qDebug()<<"slot forget pwd";emit switchReset();}
我们在mainwindow中连接了重置密码的信号和槽
//连接登录界面忘记密码信号connect(_login_dlg, &LoginDialog::switchReset, this, &MainWindow::SlotSwitchReset);
实现SlotSwitchReset
void MainWindow::SlotSwitchReset(){//创建一个CentralWidget, 并将其设置为MainWindow的中心部件_reset_dlg = new ResetDialog(this);_reset_dlg->setWindowFlags(Qt::CustomizeWindowHint|Qt::FramelessWindowHint);setCentralWidget(_reset_dlg);_login_dlg->hide();_reset_dlg->show();//注册返回登录信号和槽函数connect(_reset_dlg, &ResetDialog::switchLogin, this, &MainWindow::SlotSwitchLogin2);}
ResetDialog是我们添加的界面类,新建ResetDialog界面类,界面布局如下

重置界面
#include "resetdialog.h"#include "ui_resetdialog.h"#include <QDebug>#include <QRegularExpression>#include "global.h"#include "httpmgr.h"ResetDialog::ResetDialog(QWidget *parent) :QDialog(parent),ui(new Ui::ResetDialog){ui->setupUi(this);connect(ui->user_edit,&QLineEdit::editingFinished,this,[this](){checkUserValid();});connect(ui->email_edit, &QLineEdit::editingFinished, this, [this](){checkEmailValid();});connect(ui->pwd_edit, &QLineEdit::editingFinished, this, [this](){checkPassValid();});connect(ui->varify_edit, &QLineEdit::editingFinished, this, [this](){checkVarifyValid();});//连接reset相关信号和注册处理回调initHandlers();connect(HttpMgr::GetInstance().get(), &HttpMgr::sig_reset_mod_finish, this,&ResetDialog::slot_reset_mod_finish);}
下面是检测逻辑
bool ResetDialog::checkUserValid(){if(ui->user_edit->text() == ""){AddTipErr(TipErr::TIP_USER_ERR, tr("用户名不能为空"));return false;}DelTipErr(TipErr::TIP_USER_ERR);return true;}bool ResetDialog::checkPassValid(){auto pass = ui->pwd_edit->text();if(pass.length() < 6 || pass.length()>15){//提示长度不准确AddTipErr(TipErr::TIP_PWD_ERR, tr("密码长度应为6~15"));return false;}// 创建一个正则表达式对象,按照上述密码要求// 这个正则表达式解释:// ^[a-zA-Z0-9!@#$%^&*]{6,15}$ 密码长度至少6,可以是字母、数字和特定的特殊字符QRegularExpression regExp("^[a-zA-Z0-9!@#$%^&*]{6,15}$");bool match = regExp.match(pass).hasMatch();if(!match){//提示字符非法AddTipErr(TipErr::TIP_PWD_ERR, tr("不能包含非法字符"));return false;;}DelTipErr(TipErr::TIP_PWD_ERR);return true;}bool ResetDialog::checkEmailValid(){//验证邮箱的地址正则表达式auto email = ui->email_edit->text();// 邮箱地址的正则表达式QRegularExpression regex(R"((\w+)(\.|_)?(\w*)@(\w+)(\.(\w+))+)");bool match = regex.match(email).hasMatch(); // 执行正则表达式匹配if(!match){//提示邮箱不正确AddTipErr(TipErr::TIP_EMAIL_ERR, tr("邮箱地址不正确"));return false;}DelTipErr(TipErr::TIP_EMAIL_ERR);return true;}bool ResetDialog::checkVarifyValid(){auto pass = ui->varify_edit->text();if(pass.isEmpty()){AddTipErr(TipErr::TIP_VARIFY_ERR, tr("验证码不能为空"));return false;}DelTipErr(TipErr::TIP_VARIFY_ERR);return true;}void ResetDialog::AddTipErr(TipErr te, QString tips){_tip_errs[te] = tips;showTip(tips, false);}void ResetDialog::DelTipErr(TipErr te){_tip_errs.remove(te);if(_tip_errs.empty()){ui->err_tip->clear();return;}showTip(_tip_errs.first(), false);}
显示接口
void ResetDialog::showTip(QString str, bool b_ok){if(b_ok){ui->err_tip->setProperty("state","normal");}else{ui->err_tip->setProperty("state","err");}ui->err_tip->setText(str);repolish(ui->err_tip);}
获取验证码
void ResetDialog::on_varify_btn_clicked(){qDebug()<<"receive varify btn clicked ";auto email = ui->email_edit->text();auto bcheck = checkEmailValid();if(!bcheck){return;}//发送http请求获取验证码QJsonObject json_obj;json_obj["email"] = email;HttpMgr::GetInstance()->PostHttpReq(QUrl(gate_url_prefix+"/get_varifycode"),json_obj, ReqId::ID_GET_VARIFY_CODE,Modules::RESETMOD);}
初始化回包处理逻辑
void ResetDialog::initHandlers(){//注册获取验证码回包逻辑_handlers.insert(ReqId::ID_GET_VARIFY_CODE, [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 ;});//注册注册用户回包逻辑_handlers.insert(ReqId::ID_RESET_PWD, [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 ;qDebug()<< "user uuid is " << jsonObj["uuid"].toString();});}
根据返回的id调用不同的回报处理逻辑
void ResetDialog::slot_reset_mod_finish(ReqId id, QString res, ErrorCodes err){if(err != ErrorCodes::SUCCESS){showTip(tr("网络请求错误"),false);return;}// 解析 JSON 字符串,res需转化为QByteArrayQJsonDocument jsonDoc = QJsonDocument::fromJson(res.toUtf8());//json解析错误if(jsonDoc.isNull()){showTip(tr("json解析错误"),false);return;}//json解析错误if(!jsonDoc.isObject()){showTip(tr("json解析错误"),false);return;}//调用对应的逻辑,根据id回调。_handlers[id](jsonDoc.object());return;}
这里实现发送逻辑
void ResetDialog::on_sure_btn_clicked(){bool valid = checkUserValid();if(!valid){return;}valid = checkEmailValid();if(!valid){return;}valid = checkPassValid();if(!valid){return;}valid = checkVarifyValid();if(!valid){return;}//发送http重置用户请求QJsonObject json_obj;json_obj["user"] = ui->user_edit->text();json_obj["email"] = ui->email_edit->text();json_obj["passwd"] = xorString(ui->pwd_edit->text());json_obj["varifycode"] = ui->varify_edit->text();HttpMgr::GetInstance()->PostHttpReq(QUrl(gate_url_prefix+"/reset_pwd"),json_obj, ReqId::ID_RESET_PWD,Modules::RESETMOD);}
注册、重置、登录切换
我们要实现注册、重置、登录三个界面的替换,就需要在MainWindow中添加SlotSwitchLogin2的实现
//从重置界面返回登录界面void MainWindow::SlotSwitchLogin2(){//创建一个CentralWidget, 并将其设置为MainWindow的中心部件_login_dlg = new LoginDialog(this);_login_dlg->setWindowFlags(Qt::CustomizeWindowHint|Qt::FramelessWindowHint);setCentralWidget(_login_dlg);_reset_dlg->hide();_login_dlg->show();//连接登录界面忘记密码信号connect(_login_dlg, &LoginDialog::switchReset, this, &MainWindow::SlotSwitchReset);//连接登录界面注册信号connect(_login_dlg, &LoginDialog::switchRegister, this, &MainWindow::SlotSwitchReg);}
服务端响应重置
在LogicSystem的构造函数中增加注册逻辑
//重置回调逻辑RegPost("/reset_pwd", [](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();//先查找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;}//查询数据库判断用户名和邮箱是否匹配bool email_valid = MysqlMgr::GetInstance()->CheckEmail(name, email);if (!email_valid) {std::cout << " user email not match" << std::endl;root["error"] = ErrorCodes::EmailNotMatch;std::string jsonstr = root.toStyledString();beast::ostream(connection->_response.body()) << jsonstr;return true;}//更新密码为最新密码bool b_up = MysqlMgr::GetInstance()->UpdatePwd(name, pwd);if (!b_up) {std::cout << " update pwd failed" << std::endl;root["error"] = ErrorCodes::PasswdUpFailed;std::string jsonstr = root.toStyledString();beast::ostream(connection->_response.body()) << jsonstr;return true;}std::cout << "succeed to update password" << pwd << std::endl;root["error"] = 0;root["email"] = email;root["user"] = name;root["passwd"] = pwd;root["varifycode"] = src_root["varifycode"].asString();std::string jsonstr = root.toStyledString();beast::ostream(connection->_response.body()) << jsonstr;return true;});
在Mysql中新增CheckEmail和UpdatePwd函数
bool MysqlMgr::CheckEmail(const std::string& name, const std::string& email) {return _dao.CheckEmail(name, email);}bool MysqlMgr::UpdatePwd(const std::string& name, const std::string& pwd) {return _dao.UpdatePwd(name, pwd);}
DAO这一层写具体的逻辑, 检测邮箱是否合理
bool MysqlDao::CheckEmail(const std::string& name, const std::string& email) {auto con = pool_->getConnection();try {if (con == nullptr) {pool_->returnConnection(std::move(con));return false;}// 准备查询语句std::unique_ptr<sql::PreparedStatement> pstmt(con->prepareStatement("SELECT email FROM user WHERE name = ?"));// 绑定参数pstmt->setString(1, name);// 执行查询std::unique_ptr<sql::ResultSet> res(pstmt->executeQuery());// 遍历结果集while (res->next()) {std::cout << "Check Email: " << res->getString("email") << std::endl;if (email != res->getString("email")) {pool_->returnConnection(std::move(con));return false;}pool_->returnConnection(std::move(con));return true;}}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 false;}}
更新密码
bool MysqlDao::UpdatePwd(const std::string& name, const std::string& newpwd) {auto con = pool_->getConnection();try {if (con == nullptr) {pool_->returnConnection(std::move(con));return false;}// 准备查询语句std::unique_ptr<sql::PreparedStatement> pstmt(con->prepareStatement("UPDATE user SET pwd = ? WHERE name = ?"));// 绑定参数pstmt->setString(2, name);pstmt->setString(1, newpwd);// 执行更新int updateCount = pstmt->executeUpdate();std::cout << "Updated rows: " << updateCount << std::endl;pool_->returnConnection(std::move(con));return true;}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 false;}}
有个问题,在检查邮箱号里,不应该是遍历结果集吗,但你的实现只能循环一次就会退出。
下面是我改的实现。