注册用户流程介绍
注册新用户涉及到用户重名检测,服务器防止DOS攻击的验证码请求,用户密码二次check,提交服务器进行用户注册等流程,下面用简要流程图表示
重名检查
用户注册采用http/https的api接口,使用drogon框架web开发框架,更多使用流程请查看drogon的相关文档,本文介绍http客户端请求相关流程。
drogon框架http客户端独立线程封装ServerApi
http客户端封装为独立线程,与mainwindow的ui直接用QT的信号与槽进行交互。
//http客户端api调用独立线程
class ServerApi : public QThread
{
Q_OBJECT
public:
explicit ServerApi(QObject* parent = Q_NULLPTR);
~ServerApi() = default;
bool init(const std::string& userName, const std::string& password); //初始化数据库
ChatMessagePtr findChatMessageById(const std::string& messageId); //根据ID同步查询某条消息, 在消息引用时需要
bool updateChatMessageStatus(std::string messageId, proto::EMessageStatus status); //更新某条聊天消息状态
void deleteChatMessageById(const std::string& messageId); //根据ID将消除从本地sqliet3数据库中删除
std::deque<ChatMessagePtr> findFriendChatMessages(int64_t friendCode, int64_t createTime, bool group = false); //查找某个好友的聊天数据
Task<std::deque<ChatMessagePtr>> findFriendChatMessagesTask(int64_t friendCode, int64_t createTime, bool group = false);
void stop(); //停止drogon 的http client线程
void setAuthServer(std::string server); //设置在线服务器
public slots:
void onRegisterUserRequested(QString userName, QString password, int64_t timestamp, QString requestId, QString imageCode, QString nickname); //1. 用户注册
void onCheckUserRequested(QString userName); //2. 重名检查
void onImageCodeRequested(QString requestId, QString userName, IMAGE_CODE_TYPE source); //3. 图片验证码
void onEmailCodeRequested(std::string requestId, std::string email, std::string imageCode, std::string imageCodeRequestId); //5. 请求邮箱验证码
void onFindUserRequested(std::string requestId, std::string email, std::string emailCode, std::string imageCode, std::string emailCodeRequestId, std::string imageCodeRequestId);
void onResetPasswordRequested(std::string requestId, std::string userName, std::string email, std::string password, int64_t timestamp, std::string emailCode, std::string emailCodeRequestId,
std::string imageCode, std::string imageCodeRequestId); //4. 请求 重置密码 http通道//6. 通过email找回用户
signals:
void registerUserResult(bool result, QString userName, QString password, qint64 timestamp, QString message); //注册用户结果
void checkUserResult(bool result); //重名检查结果
void imageCodeResultForChangePwd(bool result, QString requestId, QString base64Png, QString message); //图形验证码 修改密码
void imageCodeResultForUserRegister(bool result, QString requestId, QString base64Png, QString message); //图形验证码 用户注册
void imageCodeResultForFindPassword(bool result, QString requestId, QString base64Png, QString message); //图形验证码 找回密码
void emailCodeResult(bool result, std::string requestId, std::string email, std::string message); //email验证码 结果
void findUserResult(bool result, std::string requestId, std::string email, std::string userName, std::string message); //找回用户结果
void resetPasswordResult(bool result, std::string requestId, std::string email, std::string userName, std::string message); //重置密码结果
void changePasswordResult(bool result, QString newPassword, int64_t timestamp); //修改密码结果
protected:
void run();
private:
void handleImageCodeResult(bool result, QString requestId, QString base64Png, QString message, IMAGE_CODE_TYPE source); //验证码结果
DbClientPtr dbClient_ = nullptr;
std::string userName_; //用户名
QString password_; //密码
std::string server_; //服务器信息
QString userId_; //用户id
QString token_; //token
DISALLOW_COPY_AND_ASSIGN(ServerApi);
};
} // namespace client
api封装实现举例,1. drogon线程运行run;2. 重名检测;3. 用户注册请求
ServerApi::ServerApi(QObject* parent)
{}
//线程启动运行, drogon eventloop独立线程
void ServerApi::run()
{
//Run HTTP framework,the method will block in the internal event loop
trantor::Logger::setLogLevel(trantor::Logger::kInfo);
std::cout << "trantor logger level: " << trantor::Logger::logLevel();
qRegisterMetaType<ChatMessageDeque>("ChatMessageDeque&");
app().run();
}
//停止drogon 的http client线程
void ServerApi::stop()
{
app().quit();
}
/* 1.请求注册用户*/
void ServerApi::onRegisterUserRequested(QString userName, QString password, qint64 timestamp, QString requestId, QString imageCode, QString nickname)
{
//参数错误
if (userName.isEmpty() || password.isEmpty() || timestamp <= 0 || imageCode.isEmpty() || nickname.isEmpty())
return;
//http 请求的body中设置json的key value
Json::Value data;
data["requestId"] = requestId.toStdString(); //requestId
data["imageCode"] = imageCode.toStdString(); //imagecode
data["timestamp"] = timestamp; //加密用的时戳
data["userName"] = userName.toStdString(); //用户名
data["password"] = password.toStdString(); //密码
data["nickname"] = nickname.toStdString(); //昵称
qDebug("request register user, name: %s, password: %s, timestamp: %lld", userName.toStdString().c_str(), password.toStdString().c_str(), timestamp);
//json to string
std::string uri = "/api/users/register";
//http请求参数设置
auto req = HttpRequest::newHttpJsonRequest(data);
req->setMethod(drogon::Post);
req->setPath(uri);
//http请求响应处理
auto client = HttpClient::newHttpClient(server_);
client->sendRequest(req, [this, uri, userName, password, timestamp](ReqResult result, const HttpResponsePtr& resp) {
//获取请求结果失败
if (result != ReqResult::Ok) {
LOG(LS_ERROR) << "Failed to sendRequest uri: " << uri;
Q_EMIT registerUserResult(false, userName, password, timestamp, QString::fromStdString("server return error json message"));
return;
}
LOG(LS_INFO) << "Result: " << resp->getBody();
//响应类型是否为json
if (resp->contentType() != CT_APPLICATION_JSON) {
LOG(LS_ERROR) << "uri: " << uri << ", response content type error";
Q_EMIT registerUserResult(false, userName, password, timestamp, QString::fromStdString("response content type error"));
return;
}
//解析json数据
auto resultJson = resp->getJsonObject();
if (!resultJson) {
LOG(LS_ERROR) << "Error: uri: " << uri << ", Failed to get response json";
Q_EMIT registerUserResult(false, userName, password, timestamp, QString::fromStdString("server return error json message"));
return;
}
else {
std::string code = (*resultJson)["code"].asString();
std::string message = (*resultJson)["message"].asString();
if (HTTP_RESPONSE_OK == std::stoi(code)) {
//注册成功, 回传新密码 时戳信息保存到本地
LOG(LS_INFO) << "Success: uri: " << uri;
Q_EMIT registerUserResult(true, userName, password, timestamp, QString::fromStdString(message));
}
else {
LOG(LS_INFO) << "uri: " << uri << ", response error code: " << code << ", message: " << message;
Q_EMIT registerUserResult(false, userName, password, timestamp, QString::fromStdString(message));
}
}
});
}
/* 2.注册用户的重名检查*/
void ServerApi::onCheckUserRequested(QString userName)
{
//参数错误
if (userName.isEmpty())
return;
//http 请求的body中设置json的key value
Json::Value data;
data["userName"] = userName.toStdString(); //要检查的用户名
LOG(LS_INFO) << "request check username: " << userName.toStdString();
//json to string
std::string uri = "/api/users/nameCheck";
//http请求参数设置
auto req = HttpRequest::newHttpJsonRequest(data);
req->setMethod(drogon::Post);
req->setPath(uri);
//http请求响应处理
auto client = HttpClient::newHttpClient(server_);
client->sendRequest(req, [this, uri, userName](ReqResult result, const HttpResponsePtr& resp) {
//获取请求结果失败
if (result != ReqResult::Ok) {
LOG(LS_ERROR) << "Failed to sendRequest uri: " << uri;
Q_EMIT checkUserResult(false); //检查结果 信号, 由 registerDialog 处理
return;
}
LOG(LS_INFO) << "Result: " << resp->getBody();
//响应类型是否为json
if (resp->contentType() != CT_APPLICATION_JSON) {
LOG(LS_ERROR) << "uri: " << uri << ", response content type error";
Q_EMIT checkUserResult(false);
return;
}
//解析服务器返回的json格式数据
//解析json数据
auto resultJson = resp->getJsonObject();
if (!resultJson) {
LOG(LS_ERROR) << "Error: uri: " << uri << ", Failed to get response json";
Q_EMIT checkUserResult(false); //检查结果 信号, 由 registerDialog 处理
return;
}
else {
std::string code = (*resultJson)["code"].asString();
std::string message = (*resultJson)["message"].asString();
if (HTTP_RESPONSE_OK == std::stoi(code)) {
//注册成功, 回传新密码 时戳信息保存到本地
LOG(LS_INFO) << "Success: uri: " << uri;
auto dataObj = (*resultJson)["data"];
if (!dataObj) {
LOG(LS_ERROR) << "uri: " << uri << "Failed to parse data json obj";
Q_EMIT checkUserResult(false);
return;
}
bool checkResult = dataObj["exist"].asBool();
if (true == checkResult) {
//注册成功, 回传新密码 时戳信息保存到本地
Q_EMIT checkUserResult(false);
}
else {
//注册成功, 回传新密码 时戳信息保存到本地
Q_EMIT checkUserResult(true);
}
qDebug() << "request check user name ok, result: " << checkResult;
}
else {
LOG(LS_INFO) << "uri: " << uri << ", response error code: " << code << ", message: " << message;
Q_EMIT checkUserResult(false); //检查结果 信号, 由 registerDialog 处理
}
}
});
}
mainwindow中调用http的api接口
这里要处理的api接口比较多,本节介绍重名检查接口调用,其他接口调用使用类似。
-
- 在mainwindow中定义并创建server api独立线程的对象,启动drogon http客户端独立线程
//http接口的 drogon 线程
std::shared_ptr <ServerApi> serverApi_; //drogon http/数据库api
// 构造函数
MainWindow::MainWindow(QWidget* parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
......
//drogon的http和数据库操作
serverApi_ = std::make_shared<ServerApi>(); //auth server api
serverApi_->start();
......
-
- 用户注册处理函数中链接重名检查,验证码,用户注册等信号与槽
//注册新用户
void MainWindow::onRegisterUser()
{
RegisterDialog dialog;
dialog.setWindowTitle(tr("New user"));
connect(&dialog, &RegisterDialog::requestCheckName, serverApi_.get(), &ServerApi::onCheckUserRequested); //1. 请求检查用户重名
connect(serverApi_.get(), &ServerApi::checkUserResult, &dialog, &RegisterDialog::onCheckUserResult); //2. 收到 用户重名检查结果
connect(&dialog, &RegisterDialog::requestImageCode, serverApi_.get(), &ServerApi::onImageCodeRequested); //3. 图形验证码请求
connect(serverApi_.get(), &ServerApi::imageCodeResultForUserRegister, &dialog, &RegisterDialog::onImageCodeResult); //4. 收到 图形验证码 结果
connect(&dialog, &RegisterDialog::requestRegisterUser, serverApi_.get(), &ServerApi::onRegisterUserRequested); //5. 请求注册用户
connect(serverApi_.get(), &ServerApi::registerUserResult, &dialog, &RegisterDialog::onRegisterUserResult); //6. 注册用户结果
//打开对话框
if (dialog.exec() == QDialog::Accepted) {
//将用户名 密码 加密用的时戳信息保存到本地
userName_ = dialog.getUserName();
password_ = dialog.getPassword();
timestamp_ = dialog.timestamp();
//发送消息给service
if (connectServiceState_) {
sendCredentialsRequest(true);
//ui->ipcStateLabel->setText(userName_);
//ui->ipcStateLabel->setStyleSheet("color:#CFCFCF;");
}
}
}
//检查用户重名结果
void RegisterDialog::onCheckUserResult(bool result)
{
if (true != result) {
userCheckResultLabel_->setText("NOK");
userCheckResultLabel_->setStyleSheet("background: transparent; border-style:none; color: red"); //设置按钮 透明背景, 无边框, 字体颜色 蓝色
}
else {
userCheckResultLabel_->setText("OK");
userCheckResultLabel_->setStyleSheet("background: transparent; border-style:none; color: green"); //设置按钮 透明背景, 无边框, 字体颜色 蓝色
}
}
/*
* 注册用户结果
* true: 注册成功 保存用户名 密码 时戳信息到本地的 QSetting 中
*/
void MainWindow::onRegisterUserResult(bool result, QString userName, QString password, int64_t timestamp, QString message)
{
if (!result) {
QMessageBox msg;
msg.setText(message);
msg.setStandardButtons(QMessageBox::Yes);
msg.exec();
QMessageBox::warning(this, tr("Register new user"), tr("Restart service first , login to server using new user") + message, QMessageBox::Ok);
return;
}
//将用户名 密码 加密用的时戳信息保存到本地
userName_ = userName;
password_ = password;
timestamp_ = timestamp;
//发送消息给service
if (connectServiceState_) {
sendCredentialsRequest(true);
//ui->ipcStateLabel->setText(userName_);
//ui->ipcStateLabel->setStyleSheet("color:#CFCFCF;");
QMessageBox::information(this, tr("Register new user"), tr("new user register ok, starting service..."), QMessageBox::Ok);
}
else {
QMessageBox::warning(this, tr("Register new user"), tr("Restart service first , login to server using new user"), QMessageBox::Ok);
}
}
点击注册按钮流程
//确认按钮发起注册用户
void RegisterDialog::onOk()
{
userName_ = ui->userNameEdit->text();
password_ = ui->passwordEdit->text();
imageCode_ = ui->imageCodeEdit->text();
QString confirmPwd = ui->confirmPasswordEdit->text();
//用户名 密码不能为空
if (userName_.isEmpty() || password_.isEmpty() || confirmPwd.isEmpty()) {
QMessageBox msg;
msg.setText(tr("User or password cannot be empty"));
msg.setStandardButtons(QMessageBox::Yes);
msg.exec();
return;
}
if (userName_.size() < 4) {
QMessageBox msg;
msg.setText(tr("Cannot be less than 4 characters"));
msg.setStandardButtons(QMessageBox::Yes);
msg.exec();
return;
}
//前后密码要一直
if (password_ != confirmPwd) {
QMessageBox msg;
msg.setText(tr("Check your password again"));
msg.setStandardButtons(QMessageBox::Yes);
msg.exec();
return;
}
if (imageCode_.isEmpty()) {
QMessageBox msg;
msg.setText(tr("Image code cannot be empty"));
msg.setStandardButtons(QMessageBox::Yes);
msg.exec();
return;
}
if (ui->nicknameEdit->text().isEmpty()) {
QMessageBox::warning(this, tr("Register a new user"), tr("nickname cannot be empty"), QMessageBox::Yes);
return;
}
//aes加密后传输
QDateTime currentTime = QDateTime::currentDateTime();
int64_t timestamp = QDateTime::currentMSecsSinceEpoch();
std::string encryptPassword; //加密后的老密码
bool ret = base::AesUtils::encryptPassword(userName_.toStdString(), password_.toStdString(), timestamp, encryptPassword);
if (true != ret) {
return;
}
//提交注册新用户信号
emit requestRegisterUser(userName_, QString::fromStdString(encryptPassword), timestamp, requestId_, imageCode_, ui->nicknameEdit->text());
}