Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,6 @@ ui_*.h
/server/VarifyServer/.idea
/server/ChatServer/*.log
/client/llfcchat/llfcchat.pro.user.92bdd7d
client/llfcchat/release/
bin/static/*

38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,44 @@ C++ 聊天项目实战案例,涵盖grpc,并发线程,网络编程,qt开

[day29-好友认证和聊天](https://gitee.com/secondtonone1/llfcchat/blob/master/%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/day29-%E5%A5%BD%E5%8F%8B%E8%AE%A4%E8%AF%81%E5%92%8C%E8%81%8A%E5%A4%A9%E9%80%9A%E4%BF%A1.md)

[day30-项目难点和面试技巧](https://gitee.com/secondtonone1/llfcchat/blob/master/%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/day30-%E9%A1%B9%E7%9B%AE%E9%9A%BE%E7%82%B9%E5%92%8C%E9%9D%A2%E8%AF%95%E6%8A%80%E5%B7%A7.md)

[day31-好友认证和聊天](https://gitee.com/secondtonone1/llfcchat/blob/master/%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/day31-%E6%96%87%E4%BB%B6%E4%BC%A0%E8%BE%93.md)

**第二季**

代码需要切换到Season_2分支,可查看第二季逻辑

[day32-分布式锁设计思路](https://gitee.com/secondtonone1/llfcchat/blob/master/%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/day32%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E8%AE%BE%E8%AE%A1%E6%80%9D%E8%B7%AF.md)

[day33-单服务器踢人逻辑](https://gitee.com/secondtonone1/llfcchat/blob/master/%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/day33%E5%8D%95%E6%9C%8D%E7%A8%8B%E8%B8%A2%E4%BA%BA%E9%80%BB%E8%BE%91.md)

[day34-多服务器踢人逻辑](https://gitee.com/secondtonone1/llfcchat/blob/master/%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/day34%E5%A4%9A%E6%9C%8D%E7%A8%8B%E8%B8%A2%E4%BA%BA%E9%80%BB%E8%BE%91.md)

[day35-心跳逻辑](https://gitee.com/secondtonone1/llfcchat/blob/master/%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/day35%E5%BF%83%E8%B7%B3%E9%80%BB%E8%BE%91.md)

[day36-实现头像编辑框](https://gitee.com/secondtonone1/llfcchat/blob/master/%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/day36-%E5%AE%9E%E7%8E%B0%E5%A4%B4%E5%83%8F%E7%BC%96%E8%BE%91%E6%A1%86.md)

[day37-聊天信息存储方案](https://gitee.com/secondtonone1/llfcchat/blob/master/%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/day37-%E8%81%8A%E5%A4%A9%E4%BF%A1%E6%81%AF%E5%AD%98%E5%82%A8%E6%96%B9%E6%A1%88.md)

[day38-断点续传](https://gitee.com/secondtonone1/llfcchat/blob/master/%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/day38-%E6%96%AD%E7%82%B9%E7%BB%AD%E4%BC%A0.md)

[day39-多媒体信息传输](https://gitee.com/secondtonone1/llfcchat/blob/master/%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/day39-%E5%A4%9A%E5%AA%92%E4%BD%93%E4%BF%A1%E6%81%AF%E4%BC%A0%E8%BE%93.md)

[day40-聊天图片资源续传和进度显示](https://gitee.com/secondtonone1/llfcchat/blob/master/%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/day40-%E8%81%8A%E5%A4%A9%E5%9B%BE%E7%89%87%E8%B5%84%E6%BA%90%E7%BB%AD%E4%BC%A0%E5%92%8C%E8%BF%9B%E5%BA%A6%E6%98%BE%E7%A4%BA.md)

[day41-分布式事务死锁解决方案](https://gitee.com/secondtonone1/llfcchat/blob/master/%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/day41-%E5%88%86%E5%B8%83%E5%BC%8F%E6%AD%BB%E9%94%81%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88.md)

[day42-Qt粘包引发的血案](https://gitee.com/secondtonone1/llfcchat/blob/master/%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/day42-Qt%E7%B2%98%E5%8C%85%E5%BC%95%E5%8F%91%E7%9A%84%E8%A1%80%E6%A1%88.md)

[day43-Grpc通知客户端聊天资源下载](https://gitee.com/secondtonone1/llfcchat/blob/Season_2/%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/day41-%E9%80%9A%E7%9F%A5%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%BC%82%E6%AD%A5%E4%B8%8B%E8%BD%BD%E8%81%8A%E5%A4%A9%E5%9B%BE%E7%89%87.md)

[day43-用户登录加载聊天资源](https://gitee.com/secondtonone1/llfcchat/blob/Season_2/%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/day42-%E7%94%A8%E6%88%B7%E5%8A%A0%E8%BD%BD%E8%81%8A%E5%A4%A9%E8%B5%84%E6%BA%90.md)

[day44-webrtc的coturn服务器搭建](https://gitee.com/secondtonone1/llfcchat/blob/Season_2/%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/day43-webrtc%E7%9A%84coturn%E6%9C%8D%E5%8A%A1%E6%90%AD%E5%BB%BA.md)

[day45-信令服务器实现视频通信](https://gitee.com/secondtonone1/llfcchat/blob/Season_2/%E5%BC%80%E5%8F%91%E6%96%87%E6%A1%A3/day44-webrtc%E4%BF%A1%E4%BB%A4%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%AE%9E%E7%8E%B0.md)

#### 赞赏

您的赞赏是我更新的动力,谢谢支持!
Expand Down
9 changes: 9 additions & 0 deletions client/llfcchat/ChatItemBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,12 @@ void ChatItemBase::setStatus(int status)
return ;
}
}


QLabel* ChatItemBase::getIconLabel() {
return m_pIconLabel;
}

QWidget* ChatItemBase::getBubble() {
return m_pBubble;
}
2 changes: 2 additions & 0 deletions client/llfcchat/ChatItemBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ class ChatItemBase : public QWidget
void setUserIcon(const QPixmap &icon);
void setWidget(QWidget *w);
void setStatus(int status);
QLabel* getIconLabel();
QWidget* getBubble();
private:
ChatRole m_role;
QLabel *m_pNameLabel;
Expand Down
74 changes: 74 additions & 0 deletions client/llfcchat/ClickableLabel.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#include "ClickableLabel.h"
#include <QMouseEvent>
#include <QPainter>
// ClickableLabel.cpp
ClickableLabel::ClickableLabel(QWidget* parent)
: QLabel(parent)
, m_showOverlay(false)
, m_hovered(false)
{
setCursor(Qt::PointingHandCursor);
setMouseTracking(true);
}

void ClickableLabel::mousePressEvent(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton) {
emit clicked();
}
QLabel::mousePressEvent(event);
}

void ClickableLabel::enterEvent(QEvent* event)
{
m_hovered = true;
update();
QLabel::enterEvent(event);
}

void ClickableLabel::leaveEvent(QEvent* event)
{
m_hovered = false;
update();
QLabel::leaveEvent(event);
}

void ClickableLabel::paintEvent(QPaintEvent* event)
{
QLabel::paintEvent(event);

if (m_showOverlay && !m_overlayIcon.isNull()) {
QPainter painter(this);

// ���ư�͸������
if (m_hovered) {
painter.fillRect(rect(), QColor(0, 0, 0, 100));
}
else {
painter.fillRect(rect(), QColor(0, 0, 0, 60));
}

// ����ͼ��
int iconSize = qMin(width(), height()) / 3; // ͼ���СΪͼƬ��1/3
QRect iconRect(
(width() - iconSize) / 2,
(height() - iconSize) / 2,
iconSize,
iconSize
);

m_overlayIcon.paint(&painter, iconRect);
}
}

void ClickableLabel::setIconOverlay(const QIcon& icon)
{
m_overlayIcon = icon;
update();
}

void ClickableLabel::showIconOverlay(bool show)
{
m_showOverlay = show;
update();
}
28 changes: 28 additions & 0 deletions client/llfcchat/ClickableLabel.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#pragma once
#include <QLabel>
#include <QWidget>
#include <QIcon>

class ClickableLabel :
public QLabel
{
Q_OBJECT
public:
explicit ClickableLabel(QWidget* parent = nullptr);
void setIconOverlay(const QIcon& icon); //��������ͼ��
void showIconOverlay(bool show); //��ʾ/��������ͼ��
protected:
void mousePressEvent(QMouseEvent* event) override;
void enterEvent(QEvent* event) override;
void leaveEvent(QEvent* event) override;
void paintEvent(QPaintEvent* event) override;

signals:
void clicked();

private:
QIcon m_overlayIcon;
bool m_showOverlay;
bool m_hovered;
};

113 changes: 80 additions & 33 deletions client/llfcchat/MessageTextEdit.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#include "MessageTextEdit.h"
#include <QDebug>
#include <QMessageBox>

#include "global.h"

MessageTextEdit::MessageTextEdit(QWidget *parent)
: QTextEdit(parent)
Expand All @@ -19,15 +19,14 @@ MessageTextEdit::~MessageTextEdit()

}

QVector<MsgInfo> MessageTextEdit::getMsgList()
QVector<std::shared_ptr<MsgInfo>> MessageTextEdit::getMsgList()
{
mGetMsgList.clear();

_total_msg_list.clear();
QString doc = this->document()->toPlainText();
QString text="";//存储文本信息
int indexUrl = 0;
//这个是存储的富文本信息,包括图片的url以及文件的url
int count = mMsgList.size();
int count = _img_or_file_list.size();

for(int index=0; index<doc.size(); index++)
{
Expand All @@ -36,17 +35,16 @@ QVector<MsgInfo> MessageTextEdit::getMsgList()
{
if(!text.isEmpty())
{
QPixmap pix;
insertMsgList(mGetMsgList,"text",text,pix);
insertMsgList(_total_msg_list, MsgType::TEXT_MSG, text, QPixmap(),"",0,"");
text.clear();
}
while(indexUrl<count)
{
MsgInfo msg = mMsgList[indexUrl];
if(this->document()->toHtml().contains(msg.content,Qt::CaseSensitive))
std::shared_ptr<MsgInfo> msg = _img_or_file_list[indexUrl];
if(this->document()->toHtml().contains(msg->_text_or_url,Qt::CaseSensitive))
{
indexUrl++;
mGetMsgList.append(msg);
_total_msg_list.append(msg);
break;
}
indexUrl++;
Expand All @@ -60,13 +58,12 @@ QVector<MsgInfo> MessageTextEdit::getMsgList()
}
if(!text.isEmpty())
{
QPixmap pix;
insertMsgList(mGetMsgList,"text",text,pix);
insertMsgList(_total_msg_list, MsgType::TEXT_MSG, text, QPixmap(), "", 0, "");
text.clear();
}
mMsgList.clear();
_img_or_file_list.clear();
this->clear();
return mGetMsgList;
return _total_msg_list;
}

void MessageTextEdit::dragEnterEvent(QDragEnterEvent *event)
Expand Down Expand Up @@ -102,12 +99,40 @@ void MessageTextEdit::insertFileFromUrl(const QStringList &urls)
if(isImage(url))
insertImages(url);
else
insertTextFile(url);
insertFiles(url);
}
}

void MessageTextEdit::insertImages(const QString &url)
{
//文件信息
QFileInfo fileInfo(url);
if (fileInfo.isDir())
{
QMessageBox::information(this, "提示", "只允许拖拽单个文件!");
return;
}

auto total_size = fileInfo.size();

qint64 max_size = qint64(2) * 1024 * 1024 * 1024;

if (total_size > max_size)
{
QMessageBox::information(this, "提示", "发送的文件大小不能大于2G");
return;
}

// 计算文件MD5
QString fileMd5 = calculateFileHash(url);

if (fileMd5.isEmpty())
{
QMessageBox::warning(this, "错误", "无法计算文件MD5");
return;
}


QImage image(url);
//按比例缩放图片
if(image.width()>120||image.height()>80)
Expand All @@ -123,31 +148,51 @@ void MessageTextEdit::insertImages(const QString &url)
// QTextDocument *document = this->document();
// document->addResource(QTextDocument::ImageResource, QUrl(url), QVariant(image));
cursor.insertImage(image,url);

insertMsgList(mMsgList,"image",url,QPixmap::fromImage(image));
QString origin_name = fileInfo.fileName();
QString unique_name = generateUniqueFileName(origin_name);
insertMsgList(_img_or_file_list, MsgType::IMG_MSG, url, QPixmap::fromImage(image), unique_name,
total_size, fileMd5);
}

void MessageTextEdit::insertTextFile(const QString &url)
{
void MessageTextEdit::insertFiles(const QString& url) {
QFileInfo fileInfo(url);
if(fileInfo.isDir())
if (fileInfo.isDir())
{
QMessageBox::information(this,"提示","只允许拖拽单个文件!");
QMessageBox::information(this, "提示", "只允许拖拽单个文件!");
return;
}

if(fileInfo.size()>100*1024*1024)
auto total_size = fileInfo.size();

qint64 max_size = qint64(2) * 1024 * 1024 * 1024;

if (total_size > max_size)
{
QMessageBox::information(this, "提示", "发送的文件大小不能大于100M");
return;
}

// 计算文件MD5
QString fileMd5 = calculateFileHash(url);

if (fileMd5.isEmpty())
{
QMessageBox::information(this,"提示","发送的文件大小不能大于100M");
QMessageBox::warning(this, "错误", "无法计算文件MD5");
return;
}

QPixmap pix = getFileIconPixmap(url);
QTextCursor cursor = this->textCursor();
cursor.insertImage(pix.toImage(),url);
insertMsgList(mMsgList,"file",url,pix);
cursor.insertImage(pix.toImage(), url);

QString origin_name = fileInfo.fileName();
QString unique_name = generateUniqueFileName(origin_name);
insertMsgList(_img_or_file_list, MsgType::FILE_MSG, url, pix, unique_name,
total_size, fileMd5);
}



bool MessageTextEdit::canInsertFromMimeData(const QMimeData *source) const
{
return QTextEdit::canInsertFromMimeData(source);
Expand All @@ -165,7 +210,7 @@ void MessageTextEdit::insertFromMimeData(const QMimeData *source)
if(isImage(url))
insertImages(url);
else
insertTextFile(url);
insertFiles(url);
}
}

Expand All @@ -181,15 +226,17 @@ bool MessageTextEdit::isImage(QString url)
return false;
}

void MessageTextEdit::insertMsgList(QVector<MsgInfo> &list, QString flag, QString text, QPixmap pix)
{
MsgInfo msg;
msg.msgFlag=flag;
msg.content = text;
msg.pixmap = pix;
list.append(msg);
void MessageTextEdit::insertMsgList(QVector<std::shared_ptr<MsgInfo>> &list, MsgType msgtype,
QString text_or_url, QPixmap preview_pix,
QString unique_name, uint64_t total_size, QString md5) {

auto msg_info = std::make_shared<MsgInfo>(msgtype, text_or_url, preview_pix, unique_name, total_size, md5);
list.append(msg_info);

}



QStringList MessageTextEdit::getUrl(QString text)
{
QStringList urls;
Expand Down
Loading