完善proto
在proto文件里新增登陆验证服务
message LoginReq{int32 uid = 1;string token= 2;}message LoginRsp {int32 error = 1;int32 uid = 2;string token = 3;}service StatusService {rpc GetChatServer (GetChatServerReq) returns (GetChatServerRsp) {}rpc Login(LoginReq) returns(LoginRsp);}
接下来是调用grpc命令生成新的pb文件覆盖原有的,并且也拷贝给StatusServer一份
我们完善登陆逻辑,先去StatusServer验证token是否合理,如果合理再从内存中寻找用户信息,如果没找到则从数据库加载一份。
void LogicSystem::LoginHandler(shared_ptr<CSession> session, const short &msg_id, const string &msg_data) {Json::Reader reader;Json::Value root;reader.parse(msg_data, root);auto uid = root["uid"].asInt();std::cout << "user login uid is " << uid << " user token is "<< root["token"].asString() << endl;//从状态服务器获取token匹配是否准确auto rsp = StatusGrpcClient::GetInstance()->Login(uid, root["token"].asString());Json::Value rtvalue;Defer defer([this, &rtvalue, session]() {std::string return_str = rtvalue.toStyledString();session->Send(return_str, MSG_CHAT_LOGIN_RSP);});rtvalue["error"] = rsp.error();if (rsp.error() != ErrorCodes::Success) {return;}//内存中查询用户信息auto find_iter = _users.find(uid);std::shared_ptr<UserInfo> user_info = nullptr;if (find_iter == _users.end()) {//查询数据库user_info = MysqlMgr::GetInstance()->GetUser(uid);if (user_info == nullptr) {rtvalue["error"] = ErrorCodes::UidInvalid;return;}_users[uid] = user_info;}else {user_info = find_iter->second;}rtvalue["uid"] = uid;rtvalue["token"] = rsp.token();rtvalue["name"] = user_info->name;}
StatusServer验证token
在StatusServer验证token之前,我们需要在StatusServer中的GetServer的服务里将token写入内存
Status StatusServiceImpl::GetChatServer(ServerContext* context, const GetChatServerReq* request, GetChatServerRsp* reply){std::string prefix("llfc status server has received : ");const auto& server = getChatServer();reply->set_host(server.host);reply->set_port(server.port);reply->set_error(ErrorCodes::Success);reply->set_token(generate_unique_string());insertToken(request->uid(), reply->token());return Status::OK;}
接下来我们实现登陆验证服务
Status StatusServiceImpl::Login(ServerContext* context, const LoginReq* request, LoginRsp* reply){auto uid = request->uid();auto token = request->token();std::lock_guard<std::mutex> guard(_token_mtx);auto iter = _tokens.find(uid);if (iter == _tokens.end()) {reply->set_error(ErrorCodes::UidInvalid);return Status::OK;}if (iter->second != token) {reply->set_error(ErrorCodes::TokenInvalid);return Status::OK;}reply->set_error(ErrorCodes::Success);reply->set_uid(uid);reply->set_token(token);return Status::OK;}
这样当GateServer访问StatusServer的Login服务做验证后,就可以将数据返回给QT前端了。
客户端处理登陆回包
QT 的客户端TcpMgr收到请求后要进行对应的逻辑处理。所以我们在TcpMgr的构造函数中调用initHandlers注册消息
void TcpMgr::initHandlers(){//auto self = shared_from_this();_handlers.insert(ID_CHAT_LOGIN_RSP, [this](ReqId id, int len, QByteArray data){qDebug()<< "handle id is "<< id << " data is " << data;// 将QByteArray转换为QJsonDocumentQJsonDocument jsonDoc = QJsonDocument::fromJson(data);// 检查转换是否成功if(jsonDoc.isNull()){qDebug() << "Failed to create QJsonDocument.";return;}QJsonObject jsonObj = jsonDoc.object();if(!jsonObj.contains("error")){int err = ErrorCodes::ERR_JSON;qDebug() << "Login Failed, err is Json Parse Err" << err ;emit sig_login_failed(err);return;}int err = jsonObj["error"].toInt();if(err != ErrorCodes::SUCCESS){qDebug() << "Login Failed, err is " << err ;emit sig_login_failed(err);return;}UserMgr::GetInstance()->SetUid(jsonObj["uid"].toInt());UserMgr::GetInstance()->SetName(jsonObj["name"].toString());UserMgr::GetInstance()->SetToken(jsonObj["token"].toString());emit sig_swich_chatdlg();});}
并且增加处理请求
void TcpMgr::handleMsg(ReqId id, int len, QByteArray data){auto find_iter = _handlers.find(id);if(find_iter == _handlers.end()){qDebug()<< "not found id ["<< id << "] to handle";return ;}find_iter.value()(id,len,data);}
用户管理
为管理用户数据,需要创建一个UserMgr类,统一管理用户数据,我们这么声明
#ifndef USERMGR_H#define USERMGR_H#include <QObject>#include <memory>#include <singleton.h>class UserMgr:public QObject,public Singleton<UserMgr>,public std::enable_shared_from_this<UserMgr>{Q_OBJECTpublic:friend class Singleton<UserMgr>;~ UserMgr();void SetName(QString name);void SetUid(int uid);void SetToken(QString token);private:UserMgr();QString _name;QString _token;int _uid;};#endif // USERMGR_H
简单实现几个功能
#include "usermgr.h"UserMgr::~UserMgr(){}void UserMgr::SetName(QString name){_name = name;}void UserMgr::SetUid(int uid){_uid = uid;}void UserMgr::SetToken(QString token){_token = token;}UserMgr::UserMgr(){}
详细和复杂的管理后续不断往这里补充就行了。
登陆界面
登陆界面响应TcpMgr返回的登陆请求,在其构造函数中添加
//连接tcp管理者发出的登陆失败信号connect(TcpMgr::GetInstance().get(), &TcpMgr::sig_login_failed, this, &LoginDialog::slot_login_failed);
并实现槽函数
void LoginDialog::slot_login_failed(int err){QString result = QString("登录失败, err is %1").arg(err);showTip(result,false);enableBtn(true);}
到此完成了登陆的请求和响应,接下来要实现响应登陆成功后跳转到聊天界面。下一篇先实现聊天布局。