QT聊天项目DAY17

发布于:2025-08-01 ⋅ 阅读:(19) ⋅ 点赞:(0)

1.分布式登录逻辑梳理

客户端在发送Http的登录请求并成功获取到ChatServer的地址后,会尝试建立稳定的TCP连接,并向该服务器发送登录请求

1.1 登录

1.1.1 客户端建立TCP连接并发送登录请求

在登录窗口处理服务器发来的对客户端登陆请求的响应

connect(this, &LoginWidget::sig_connect_tcp, TCPMgr::Instance().get(), &TCPMgr::SlotTcpConnect);

/* 网络回包的处理函数 */
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);																// 与ChatServer建立稳定的TCP连接

			ShowTipLabel(QString::fromLocal8Bit("登陆成功"), "normal");
			qDebug() << QString::fromLocal8Bit("登陆成功 user: ") << email;
		});
}

TCP管理类发送连接请求

/* 登录成功时连接到服务器 */
void TCPMgr::SlotTcpConnect(ServerInfo serverInfo)
{
	qDebug() << QString::fromLocal8Bit("开始连接服务器...");
	m_Host = serverInfo.Host;
	m_Port = serverInfo.Port.toInt();
	m_TcpSocket->connectToHost(m_Host, m_Port);
}

当成功连接时发送登录请求

connect(TCPMgr::Instance().get(), &TCPMgr::SigConnectSuccess, this, &LoginWidget::slot_tcp_con_finished);
/* 如果连接聊天服务器成功,则发送登录请求 */
void LoginWidget::slot_tcp_con_finished(bool success)
{
	if (success)
	{
		ShowTipLabel(QString::fromLocal8Bit("连接聊天服务器成功,正在登录..."), "normal");
		QJsonObject json;
		json["uid"] = _Uid;
		json["token"] = _Token;

		QJsonDocument doc(json);
		QString strJson = doc.toJson(QJsonDocument::Indented);

		TCPMgr::Instance()->SigSendData(ReqID::ID_CHAT_LOGIN, strJson.toUtf8());												// 发送登录请求
	}
	else
	{
		ShowTipLabel(QString::fromLocal8Bit("连接聊天服务器失败"), "error");
		EnableBtn(true);
	}
}

1.1.2 服务器处理客户端发来的登录请求

在Http请求登录时,GateServer服务器会向StatuServer服务器请求登录

StatuServer处理登录请求

Token是给这个用户分配的,只有Http登录成功才会分配,用来标识用户的,并插入到Redis中

聊天服务器处理登录请求时会验证该用户的UID和Token是否已经注册

登陆成功尝试获取用户信息 + 好友申请信息 + 好友列表

获取服务器的名字,用来获取当前在线用户并进行++,并存储到redis中,然后记录当前用户所登陆的服务器也一并存储到服务器中

最后服务器中保存该TCP连接的用户ID,并在用户管理类中(全局唯一实例)存储该连接,方便后续的超时断开

void LogicSystem::LoginHandler(CSession* session, const short& msg_id, const string& msg_data)
{
	// 1.解析客户端发来的登录信息
	Json::Reader reader;
	Json::Value jsonResult;
	Json::Value jsonReturn;
	reader.parse(msg_data, jsonResult);

	// 2.获取用户信息
	auto uid = jsonResult["uid"].asInt();																				// uid是由存储过程自发分配的
	auto TokenStr = jsonResult["token"].asString();																		// token 则是由状态服务器自己分配的
	cout << "user uid = " << uid << "Token = " << TokenStr << "\n";

	// 3.当所有信息都处理完时,返回登录结果
	// 这里采用引用捕获,不担心jsonReturn被销毁,因为C++局部变量按创建逆序销毁,jsonReturn先于conn创建,这是一个栈机制,先创建后销毁
	ConnectionRAII conn([&jsonReturn, session]() {
		string jsonReturnStr = jsonReturn.toStyledString();
		session->Send(jsonReturnStr, MSG_CHAT_LOGIN_RESPONSE);
		});

	// 4.从redis中获取用户token是否正确, 该用户UID和Token在状态服务器进行Token分配时,就已经插入到redis中, 用户每登录一次就更新插入一次
	string uidStr = to_string(uid);
	string tokenKey = USERTOKENPREFIX + uidStr;
	string tokenVal = "";
	bool bRet = RedisManage::GetInstance()->Get(tokenKey, tokenVal);
	if (!bRet)
	{
		jsonReturn["error"] = ErrorCodes::UID_INVALID;										// uid失效
		return;
	}

	if (tokenVal != tokenKey)
	{
		jsonReturn["error"] = ErrorCodes::TOKEN_INVALID;									// token错误
		return;
	}

	// 5.获取用户基本信息
	string baseKey = USER_BASE_INFO + uidStr;
	UserInfo* userInfo = new UserInfo;
	bool bRet = GetBaseInfo(baseKey, uid, userInfo);
	if (!bRet)
	{
		jsonReturn["error"] = ErrorCodes::UID_INVALID;										// uid失效
		return;
	}

	jsonReturn["uid"] = uid;
	jsonReturn["pwd"] = userInfo->pwd;
	jsonReturn["name"] = userInfo->name;
	jsonReturn["email"] = userInfo->email;
	jsonReturn["nick"] = userInfo->nick;
	jsonReturn["desc"] = userInfo->desc;
	jsonReturn["sex"] = userInfo->sex;
	jsonReturn["icon"] = userInfo->icon;
	jsonReturn["error"] = ErrorCodes::SUCCESS;

	// 6.从数据库中获取好友申请列表
	vector<ApplyInfo*> applyList;
	bool bApply = GetFriendApplyInfo(uid, applyList);
	if (bApply)
	{
		for (auto& apply : applyList)
		{
			Json::Value applyJson;
			applyJson["name"] = apply->_name;
			applyJson["uid"] = apply->_uid;
			applyJson["icon"] = apply->_icon;
			applyJson["desc"] = apply->_desc;
			applyJson["sex"] = apply->_sex;
			applyJson["status"] = apply->_status;
			jsonReturn["applyList"].append(applyJson);
		}
	}

	// 7.获取好友列表
	vector<UserInfo*> friendList;
	bool bFriend = GetFriendList(uid, friendList);
	if (bFriend)
	{
		for (auto& friendInfo : friendList)
		{
			Json::Value friendJson;
			friendJson["uid"] = friendInfo->uid;
			friendJson["name"] = friendInfo->name;
			friendJson["icon"] = friendInfo->icon;
			friendJson["desc"] = friendInfo->desc;
			friendJson["nick"] = friendInfo->nick;
			friendJson["remark"] = friendInfo->remark;
			friendJson["sex"] = friendInfo->sex;
			jsonReturn["friendList"].append(friendJson);
		}
	}

	// 8.获取服务器的名字
	string serverName = get<string>(ServerStatic::ParseConfig("SelfServer", "Name"));
	
	// 9.获取登录人数,并更新redis中登录人数
	string loginNumStr = RedisManage::GetInstance()->HGet(LOGIN_COUNT, serverName);
	int loginNum = 0;
	if (!loginNumStr.empty())
	{
		loginNum = stoi(loginNumStr);
	}
	loginNum++;
	RedisManage::GetInstance()->HSet(LOGIN_COUNT, serverName, to_string(loginNum));

	// 10.为该会话设置客户端的用户UID
	session->_userUid = uid;

	// 11.记录该用户登录的服务器名
	string ipKey = USERIPPREFIX + uidStr;
	RedisManage::GetInstance()->Set(ipKey, serverName);
	
	// 12.将UID和会话绑定管理
	UserMgr::GetInstance()->SetUserSession(uid, session);
	return;
}

1.1.3 客户端处理登陆响应

将读取到的用户信息,Token,以及申请列表和好友列表添加到UserMgr里面,这是每个客户端存储自己的信息好友信息以及好友申请信息里的全局静态实例

_handlers.insert(ReqID::ID_CHAT_LOGIN_RESPONSE, [this](ReqID id, int len, QByteArray data)
	{
		Q_UNUSED(id);
		qDebug() << "handle id is" << id << "len is" << len << "data is" << data;

		// 解析消息体
		QJsonDocument jsonDoc = QJsonDocument::fromJson(data);
		if (jsonDoc.isNull())
		{
			qDebug() << QString::fromLocal8Bit("Json解析失败!!!");
			return;
		}

		// 如果解析失败,则返回空指针
		QJsonObject jsonObj = jsonDoc.object();
		if (!jsonObj.contains("error"))
		{
			int error = ErrorCodes::ERROR_JSON;
			qDebug() << QString::fromLocal8Bit("Json中没有error字段!!!");
			emit SigLoginFailed(error);
			return;
		}

		// 如果服务器返回错误码,则返回空指针
		int error = jsonObj["error"].toInt();
		if (error != ErrorCodes::SUCCESS)
		{
			qDebug() << QString::fromLocal8Bit("登录失败!!!") << error;
			emit SigLoginFailed(error);
			return;
		}

		// 存储用户信息到全局静态实例中
		auto uid = jsonObj["uid"].toInt();
		auto name = jsonObj["name"].toString();
		auto nick = jsonObj["nick"].toString();
		auto icon = jsonObj["icon"].toString();
		auto sex = jsonObj["sex"].toInt();
		auto desc = jsonObj["desc"].toString();

		QSharedPointer<UserInfo> userInfo(new UserInfo(uid, name, nick, icon, sex, desc));
		UserMgr::Instance()->SetUserInfo(userInfo);
		UserMgr::Instance()->SetToken(jsonObj["token"].toString());													// 设置连接的Token

		// 存储申请列表和好友列表
		if (jsonObj.contains("applyList"))
		{
			UserMgr::Instance()->AppendApplyList(jsonObj["applyList"].toArray());
		}
		if (jsonObj.contains("friendList"))
		{
			UserMgr::Instance()->AppendFriendList(jsonObj["friendList"].toArray());
		}

		emit SigSwitchChatWnd();
	});

成功登录,客户端在登录成功之后,会切换到聊天界面

1.2 聊天项和聊天窗口的绑定

 从登录界面切换到聊天界面,首先初始化一系列界面控件,如下图

重要的是聊天界面和聊天项是如何绑定的,就像微信一样,当点击某一个联系人,会弹出和该联系人聊天的界面

当点击聊天列表项容器时会发射信号到ChatWidget类下

在对应的槽函数中,会获取该聊天项中存储的聊天信息,所有的好友的用户信息都是在聊天列表项中存储

只有一个唯一的聊天窗口,将该聊天窗口的所属与点击的聊天项所代表的好友用户绑定

重新设置聊天界面所属用户,并更新聊天记录

1.3 搜索好友

当点击查找时,会发送查找信息给服务器

1.3.1 客户端发送搜索请求

客户端发送Uid

1.3.2 服务器处理搜索请求

解析客户端发来的Uid,看是名字还是uid,如果是uid根据uid获取用户信息

并返回搜索响应给客户端

服务器查Mysql或者Redis表获取下列用户信息

1.3.3 客户端处理搜索响应

当成功接收到消息时,会将查找的用户信息通过信号的方式发送搜索列表去处理

搜索列表处理搜索到的用户信息,如果搜索失败,显示查找失败窗口,否则显示搜索成功窗口

搜索成功窗口

1.4 添加好友

1.4.1 申请者申请添加好友

当点击添加到通讯录时,客户端会弹出添加好友的申请窗口来申请添加好友

当点击确认按钮时,客户端发送添加好友申请

1.4.2 服务器处理添加好友请求

解析添加好友请求,并获取被添加者的信息,向好友申请表中添加好友插入好友申请,利用栈的生命周期自动向客户端发送添加好友响应

获取被添加者所在的服务器,如果是在同一个服务器上可以直接根据uid获取对应的TCP连接

将申请者的信息发送给客户端

1.4.3 授权者处理服务器发来的通知授权者添加好友请求

客户端获取添加好友的申请者的详细信息

在ChatWidget界面处理好友申请

好友申请界面添加新的好友申请列表项

1.4.4 修复再次登录丢失好友请求Bug

客户端有获取这两个列表的,查看服务器有没有正确返回

服务器也在尝试去Mysql中获取这两个列表

查看获取申请者列表中发现这个sql语句需要标注用户id,也就是用户id不能为空,之前创建表的时候没有把该id设置成自动递增的键

登录时成功获取好友申请列表,大小为1,说明成功查找到了,接下来是查看客户端怎么加载的

查看在哪里获取的好友申请列表

在好友申请界面获取了好友申请列表,加载了好友申请项,将测试用的数据注释掉,看效果

如下图所示,成功加载了该好友申请

1.4.5 被添加者同意好友申请

当点击添加按钮时

好友申请列表项中的添加按钮对应的回调函数,发送授权好友请求,该信号槽百分之一万是在创建好友申请列表项中绑定的

显示授权好友界面

授权好友界面,有点丑,将就看

确认按钮点击对应的槽函数

向ChatServer发送同意添加好友请求

1.4.6 服务器处理授权者的同意好友请求

解析客户端请求,并利用栈对象的生命周期自动回送授权好友响应

获取被添加好友的基本信息

更新数据库好友状态

1. 修改好友申请信息表,将status设置为1,这里是2添加1为好友,1同意好友请求,服务器修改当前好友申请状态,并且添加申请者的备注

2.获取申请者和同意者的备注

3. 向好友表中插入好友信息

查找申请者的服务器,判断如果是在同一个服务器上获取对应的TCP连接,发送通知授权好友请求

这个备注搞反了,1的名字是AlientObjects,给2的备注应该是Alient

为了避免歧义,全部改成下列命名

1.4.7 申请者处理服务器发来的授权完成的请求

同意添加好友

向本地客户端添加该好友,并生成好友聊天项

1.4.8 申请者处理服务器发来的授权请求时崩溃

这说明这里friendInfo是空指针

已解决

1.4.9 申请者处理服务器发来的添加好友结果

服务器返回添加好友结果

1.4.10 授权者处理服务器发来的授权好友结果

服务器返回授权好友结果

获取申请者的信息,然后在本地客户端中记录好友信息,顺便生成好友聊天项

1.5 通信

1.5.1 客户端(发送者)发送信息

当发送按钮点击时或者Enter按键按下时,发送文本信息

首先获取本地用户信息,然后提取发送的文本

在文本编辑框中提取发送的文本

遍历文本的每一个字符,然后查看是否有文本替换符,如果有,就查找该文本的html中包含的图片路径是否被记录

当遇到文本替换符时,将遇到文本替换符之前的文本统一插入到消息列表中,然后清空文本,当整个待发送的文本信息遍历完毕时,判断text是否为空,如果为空说明最后一个字符是文本替换符,如果不为空说明最后一个字符不是文本替换符,将文本信息也插入到消息列表中

如果有文本替换符,去mMsgList中根据路径查找对应的图片,该HTML中包含了文本路径

在粘贴和拖入图片时,记录了图片路径

提取文本结束

遍历消息列表,每一次遍历都创建一个聊天窗口;

然后根据消息类型,创建对应的气泡,在遍历文本时为每一个文本生成一个uuid,由于发送机制,当遍历完当前文本编辑框中解析的消息列表时,才会发送一个TCP请求,如果消息列表是文本 + 图片 + 文本类型的话,遍历第一个文本不会发送TCP,如果第二个文本的长度和第一个加起来也没有超过最大长度也不会发送,会在遍历结束后统一发送

每次遍历一个文本消息,就会创建一个QJsonObject,填充消息和对应的消息id(uuidStr),然后插入到jsonArray里面,这样在整合消息发送时,就不会出现消息叠加

// 遍历消息列表,创建对应的消息气泡并添加到聊天视图中
for (int i = 0; i < msgList.size(); i++)
{
	// 限制文本长度
	if (msgList[i].MsgContent.length() > MAX_TEXT_LEN)
		continue;

	QString type = msgList[i].MsgType;
	ChatItemBase* item = new ChatItemBase(role);														// 聊天窗口
	item->SetUserName(userName);
	item->SetUserIcon(QPixmap(userIcon));
	QWidget* bubble = nullptr;																			// 根据消息类型来创建气泡类型

	if (type == MSG_TYPE_TEXT)
	{
		QUuid uuid = QUuid::createUuid();
		QString uuidStr = uuid.toString();
		bubble = new TextBubble(role, msgList[i].MsgContent);											// 文本气泡

		// 如果文本长度超过限制,则发送给服务器
		if (textSize + msgList[i].MsgContent.length() > MAX_TEXT_LEN)
		{
			SendMsgToTcp(textObj, textArray, textSize, userInfo->_uid);
		}

		textSize += msgList[i].MsgContent.length();
		QJsonObject obj;
		obj["content"] = msgList[i].MsgContent;
		obj["msgId"] = uuidStr;																			// 标识消息的ID
		textArray.append(obj);																			// 添加到文本数组中
		QSharedPointer<TextChatData> chatMsg(new TextChatData(uuidStr, obj["content"].toString(),
			userInfo->_uid, _userInfo->_uid));

		emit SigAppendChatMsgToChatUserWnd(chatMsg);													// 将与该用户的聊天信息添加到对应的聊天小窗口中
	}
	else if (type == MSG_TYPE_IMAGE)
	{
		bubble = new PictureBubble(role, msgList[i].MsgPicture);
	}
	else if (type == MSG_TYPE_FILE)
	{

	}

	// 添加到聊天窗口中
	if (bubble)
	{
		item->SetWidget(bubble);
		ui.chatView->AppendChatItem(item);
	}
}

最后遍历完消息列表时,发送剩余的文本信息

1.5.2 服务器接受消息

解析发送者发送的聊天信息,并在结束时回发给发送者接受结果

查找serverIP,如果在一个服务器上直接获取TCP连接,发送接受文本请求

1.5.3 客户端(接收者)接收消息

整理文本信息,将JsonArray整理成QVectot,转交给ChatWidget处理

找发送者的聊天项,然后获取该聊天项的聊天窗口,更新该窗口的信息,然后更新对应的聊天界面的信息,并存储本地客户端和当前好友的聊天信息

更新聊天界面文本,遍历消息列表,生成对应的聊天气泡

1.5.4 图片无法发送

目前看来消息列表,不会造成消息叠加,但是会丢失文本消息

查看日志发现,我发送的是哎,图片 + 111

并没有?所以这个?就是文本替换符,而在图片粘贴到编辑框的那一刻就会将图片转换成mimeData,解析出该图片的路径,并保存到QVector中,方便遇到文本替换符时获取路径并插入图片到文本编辑框中

2025:07:29:10:59:19.304  [Debug]:  source->text() =  "file:///C:/Users/Mars/Desktop/11111111111111111.png"
2025:07:29:10:59:19.305  [Debug]:  line =  "file:///C:/Users/Mars/Desktop/11111111111111111.png"
2025:07:29:10:59:23.694  [Debug]:  doc =  "哎,?111"
bool MessagetextEdit::eventFilter(QObject* obj, QEvent* event)
{
	if (obj == this && event->type() == QEvent::KeyPress)
	{
		QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
		if (keyEvent->matches(QKeySequence::Paste))
		{
			const QMimeData* mimeData = QApplication::clipboard()->mimeData();
			InsertFromMimeData(mimeData);
			return true;
		}


通过日志发现,文本编辑框中获取的图片,即便转换成html,如果没有该图片的路径在另一台电脑上没有,另一个客户端也是无法发送图片的

所以怎么将图片转换成二进制信息呢?或者说怎么把图片发送给服务器再由服务器发送给客户端?

1. 将图片转换成二进制信息

在插入图片信息时依旧记录图片的路径,不过会读取图片的原始数据二进制数据,然后将该数据转换成ASCII方便存放到Json中,最后转换成QString

2. 当发送按钮点击时

3. 服务器接受数据

由于之前用的是short存储的文本长度,对于只有2的16次方,也就是32767这么大,无法表示更长的,但是使用uint16_t 光传输图片来说完全够用了,0 ~ 65535

没有做任何更改只是传递了类型

4. 客户端接受数据

初始化聊天信息时,修改了每条聊天数据的构造函数,添加了消息类型这个参数

在接受数据时,将jaso中的字符串转换成ASCII数组,然后再转换成二进制数据,最后创建图片气泡

1.5.5 使用拖拽图片后鼠标光标异常

现在填充文本,没有任何问题,也就是光标不仅能闪烁,也能随着上下左右键移动,并且使用crtl + v也没有任何问题

但是采用图片拖拽后,光标就有问题了,不闪烁,也不能随着文本输入的时候移动而且上下左右按键无法控制光标

void MessagetextEdit::InsertImages(const QString& url)
{
	QString type = MSG_TYPE_IMAGE;
	QImage image(url);
	if (image.width() > 120 || image.height() > 80)
	{
		if (image.width() > image.height())
			image = image.scaledToWidth(120, Qt::SmoothTransformation);
		else
			image = image.scaledToHeight(80, Qt::SmoothTransformation);
	}

	QTextCursor cursor = textCursor();
	cursor.insertImage(image, url);

	InsertMsgList(mMsgList, type, url, QPixmap::fromImage(image));
}

拖拽的代码

void MessagetextEdit::dragEnterEvent(QDragEnterEvent* event)
{
	// 自己发起的拖拽事件忽略
	if (event->source() == this)
	{
		event->ignore();
	}
	else
	{
		event->accept();
	}
}

void MessagetextEdit::dropEvent(QDropEvent* event)
{
	InsertFromMimeData(event->mimeData());															// 向mMsgList插入拖拽的数据
	event->accept();

}

粘贴的事件

bool MessagetextEdit::eventFilter(QObject* obj, QEvent* event)
{
	if (obj == this && event->type() == QEvent::KeyPress)
	{
		QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
		if (keyEvent->matches(QKeySequence::Paste))
		{
			const QMimeData* mimeData = QApplication::clipboard()->mimeData();
			InsertFromMimeData(mimeData);
			return true;
		}

		// 获取当前文本长度,避免超过最大长度
		int length = document()->toPlainText().length();
		if (length > MAX_TEXT_LENGTH && keyEvent->key() != Qt::Key_Backspace
			&& keyEvent->key() != Qt::Key_Delete)
		{
			return true;
		}
	}
	return false;
}

插入图片

void MessagetextEdit::InsertImages(const QString& url)
{
	QString type = MSG_TYPE_IMAGE;
	QImage image(url);
	if (image.width() > 120 || image.height() > 80)
	{
		if (image.width() > image.height())
			image = image.scaledToWidth(120, Qt::SmoothTransformation);
		else
			image = image.scaledToHeight(80, Qt::SmoothTransformation);
	}

	QTextCursor cursor = textCursor();
	cursor.insertImage(image, url);																				// 会将该url插入到document()->html()中

	InsertMsgList(mMsgList, type, url, QPixmap::fromImage(image));
}

是拖拽事件没有先调用父类的事件导致,会导致光标出现异常,现已修改如下

void MessagetextEdit::dropEvent(QDropEvent* event)
{
	if (event->mimeData()->hasImage() || event->mimeData()->hasUrls())
	{
		// 使用父类默认处理拖拽(自动插入图片,并更新光标等)
		QTextEdit::dropEvent(event);
		clear();																									// 不需要文件路径
		InsertFromMimeData(event->mimeData());
	}
	else
	{
		event->ignore();
	}
}

1.5.6 当用户点击图片时,能够查看原图

创建查看原图窗口

#ifndef IMAGEVIEWWND_H
#define IMAGEVIEWWND_H

#include <QWidget>
#include <QPixmap>
#include "ui_ImageViewWnd.h"

class QLabel;

class ImageViewWnd : public QWidget
{
	Q_OBJECT

public:
	ImageViewWnd(QWidget *parent = nullptr);
	~ImageViewWnd();

	void setImage(const QPixmap &pixmap);

private:
	Ui::ImageViewWndClass ui;
	QPixmap m_pixmap;
	QLabel* m_imageLabel;
};

#endif // IMAGEVIEWWND_H



#include "ImageViewWnd.h"
#include <QLabel>
#include <QScrollArea>
#include <QVBoxLayout>

ImageViewWnd::ImageViewWnd(QWidget *parent)
	: QWidget(parent)
{
	ui.setupUi(this);

	setWindowTitle(QString::fromLocal8Bit("查看原图"));
	setMinimumSize(600, 400);

	m_imageLabel = new QLabel(this);

	QScrollArea* scroll = new QScrollArea;																					// 滚动窗口
	scroll->setWidget(m_imageLabel);
	scroll->setWidgetResizable(true);

	QVBoxLayout* layout = new QVBoxLayout(this);																			// 垂直布局
	layout->addWidget(scroll);
}

ImageViewWnd::~ImageViewWnd()
{}

void ImageViewWnd::setImage(const QPixmap & pixmap)
{
	m_pixmap = pixmap.scaled(m_imageLabel->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
	m_imageLabel->setPixmap(m_pixmap);
	show();
}

在图片气泡中重载鼠标释放事件

void PictureBubble::mousePressEvent(QMouseEvent* event)
{
	QWidget::mousePressEvent(event);

	ImageViewWnd* imageViewWnd = new ImageViewWnd;
	imageViewWnd->setImage(m_picture);
}

缺点 高糊

该m_picture 是在接受到TCP信息时,设置了对应的图片,但是看起来还是高糊,所以采用在图片气泡中保留这个图片的Base64数据,然后当鼠标点击时,直接加载图片的原始数据,这样就不会出现高糊了

展示:

发送者和接收者,都已允许查看原图

1.6 聊天气泡ChatItemBase

由三部分组成,名称,头像以及气泡,并且创建一个2行3列的栅格布局,来管理组件

根据消息类型来生成气泡,然后放置到对应的栅格中

右边的消息有问题,不是想要的,在添加气泡是进行右对齐即可

如下图是正确的,但是发送中文时会出现乱码,说明在TCP通信出现了乱码

已解决

1.7 总结

不知不觉第一季已经学完了,不过分布式的精髓并没有完全的应用到,目前还只是基于单服务器做了一些简单的测试,这个项目让我熟悉了数据库,SQL语句这些我之前学过但是没机会实践的领域

熟练的使用池性组件,比如线程池,连接池等等,重要的是该池性组件在项目运行的过程中能够稳定使用(一天不停的登录测试一些UI效果和图片传输时都在稳定运行)

其次就是前端,之前我一直对QT的控件和UI只停留在自定义控件和C++构造函数中搭建界面的阶段,目前来说能够熟练使用样式表,以及使用图形化界面和界面布局和控件的上下级关系有了更深的理解。

再然后就是云服务器,虽然这个项目目前是部署在本地的,但是在我工作的时候,有用到了UDP云服务器,再加上目前使用的TCP和html算是都涉及到了一些,浅尝辄止。

最重要的应该是肉编器吧,也就是在一次一次的梳理项目思路中,不断地将这些知识提炼到脑子里,目前对这个项目也不能说是随心所欲,但是在每个模块间来回自由的穿梭,还是可以做到的。

由于这个项目目前所有的代码纯手敲,所以代码量以及积累足够了

当时年少掷春光,花马踏蹄酒溅香。

爱恨情仇随浪来,夏蝉歌醒夜未央。

光阴长河种红莲,韶光重回泪已干。

今刻沧桑登舞榭,万灵且待命无缰!

坚持仙蛊,在这一刻,炼成!!!


网站公告

今日签到

点亮在社区的每一天
去签到