当前位置: 首页 > news >正文

QT聊天项目DAY14

1. 客户端登录

1.1 初始化玩家头像

将头像的大小固定在250 * 250

void InitHeadImage();																			// 初始化头像/* 初始化头像 */
void LoginWidget::InitHeadImage()
{// 加载头像QPixmap OriginalPixmap(":/Chat/Images/head_5.jpg");OriginalPixmap = OriginalPixmap.scaled(ui.Head_Label->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);// 绘制圆形头像QPixmap RoundPixmap(ui.Head_Label->size());RoundPixmap.fill(Qt::transparent);														// 用透明色填充QPainter painter(&RoundPixmap);painter.setRenderHint(QPainter::Antialiasing);											// 抗锯齿painter.setRenderHint(QPainter::SmoothPixmapTransform);									// 平滑缩放// 使用QPainterPath设置圆角QPainterPath path;path.addRoundedRect(0, 0, ui.Head_Label->width(), ui.Head_Label->height(), 10, 10);painter.setClipPath(path);// 将原始图片绘制到圆形头像上painter.drawPixmap(0, 0, OriginalPixmap);// 设置头像ui.Head_Label->setPixmap(RoundPixmap);
}

1.2 登录界面新增err_Tip

设置成水平居中,并且最大最小高度为25

添加文本刷新Function

void ShowTipLabel(const QString& tip, const QString& State);									// 显示提示信息
void AddTipErr(TipErr err, const QString& tips);												// 添加错误提示
void DelTipErr(TipErr err);																		// 清除错误提示QMap<TipErr, QString> _TipErrs;																	// 错误提示记录
#include "Global.h"
ui.Tip_Label->clear();/* 刷新文本提示标签的样式 */
void LoginWidget::ShowTipLabel(const QString& tip, const QString& State)
{ui.Tip_Label->setText(tip);ui.Tip_Label->setProperty("state", State);repolish(ui.Tip_Label);
}/* 添加错误提示 */
void LoginWidget::AddTipErr(TipErr err, const QString& tips)
{_TipErrs[err] = tips;ShowTipLabel(tips, "error");
}/* 删除错误提示 */
void LoginWidget::DelTipErr(TipErr err)
{_TipErrs.remove(err);if (_TipErrs.isEmpty()){ui.Tip_Label->clear();return;}ShowTipLabel(_TipErrs.first(), "error");
}

1.3 添加控制密码是否可见的标签

添加标签控件,将该标签提升为clickedLabel,重定义名称为PassWord_Visible并设置固定大小为20 * 20;

为标签设置样式

/* 初始化可视化控件 */
ui.PassWord_Visible->SetState("unvisible", "unvisible_hover", "", "visible","visible_hover", "");/* 密码可见性 */
connect(ui.PassWord_Visible, &ClickedLabel::clicked, this, [this](){auto state = ui.PassWord_Visible->GetState();if (state == ClickLabelState::Normal){ui.PassWord_Edit->setEchoMode(QLineEdit::Password);}else if (state == ClickLabelState::Selected){ui.PassWord_Edit->setEchoMode(QLineEdit::Normal);}});

1.4 登陆验证

点击登录需要发送http请求到GateServer,GateServer先验证登录密码,在调用grpc请求StatusServer,获取聊天服务器ip信息和token信息反馈给客户端

1.4.1 添加登录按钮的槽函数

1. 检查用户名输入以及密码输入是否有问题

bool CheckUserValid();																			// 检查用户名是否合法
bool CheckPasswordValid();																		// 检查密码是否合法
/* 检查用户名是否合法 */
bool LoginWidget::CheckUserValid()
{if (ui.User_Edit->text().isEmpty()){AddTipErr(TipErr::TIP_USER_ERR, QString::fromLocal8Bit("用户名不能为空"));return false;}/* 一切正常则删除错误提示 */DelTipErr(TipErr::TIP_USER_ERR);return true;
}/* 检验密码是否合法 */
bool LoginWidget::CheckPasswordValid()
{QString passText = ui.PassWord_Edit->text();if (passText.length() < 6 || passText.length() > 15){AddTipErr(TipErr::TIP_PWD_ERR, QString::fromLocal8Bit("密码长度必须在6-15位之间"));return false;}/* 密码长度至少6位 可以是字母、数字、特定的特殊字符 */QRegularExpression regExp("^[a-zA-Z0-9!@#$%^&*]{6,15}$");bool match = regExp.match(passText).hasMatch();if (!match){AddTipErr(TipErr::TIP_PWD_ERR, QString::fromLocal8Bit("不能包含非法字符"));return false;}/* 一切正常则删除错误提示 */DelTipErr(TipErr::TIP_PWD_ERR);return true;
}

2. 实现槽函数逻辑

enum ReqID
{ID_GET_VARIFY_CODE = 1001,			// 获取验证码ID_REG_USER		   = 1002,			// 注册用户ID_RESET_PWD       = 1003,          // 重置密码ID_LOGIN_USER      = 1004,          // 登录用户
};enum Modules
{REGISTERMOD		   = 0,				// 注册模块LOGINMOD		   = 1,				// 登录模块
};[GateServer]
host=localhost
port=8080
getVerifycode=getVarifycode
RegisterUser=RegisterUser
ResetUserPassword=ResetUserPassword
LoginUser=LoginUser/* 登录按钮点击时 */
void LoginWidget::OnLoginButtonClicked()
{if (!CheckUserValid())return;if (!CheckPasswordValid())return;QString user = ui.User_Edit->text();QString pass = ui.PassWord_Edit->text();// 发送http请求QJsonObject json;json["user"] = user;json["password"] = xorString(pass);// 创建URLQString urlStr = "http://" +ConfigSettings->value("GateServer/host").toString() + ":"+ ConfigSettings->value("GateServer/port").toString() + "//"+ ConfigSettings->value("GateServer/LoginUser").toString();QUrl url(urlStr);HttpManager::Instance()->PostHttpReq(url, json, ReqID::ID_LOGIN_USER, Modules::LOGINMOD);
}

1.4.2 处理客户端发来的响应

1. 添加对应ReqId的回调

Struct.h

#ifndef STRUCT_H
#define STRUCT_H#include <QString>/* 服务器信息 */
struct ServerInfo {QString Host;QString Port;QString Token;int Uid;
};#endif // STRUCT_H
void sig_connect_tcp(ServerInfo serverInfo);													// 连接聊天服务器void InitHttpHandlers();																		// 设置网络回包的回调函数QMap<ReqID, std::function<void(const QJsonObject&)>> _handlers;									// 不同类型的回调函数/* 网络回包的处理函数 */
void LoginWidget::InitHttpHandlers()
{// 注册获取验证码回包的逻辑_handlers.insert(ReqID::ID_LOGIN_USER, [this](const QJsonObject& jsonObj){int error = jsonObj["error"].toInt();if (error != ErrorCodes::SUCCESS){ShowTipLabel(QString::fromLocal8Bit("参数错误"), "error");return;}auto email = jsonObj["email"].toString();// 发送信号通知TcpMgr发送长链接ServerInfo serverInfo;serverInfo.Uid = jsonObj["uid"].toInt();serverInfo.Host = jsonObj["host"].toString();serverInfo.Port = jsonObj["port"].toInt();serverInfo.Token = jsonObj["token"].toString();_Uid = serverInfo.Uid;_Token = serverInfo.Token;emit sig_connect_tcp(serverInfo);ShowTipLabel(QString::fromLocal8Bit("登陆成功"), "normal");qDebug() << QString::fromLocal8Bit("登陆成功 user: ") << email;});
}

2. 实现处理服务器响应的槽函数

/* 服务器处理完登录请求回复的响应 */
void LoginWidget::slot_login_mod_finished(ReqID reqId, QString res, ErrorCodes errCode)
{if (errCode != ErrorCodes::SUCCESS) {ShowTipLabel(QString::fromLocal8Bit("网络请求错误"), "error");return;}QJsonDocument jsonDoc = QJsonDocument::fromJson(res.toUtf8());								// .json文件if (jsonDoc.isNull()){ShowTipLabel(QString::fromLocal8Bit("json 是空的"), "error");return;}// 解析json数据if (!jsonDoc.isObject()){ShowTipLabel(QString::fromLocal8Bit("json 格式不正确"), "error");return;}// 回调函数_handlers[reqId](jsonDoc.object());
}

客户端登录请求发送的模块封装完毕

2. 服务器处理登录请求

2.1 使用RAII思想来归还池式组件的连接

// RAII 归还连接
class ConnectionRAII
{
public:ConnectionRAII(function<void()> RetunConnection): m_RetunConnection(RetunConnection){}~ConnectionRAII(){m_RetunConnection();}private:function<void()> m_RetunConnection;
};

2.2 重新定义GRPC服务并编译

message.proto

syntax = "proto3";package message;service VerifyService {rpc GetVerifyCode (GetVerifyRequest) returns (GetVerifyRsponse) {}
}message GetVerifyRequest {string email = 1;
}message GetVerifyRsponse {int32 error = 1;string email = 2;string code = 3;
}message GetChatServerRequest {int32 uid = 1;
}message GetChatServerResponse {int32 error = 1;string host = 2;string port = 3;string token = 4;
}message LoginRequest {int32 uid = 1;string token = 2;
}message LoginResponse {int32 error = 1;int32 uid = 2;string token = 3;
}service StatusService {rpc GetChatServer (GetChatServerRequest) returns (GetChatServerResponse) {}rpc Login (LoginRequest) returns (LoginResponse) {}
}

编译生成新的通信文件

H:\BoostNetLib\grpc\visualpro\third_party\protobuf\Debug\protoc.exe  -I="." --grpc_out="." --plugin=protoc-gen-grpc="H:\BoostNetLib\grpc\visualpro\Debug\grpc_cpp_plugin.exe" "message.proto"

编译生成新的序列化和反序列化文件

H:\BoostNetLib\grpc\visualpro\third_party\protobuf\Debug\protoc.exe --cpp_out=. "message.proto"

2.3 创建StatusGrpcClient

#ifndef STATUSGRPCCLIENT_H
#define STATUSGRPCCLIENT_H
#include "Singletion.h"
#include "GlobalHead.h"
#include <grpcpp/grpcpp.h>
#include "message.grpc.pb.h"using grpc::Channel;
using grpc::Status;
using grpc::ClientContext;using message::GetChatServerRequest;
using message::GetChatServerResponse;
using message::StatusService;class StatusConPool
{
public:StatusConPool(int _PoolSize, string IP):PoolSize(_PoolSize), bStop(false){for (int i = 0; i < PoolSize; i++){StatusService::Stub* stub = new StatusService::Stub(grpc::CreateChannel(IP, grpc::InsecureChannelCredentials()));conPool.push(stub);}}~StatusConPool() {lock_guard<mutex> lock(mtx);bStop = true;cv.notify_all();for (int i = 0; i < PoolSize; i++){delete conPool.front();conPool.pop();}}StatusService::Stub* GetCon(){unique_lock<mutex> lock(mtx);cv.wait(lock, [this]() {return !conPool.empty() || bStop; });if(bStop)return nullptr;StatusService::Stub* stub = conPool.front();conPool.pop();return stub;}void ReturnCon(StatusService::Stub* _con){lock_guard<mutex> lock(mtx);if(bStop)return;conPool.push(_con);cv.notify_one();}private:int PoolSize;/* 锁相关 */atomic<bool> bStop;mutex mtx;condition_variable cv;/* 连接池 */queue<StatusService::Stub*> conPool;
};class StatusGrpcClient : public Singletion<StatusGrpcClient>
{friend class Singletion<StatusGrpcClient>;
public:~StatusGrpcClient() {}GetChatServerResponse GetChatServer(int uid);private:StatusGrpcClient();private:StatusConPool* conPool = nullptr;
};#endif // STATUSGRPCCLIENT_H
#include "StatusGrpcClient.h"
#include "ServerStatic.h"StatusGrpcClient::StatusGrpcClient()
{int Port = get<int>(ServerStatic::ParseConfig("StatusServer", "Port"));string Ip = "localhost:" + to_string(Port);conPool = new StatusConPool(5, Ip);
}GetChatServerResponse StatusGrpcClient::GetChatServer(int uid)
{/* 向GRPC服务端发送请求,获取聊天服务器信息 */ClientContext context;GetChatServerResponse response;GetChatServerRequest request;request.set_uid(uid);auto stub = conPool->GetCon();Status status = stub->GetChatServer(&context, request, &response);ConnectionRAII ConRAII([this,&stub](){conPool->ReturnCon(stub);});if (!status.ok())response.set_error(ErrorCodes::RPC_FAILED);return response;
}

修改Config.json

{"GateServer": {"Port": 8080},"VerifyServer": {"Port": 50051},"StatusServer": {"Port" : 50052},"Redis": {"Host": "127.0.0.1","Port": 6380,"Password": "123456"},"Mysql": {"Host": "127.0.0.1","Port": 3306,"Username": "root","Password": "123456","Database": "YjjChat"}
}

2.4 处理登陆请求

/* 用户登录 */
RegisterPost("/LoginUser", [](HttpConnection* connection){if (connection){auto bodyStr = boost::beast::buffers_to_string(connection->_request.body().data());	// 获取 Http请求体中的内容cout << "receive body is \n" << bodyStr << endl;connection->_response.set(http::field::content_type, "text/json");					// 设置 Http响应头中的 content-typeJson::Value jsonResonse;															// 响应用的JsonJson::Value jsonResult;																// 请求体解析出来的JsonJson::Reader reader;																// Json解析器bool parseSuccess = reader.parse(bodyStr, jsonResult);								// 将请求体解析为Jsonif (!parseSuccess){cout << "parse json failed" << endl;jsonResonse["error"] = ErrorCodes::ERROR_JSON;									// 设置响应的错误码string jsonStr = jsonResonse.toStyledString();beast::ostream(connection->_response.body()) << jsonStr;						// 向 Http响应体中写入错误码内容return;}UserInfo userInfo;/* 访问Mysql检查密码是否匹配 */bool bUpdatePassword = MySqlManage::GetInstance()->CheckPassword(jsonResult["user"].asString(), jsonResult["password"].asString(), userInfo);if (!bUpdatePassword){cout << "update password failed\n";jsonResonse["error"] = ErrorCodes::Update_Password_Failed;string jsonStr = jsonResonse.toStyledString();beast::ostream(connection->_response.body()) << jsonStr;return;}/* 查询StatusServer匹配对应的服务器 */GetChatServerResponse response = StatusGrpcClient::GetInstance()->GetChatServer(userInfo.uid);if (response.error()){cout << "grpc get chat server failed: error is " << response.error() << endl;jsonResonse["error"] = response.error();string jsonStr = jsonResonse.toStyledString();beast::ostream(connection->_response.body()) << jsonStr;return;}/* 返回响应报文给客户端 */cout << "succeed to load userinfo uid is " << userInfo.uid << endl;jsonResonse["error"] = 0;jsonResonse["user"] = jsonResult["user"];jsonResonse["password"] = jsonResult["password"];string jsonStr = jsonResonse.toStyledString();beast::ostream(connection->_response.body()) << jsonStr;							// 向 Http响应体中写入Json内容return;}else{std::cout << "connection is null" << std::endl;}});

3. 创建状态服务器

3.1 创建属性页

视图->其他窗口->属性管理器 之前的项目怎么配置的现在怎么配置就行了

将pdb文件添加到静态库文件夹下

此时还是有,这是因为原本的redis库中Win32库与别的库冲突,重新修改了一份redis

3.2 填充服务器

3.2.1. 将这些文件全都添加到项目中

3.2.2. 创建新的StatusServiceImpl类

#ifndef STATUS_SERVICE_IMPL_H
#define STATUS_SERVICE_IMPL_H
#include <string>
#include <grpcpp/grpcpp.h>
#include "message.grpc.pb.h"
using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::Status;
using message::GetChatServerRequest;
using message::GetChatServerResponse;
using message::StatusService;
using namespace std;struct ChatServer
{string Host;int Port;
};class StatusServiceImpl final : public StatusService::Service
{
public:StatusServiceImpl();Status GetChatServer(ServerContext* context, const GetChatServerRequest* request,GetChatServerResponse* response) override;private:string generate_unique_string();public:std::vector<ChatServer> _servers;int ServerIndex = 0;
};
#endif // STATUS_SERVICE_IMPL_H
#include "StatusServiceImpl.h"#include "ServerStatic.h"
#include <boost/beast/http.hpp>
#include <boost/beast.hpp>
#include <boost/asio.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>StatusServiceImpl::StatusServiceImpl():ServerIndex(0)
{ChatServer server1;string Host1 = get<string>(ServerStatic::ParseConfig("ChatServer1", "Host"));int Port1 = get<int>(ServerStatic::ParseConfig("ChatServer1", "Port"));server1.Host = Host1;server1.Port = Port1;ChatServer server2;string Host2 = get<string>(ServerStatic::ParseConfig("ChatServer2", "Host"));int Port2 = get<int>(ServerStatic::ParseConfig("ChatServer2", "Port"));server2.Host = Host2;server2.Port = Port2;_servers.push_back(server1);_servers.push_back(server2);
}Status StatusServiceImpl::GetChatServer(ServerContext* context, const GetChatServerRequest* request, GetChatServerResponse* response)
{ServerIndex = (ServerIndex + 1) % _servers.size();ChatServer server = _servers[ServerIndex];response->set_host(server.Host);response->set_port(to_string(server.Port));response->set_error(ErrorCodes::SUCCESS);string token = generate_unique_string();response->set_token(token);return Status::OK;
}string StatusServiceImpl::generate_unique_string()
{// 创建UUID对象boost::uuids::uuid uuid = boost::uuids::random_generator()();// 将UUID转换为字符串std::string unique_string = to_string(uuid);return unique_string;
}

4. C++中线程的使用方法

4.1 线程创建

#include <iostream>
#include <thread>
using namespace std;void Function()
{std::cout << "This is a thread function.\n";
}class HelloFunction
{
public:void operator()(){std::cout << "Hello, world!\n";}
};int main()
{//1. 普通函数thread t1(Function);t1.join();// 2.函数对象HelloFunction hello;thread t2(hello);t2.join();// 3.lambda表达式thread t3([]() {std::cout << "Hello, lambda!\n";});t3.join();return 0;
}

4.2 等待线程完成

使用join()成员函数来等待线程完成。如果不等待,则线程可能在主线程结束后仍然运行,导致未定义行为(除非将线程分离)

4.3 分离线程

使用detach()成员函数将线程与thread对象分离,这样线程将在后台独立运行,不再收到thread对象的控制;

4.4 传递参数

线程函数可以接受参数。参数默认以值传递方式传递,如果需要传递引用必须使用ref进行包装

#include <iostream>
#include <thread>
using namespace std;void Function(int a)
{a++;std::cout << "a = " << a << "\n";
}void RefFunction(int& a, int b)
{a += b;std::cout << "a = " << a << "\n";
}int main()
{// 1.值传递int a = 10;thread t1(Function, 10);t1.join();cout << "a = " << a << "\n";// 2.引用传递thread t2(RefFunction, std::ref(a), 5);t2.join();cout << "a = " << a << "\n";return 0;
}

5. 测试

5.1 逻辑梳理

1. 客户端登录

登录按钮点击时,发送Post请求,等待服务器的响应报文

2. 服务器监听客户端发来的Http

只关注Post请求,

处理登录请求时,先拆解出用户发来的用户名和密码,将用户信息(用户名,邮件,密码,uid)读取出来,利用用户的uid向StatusServer发送请求

3. StatusServer处理请求

监听端口号并添加服务(客户端发来对应请求的回调函数)

再创建一个事件循环对象来监听Crtl + C用来优雅退出,再后台线程中进行监听

服务(返回聊天服务器的IP地址和端口号)

登陆成功

添加打印日志查看StatusServer是否监听到发来的请求

测试成功!!!

6. Git管理

在项目中建立分支将所有的项目通过分支的形式去提交,不提交Master

拉取分支,创建空项目,复制URL,按照下面操作就能得到想要的分支的项目了

相关文章:

  • 第4章:Cypher查询语言基础
  • DNAMAN汉化版免费下载教程---WIN11
  • LeetCode 239. 滑动窗口最大值(单调队列)
  • sql中group by使用场景
  • 项目-- Json-Rpc框架
  • 有没有 MariaDB 5.5.56 对应 MySQL CONNECTION_CONTROL 插件
  • 家政小程序开发——AI+IoT技术融合,打造“智慧家政”新物种
  • Cline核心说明文档
  • 计算机组织原理第五章
  • 【图像处理基石】如何构建一个简单好用的美颜算法?
  • 2007-2023年数字经济上市公司专利申请获得数据
  • [最全总结]城市灾害应急管理系统
  • 【AI系列】BM25 与向量检索
  • JDK21深度解密 Day 15:JDK21实战最佳实践总结
  • 使用柏林噪声生成随机地图
  • C++ 信息学奥赛总复习题答案解析
  • 将单体架构项目拆分成微服务时的两种工程结构
  • DL00335-基于深度学习YOLOv11的煤矸石检测含完整数据集
  • JUC 串讲
  • Ubuntu挂载本地镜像源(像CentOS 一样挂载本地镜像源)
  • 北京大兴专业网站建设公司/今日头条网站推广
  • 南宁学网站建设/百度网站收录链接提交
  • 信息推广网站点不开的那种怎么做/seo优化收费
  • 成功的微网站/中文域名注册官网入口
  • 网上推广是什么意思/seo外包费用
  • 做网站多少钱赚钱吗/建站是什么意思