├── .gitignore ├── LICENSE ├── README.md ├── assets ├── ERDiagram.png ├── UIIntroduction.png ├── client.png ├── clientChat.png ├── clientChatlist.png ├── clientLoginwindow.png ├── clientMainwindow.png ├── network.png ├── server.png ├── serverAdminmanagement1.png ├── serverAdminmanagement2.png ├── serverMessagemanage.png ├── serverUsermanage.png └── serverWidget.png ├── client └── src │ ├── ImageRes.qrc │ ├── chat.cpp │ ├── chat.h │ ├── chat.ui │ ├── chatlist.cpp │ ├── chatlist.h │ ├── chatlist.ui │ ├── client.pro │ ├── clientcore.cpp │ ├── clientcore.h │ ├── icon │ └── close.png │ ├── loginwindow.cpp │ ├── loginwindow.h │ ├── loginwindow.ui │ ├── main.cpp │ ├── mainwindow.cpp │ ├── mainwindow.h │ ├── mainwindow.ui │ ├── messagebox.cpp │ ├── messagebox.h │ └── pic │ ├── 0.jpg │ ├── 1.jpg │ ├── 10.jpg │ ├── 2.jpg │ ├── 3.jpg │ ├── 4.jpg │ ├── 5.jpg │ ├── 6.jpg │ ├── 7.jpg │ ├── 8.jpg │ ├── 9.jpg │ ├── listback0.jpg │ ├── listback1.jpg │ ├── listback2.jpg │ └── listback3.jpg └── server └── src ├── adminmanagement.cpp ├── adminmanagement.h ├── adminmanagement.ui ├── generatecertandkey.py ├── main.cpp ├── manage.ico ├── manage.png ├── messagemanage.cpp ├── messagemanage.h ├── messagemanage.ui ├── res.qrc ├── server.cpp ├── server.h ├── server.pro ├── servercore.cpp ├── servercore.h ├── usermanage.cpp ├── usermanage.h ├── usermanage.ui ├── widget.cpp ├── widget.h └── widget.ui /.gitignore: -------------------------------------------------------------------------------- 1 | <<<<<<< HEAD 2 | # This file is used to ignore files which are generated 3 | # ---------------------------------------------------------------------------- 4 | 5 | *~ 6 | *.autosave 7 | *.a 8 | *.core 9 | *.moc 10 | *.o 11 | *.obj 12 | *.orig 13 | *.rej 14 | *.so 15 | *.so.* 16 | *_pch.h.cpp 17 | *_resource.rc 18 | *.qm 19 | .#* 20 | *.*# 21 | core 22 | !core/ 23 | tags 24 | .DS_Store 25 | .directory 26 | *.debug 27 | Makefile* 28 | *.prl 29 | *.app 30 | moc_*.cpp 31 | ui_*.h 32 | qrc_*.cpp 33 | Thumbs.db 34 | *.res 35 | *.rc 36 | /.qmake.cache 37 | /.qmake.stash 38 | 39 | # qtcreator generated files 40 | *.pro.user* 41 | 42 | # xemacs temporary files 43 | *.flc 44 | 45 | # Vim temporary files 46 | .*.swp 47 | 48 | # Visual Studio generated files 49 | *.ib_pdb_index 50 | *.idb 51 | *.ilk 52 | *.pdb 53 | *.sln 54 | *.suo 55 | *.vcproj 56 | *vcproj.*.*.user 57 | *.ncb 58 | *.sdf 59 | *.opensdf 60 | *.vcxproj 61 | *vcxproj.* 62 | .idea 63 | .idea/* 64 | 65 | # MinGW generated files 66 | *.Debug 67 | *.Release 68 | 69 | # Python byte code 70 | *.pyc 71 | 72 | # Binaries 73 | # -------- 74 | *.dll 75 | *.exe 76 | 77 | # ignore build directory 78 | build* 79 | 80 | ======= 81 | # C++ objects and libs 82 | *.slo 83 | *.lo 84 | *.o 85 | *.a 86 | *.la 87 | *.lai 88 | *.so 89 | *.so.* 90 | *.dll 91 | *.dylib 92 | 93 | # Qt-es 94 | object_script.*.Release 95 | object_script.*.Debug 96 | *_plugin_import.cpp 97 | /.qmake.cache 98 | /.qmake.stash 99 | *.pro.user 100 | *.pro.user.* 101 | *.qbs.user 102 | *.qbs.user.* 103 | *.moc 104 | moc_*.cpp 105 | moc_*.h 106 | qrc_*.cpp 107 | ui_*.h 108 | *.qmlc 109 | *.jsc 110 | Makefile* 111 | *build-* 112 | *.qm 113 | *.prl 114 | 115 | # Qt unit tests 116 | target_wrapper.* 117 | 118 | # QtCreator 119 | *.autosave 120 | 121 | # QtCreator Qml 122 | *.qmlproject.user 123 | *.qmlproject.user.* 124 | 125 | # QtCreator CMake 126 | CMakeLists.txt.user* 127 | 128 | # QtCreator 4.8< compilation database 129 | compile_commands.json 130 | 131 | # QtCreator local machine specific files for imported projects 132 | *creator.user* 133 | 134 | *_qmlcache.qrc 135 | >>>>>>> aca94a1b15613230c7423b84d291ed64954b6cf8 136 | 137 | 138 | DB8* 139 | 0* 140 | *.zip 141 | *.mp4 142 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Wu Maojia 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QTChatroom 2 | 3 | ## 简介 4 | 本项目是一个用QT实现的桌面端自部署聊天室系统软件,含服务端和客户端,使用经过SSL加密的TCP通信和SQLite数据库管理系统。 5 | 6 | 软件作者:[@omigeft](https://github.com/omigeft) (后端开发),[@Futurow](https://github.com/Futurow) (UI设计),[@123duduke](https://github.com/123duduke) (数据库设计) 7 | 8 | #### 运行方法 9 | 10 | 使用QT编译即可运行。作者测试时使用Qt5.12.9的MinGW64bit编译。 11 | 12 | 请注意一些约束比如用户名字符数1-20,密码字符数6-20且必须包含数字和字母。 13 | 14 | 服务器启动需要有SSL证书和密钥,作者编写了一个[Python脚本](server/src/generatecertandkey.py)用于生成自签名证书和密钥,如果服务器启动时找不到证书和密钥,会自动调用该脚本生成证书和密钥,需要本地配置了Python开发环境并安装了相应的库。此外,客户端会忽略自签名证书SSL错误来接受自签名证书。自签名证书与密钥仅用于测试和学习,请勿在生产环境中使用。对于生产环境,推荐使用由受信任证书颁发机构(CA)签发的证书。 15 | 16 | #### 已实现的主要功能有 17 | 1. 建立服务器 18 | 2. 局域网通信 19 | 3. 用户、聊天室管理,包括用户聊天室的创建和消息撤回等 20 | 4. 客户端可以进行注册、登录、创建和加入聊天室以及文本聊天等操作 21 | 5. 生成自签名SSL证书和密钥来加密TCP通信过程 22 | 23 | #### 计划表 24 | 1. 公网通信 25 | 2. 图片、文件传输等多媒体通信 26 | 3. 支持多国语言 27 | 4. 多线程的使用 28 | 5. 自定义样式皮肤、聊天室头像等 29 | 6. 将错误提示从终端显示改为弹窗/悬浮窗提示 30 | 7. 本地缓存聊天记录 31 | 32 | #### 软件UI各界面介绍 33 | 34 | ![UIIntroduction](assets/UIIntroduction.png) 35 | 36 | #### ER图 37 | 38 | ![ERDiagram](assets/ERDiagram.png) 39 | 40 | #### 网络通信示意图 41 | 42 | ![network](assets/network.png) 43 | 44 | #### 服务端用户逻辑 45 | 46 | ![server](assets/server.png) 47 | 48 | #### 客户端用户逻辑 49 | 50 | ![cilent](assets/client.png) 51 | 52 | #### 服务端界面截图 53 | 54 | ![serverWidget](assets/serverWidget.png) 55 | 56 | ![serverAdminmanagement1](assets/serverAdminmanagement1.png) 57 | 58 | ![serverAdminmanagement2](assets/serverAdminmanagement2.png) 59 | 60 | ![serverUsermanage](assets/serverUsermanage.png) 61 | 62 | ![serverMessagemanage](assets/serverMessagemanage.png) 63 | 64 | #### 客户端界面截图 65 | 66 | ![clientMainwindow](assets/clientMainwindow.png) 67 | 68 | ![cilentLoginwindow](assets/clientLoginwindow.png) 69 | 70 | ![cilentChatlist](assets/clientChatlist.png) 71 | 72 | ![cilentChat](assets/clientChat.png) 73 | 74 | #### 开源许可证 75 | 76 | 本项目不作盈利目的,仅供学习交流用。项目代码的授权协议为[MIT License](LICENSE),使用本源码时请遵守该协议。 77 | -------------------------------------------------------------------------------- /assets/ERDiagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omigeft/QTChatroom/cf1146f2a3517bea1c44a96210eb56a1c5c71b6f/assets/ERDiagram.png -------------------------------------------------------------------------------- /assets/UIIntroduction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omigeft/QTChatroom/cf1146f2a3517bea1c44a96210eb56a1c5c71b6f/assets/UIIntroduction.png -------------------------------------------------------------------------------- /assets/client.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omigeft/QTChatroom/cf1146f2a3517bea1c44a96210eb56a1c5c71b6f/assets/client.png -------------------------------------------------------------------------------- /assets/clientChat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omigeft/QTChatroom/cf1146f2a3517bea1c44a96210eb56a1c5c71b6f/assets/clientChat.png -------------------------------------------------------------------------------- /assets/clientChatlist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omigeft/QTChatroom/cf1146f2a3517bea1c44a96210eb56a1c5c71b6f/assets/clientChatlist.png -------------------------------------------------------------------------------- /assets/clientLoginwindow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omigeft/QTChatroom/cf1146f2a3517bea1c44a96210eb56a1c5c71b6f/assets/clientLoginwindow.png -------------------------------------------------------------------------------- /assets/clientMainwindow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omigeft/QTChatroom/cf1146f2a3517bea1c44a96210eb56a1c5c71b6f/assets/clientMainwindow.png -------------------------------------------------------------------------------- /assets/network.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omigeft/QTChatroom/cf1146f2a3517bea1c44a96210eb56a1c5c71b6f/assets/network.png -------------------------------------------------------------------------------- /assets/server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omigeft/QTChatroom/cf1146f2a3517bea1c44a96210eb56a1c5c71b6f/assets/server.png -------------------------------------------------------------------------------- /assets/serverAdminmanagement1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omigeft/QTChatroom/cf1146f2a3517bea1c44a96210eb56a1c5c71b6f/assets/serverAdminmanagement1.png -------------------------------------------------------------------------------- /assets/serverAdminmanagement2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omigeft/QTChatroom/cf1146f2a3517bea1c44a96210eb56a1c5c71b6f/assets/serverAdminmanagement2.png -------------------------------------------------------------------------------- /assets/serverMessagemanage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omigeft/QTChatroom/cf1146f2a3517bea1c44a96210eb56a1c5c71b6f/assets/serverMessagemanage.png -------------------------------------------------------------------------------- /assets/serverUsermanage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omigeft/QTChatroom/cf1146f2a3517bea1c44a96210eb56a1c5c71b6f/assets/serverUsermanage.png -------------------------------------------------------------------------------- /assets/serverWidget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omigeft/QTChatroom/cf1146f2a3517bea1c44a96210eb56a1c5c71b6f/assets/serverWidget.png -------------------------------------------------------------------------------- /client/src/ImageRes.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | pic/0.jpg 4 | pic/1.jpg 5 | pic/2.jpg 6 | pic/3.jpg 7 | pic/4.jpg 8 | pic/5.jpg 9 | pic/6.jpg 10 | pic/7.jpg 11 | pic/8.jpg 12 | pic/9.jpg 13 | pic/10.jpg 14 | pic/listback0.jpg 15 | pic/listback1.jpg 16 | pic/listback2.jpg 17 | pic/listback3.jpg 18 | 19 | 20 | icon/close.png 21 | 22 | 23 | -------------------------------------------------------------------------------- /client/src/chat.cpp: -------------------------------------------------------------------------------- 1 | #include "chat.h" 2 | #include 3 | Chat::Chat(const QString &chatName, QWidget *parent) : 4 | QWidget(parent), 5 | ui(new Ui::Chat), 6 | currentChatName(chatName) 7 | { 8 | ui->setupUi(this); 9 | setAttribute(Qt::WA_QuitOnClose,false); //关闭窗口时不退出程序 10 | ui->chatNamelabel->setText("当前聊天:" + currentChatName); 11 | 12 | // 获取核心实例 13 | core = &ClientCore::getInstance(); 14 | 15 | this->setWindowFlags(Qt::FramelessWindowHint);//去除标题栏 16 | this->setAttribute(Qt::WA_TranslucentBackground);//透明 17 | //绘制阴影 18 | QGraphicsDropShadowEffect * shadowEffect = new QGraphicsDropShadowEffect(); 19 | shadowEffect->setOffset(0, 0); 20 | QColor color = Qt::black; 21 | color.setAlpha(64); 22 | shadowEffect->setColor(color); 23 | shadowEffect->setBlurRadius(20); 24 | ui->frame->setGraphicsEffect(shadowEffect); 25 | // 群头像 26 | QString picPath = ":/pic/"+QString::number(QRandomGenerator::global()->bounded(10))+".jpg"; 27 | qDebug()<Imagelabel->setPixmap(QPixmap(picPath)); 29 | //设置关闭按钮 30 | ui->CloseButton->setIcon(QPixmap(":/icon/icon/close.png")); 31 | 32 | ui->SendButton->setEnabled(false); 33 | // 如果ui->MessageInput长度在1~200之间,激活ui->SendButton 34 | connect(ui->MessageInput, &QTextEdit::textChanged, [=]() { 35 | if (ui->MessageInput->toPlainText().length() >= 1 && ui->MessageInput->toPlainText().length() <= 200) { 36 | ui->SendButton->setEnabled(true); 37 | } else { 38 | ui->SendButton->setEnabled(false); 39 | } 40 | }); 41 | 42 | latestMessageID = 0; 43 | lastTime = "yyyy-MM-dd hh:mm:ss"; 44 | 45 | refreshUserList(); 46 | refreshChat(); 47 | 48 | // 每隔1秒重复刷新一次聊天记录 49 | QTimer *timer = new QTimer(this); 50 | connect(timer, &QTimer::timeout, this, &Chat::refreshChat); 51 | timer->start(1000); 52 | 53 | // 每隔5秒重复刷新一次聊天室成员列表 54 | QTimer *timer2 = new QTimer(this); 55 | connect(timer2, &QTimer::timeout, this, &Chat::refreshUserList); 56 | timer2->start(5000); 57 | } 58 | 59 | Chat::~Chat() 60 | { 61 | delete ui; 62 | } 63 | 64 | void Chat::refreshUserList() { 65 | // 从服务器上获取一次聊天室成员列表 66 | QJsonArray userListArray = core->getChatUserListRequest(currentChatName); 67 | userList.clear(); 68 | for (int i = 0; i < userListArray.size(); i++) { 69 | userList.append(userListArray[i].toString()); 70 | qDebug() << i << userListArray[i].toString(); 71 | } 72 | 73 | // 清空聊天室成员列表 74 | ui->UserlistWidget->clear(); 75 | 76 | // 更新聊天室成员列表 77 | ui->UserlistWidget->addItems(userList); 78 | } 79 | 80 | void Chat::refreshChat() 81 | { 82 | // 从服务器上获取一次最新聊天记录 83 | QJsonArray newMessageArray = core->getMessageRequest(currentChatName, latestMessageID, lastTime); 84 | 85 | // 更新QStringList chatHistory和显示的聊天记录 86 | for (int i = 0; i < newMessageArray.size(); i++) { 87 | QJsonObject message = newMessageArray[i].toObject(); 88 | if (message["time"].toString().isEmpty()) { 89 | // 该条消息是撤回消息,需要在聊天记录中找到该条消息并删除 90 | int id = message["id"].toInt(); 91 | qDebug() << "撤回消息" << id; 92 | // 在chatMessageID中调用std二分查找 93 | int index = std::lower_bound(chatMessageID.begin(), chatMessageID.end(), id) - chatMessageID.begin(); 94 | if (index < chatMessageID.size() && chatMessageID[index] == id) { 95 | // 找到了该条消息,删除 96 | chatMessageID.removeAt(index); 97 | chatHistory.removeAt(index); 98 | } 99 | qDebug() << "成功撤回消息" << id; 100 | } else { 101 | int id = message["id"].toInt(); 102 | QString name = message["name"].toString(); 103 | QString content = message["content"].toString(); 104 | QString time = message["time"].toString(); 105 | chatMessageID.append(id); 106 | chatHistory.append("用户:" + name + " [" + time + "]\n" + content); 107 | latestMessageID = std::max(latestMessageID, id); 108 | } 109 | } 110 | 111 | // 更新lastTime 112 | lastTime = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"); 113 | 114 | // 同步 115 | ui->chatBrowser->setPlainText(chatHistory.join("\n")); 116 | 117 | // textBrowser自动滚动到最底部 118 | ui->chatBrowser->moveCursor(QTextCursor::End); 119 | } 120 | 121 | void Chat::on_CloseButton_clicked() 122 | { 123 | this->close(); 124 | } 125 | 126 | void Chat::on_SendButton_clicked() 127 | { 128 | // 获取输入的消息 129 | QString message=ui->MessageInput->toPlainText(); 130 | 131 | // 发送消息 132 | core->sendMessageRequest(currentChatName, core->currentUserName, message); 133 | 134 | // 清空输入框 135 | ui->MessageInput->clear(); 136 | 137 | refreshChat(); 138 | } 139 | void Chat::mousePressEvent(QMouseEvent * event) 140 | { 141 | diff_pos = this->pos()-event->globalPos(); 142 | } 143 | void Chat::mouseMoveEvent(QMouseEvent *event) 144 | { 145 | this->move(event->globalPos()+diff_pos); 146 | } 147 | -------------------------------------------------------------------------------- /client/src/chat.h: -------------------------------------------------------------------------------- 1 | #ifndef CHAT_H 2 | #define CHAT_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "ui_chat.h" 13 | #include "clientcore.h" 14 | 15 | namespace Ui { 16 | class Chat; 17 | } 18 | 19 | class Chat : public QWidget 20 | { 21 | Q_OBJECT 22 | 23 | public: 24 | explicit Chat(const QString &chatName, QWidget *parent = nullptr); 25 | ~Chat(); 26 | 27 | private slots: 28 | void refreshUserList(); 29 | 30 | void refreshChat(); 31 | 32 | void on_CloseButton_clicked(); 33 | 34 | void on_SendButton_clicked(); 35 | 36 | private: 37 | Ui::Chat *ui; 38 | ClientCore *core; 39 | 40 | QString currentChatName; 41 | QStringList userList; 42 | QStringList chatHistory; 43 | QList chatMessageID; 44 | void mousePressEvent(QMouseEvent *event) override; 45 | void mouseMoveEvent(QMouseEvent *event) override; 46 | QPoint diff_pos; 47 | 48 | int latestMessageID; 49 | QString lastTime; 50 | QTimer *timer; 51 | }; 52 | 53 | #endif // CHAT_H 54 | -------------------------------------------------------------------------------- /client/src/chat.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Chat 4 | 5 | 6 | 7 | 0 8 | 0 9 | 861 10 | 548 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | #frame{ 24 | background-color: rgba(255, 255, 255,0.9); 25 | border-radius: 16px; 26 | } 27 | QLabel{ 28 | front-color:rgb(255, 255, 255); 29 | } 30 | 31 | 32 | QFrame::StyledPanel 33 | 34 | 35 | QFrame::Raised 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | TextLabel 47 | 48 | 49 | 50 | 51 | 52 | 53 | Qt::Horizontal 54 | 55 | 56 | 57 | 40 58 | 20 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 20 68 | 20 69 | 70 | 71 | 72 | #CloseButton{ 73 | border:none; 74 | background:rgba(255,255,255,0.4); 75 | } 76 | #CloseButton:hover { 77 | background: rgba(255,255,255,0.7); 78 | } 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | QTextBrowser{ 95 | background-color:#D4D4D3; 96 | } 97 | 98 | 99 | 100 | 101 | 102 | 103 | Qt::Vertical 104 | 105 | 106 | QSizePolicy::Fixed 107 | 108 | 109 | 110 | 20 111 | 13 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 16777215 121 | 200 122 | 123 | 124 | 125 | QTextEdit{ 126 | background-color:#D4D4D3; 127 | } 128 | 129 | 130 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> 131 | <html><head><meta name="qrichtext" content="1" /><style type="text/css"> 132 | p, li { white-space: pre-wrap; } 133 | </style></head><body style=" font-family:'SimSun'; font-size:9pt; font-weight:400; font-style:normal;"> 134 | <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> 135 | 136 | 137 | 138 | 139 | 140 | 141 | QPushButton { 142 | background-color:#030408; 143 | color:#db9a45; 144 | font-size: 20px; 145 | font:10pt "Microsoft YaHei UI"; 146 | border-radius:8px; 147 | font-weight:700; 148 | height:35px; 149 | width: 100%; 150 | } 151 | QPushButton:disabled { 152 | background-color: #787a7e; 153 | } 154 | QPushButton:hover { 155 | background-color: #383a3e; 156 | } 157 | 158 | 159 | 发送 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | QLayout::SetFixedSize 169 | 170 | 171 | 172 | 173 | 174 | 200 175 | 200 176 | 177 | 178 | 179 | 180 | 200 181 | 200 182 | 183 | 184 | 185 | 186 | 187 | 188 | :/pic/0.jpg 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 聊天室成员 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 200 206 | 16777215 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | chatBrowser 227 | MessageInput 228 | SendButton 229 | UserlistWidget 230 | 231 | 232 | 233 | 234 | 235 | 236 | -------------------------------------------------------------------------------- /client/src/chatlist.cpp: -------------------------------------------------------------------------------- 1 | #include "chatlist.h" 2 | #include "messagebox.h" 3 | #include 4 | #include 5 | ChatList::ChatList(QWidget *parent) : 6 | QWidget(parent), 7 | ui(new Ui::ChatList) { 8 | ui->setupUi(this); 9 | 10 | // 获取核心实例 11 | core = &ClientCore::getInstance(); 12 | 13 | this->setWindowFlags(Qt::FramelessWindowHint);//去除标题栏 14 | this->setAttribute(Qt::WA_TranslucentBackground);//透明 15 | //绘制阴影 16 | QGraphicsDropShadowEffect * shadowEffect = new QGraphicsDropShadowEffect(); 17 | shadowEffect->setOffset(0, 0); 18 | QColor color = Qt::black; 19 | color.setAlpha(64); 20 | shadowEffect->setColor(color); 21 | shadowEffect->setBlurRadius(20); 22 | ui->frame->setGraphicsEffect(shadowEffect); 23 | //设置该聊天窗口的标题-----用户名 24 | ui->UserNameLabel->setText("当前用户:" + core->currentUserName); 25 | //设置背景 26 | //QString qss = "#frame{border-image:url(:/pic/listback"+QString::number(QRandomGenerator::global()->bounded(4))+".jpg)}"; 27 | //ui->frame->setStyleSheet(qss); 28 | //设置关闭按钮 29 | ui->closeButton->setIcon(QPixmap(":/icon/icon/close.png")); 30 | 31 | ui->NewChatButton->setEnabled(false); 32 | 33 | // 如果ui->ChatNameInput编辑了不为空,则激活ui->NewChatButton 34 | connect(ui->ChatNameInput, &QLineEdit::textChanged, [=](){ 35 | ui->NewChatButton->setEnabled(!ui->ChatNameInput->text().isEmpty()); 36 | }); 37 | 38 | // 刷新列表 39 | refreshChatList(); 40 | 41 | // 每隔5秒重复刷新一次聊天室列表 42 | QTimer *timer = new QTimer(this); 43 | connect(timer, &QTimer::timeout, this, &ChatList::refreshChatList); 44 | timer->start(5000); 45 | } 46 | 47 | ChatList::~ChatList() { 48 | delete ui; 49 | } 50 | 51 | void ChatList::refreshChatList() { 52 | // 从服务器上获取一次聊天室列表 53 | core->getChatListRequest(core->currentUserName); 54 | 55 | // 清空列表 56 | ui->HJoinChatListWidget->clear(); 57 | ui->UJoinChatListWidget->clear(); 58 | 59 | // 更新列表 60 | if (findName.isEmpty()) { 61 | ui->HJoinChatListWidget->addItems(core->joinedList); 62 | ui->UJoinChatListWidget->addItems(core->unjoinedList); 63 | } else { 64 | for (auto it = core->joinedList.begin(); it != core->joinedList.end(); it++) { 65 | if (it->contains(findName)) { 66 | ui->HJoinChatListWidget->addItem(*it); 67 | } 68 | } 69 | for (auto it = core->unjoinedList.begin(); it != core->unjoinedList.end(); it++) { 70 | if (it->contains(findName)) { 71 | ui->UJoinChatListWidget->addItem(*it); 72 | } 73 | } 74 | } 75 | } 76 | 77 | void ChatList::on_OpenChatButton_clicked() { 78 | if (ui->HJoinChatListWidget->count() > 0 && ui->HJoinChatListWidget->currentItem() != NULL) { 79 | qDebug() << ui->HJoinChatListWidget->count(); 80 | QString chatName = ui->HJoinChatListWidget->currentItem()->text(); 81 | qDebug() << chatName; 82 | 83 | //打开聊天界面 84 | Chat * userChat = new Chat(chatName); 85 | userChat->show(); 86 | core->nameChatMap.insert(chatName, userChat); 87 | } else { 88 | MessageBox::critical(this, "错误", "打开失败,请检查是否选中聊天室"); 89 | } 90 | } 91 | 92 | void ChatList::on_JoinButton_clicked() { 93 | if (ui->UJoinChatListWidget->count() > 0 && ui->UJoinChatListWidget->currentItem() != NULL) { 94 | QString chatName = ui->UJoinChatListWidget->currentItem()->text(); 95 | core->joinChatroomRequest(chatName, core->currentUserName); 96 | refreshChatList(); 97 | } else { 98 | MessageBox::critical(this, "错误", "加入失败,请检查是否选中聊天室"); 99 | } 100 | } 101 | 102 | void ChatList::on_NewChatButton_clicked() { 103 | QString chatName = ui->ChatNameInput->text(); 104 | if (chatName.length() > 0 && chatName.length() <= 20) { 105 | core->createChatroomRequest(chatName, core->currentUserName); 106 | refreshChatList(); 107 | } else { 108 | QMessageBox::critical(this, "错误", "群聊名长度应在1~20个字符之间"); 109 | } 110 | } 111 | 112 | void ChatList::on_FindChatButton_clicked() { 113 | findName = ui->ChatNameInput->text(); 114 | refreshChatList(); 115 | } 116 | 117 | void ChatList::on_closeButton_clicked() { 118 | this->close(); 119 | } 120 | void ChatList::mousePressEvent(QMouseEvent * event) { 121 | diff_pos = this->pos()-event->globalPos(); 122 | } 123 | void ChatList::mouseMoveEvent(QMouseEvent *event) { 124 | this->move(event->globalPos()+diff_pos); 125 | } 126 | -------------------------------------------------------------------------------- /client/src/chatlist.h: -------------------------------------------------------------------------------- 1 | #ifndef CHATLIST_H 2 | #define CHATLIST_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "ui_chatlist.h" 12 | #include "chat.h" 13 | #include "clientcore.h" 14 | 15 | namespace Ui { 16 | class ChatList; 17 | } 18 | 19 | class ChatList : public QWidget 20 | { 21 | Q_OBJECT 22 | 23 | public: 24 | explicit ChatList(QWidget *parent = nullptr); 25 | ~ChatList(); 26 | 27 | private slots: 28 | void refreshChatList(); 29 | 30 | void on_OpenChatButton_clicked(); 31 | 32 | void on_JoinButton_clicked(); 33 | 34 | void on_NewChatButton_clicked(); 35 | 36 | void on_FindChatButton_clicked(); 37 | 38 | void on_closeButton_clicked(); 39 | 40 | private: 41 | void mousePressEvent(QMouseEvent *event) override; 42 | void mouseMoveEvent(QMouseEvent *event) override; 43 | QPoint diff_pos; 44 | Ui::ChatList *ui; 45 | ClientCore *core; 46 | QString findName; 47 | }; 48 | 49 | #endif // CHATLIST_H 50 | -------------------------------------------------------------------------------- /client/src/chatlist.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | ChatList 4 | 5 | 6 | 7 | 0 8 | 0 9 | 500 10 | 606 11 | 12 | 13 | 14 | 15 | 500 16 | 930 17 | 18 | 19 | 20 | 群聊列表 21 | 22 | 23 | #frame{ 24 | border: 1px solid rgb(41, 57, 85); 25 | border-radius: 10px; 26 | border-color:#dcdee0; 27 | background-color: rgba(255, 255, 255,0.9); 28 | } 29 | QLabel{ 30 | color:rgb(0, 0, 0) 31 | } 32 | QPushButton { 33 | background-color:#030408; 34 | color:#db9a45; 35 | font:9pt "Microsoft YaHei UI"; 36 | border-radius:5px; 37 | font-weight:650; 38 | height:25px; 39 | width: 80%; 40 | } 41 | QPushButton:disabled { 42 | background-color: #787a7e; 43 | } 44 | QPushButton:hover { 45 | background-color: #383a3e; 46 | } 47 | QListWidget{ 48 | background:rgba(255,255,255,0.7); 49 | } 50 | 51 | 52 | 53 | 54 | 55 | 56 | 500 57 | 930 58 | 59 | 60 | 61 | #frame{border-image:url(pic/listback3.jpg)} 62 | 63 | 64 | QFrame::StyledPanel 65 | 66 | 67 | QFrame::Raised 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | TextLabel 76 | 77 | 78 | 79 | 80 | 81 | 82 | Qt::Horizontal 83 | 84 | 85 | 86 | 40 87 | 20 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 20 97 | 20 98 | 99 | 100 | 101 | #closeButton{ 102 | border:none; 103 | background:rgba(255,255,255,0.4); 104 | } 105 | #closeButton:hover { 106 | background: rgba(255,255,255,0.7); 107 | } 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 已加入的群聊 124 | 125 | 126 | 127 | 128 | 129 | 130 | Qt::Horizontal 131 | 132 | 133 | 134 | 40 135 | 20 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 打开群聊 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | QLineEdit{ 162 | border: 1px solid rgb(41, 57, 85); 163 | border-radius: 3px; 164 | border-color:#dcdee0; 165 | background: rgba(255,255,255,0.6); 166 | padding-left: 12px; 167 | padding-right: 12px; 168 | border-color:#e4e5eb; 169 | } 170 | QLineEdit:hover { 171 | border-color: #db9a45; 172 | } 173 | 174 | 175 | 176 | 177 | 178 | 179 | 搜索 180 | 181 | 182 | 183 | 184 | 185 | 186 | 创建新群聊 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 未加入的群聊 198 | 199 | 200 | 201 | 202 | 203 | 204 | Qt::Horizontal 205 | 206 | 207 | 208 | 40 209 | 20 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 加入 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | -------------------------------------------------------------------------------- /client/src/client.pro: -------------------------------------------------------------------------------- 1 | QT += core gui network concurrent 2 | 3 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 4 | 5 | CONFIG += c++11 6 | 7 | # The following define makes your compiler emit warnings if you use 8 | # any Qt feature that has been marked deprecated (the exact warnings 9 | # depend on your compiler). Please consult the documentation of the 10 | # deprecated API in order to know how to port your code away from it. 11 | DEFINES += QT_DEPRECATED_WARNINGS 12 | 13 | # You can also make your code fail to compile if it uses deprecated APIs. 14 | # In order to do so, uncomment the following line. 15 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 16 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 17 | 18 | SOURCES += \ 19 | chat.cpp \ 20 | chatlist.cpp \ 21 | clientcore.cpp \ 22 | loginwindow.cpp \ 23 | main.cpp \ 24 | mainwindow.cpp \ 25 | messagebox.cpp 26 | 27 | HEADERS += \ 28 | chat.h \ 29 | chatlist.h \ 30 | clientcore.h \ 31 | loginwindow.h \ 32 | mainwindow.h \ 33 | messagebox.h 34 | 35 | FORMS += \ 36 | chat.ui \ 37 | chatlist.ui \ 38 | loginwindow.ui \ 39 | mainwindow.ui 40 | 41 | # Default rules for deployment. 42 | qnx: target.path = /tmp/$${TARGET}/bin 43 | else: unix:!android: target.path = /opt/$${TARGET}/bin 44 | !isEmpty(target.path): INSTALLS += target 45 | 46 | RESOURCES += \ 47 | ImageRes.qrc 48 | -------------------------------------------------------------------------------- /client/src/clientcore.cpp: -------------------------------------------------------------------------------- 1 | #include "chat.h" 2 | #include "clientcore.h" 3 | 4 | bool ClientCore::connectServer(const QHostAddress &address, const quint16 &port) { 5 | // 检查格式是否正确 6 | if(!address.isNull() && port > 0) { 7 | qDebug() << "正在连接到服务端..."; 8 | } else { 9 | qDebug() << "IP地址或端口号格式错误!"; 10 | return false; 11 | } 12 | 13 | serverAddress = address; 14 | serverPort = port; 15 | 16 | // 连接到服务端 17 | // socket.connectToHost(serverAddress.toString(), serverPort); 18 | socket.connectToHostEncrypted(serverAddress.toString(), serverPort); 19 | 20 | // 检查连接是否成功 21 | // if(socket.waitForConnected(3000)) { 22 | if(socket.waitForEncrypted(3000)) { 23 | qDebug() << "连接成功!"; 24 | 25 | // 测试发送数据 26 | 27 | // 创建一个 JSON 对象 28 | QJsonObject jsonObj; 29 | jsonObj["type"] = "test"; 30 | jsonObj["state"] = "request"; 31 | 32 | // 创建一个嵌套的JSON数据对象 33 | QJsonObject dataObj; 34 | dataObj["test"] = "test"; 35 | 36 | // 将嵌套的JSON数据对象添加到 "data" 字段 37 | jsonObj["data"] = dataObj; 38 | 39 | QString response; 40 | 41 | if(sendAndWait(response, jsonObj)) { 42 | qDebug() << "connectServer()收到服务端消息:" << response; 43 | 44 | if(!checkMessage(response, jsonObj["type"].toString(), "success")) { 45 | return false; 46 | } 47 | 48 | qDebug() << "收到响应:测试成功"; 49 | } else { 50 | qDebug() << "未能接收到服务端响应"; 51 | return false; 52 | } 53 | } else { 54 | qDebug() << "连接失败:" << socket.errorString(); 55 | return false; 56 | } 57 | 58 | return true; 59 | } 60 | 61 | bool ClientCore::registerRequest(const QString &userName, const QString &password, const QString &role) { 62 | // 检查用户名和密码是否在1-20个字符之间 63 | if (userName.length() < 1 || userName.length() > 20) { 64 | qDebug() << "用户名长度不符合要求!"; 65 | return false; 66 | } 67 | if (password.length() < 6 || password.length() > 20) { 68 | qDebug() << "密码长度不符合要求!"; 69 | return false; 70 | } 71 | 72 | // 限制role必须为("root", "admin", "user")中的一个 73 | if (role != "root" && role != "admin" && role != "user") { 74 | qDebug() << "角色必须为(\"root\", \"admin\", \"user\")中的一个!"; 75 | return false; 76 | } 77 | 78 | // pw must contain at least one number, one letter 79 | if (!password.contains(QRegularExpression("^(?=.*[0-9])(?=.*[a-zA-Z])(.{6,20})$"))) { 80 | qDebug() << "密码必须包含至少一个数字,一个字母!"; 81 | return false; 82 | } 83 | 84 | QJsonObject jsonObj = baseJsonObj("register", "request"); 85 | 86 | // 编辑数据字段 87 | QJsonObject dataObj = jsonObj["data"].toObject(); 88 | dataObj["userName"] = userName; 89 | dataObj["password"] = password; 90 | dataObj["role"] = role; 91 | jsonObj["data"] = dataObj; 92 | 93 | QString response; 94 | 95 | // 等待数据接收 96 | if (sendAndWait(response, jsonObj)) { 97 | if (!checkMessage(response, jsonObj["type"].toString(), "success")) { 98 | return false; 99 | } 100 | 101 | QJsonDocument resJsonDoc = QJsonDocument::fromJson(response.toUtf8()); 102 | QJsonObject resJsonObj = resJsonDoc.object(); 103 | QJsonObject resDataObj = resJsonObj["data"].toObject(); 104 | 105 | if (resDataObj["userName"].toString() != userName) { 106 | qDebug() << "收到响应:用户名不匹配"; 107 | return false; 108 | } 109 | 110 | qDebug() << "收到响应:新账号注册成功"; 111 | 112 | } else { 113 | qDebug() << "未能接收到服务端响应"; 114 | return false; 115 | } 116 | 117 | return true; 118 | } 119 | 120 | bool ClientCore::loginRequest(const QString &userName, const QString &password) { 121 | // 检查用户名和密码是否在1-20个字符之间 122 | if (userName.length() < 1 || userName.length() > 20) { 123 | qDebug() << "用户名长度不符合要求!"; 124 | return false; 125 | } 126 | if (password.length() < 6 || password.length() > 20) { 127 | qDebug() << "密码长度不符合要求!"; 128 | return false; 129 | } 130 | 131 | // pw must contain at least one number, one letter 132 | if (!password.contains(QRegularExpression("^(?=.*[0-9])(?=.*[a-zA-Z])(.{6,20})$"))) { 133 | qDebug() << "密码必须包含至少一个数字,一个字母!"; 134 | return false; 135 | } 136 | 137 | QJsonObject jsonObj = baseJsonObj("login", "request"); 138 | 139 | // 编辑数据字段 140 | QJsonObject dataObj = jsonObj["data"].toObject(); 141 | dataObj["userName"] = userName; 142 | dataObj["password"] = password; 143 | jsonObj["data"] = dataObj; 144 | 145 | QString response; 146 | 147 | // 等待数据接收 148 | if (sendAndWait(response, jsonObj)) { 149 | if (!checkMessage(response, jsonObj["type"].toString(), "success")) { 150 | return false; 151 | } 152 | 153 | QJsonDocument resJsonDoc = QJsonDocument::fromJson(response.toUtf8()); 154 | QJsonObject resJsonObj = resJsonDoc.object(); 155 | QJsonObject resDataObj = resJsonObj["data"].toObject(); 156 | 157 | if (resDataObj["userName"].toString() != userName) { 158 | qDebug() << "收到响应:用户名不匹配"; 159 | return false; 160 | } 161 | 162 | qDebug() << "收到响应:登录成功"; 163 | 164 | currentUserName = userName; 165 | 166 | } else { 167 | qDebug() << "未能接收到服务端响应"; 168 | return false; 169 | } 170 | 171 | return true; 172 | } 173 | 174 | bool ClientCore::createChatroomRequest(const QString &chatName, const QString &creatorName) { 175 | // 检查聊天室名称是否在1-20个字符之间 176 | if (chatName.length() < 1 || chatName.length() > 20) { 177 | qDebug() << "聊天室名称长度不符合要求!"; 178 | return false; 179 | } 180 | 181 | QJsonObject jsonObj = baseJsonObj("createChatroom", "request"); 182 | 183 | // 编辑数据字段 184 | QJsonObject dataObj = jsonObj["data"].toObject(); 185 | dataObj["chatName"] = chatName; 186 | dataObj["creatorName"] = creatorName; 187 | jsonObj["data"] = dataObj; 188 | 189 | QString response; 190 | 191 | // 等待数据接收 192 | if (sendAndWait(response, jsonObj)) { 193 | if (!checkMessage(response, jsonObj["type"].toString(), "success")) { 194 | return false; 195 | } 196 | 197 | QJsonDocument resJsonDoc = QJsonDocument::fromJson(response.toUtf8()); 198 | QJsonObject resJsonObj = resJsonDoc.object(); 199 | QJsonObject resDataObj = resJsonObj["data"].toObject(); 200 | 201 | if (resDataObj["chatName"].toString() != chatName) { 202 | qDebug() << "收到响应:聊天室名称不匹配"; 203 | return false; 204 | } 205 | 206 | qDebug() << "收到响应:创建聊天室成功"; 207 | 208 | } else { 209 | qDebug() << "未能接收到服务端响应"; 210 | return false; 211 | } 212 | 213 | return true; 214 | } 215 | 216 | bool ClientCore::joinChatroomRequest(const QString &chatName, const QString &userName) { 217 | // 检查聊天室名称是否在1-20个字符之间 218 | if (chatName.length() < 1 || chatName.length() > 20) { 219 | qDebug() << "聊天室名称长度不符合要求!"; 220 | return false; 221 | } 222 | 223 | QJsonObject jsonObj = baseJsonObj("joinChatroom", "request"); 224 | 225 | // 编辑数据字段 226 | QJsonObject dataObj = jsonObj["data"].toObject(); 227 | dataObj["chatName"] = chatName; 228 | dataObj["userName"] = userName; 229 | jsonObj["data"] = dataObj; 230 | 231 | QString response; 232 | 233 | // 等待数据接收 234 | if (sendAndWait(response, jsonObj)) { 235 | if (!checkMessage(response, jsonObj["type"].toString(), "success")) { 236 | return false; 237 | } 238 | 239 | QJsonDocument resJsonDoc = QJsonDocument::fromJson(response.toUtf8()); 240 | QJsonObject resJsonObj = resJsonDoc.object(); 241 | QJsonObject resDataObj = resJsonObj["data"].toObject(); 242 | 243 | if (resDataObj["chatName"].toString() != chatName) { 244 | qDebug() << "收到响应:聊天室名称不匹配"; 245 | return false; 246 | } 247 | 248 | qDebug() << "收到响应:加入聊天室成功"; 249 | 250 | } else { 251 | qDebug() << "未能接收到服务端响应"; 252 | return false; 253 | } 254 | 255 | return true; 256 | } 257 | 258 | bool ClientCore::getChatListRequest(const QString &userName) { 259 | QJsonObject jsonObj = baseJsonObj("getChatList", "request"); 260 | 261 | // 编辑数据字段 262 | QJsonObject dataObj = jsonObj["data"].toObject(); 263 | dataObj["userName"] = userName; 264 | jsonObj["data"] = dataObj; 265 | 266 | QString response; 267 | 268 | // 等待数据接收 269 | if (sendAndWait(response, jsonObj)) { 270 | if (!checkMessage(response, jsonObj["type"].toString(), "success")) { 271 | return false; 272 | } 273 | 274 | QJsonDocument resJsonDoc = QJsonDocument::fromJson(response.toUtf8()); 275 | QJsonObject resJsonObj = resJsonDoc.object(); 276 | QJsonObject resDataObj = resJsonObj["data"].toObject(); 277 | 278 | if (resDataObj["userName"].toString() != userName) { 279 | qDebug() << "收到响应:用户名不匹配"; 280 | return false; 281 | } 282 | 283 | // 获取聊天室列表 284 | QJsonArray joinedChatList = resDataObj["joinedChatList"].toArray(); 285 | joinedList.clear(); 286 | for (int i = 0; i < joinedChatList.size(); ++i) { 287 | joinedList.append(joinedChatList[i].toString()); 288 | } 289 | 290 | QJsonArray unjoinedChatList = resDataObj["unjoinedChatList"].toArray(); 291 | unjoinedList.clear(); 292 | for (int i = 0; i < unjoinedChatList.size(); ++i) { 293 | unjoinedList.append(unjoinedChatList[i].toString()); 294 | } 295 | 296 | qDebug() << "收到响应:获取用户可访问的聊天室列表成功"; 297 | 298 | } else { 299 | qDebug() << "未能接收到服务端响应"; 300 | return false; 301 | } 302 | 303 | return true; 304 | } 305 | 306 | QJsonArray ClientCore::getChatUserListRequest(const QString &chatName) { 307 | QJsonObject jsonObj = baseJsonObj("getChatUserList", "request"); 308 | 309 | // 编辑数据字段 310 | QJsonObject dataObj = jsonObj["data"].toObject(); 311 | dataObj["chatName"] = chatName; 312 | jsonObj["data"] = dataObj; 313 | 314 | QString response; 315 | 316 | // 等待数据接收 317 | if (sendAndWait(response, jsonObj)) { 318 | if (!checkMessage(response, jsonObj["type"].toString(), "success")) { 319 | return QJsonArray(); 320 | } 321 | 322 | QJsonDocument resJsonDoc = QJsonDocument::fromJson(response.toUtf8()); 323 | QJsonObject resJsonObj = resJsonDoc.object(); 324 | QJsonObject resDataObj = resJsonObj["data"].toObject(); 325 | 326 | if (resDataObj["chatName"].toString() != chatName) { 327 | qDebug() << "收到响应:聊天室名称不匹配"; 328 | return QJsonArray(); 329 | } 330 | 331 | // 获取聊天室用户列表 332 | QJsonArray chatUserList = resDataObj["chatUserList"].toArray(); 333 | 334 | qDebug() << "收到响应:获取聊天室用户列表成功"; 335 | 336 | return chatUserList; 337 | 338 | } else { 339 | qDebug() << "未能接收到服务端响应"; 340 | return QJsonArray(); 341 | } 342 | } 343 | 344 | QJsonArray ClientCore::getMessageRequest(const QString &chatName, const int latestMessageID, const QString &lastTime) { 345 | QJsonObject jsonObj = baseJsonObj("getMessage", "request"); 346 | 347 | // 编辑数据字段 348 | QJsonObject dataObj = jsonObj["data"].toObject(); 349 | dataObj["chatName"] = chatName; 350 | dataObj["latestMessageID"] = latestMessageID; 351 | dataObj["lastTime"] = lastTime; 352 | jsonObj["data"] = dataObj; 353 | 354 | QString response; 355 | 356 | // 等待数据接收 357 | if (sendAndWait(response, jsonObj)) { 358 | if (!checkMessage(response, jsonObj["type"].toString(), "success")) { 359 | return QJsonArray(); 360 | } 361 | 362 | QJsonDocument resJsonDoc = QJsonDocument::fromJson(response.toUtf8()); 363 | QJsonObject resJsonObj = resJsonDoc.object(); 364 | QJsonObject resDataObj = resJsonObj["data"].toObject(); 365 | 366 | if (resDataObj["chatName"].toString() != chatName) { 367 | qDebug() << "收到响应:聊天室名称不匹配"; 368 | return QJsonArray(); 369 | } 370 | 371 | if (resDataObj["latestMessageID"].toInt() != latestMessageID) { 372 | qDebug() << "收到响应:最新消息ID不匹配"; 373 | return QJsonArray(); 374 | } 375 | 376 | if (resDataObj["lastTime"].toString() != lastTime) { 377 | qDebug() << "收到响应:时间不匹配"; 378 | return QJsonArray(); 379 | } 380 | 381 | // 获取聊天室消息列表 382 | QJsonArray messageList = resDataObj["messageList"].toArray(); 383 | 384 | qDebug() << "收到响应:获取聊天室消息列表成功"; 385 | 386 | return messageList; 387 | 388 | } else { 389 | qDebug() << "未能接收到服务端响应"; 390 | return QJsonArray(); 391 | } 392 | } 393 | 394 | bool ClientCore::sendMessageRequest(const QString &chatName, const QString &senderName, const QString &message) { 395 | QJsonObject jsonObj = baseJsonObj("sendMessage", "request"); 396 | 397 | // 编辑数据字段 398 | QJsonObject dataObj = jsonObj["data"].toObject(); 399 | dataObj["chatName"] = chatName; 400 | dataObj["senderName"] = senderName; 401 | dataObj["message"] = message; 402 | jsonObj["data"] = dataObj; 403 | 404 | QString response; 405 | 406 | // 等待数据接收 407 | if (sendAndWait(response, jsonObj)) { 408 | if (!checkMessage(response, jsonObj["type"].toString(), "success")) { 409 | return false; 410 | } 411 | 412 | QJsonDocument resJsonDoc = QJsonDocument::fromJson(response.toUtf8()); 413 | QJsonObject resJsonObj = resJsonDoc.object(); 414 | QJsonObject resDataObj = resJsonObj["data"].toObject(); 415 | 416 | if (resDataObj["chatName"].toString() != chatName) { 417 | qDebug() << "收到响应:聊天室名称不匹配"; 418 | return false; 419 | } 420 | 421 | qDebug() << "收到响应:发送消息成功"; 422 | 423 | return true; 424 | 425 | } else { 426 | qDebug() << "未能接收到服务端响应"; 427 | return false; 428 | } 429 | } 430 | 431 | void ClientCore::processReadMessage(const QString &message) { 432 | qDebug() << "收到服务端消息:" << message; 433 | 434 | emit readMessage(message); 435 | 436 | return; // TODO 437 | 438 | // 无聊天室打开时,不处理消息 439 | if (nameChatMap.isEmpty()) { 440 | return; 441 | } 442 | 443 | if (!checkMessage(message, "synchronization", "remind")) { 444 | return; 445 | } 446 | 447 | QJsonDocument resJsonDoc = QJsonDocument::fromJson(message .toUtf8()); 448 | QJsonObject resJsonObj = resJsonDoc.object(); 449 | QJsonObject resDataObj = resJsonObj["data"].toObject(); 450 | 451 | // 获取聊天室名称 452 | QString chatName = resDataObj["chatName"].toString(); 453 | 454 | // 被提醒,表示有新消息,需要更新消息列表 455 | // nameChatMap[chatName]->refreshChat(); 456 | } 457 | 458 | void ClientCore::onReadyRead() { 459 | // 读取服务端的消息 460 | QString message = QString::fromUtf8(socket.readAll()); 461 | 462 | // 如果包含多个报文,需要分割 463 | QStringList messageList = message.split("}{"); 464 | 465 | for (int i = 0; i < messageList.size(); i++) { 466 | // 如果不是第一个报文,需要加上 { 467 | if (i != 0) { 468 | messageList[i] = "{" + messageList[i]; 469 | } 470 | 471 | // 如果不是最后一个报文,需要加上 } 472 | if (i != messageList.size() - 1) { 473 | messageList[i] = messageList[i] + "}"; 474 | } 475 | 476 | processReadMessage(messageList[i]); 477 | } 478 | } 479 | 480 | void ClientCore::onSslErrors(const QList &errors) { 481 | for (const QSslError &error : errors) { 482 | if (error.error() == QSslError::SelfSignedCertificate) { 483 | socket.ignoreSslErrors(); 484 | } 485 | } 486 | } 487 | 488 | ClientCore::ClientCore() { 489 | // 收到服务器消息的信号槽连接 490 | connect(&socket, &QSslSocket::readyRead, this, &ClientCore::onReadyRead); 491 | 492 | // 通过忽略特定的SSL错误来接受自签名证书 493 | connect(&socket, static_cast &)>(&QSslSocket::sslErrors), 494 | this, &ClientCore::onSslErrors); 495 | } 496 | 497 | bool ClientCore::checkMessage(const QString &message, const QString &type, const QString &state) { 498 | // 解析服务端响应的 JSON 字符串 499 | QJsonDocument resJsonDoc = QJsonDocument::fromJson(message.toUtf8()); 500 | 501 | if (resJsonDoc.isNull() || !resJsonDoc.isObject()) { 502 | qDebug() << "数据报文解析失败!"; 503 | return false; 504 | } 505 | 506 | QJsonObject resJsonObj = resJsonDoc.object(); 507 | 508 | if (resJsonObj["type"].toString() != type) { 509 | // qDebug() << "响应数据报文类型" << resJsonObj["type"].toString() << "!=" << type << ",不是同步提醒请求"; 510 | return false; 511 | } 512 | 513 | if (resJsonObj["state"].toString() != state) { 514 | qDebug() << "收到响应:请求没有成功"; 515 | return false; 516 | } 517 | 518 | return true; 519 | } 520 | 521 | QJsonObject ClientCore::baseJsonObj(const QString &type, const QString &state) { 522 | // 创建一个 JSON 对象 523 | QJsonObject jsonObj; 524 | jsonObj["type"] = type; 525 | jsonObj["state"] = state; 526 | 527 | // 创建一个嵌套的JSON数据对象 528 | QJsonObject dataObj; 529 | jsonObj["data"] = dataObj; 530 | 531 | return jsonObj; 532 | } 533 | 534 | void ClientCore::sendJsonObj(const QJsonObject &jsonObj) { 535 | // 使用 QJsonDocument 生成 JSON 字符串,并发送报文 536 | QJsonDocument jsonDoc(jsonObj); 537 | QString message = jsonDoc.toJson(QJsonDocument::Compact); 538 | socket.write(message.toUtf8()); 539 | } 540 | 541 | bool ClientCore::sendAndWait(QString &response, const QJsonObject &jsonObj) { 542 | QTimer timer; 543 | QEventLoop loop; 544 | bool readFlag = false; // 区分是否在规定时间内接收到数据的标志 545 | 546 | connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); 547 | connect(this, &ClientCore::readMessage, &loop, [&](const QString &message){ 548 | response = message; 549 | readFlag = true; 550 | loop.quit(); 551 | }); 552 | 553 | timer.start(10000); 554 | sendJsonObj(jsonObj); 555 | loop.exec(); 556 | 557 | // 判断是否在规定时间内接收到数据 558 | return readFlag; 559 | } 560 | -------------------------------------------------------------------------------- /client/src/clientcore.h: -------------------------------------------------------------------------------- 1 | #ifndef CLIENTCORE_H 2 | #define CLIENTCORE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class Chat; 10 | 11 | class ClientCore : public QObject 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | static ClientCore& getInstance() { 17 | static ClientCore instance; // 单例对象 18 | return instance; 19 | } 20 | 21 | bool connectServer(const QHostAddress &address, const quint16 &port); 22 | 23 | bool registerRequest(const QString &userName, const QString &password, const QString &role); 24 | 25 | bool loginRequest(const QString &userName, const QString &password); 26 | 27 | bool createChatroomRequest(const QString &chatName, const QString &creatorName); 28 | 29 | bool joinChatroomRequest(const QString &chatName, const QString &userName); 30 | 31 | bool getChatListRequest(const QString &userName); 32 | 33 | QJsonArray getChatUserListRequest(const QString &chatName); 34 | 35 | QJsonArray getMessageRequest(const QString &chatName, const int latestMessageID, const QString &lastTime); 36 | 37 | bool sendMessageRequest(const QString &chatName, const QString &senderName, const QString &message); 38 | 39 | void processReadMessage(const QString &message); 40 | 41 | signals: 42 | void readMessage(const QString &message); 43 | 44 | void remindSignal(const QString &chatName); 45 | 46 | private slots: 47 | void onReadyRead(); 48 | 49 | void onSslErrors(const QList &errors); 50 | 51 | private: 52 | ClientCore(); // 私有构造函数,确保单例 53 | 54 | bool checkMessage(const QString &message, const QString &type, const QString &state); 55 | 56 | QJsonObject baseJsonObj(const QString &type, const QString &state); 57 | 58 | void sendJsonObj(const QJsonObject &jsonObj); 59 | 60 | bool sendAndWait(QString &response, const QJsonObject &jsonObj); 61 | 62 | public: 63 | QSslSocket socket; // 套接字 64 | QHostAddress serverAddress; // 服务器IP地址 65 | quint16 serverPort; // 服务器端口 66 | 67 | QString currentUserName; // 当前登录的用户名 68 | 69 | QStringList joinedList; 70 | QStringList unjoinedList; 71 | QStringList selectList; 72 | 73 | QMap nameChatMap; // 打开的聊天室的聊天室名与窗口实例的映射 74 | }; 75 | 76 | 77 | #endif // CLIENTCORE_H 78 | -------------------------------------------------------------------------------- /client/src/icon/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omigeft/QTChatroom/cf1146f2a3517bea1c44a96210eb56a1c5c71b6f/client/src/icon/close.png -------------------------------------------------------------------------------- /client/src/loginwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "loginwindow.h" 2 | #include "ui_loginwindow.h" 3 | #include "mainwindow.h" 4 | #include "chatlist.h" 5 | #include "messagebox.h" 6 | #include 7 | #include 8 | LoginWindow::LoginWindow(QWidget *parent) : 9 | QWidget(parent), 10 | ui(new Ui::LoginWindow) { 11 | ui->setupUi(this); 12 | this->setWindowFlags(Qt::FramelessWindowHint);//去除标题栏 13 | this->setAttribute(Qt::WA_TranslucentBackground);//透明 14 | //绘制阴影 15 | QGraphicsDropShadowEffect * shadowEffect = new QGraphicsDropShadowEffect(); 16 | shadowEffect->setOffset(0, 0); 17 | QColor color = Qt::black; 18 | color.setAlpha(64); 19 | shadowEffect->setColor(color); 20 | shadowEffect->setBlurRadius(20); 21 | ui->frame->setGraphicsEffect(shadowEffect); 22 | // 获取核心实例 23 | core = &ClientCore::getInstance(); 24 | } 25 | 26 | LoginWindow::~LoginWindow() { 27 | delete ui; 28 | } 29 | 30 | void LoginWindow::on_CloseButton_clicked() { 31 | this->close(); 32 | } 33 | 34 | void LoginWindow::on_RegisterButton_clicked() { 35 | if (core->registerRequest(ui->NameInput->text(), ui->PassWordInput->text(), "user")) { 36 | MessageBox::information(this, "提示", "注册成功"); 37 | } else { 38 | MessageBox::critical(this, "错误", "注册失败"); 39 | } 40 | } 41 | 42 | void LoginWindow::on_LoginButton_clicked() 43 | { 44 | if (core->loginRequest(ui->NameInput->text(), ui->PassWordInput->text())) { 45 | //切换到用户列表 46 | ChatList * userChatList = new ChatList(); 47 | this->close(); 48 | userChatList->show(); 49 | } else { 50 | MessageBox::critical(this, "错误", "登录失败"); 51 | } 52 | } 53 | 54 | void LoginWindow::paintEvent(QPaintEvent *) { 55 | QStyleOption opt; 56 | opt.init(this); 57 | QPainter p(this); 58 | style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); 59 | } 60 | 61 | void LoginWindow::mousePressEvent(QMouseEvent * event) { 62 | diff_pos = this->pos()-event->globalPos(); 63 | } 64 | 65 | void LoginWindow::mouseMoveEvent(QMouseEvent *event) { 66 | this->move(event->globalPos()+diff_pos); 67 | } 68 | -------------------------------------------------------------------------------- /client/src/loginwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef LOGINWINDOW_H 2 | #define LOGINWINDOW_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "clientcore.h" 8 | 9 | namespace Ui { 10 | class LoginWindow; 11 | } 12 | 13 | class LoginWindow : public QWidget 14 | { 15 | Q_OBJECT 16 | 17 | public: 18 | explicit LoginWindow(QWidget *parent = nullptr); 19 | void paintEvent(QPaintEvent *event) override; 20 | ~LoginWindow(); 21 | 22 | private slots: 23 | void on_CloseButton_clicked(); 24 | 25 | void on_RegisterButton_clicked(); 26 | 27 | void on_LoginButton_clicked(); 28 | 29 | private: 30 | void mousePressEvent(QMouseEvent *event) override; 31 | void mouseMoveEvent(QMouseEvent *event) override; 32 | QPoint diff_pos; 33 | Ui::LoginWindow *ui; 34 | ClientCore *core; 35 | }; 36 | 37 | #endif // LOGINWINDOW_H 38 | -------------------------------------------------------------------------------- /client/src/loginwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | LoginWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 297 11 | 12 | 13 | 14 | 15 | 400 16 | 0 17 | 18 | 19 | 20 | 21 | 400 22 | 300 23 | 24 | 25 | 26 | 登录 27 | 28 | 29 | QFrame{ 30 | border: 1px solid #dcdee0;; 31 | border-radius: 12px; 32 | background-color:rgb(255, 255, 255); 33 | } 34 | QLineEdit{ 35 | border: 1px solid rgb(41, 57, 85); 36 | border-radius: 3px; 37 | border-color:#dcdee0; 38 | background: white; 39 | padding-left: 12px; 40 | padding-right: 12px; 41 | border-color:#e4e5eb; 42 | } 43 | QLineEdit:hover { 44 | border-color: #db9a45; 45 | } 46 | 47 | 48 | 49 | 50 | 51 | QFrame::StyledPanel 52 | 53 | 54 | QFrame::Raised 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | Qt::Horizontal 63 | 64 | 65 | QSizePolicy::Fixed 66 | 67 | 68 | 69 | 40 70 | 20 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 0 80 | 45 81 | 82 | 83 | 84 | 用户名 85 | 86 | 87 | 88 | 89 | 90 | 91 | Qt::Horizontal 92 | 93 | 94 | QSizePolicy::Fixed 95 | 96 | 97 | 98 | 40 99 | 20 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 0 109 | 45 110 | 111 | 112 | 113 | 114 | 115 | 116 | QLineEdit::Password 117 | 118 | 119 | 密码 120 | 121 | 122 | 123 | 124 | 125 | 126 | Qt::Vertical 127 | 128 | 129 | QSizePolicy::Fixed 130 | 131 | 132 | 133 | 20 134 | 20 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | QPushButton{ 147 | color:#db9a45; 148 | font-size: 20px; 149 | font:10pt "Microsoft YaHei UI"; 150 | border-radius:8px; 151 | font-weight:700; 152 | height:44px; 153 | width: 100%; 154 | background-color:#030408; 155 | } 156 | QPushButton:disabled { 157 | background-color: #787a7e; 158 | } 159 | QPushButton:hover { 160 | background-color: #383a3e; 161 | } 162 | 163 | 164 | 登录 165 | 166 | 167 | 168 | 169 | 170 | 171 | Qt::Horizontal 172 | 173 | 174 | QSizePolicy::Fixed 175 | 176 | 177 | 178 | 20 179 | 20 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | QPushButton{ 188 | color:#db9a45; 189 | font-size: 20px; 190 | font:10pt "Microsoft YaHei UI"; 191 | border-radius:8px; 192 | font-weight:700; 193 | height:44px; 194 | width: 100%; 195 | background-color:#030408; 196 | } 197 | QPushButton:disabled { 198 | background-color: #787a7e; 199 | } 200 | QPushButton:hover { 201 | background-color: #383a3e; 202 | } 203 | 204 | 205 | 注册 206 | 207 | 208 | 209 | 210 | 211 | 212 | Qt::Horizontal 213 | 214 | 215 | QSizePolicy::Fixed 216 | 217 | 218 | 219 | 20 220 | 20 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | QPushButton{ 229 | color:#db9a45; 230 | font-size: 20px; 231 | font:10pt "Microsoft YaHei UI"; 232 | border-radius:8px; 233 | font-weight:700; 234 | height:44px; 235 | width: 100%; 236 | background-color:#030408; 237 | } 238 | 239 | 240 | 关闭 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | -------------------------------------------------------------------------------- /client/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | 3 | #include 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | QApplication a(argc, argv); 8 | MainWindow w; 9 | w.show(); 10 | return a.exec(); 11 | } 12 | -------------------------------------------------------------------------------- /client/src/mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include "ui_mainwindow.h" 3 | #include "loginwindow.h" 4 | #include "messagebox.h" 5 | #include 6 | #include 7 | MainWindow::MainWindow(QWidget *parent) 8 | : QMainWindow(parent) 9 | , ui(new Ui::MainWindow) 10 | { 11 | ui->setupUi(this); 12 | this->setWindowFlags(Qt::FramelessWindowHint);//去除标题栏 13 | this->setAttribute(Qt::WA_TranslucentBackground);//透明 14 | //绘制阴影 15 | QGraphicsDropShadowEffect * shadowEffect = new QGraphicsDropShadowEffect(); 16 | shadowEffect->setOffset(0, 0); 17 | QColor color = Qt::black; 18 | color.setAlpha(64); 19 | shadowEffect->setColor(color); 20 | shadowEffect->setBlurRadius(20); 21 | ui->frame->setGraphicsEffect(shadowEffect); 22 | 23 | // 获取核心实例 24 | core = &ClientCore::getInstance(); 25 | } 26 | 27 | MainWindow::~MainWindow() 28 | { 29 | delete ui; 30 | } 31 | 32 | 33 | void MainWindow::on_CloseButton_clicked() 34 | { 35 | this->close(); 36 | } 37 | 38 | void MainWindow::on_ConnectButton_clicked() 39 | { 40 | // 根据IP和端口进行连接 41 | if (core->connectServer(QHostAddress(ui->IPInput->text()), ui->PortInput->text().toInt())) { 42 | //进入登录界面 43 | LoginWindow * loginwindow = new LoginWindow(); 44 | this->close(); 45 | loginwindow->show(); 46 | } else { 47 | MessageBox::critical(this, "错误", "连接失败"); 48 | } 49 | } 50 | void MainWindow::mousePressEvent(QMouseEvent * event) 51 | { 52 | diff_pos = this->pos()-event->globalPos(); 53 | } 54 | void MainWindow::mouseMoveEvent(QMouseEvent *event) 55 | { 56 | this->move(event->globalPos()+diff_pos); 57 | } 58 | -------------------------------------------------------------------------------- /client/src/mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "clientcore.h" 8 | 9 | QT_BEGIN_NAMESPACE 10 | namespace Ui { class MainWindow; } 11 | QT_END_NAMESPACE 12 | 13 | class MainWindow : public QMainWindow 14 | { 15 | Q_OBJECT 16 | 17 | public: 18 | MainWindow(QWidget *parent = nullptr); 19 | ~MainWindow(); 20 | 21 | private slots: 22 | void on_CloseButton_clicked(); 23 | 24 | void on_ConnectButton_clicked(); 25 | 26 | private: 27 | void mousePressEvent(QMouseEvent *event) override; 28 | void mouseMoveEvent(QMouseEvent *event) override; 29 | 30 | private: 31 | Ui::MainWindow *ui; 32 | ClientCore *core; 33 | QPoint diff_pos; 34 | }; 35 | #endif // MAINWINDOW_H 36 | -------------------------------------------------------------------------------- /client/src/mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | 15 | 400 16 | 300 17 | 18 | 19 | 20 | 21 | 400 22 | 300 23 | 24 | 25 | 26 | 连接服务器 27 | 28 | 29 | QPushButton { 30 | background-color:#030408; 31 | color:#db9a45; 32 | font-size: 20px; 33 | font:10pt "Microsoft YaHei UI"; 34 | border-radius:8px; 35 | font-weight:700; 36 | height:44px; 37 | width: 100%; 38 | } 39 | QPushButton:disabled { 40 | background-color: #787a7e; 41 | } 42 | QPushButton:hover { 43 | background-color: #383a3e; 44 | } 45 | QFrame{ 46 | margin:2px; 47 | border: 1px solid #dcdee0;; 48 | border-radius: 24px; 49 | background-color:rgb(255, 255, 255); 50 | padding:10px; 51 | } 52 | QLineEdit{ 53 | border: 1px solid rgb(41, 57, 85); 54 | border-radius: 3px; 55 | border-color:#dcdee0; 56 | background: white; 57 | padding-left: 12px; 58 | padding-right: 12px; 59 | border-color:#e4e5eb; 60 | } 61 | QLineEdit:hover { 62 | border-color: #db9a45; 63 | } 64 | 65 | 66 | 67 | 68 | 69 | 70 | QFrame::StyledPanel 71 | 72 | 73 | QFrame::Raised 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 0 83 | 45 84 | 85 | 86 | 87 | 88 | 89 | 90 | 端口 91 | 92 | 93 | 94 | 95 | 96 | 97 | Qt::Vertical 98 | 99 | 100 | QSizePolicy::Fixed 101 | 102 | 103 | 104 | 20 105 | 13 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | Qt::Horizontal 114 | 115 | 116 | QSizePolicy::Fixed 117 | 118 | 119 | 120 | 40 121 | 20 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | Qt::Horizontal 130 | 131 | 132 | QSizePolicy::Fixed 133 | 134 | 135 | 136 | 40 137 | 20 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 0 147 | 45 148 | 149 | 150 | 151 | 152 | 153 | 154 | IP地址 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 16 164 | 165 | 166 | QLayout::SetDefaultConstraint 167 | 168 | 169 | 0 170 | 171 | 172 | 173 | 174 | Qt::Horizontal 175 | 176 | 177 | 178 | 40 179 | 20 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 连接 188 | 189 | 190 | 191 | 192 | 193 | 194 | Qt::Horizontal 195 | 196 | 197 | QSizePolicy::Fixed 198 | 199 | 200 | 201 | 30 202 | 20 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 关闭 214 | 215 | 216 | 217 | 218 | 219 | 220 | Qt::Horizontal 221 | 222 | 223 | 224 | 40 225 | 20 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | IPInput 240 | PortInput 241 | ConnectButton 242 | CloseButton 243 | 244 | 245 | 246 | 247 | -------------------------------------------------------------------------------- /client/src/messagebox.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omigeft/QTChatroom/cf1146f2a3517bea1c44a96210eb56a1c5c71b6f/client/src/messagebox.cpp -------------------------------------------------------------------------------- /client/src/messagebox.h: -------------------------------------------------------------------------------- 1 | #ifndef MESSAGEBOX_H 2 | #define MESSAGEBOX_H 3 | 4 | #include 5 | 6 | class MessageBox : public QMessageBox 7 | { 8 | Q_OBJECT 9 | 10 | }; 11 | 12 | #endif // MESSAGEBOX_H 13 | -------------------------------------------------------------------------------- /client/src/pic/0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omigeft/QTChatroom/cf1146f2a3517bea1c44a96210eb56a1c5c71b6f/client/src/pic/0.jpg -------------------------------------------------------------------------------- /client/src/pic/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omigeft/QTChatroom/cf1146f2a3517bea1c44a96210eb56a1c5c71b6f/client/src/pic/1.jpg -------------------------------------------------------------------------------- /client/src/pic/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omigeft/QTChatroom/cf1146f2a3517bea1c44a96210eb56a1c5c71b6f/client/src/pic/10.jpg -------------------------------------------------------------------------------- /client/src/pic/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omigeft/QTChatroom/cf1146f2a3517bea1c44a96210eb56a1c5c71b6f/client/src/pic/2.jpg -------------------------------------------------------------------------------- /client/src/pic/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omigeft/QTChatroom/cf1146f2a3517bea1c44a96210eb56a1c5c71b6f/client/src/pic/3.jpg -------------------------------------------------------------------------------- /client/src/pic/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omigeft/QTChatroom/cf1146f2a3517bea1c44a96210eb56a1c5c71b6f/client/src/pic/4.jpg -------------------------------------------------------------------------------- /client/src/pic/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omigeft/QTChatroom/cf1146f2a3517bea1c44a96210eb56a1c5c71b6f/client/src/pic/5.jpg -------------------------------------------------------------------------------- /client/src/pic/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omigeft/QTChatroom/cf1146f2a3517bea1c44a96210eb56a1c5c71b6f/client/src/pic/6.jpg -------------------------------------------------------------------------------- /client/src/pic/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omigeft/QTChatroom/cf1146f2a3517bea1c44a96210eb56a1c5c71b6f/client/src/pic/7.jpg -------------------------------------------------------------------------------- /client/src/pic/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omigeft/QTChatroom/cf1146f2a3517bea1c44a96210eb56a1c5c71b6f/client/src/pic/8.jpg -------------------------------------------------------------------------------- /client/src/pic/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omigeft/QTChatroom/cf1146f2a3517bea1c44a96210eb56a1c5c71b6f/client/src/pic/9.jpg -------------------------------------------------------------------------------- /client/src/pic/listback0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omigeft/QTChatroom/cf1146f2a3517bea1c44a96210eb56a1c5c71b6f/client/src/pic/listback0.jpg -------------------------------------------------------------------------------- /client/src/pic/listback1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omigeft/QTChatroom/cf1146f2a3517bea1c44a96210eb56a1c5c71b6f/client/src/pic/listback1.jpg -------------------------------------------------------------------------------- /client/src/pic/listback2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omigeft/QTChatroom/cf1146f2a3517bea1c44a96210eb56a1c5c71b6f/client/src/pic/listback2.jpg -------------------------------------------------------------------------------- /client/src/pic/listback3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omigeft/QTChatroom/cf1146f2a3517bea1c44a96210eb56a1c5c71b6f/client/src/pic/listback3.jpg -------------------------------------------------------------------------------- /server/src/adminmanagement.cpp: -------------------------------------------------------------------------------- 1 | #include "adminmanagement.h" 2 | #include "ui_adminmanagement.h" 3 | #include "usermanage.h" 4 | #include "messagemanage.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | AdminManagement::AdminManagement(QWidget *parent) : 12 | QWidget(parent), 13 | ui(new Ui::AdminManagement) { 14 | ui->setupUi(this); 15 | // 获取核心实例 16 | core = &ServerCore::getInstance(); 17 | //:/icon/manage.png 18 | //设置该聊天窗口的标题-----管理员名 19 | this->setWindowTitle("当前管理员:" + core->adminUserName); 20 | 21 | ui->UserTable->setModel(core->userTableModel); 22 | ui->ChatTable->setModel(core->chatTableModel); 23 | 24 | ui->UserTable->setColumnWidth(3, 160); 25 | ui->UserTable->setColumnWidth(4, 160); 26 | ui->ChatTable->setColumnWidth(3, 160); 27 | ui->ChatTable->setColumnWidth(4, 160); 28 | 29 | ui->DeleteUserButton->setEnabled(false); 30 | ui->DeleteChatButton->setEnabled(false); 31 | ui->ManageUserButton->setEnabled(false); 32 | ui->ManageChatButton->setEnabled(false); 33 | 34 | // 如果ui->UserTable至少选中了一行,则激活ui->DeleteUserButton 35 | connect(ui->UserTable->selectionModel(), &QItemSelectionModel::selectionChanged, [=](){ 36 | ui->DeleteUserButton->setEnabled(!ui->UserTable->selectionModel()->selectedRows().isEmpty()); 37 | }); 38 | 39 | // 如果ui->ChatTable至少选中了一行,则激活ui->DeleteChatButton 40 | connect(ui->ChatTable->selectionModel(), &QItemSelectionModel::selectionChanged, [=](){ 41 | ui->DeleteChatButton->setEnabled(!ui->ChatTable->selectionModel()->selectedRows().isEmpty()); 42 | }); 43 | 44 | // 如果ui->ChatTable选中且仅选中了一行,则激活ui->ManageUserButton和ui->ManageChatButton 45 | connect(ui->ChatTable->selectionModel(), &QItemSelectionModel::selectionChanged, [=](){ 46 | ui->ManageUserButton->setEnabled(ui->ChatTable->selectionModel()->selectedRows().count() == 1); 47 | }); 48 | connect(ui->ChatTable->selectionModel(), &QItemSelectionModel::selectionChanged, [=](){ 49 | ui->ManageChatButton->setEnabled(ui->ChatTable->selectionModel()->selectedRows().count() == 1); 50 | }); 51 | 52 | connect(core->userTableModel, &QSqlTableModel::dataChanged, this, &AdminManagement::onUserDataChanged); 53 | connect(core->chatTableModel, &QSqlTableModel::dataChanged, this, &AdminManagement::onChatDataChanged); 54 | } 55 | 56 | AdminManagement::~AdminManagement() { 57 | delete ui; 58 | } 59 | 60 | void AdminManagement::onUserDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { 61 | core->userTableModel->select(); 62 | } 63 | 64 | void AdminManagement::onChatDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { 65 | core->chatTableModel->select(); 66 | } 67 | 68 | void AdminManagement::on_FindUserButton_clicked() { 69 | QString userName = ui->UserInput->text(); 70 | core->userTableModel->setFilter(QString("u_name LIKE '%%1%'").arg(userName)); 71 | core->userTableModel->select(); 72 | } 73 | 74 | void AdminManagement::on_FindChatButton_clicked() { 75 | QString chatName = ui->ChatInput->text(); 76 | core->chatTableModel->setFilter(QString("c_name LIKE '%%1%'").arg(chatName)); 77 | core->chatTableModel->select(); 78 | } 79 | 80 | void AdminManagement::on_NewUserButton_clicked() { 81 | core->registerAccount( 82 | QString("User_%1").arg(core->maxUserNumber + 1), 83 | QString("Password_%1").arg(core->maxUserNumber + 1), 84 | "user"); 85 | } 86 | 87 | void AdminManagement::on_NewChatButton_clicked() { 88 | core->createChatroom( 89 | QString("Chat_%1").arg(core->maxChatroomNumber + 1), 90 | core->adminUserName); 91 | } 92 | 93 | void AdminManagement::on_DeleteUserButton_clicked() { 94 | QItemSelectionModel *selectionModel = ui->UserTable->selectionModel(); 95 | QModelIndexList selectedRows = selectionModel->selectedRows(); 96 | QModelIndex index_0; 97 | int userID; 98 | QSqlQuery query; 99 | // 事务开始,保证原子性 100 | query.exec("BEGIN"); 101 | // 删除所有选中的行的用户 102 | for (const QModelIndex &index : selectedRows) { 103 | index_0 = ui->UserTable->model()->index(index.row(), 0); 104 | userID = ui->UserTable->model()->data(index_0).toInt(); 105 | query.prepare("UPDATE user SET sd_t = :time WHERE u_id = :id"); 106 | query.bindValue(":time", QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")); 107 | query.bindValue(":id", userID); 108 | query.exec(); 109 | if (query.lastError().isValid()) { 110 | qDebug() << query.lastError(); 111 | query.exec("ROLLBACK"); 112 | core->userTableModel->select(); 113 | return; 114 | } 115 | if (userID == core->adminUserID) { 116 | QMessageBox::critical(this, "错误", "不能删除管理员账户"); 117 | query.exec("ROLLBACK"); 118 | core->userTableModel->select(); 119 | return; 120 | } 121 | } 122 | // 事务提交 123 | query.exec("COMMIT"); 124 | // 刷新用户表 125 | core->userTableModel->select(); 126 | } 127 | 128 | void AdminManagement::on_DeleteChatButton_clicked() { 129 | QItemSelectionModel *selectionModel = ui->ChatTable->selectionModel(); 130 | QModelIndexList selectedRows = selectionModel->selectedRows(); 131 | QModelIndex index_0; 132 | int chatID; 133 | QSqlQuery query; 134 | // 事务开始,保证原子性 135 | query.exec("BEGIN"); 136 | for (const QModelIndex &index : selectedRows) { 137 | index_0 = ui->ChatTable->model()->index(index.row(), 0); 138 | chatID = ui->ChatTable->model()->data(index_0).toInt(); 139 | query.prepare("UPDATE chatroom SET ds_t = :time WHERE c_id = :id"); 140 | query.bindValue(":time", QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")); 141 | query.bindValue(":id", chatID); 142 | query.exec(); 143 | if (query.lastError().isValid()) { 144 | qDebug() << query.lastError(); 145 | query.exec("ROLLBACK"); 146 | core->chatTableModel->select(); 147 | return; 148 | } 149 | } 150 | // 事务提交 151 | query.exec("COMMIT"); 152 | // 刷新聊天室表 153 | core->chatTableModel->select(); 154 | } 155 | 156 | void AdminManagement::on_ManageUserButton_clicked() { 157 | QItemSelectionModel *selectionModel = ui->ChatTable->selectionModel(); 158 | QModelIndexList selectedRows = selectionModel->selectedRows(); 159 | int rowIndex = selectedRows.first().row(); 160 | QModelIndex index_0 = ui->ChatTable->model()->index(rowIndex, 0); 161 | QString chatid = ui->ChatTable->model()->data(index_0).toString(); 162 | QModelIndex index_1 = ui->ChatTable->model()->index(rowIndex, 1); 163 | QString chatname = ui->ChatTable->model()->data(index_1).toString(); 164 | UserManage * usermanage = new UserManage(chatname, chatid); 165 | usermanage->show(); 166 | } 167 | 168 | void AdminManagement::on_ManageChatButton_clicked() { 169 | QItemSelectionModel *selectionModel = ui->ChatTable->selectionModel(); 170 | QModelIndexList selectedRows = selectionModel->selectedRows(); 171 | int rowIndex = selectedRows.first().row(); 172 | QModelIndex index_0 = ui->ChatTable->model()->index(rowIndex, 0); 173 | QString chatid = ui->ChatTable->model()->data(index_0).toString(); 174 | QModelIndex index_1 = ui->ChatTable->model()->index(rowIndex, 1); 175 | QString chatname = ui->ChatTable->model()->data(index_1).toString(); 176 | MessageManage * messagemanage = new MessageManage(chatname, chatid); 177 | messagemanage->show(); 178 | } 179 | -------------------------------------------------------------------------------- /server/src/adminmanagement.h: -------------------------------------------------------------------------------- 1 | #ifndef ADMINMANAGEMENT_H 2 | #define ADMINMANAGEMENT_H 3 | 4 | #include 5 | #include "servercore.h" 6 | 7 | namespace Ui { 8 | class AdminManagement; 9 | } 10 | 11 | class AdminManagement : public QWidget 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | explicit AdminManagement(QWidget *parent = nullptr); 17 | ~AdminManagement(); 18 | 19 | private slots: 20 | void onUserDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles); 21 | void onChatDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles); 22 | void on_FindUserButton_clicked(); 23 | void on_FindChatButton_clicked(); 24 | void on_NewUserButton_clicked(); 25 | void on_NewChatButton_clicked(); 26 | void on_DeleteUserButton_clicked(); 27 | void on_DeleteChatButton_clicked(); 28 | void on_ManageUserButton_clicked(); 29 | void on_ManageChatButton_clicked(); 30 | 31 | private: 32 | Ui::AdminManagement *ui; 33 | ServerCore *core; 34 | void insertUserIitem(int row,int column,QString content); 35 | void insertChatIitem(int row,int column,QString content); 36 | }; 37 | 38 | #endif // ADMINMANAGEMENT_H 39 | -------------------------------------------------------------------------------- /server/src/adminmanagement.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | AdminManagement 4 | 5 | 6 | 7 | 0 8 | 0 9 | 973 10 | 557 11 | 12 | 13 | 14 | 15 | 960 16 | 0 17 | 18 | 19 | 20 | 管理界面 21 | 22 | 23 | 24 | :/icon/manage.ico:/icon/manage.ico 25 | 26 | 27 | QPushButton { 28 | background-color:#030408; 29 | color:#db9a45; 30 | font-size: 10px; 31 | font:10pt "Microsoft YaHei UI"; 32 | border-radius:8px; 33 | font-weight:700; 34 | height:30px; 35 | width: 100%; 36 | } 37 | QPushButton:disabled { 38 | background-color: #787a7e; 39 | } 40 | QPushButton:hover { 41 | background-color: #383a3e; 42 | } 43 | QLineEdit{ 44 | border: 1px solid rgb(41, 57, 85); 45 | border-radius: 3px; 46 | border-color:#dcdee0; 47 | height:36px; 48 | background: white; 49 | padding-left: 12px; 50 | padding-right: 12px; 51 | border-color:#e4e5eb; 52 | } 53 | QLineEdit:hover { 54 | border-color: #db9a45; 55 | } 56 | 57 | 58 | 59 | 60 | 61 | 0 62 | 63 | 64 | 65 | 用户管理 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 300 78 | 16777215 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 查询 87 | 88 | 89 | 90 | 91 | 92 | 93 | 注销选中用户 94 | 95 | 96 | 97 | 98 | 99 | 100 | 新增用户 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 聊天管理 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 300 120 | 16777215 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 查询 129 | 130 | 131 | 132 | 133 | 134 | 135 | 解散选中群聊 136 | 137 | 138 | 139 | 140 | 141 | 142 | 新增群聊 143 | 144 | 145 | 146 | 147 | 148 | 149 | 群聊用户管理 150 | 151 | 152 | 153 | 154 | 155 | 156 | 群聊消息管理 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | UserInput 173 | FindUserButton 174 | DeleteUserButton 175 | NewUserButton 176 | UserTable 177 | ChatInput 178 | FindChatButton 179 | DeleteChatButton 180 | NewChatButton 181 | ManageUserButton 182 | ManageChatButton 183 | ChatTable 184 | 185 | 186 | 187 | 188 | 189 | 190 | -------------------------------------------------------------------------------- /server/src/generatecertandkey.py: -------------------------------------------------------------------------------- 1 | from cryptography import x509 2 | from cryptography.x509.oid import NameOID 3 | from cryptography.hazmat.primitives import hashes 4 | from cryptography.hazmat.primitives.asymmetric import rsa 5 | from cryptography.hazmat.primitives import serialization 6 | from cryptography.hazmat.primitives.serialization import NoEncryption 7 | from datetime import datetime, timedelta 8 | import ipaddress 9 | 10 | # Generate a private key 11 | private_key = rsa.generate_private_key( 12 | public_exponent=65537, 13 | key_size=2048, 14 | ) 15 | 16 | # Self-signed certificate details 17 | subject = issuer = x509.Name([ 18 | x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), 19 | x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "California"), 20 | x509.NameAttribute(NameOID.LOCALITY_NAME, "Mountain View"), 21 | x509.NameAttribute(NameOID.ORGANIZATION_NAME, "My Organization"), 22 | x509.NameAttribute(NameOID.COMMON_NAME, "myserver.example.com"), 23 | ]) 24 | 25 | # Validity period 26 | valid_from = datetime.utcnow() 27 | valid_to = valid_from + timedelta(days=365) 28 | 29 | # Create a self-signed certificate 30 | certificate = x509.CertificateBuilder().subject_name( 31 | subject 32 | ).issuer_name( 33 | issuer 34 | ).public_key( 35 | private_key.public_key() 36 | ).serial_number( 37 | x509.random_serial_number() 38 | ).not_valid_before( 39 | valid_from 40 | ).not_valid_after( 41 | valid_to 42 | ).add_extension( 43 | x509.SubjectAlternativeName([x509.DNSName("myserver.example.com")]), 44 | critical=False, 45 | ).sign(private_key, hashes.SHA256()) 46 | 47 | # Serialize private key and certificate 48 | private_key_pem = private_key.private_bytes( 49 | encoding=serialization.Encoding.PEM, 50 | format=serialization.PrivateFormat.TraditionalOpenSSL, 51 | encryption_algorithm=NoEncryption() 52 | ) 53 | 54 | certificate_pem = certificate.public_bytes(serialization.Encoding.PEM) 55 | 56 | # Save the private key and certificate to files 57 | private_key_path = "serverKey.pem" 58 | certificate_path = "serverCert.pem" 59 | 60 | # Writing the private key 61 | with open(private_key_path, "wb") as private_key_file: 62 | private_key_file.write(private_key_pem) 63 | 64 | # Writing the certificate 65 | with open(certificate_path, "wb") as certificate_file: 66 | certificate_file.write(certificate_pem) -------------------------------------------------------------------------------- /server/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "widget.h" 2 | 3 | #include 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | QApplication a(argc, argv); 8 | Widget w; 9 | w.show(); 10 | return a.exec(); 11 | } 12 | -------------------------------------------------------------------------------- /server/src/manage.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omigeft/QTChatroom/cf1146f2a3517bea1c44a96210eb56a1c5c71b6f/server/src/manage.ico -------------------------------------------------------------------------------- /server/src/manage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omigeft/QTChatroom/cf1146f2a3517bea1c44a96210eb56a1c5c71b6f/server/src/manage.png -------------------------------------------------------------------------------- /server/src/messagemanage.cpp: -------------------------------------------------------------------------------- 1 | #include "messagemanage.h" 2 | #include "ui_messagemanage.h" 3 | #include 4 | MessageManage::MessageManage(const QString &name, const QString &id, QWidget *parent) : 5 | QWidget(parent), 6 | ui(new Ui::MessageManage) { 7 | ui->setupUi(this); 8 | 9 | Roomname = name; 10 | RoomID = id; 11 | ui->ChatName->setText("聊天名称:"+name); 12 | ui->ChatID->setText("聊天ID:"+id); 13 | 14 | QSqlQuery query; 15 | query.exec(QString( 16 | "CREATE VIEW IF NOT EXISTS chat_message_%1 AS " 17 | "SELECT m.m_id, m.m_u_id, u.u_name, m.m_t, m.m_db_t, m.m_text FROM message m " 18 | "JOIN user u ON m.m_u_id = u.u_id " 19 | "WHERE m.m_c_id = %1 " 20 | "ORDER BY m.m_id ASC;") 21 | .arg(RoomID)); 22 | if (query.lastError().isValid()) { 23 | qDebug() << query.lastError(); 24 | return; 25 | } 26 | 27 | chatMessageTableModel = new QSqlTableModel(this); 28 | chatMessageTableModel->setTable(QString("chat_message_%1").arg(RoomID)); 29 | chatMessageTableModel->select(); 30 | chatMessageTableModel->setHeaderData(0, Qt::Horizontal, "消息ID"); 31 | chatMessageTableModel->setHeaderData(1, Qt::Horizontal, "发送者用户ID"); 32 | chatMessageTableModel->setHeaderData(2, Qt::Horizontal, "发送者用户名"); 33 | chatMessageTableModel->setHeaderData(3, Qt::Horizontal, "发送时间"); 34 | chatMessageTableModel->setHeaderData(4, Qt::Horizontal, "撤回时间"); 35 | chatMessageTableModel->setHeaderData(5, Qt::Horizontal, "内容"); 36 | 37 | ui->MessageTable->setModel(chatMessageTableModel); 38 | ui->MessageTable->setColumnWidth(4, 160); 39 | ui->MessageTable->setColumnWidth(5, 160); 40 | 41 | ui->DeleteButton->setEnabled(false); 42 | 43 | // 如果ui->MessageTable至少选中了一行,则激活ui->DeleteButton 44 | connect(ui->MessageTable->selectionModel(), &QItemSelectionModel::selectionChanged, [=](){ 45 | ui->DeleteButton->setEnabled(!ui->MessageTable->selectionModel()->selectedRows().isEmpty()); 46 | }); 47 | } 48 | 49 | MessageManage::~MessageManage() { 50 | delete ui; 51 | } 52 | 53 | void MessageManage::on_FindUserButton_clicked() { 54 | QString userName = ui->ContentInput->text(); 55 | chatMessageTableModel->setFilter(QString("u_name LIKE '%%1%'").arg(userName)); 56 | chatMessageTableModel->select(); 57 | } 58 | 59 | void MessageManage::on_FindContentButton_clicked() { 60 | QString content = ui->ContentInput->text(); 61 | chatMessageTableModel->setFilter(QString("m_text LIKE '%%1%'").arg(content)); 62 | chatMessageTableModel->select(); 63 | } 64 | 65 | void MessageManage::on_DeleteButton_clicked() { 66 | QItemSelectionModel *selectionModel = ui->MessageTable->selectionModel(); 67 | QModelIndexList selectedRows = selectionModel->selectedRows(); 68 | QModelIndex index_0; 69 | int messageID; 70 | QSqlQuery query; 71 | // 事务开始,保证原子性 72 | query.exec("BEGIN"); 73 | // 删除所有选中的行的消息 74 | for (const QModelIndex &index : selectedRows) { 75 | index_0 = ui->MessageTable->model()->index(index.row(), 0); 76 | messageID = ui->MessageTable->model()->data(index_0).toInt(); 77 | query.prepare("UPDATE message SET m_db_t = :drawback_time WHERE m_id = :message_id AND m_db_t IS NULL;"); 78 | query.bindValue(":drawback_time", QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")); 79 | query.bindValue(":message_id", messageID); 80 | query.exec(); 81 | if (query.lastError().isValid()) { 82 | qDebug() << query.lastError(); 83 | query.exec("ROLLBACK"); 84 | chatMessageTableModel->select(); 85 | return; 86 | } 87 | } 88 | // 事务提交 89 | query.exec("COMMIT"); 90 | // 刷新用户表 91 | chatMessageTableModel->select(); 92 | } 93 | -------------------------------------------------------------------------------- /server/src/messagemanage.h: -------------------------------------------------------------------------------- 1 | #ifndef MESSAGEMANAGE_H 2 | #define MESSAGEMANAGE_H 3 | 4 | #include 5 | #include 6 | 7 | namespace Ui { 8 | class MessageManage; 9 | } 10 | 11 | class MessageManage : public QWidget 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | explicit MessageManage(const QString &name, const QString &id, QWidget *parent = nullptr); 17 | ~MessageManage(); 18 | 19 | private slots: 20 | void on_FindUserButton_clicked(); 21 | 22 | void on_FindContentButton_clicked(); 23 | 24 | void on_DeleteButton_clicked(); 25 | 26 | private: 27 | QString Roomname; 28 | QString RoomID; 29 | Ui::MessageManage *ui; 30 | QSqlTableModel* chatMessageTableModel; 31 | }; 32 | 33 | #endif // MESSAGEMANAGE_H 34 | -------------------------------------------------------------------------------- /server/src/messagemanage.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MessageManage 4 | 5 | 6 | 7 | 0 8 | 0 9 | 797 10 | 480 11 | 12 | 13 | 14 | 群聊消息管理界面 15 | 16 | 17 | QPushButton { 18 | background-color:#030408; 19 | color:#db9a45; 20 | font-size: 20px; 21 | font:10pt "Microsoft YaHei UI"; 22 | border-radius:8px; 23 | font-weight:700; 24 | height:30px; 25 | width: 100%; 26 | } 27 | QPushButton:disabled { 28 | background-color: #787a7e; 29 | } 30 | QPushButton:hover { 31 | background-color: #383a3e; 32 | } 33 | QLineEdit{ 34 | border: 1px solid rgb(41, 57, 85); 35 | border-radius: 3px; 36 | border-color:#dcdee0; 37 | height:36px; 38 | background: white; 39 | padding-left: 12px; 40 | padding-right: 12px; 41 | border-color:#e4e5eb; 42 | } 43 | QLineEdit:hover { 44 | border-color: #db9a45; 45 | } 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 300 55 | 16777215 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 查询用户名 64 | 65 | 66 | 67 | 68 | 69 | 70 | 查询消息内容 71 | 72 | 73 | 74 | 75 | 76 | 77 | 撤回选中消息 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | TextLabel 89 | 90 | 91 | 92 | 93 | 94 | 95 | Qt::Horizontal 96 | 97 | 98 | QSizePolicy::Fixed 99 | 100 | 101 | 102 | 20 103 | 20 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | TextLabel 112 | 113 | 114 | 115 | 116 | 117 | 118 | Qt::Horizontal 119 | 120 | 121 | 122 | 40 123 | 20 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | ContentInput 137 | FindUserButton 138 | FindContentButton 139 | DeleteButton 140 | MessageTable 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /server/src/res.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | manage.ico 4 | 5 | 6 | generatecertandkey.py 7 | 8 | 9 | -------------------------------------------------------------------------------- /server/src/server.cpp: -------------------------------------------------------------------------------- 1 | #include "server.h" 2 | 3 | Server::Server(QObject *parent) 4 | : QTcpServer(parent) {} 5 | 6 | void Server::sendMessage(QSslSocket *socket, const QString &message) { 7 | QByteArray data = message.toUtf8(); 8 | mutex.lock(); 9 | socket->write(data); 10 | socket->flush(); 11 | mutex.unlock(); 12 | } 13 | 14 | bool Server::loadServerCertificates(const QString &certPath, const QString &keyPath) { 15 | QFile certFile(certPath); 16 | QFile keyFile(keyPath); 17 | 18 | if (!certFile.open(QIODevice::ReadOnly) || !keyFile.open(QIODevice::ReadOnly)) { 19 | qWarning() << "无法打开证书或密钥文件"; 20 | return false; 21 | } 22 | 23 | QSslCertificate certificate(&certFile, QSsl::Pem); 24 | QSslKey privateKey(&keyFile, QSsl::Rsa, QSsl::Pem); 25 | 26 | if (certificate.isNull() || privateKey.isNull()) { 27 | qWarning() << "无法加载证书或密钥"; 28 | return false; 29 | } 30 | 31 | sslCertificate = certificate; 32 | sslPrivateKey = privateKey; 33 | 34 | return true; 35 | } 36 | 37 | void Server::incomingConnection(qintptr socketDescriptor) 38 | { 39 | // 创建一个新的SslSocket来处理连接 40 | QSslSocket *clientSocket = new QSslSocket(this); 41 | if (clientSocket->setSocketDescriptor(socketDescriptor)) 42 | { 43 | addPendingConnection(clientSocket); 44 | clientSocket->setLocalCertificate(sslCertificate); 45 | clientSocket->setPrivateKey(sslPrivateKey); 46 | connect(clientSocket, &QSslSocket::readyRead, this, &Server::onReadyRead); 47 | connect(clientSocket, &QSslSocket::bytesWritten, this, &Server::onBytesWritten); 48 | connect(clientSocket, &QSslSocket::disconnected, clientSocket, &QSslSocket::deleteLater); 49 | clientSocket->startServerEncryption(); 50 | } 51 | else 52 | { 53 | delete clientSocket; 54 | } 55 | } 56 | 57 | void Server::onReadyRead() 58 | { 59 | QSslSocket *clientSocket = qobject_cast(sender()); 60 | if (clientSocket && clientSocket->bytesAvailable()) 61 | { 62 | QByteArray data = clientSocket->readAll(); 63 | QString dataString = QString::fromUtf8(data); 64 | qDebug() << "收到数据:" << dataString; 65 | emit receiveMessage(clientSocket, dataString); 66 | } 67 | } 68 | 69 | void Server::onBytesWritten(qint64 bytes) 70 | { 71 | // QSslSocket *clientSocket = qobject_cast(sender()); 72 | qDebug() << "发送数据:" << bytes << "字节"; 73 | } 74 | 75 | -------------------------------------------------------------------------------- /server/src/server.h: -------------------------------------------------------------------------------- 1 | #ifndef SERVER_H 2 | #define SERVER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class Server : public QTcpServer 10 | { 11 | Q_OBJECT 12 | 13 | public: 14 | Server(QObject *parent = nullptr); 15 | 16 | void sendMessage(QSslSocket *socket, const QString &message); 17 | 18 | bool loadServerCertificates(const QString &certPath, const QString &keyPath); 19 | 20 | protected: 21 | void incomingConnection(qintptr socketDescriptor) override; 22 | 23 | private slots: 24 | void onReadyRead(); 25 | 26 | void onBytesWritten(qint64 bytes); 27 | 28 | signals: 29 | void receiveMessage(QSslSocket *socket, const QString &message); 30 | 31 | private: 32 | QMutex mutex; 33 | QSslCertificate sslCertificate; 34 | QSslKey sslPrivateKey; 35 | }; 36 | 37 | #endif // SERVER_H 38 | -------------------------------------------------------------------------------- /server/src/server.pro: -------------------------------------------------------------------------------- 1 | QT += core gui network sql widgets 2 | 3 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 4 | 5 | CONFIG += c++11 6 | 7 | # The following define makes your compiler emit warnings if you use 8 | # any Qt feature that has been marked deprecated (the exact warnings 9 | # depend on your compiler). Please consult the documentation of the 10 | # deprecated API in order to know how to port your code away from it. 11 | DEFINES += QT_DEPRECATED_WARNINGS 12 | 13 | # You can also make your code fail to compile if it uses deprecated APIs. 14 | # In order to do so, uncomment the following line. 15 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 16 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 17 | 18 | SOURCES += \ 19 | adminmanagement.cpp \ 20 | main.cpp \ 21 | messagemanage.cpp \ 22 | server.cpp \ 23 | servercore.cpp \ 24 | usermanage.cpp \ 25 | widget.cpp 26 | 27 | HEADERS += \ 28 | adminmanagement.h \ 29 | messagemanage.h \ 30 | server.h \ 31 | servercore.h \ 32 | usermanage.h \ 33 | widget.h 34 | 35 | FORMS += \ 36 | adminmanagement.ui \ 37 | messagemanage.ui \ 38 | usermanage.ui \ 39 | widget.ui 40 | 41 | # Default rules for deployment. 42 | qnx: target.path = /tmp/$${TARGET}/bin 43 | else: unix:!android: target.path = /opt/$${TARGET}/bin 44 | !isEmpty(target.path): INSTALLS += target 45 | 46 | RESOURCES += \ 47 | res.qrc 48 | -------------------------------------------------------------------------------- /server/src/servercore.cpp: -------------------------------------------------------------------------------- 1 | #include "servercore.h" 2 | 3 | bool ServerCore::createServer(const QHostAddress &address, quint16 port, const QString &rootUserName, const QString &password) { 4 | // 获取当前工作目录 5 | QString currentDir = QDir::currentPath(); 6 | QString certPath = currentDir + QDir::separator() + "serverCert.pem"; 7 | QString keyPath = currentDir + QDir::separator() + "serverKey.pem"; 8 | 9 | if (!generateCertAndKey(certPath, keyPath)) { 10 | qDebug() << "Failed to set SSL certificate and key!"; 11 | return false; 12 | } 13 | 14 | if (!server.loadServerCertificates(certPath, keyPath)) { 15 | qDebug() << "Failed to load SSL certificate and key!"; 16 | return false; 17 | } 18 | 19 | if (!createDatabase(rootUserName, password)) { 20 | qDebug() << "Database connection failed!"; 21 | return false; 22 | } 23 | 24 | // 检查格式是否正确 25 | if(!address.isNull() && port > 0) { 26 | qDebug() << "正在创建服务端..."; 27 | } else { 28 | qDebug() << "IP地址或端口号格式错误!"; 29 | return false; 30 | } 31 | if (!server.listen(address, port)) { 32 | qDebug() << "Server listen failed!"; 33 | return false; 34 | } 35 | serverAddress = address; 36 | serverPort = port; 37 | qDebug() << "Server listen on " << serverAddress.toString() << ":" << serverPort; 38 | 39 | return true; 40 | } 41 | 42 | bool ServerCore::generateCertAndKey(const QString &certPath, const QString &keyPath) { 43 | QFileInfo certFileInfo(certPath); 44 | QFileInfo keyFileInfo(keyPath); 45 | 46 | // 检查证书和密钥文件是否都存在,不存在则生成 47 | if (!(certFileInfo.exists() && keyFileInfo.exists())) { 48 | // Python 脚本的路径 49 | QString scriptResourcePath = ":/scripts/generatecertandkey.py"; 50 | 51 | // 读取资源中的脚本内容 52 | QFile scriptFile(scriptResourcePath); 53 | if (!scriptFile.open(QIODevice::ReadOnly)) 54 | return false; 55 | 56 | QTextStream in(&scriptFile); 57 | QString scriptContent = in.readAll(); 58 | scriptFile.close(); 59 | 60 | // 创建临时文件 61 | QTemporaryFile tempFile; 62 | if (!tempFile.open()) 63 | return false; 64 | 65 | // 写入脚本内容到临时文件 66 | QTextStream out(&tempFile); 67 | out << scriptContent; 68 | tempFile.close(); 69 | 70 | // 创建 QProcess 实例 71 | QProcess *process = new QProcess; 72 | 73 | // 启动 Python 解释器执行临时脚本文件 74 | process->start("python", QStringList() << tempFile.fileName()); 75 | 76 | // 等待执行完成,或设置一个超时 77 | if (!process->waitForFinished(3000)) return false; 78 | } 79 | 80 | return certFileInfo.isFile() && certFileInfo.isReadable() && 81 | keyFileInfo.isFile() && keyFileInfo.isReadable(); 82 | } 83 | 84 | bool ServerCore::createDatabase(const QString &rootUserName, const QString &password) { 85 | // 创建数据库(设置引擎) 86 | db = QSqlDatabase::addDatabase("QSQLITE"); 87 | // 获取当前工作目录 88 | QString currentDir = QDir::currentPath(); 89 | // 构建数据库文件的路径,这里假设你要保存的数据库文件名为mydatabase.db 90 | QString databasePath = currentDir + QDir::separator() + "server_db.db"; 91 | qDebug() << "Database saved in " << databasePath; 92 | // 设置数据库保存路径 93 | db.setDatabaseName(databasePath); 94 | if (!db.open()) return false; 95 | qDebug() << "Database connection successful!!"; 96 | 97 | QSqlQuery query; 98 | // 创建用户表user 99 | query.exec( 100 | "CREATE TABLE IF NOT EXISTS user (" 101 | "u_id INT PRIMARY KEY," // user id 102 | "u_name VARCHAR(20) UNIQUE NOT NULL," // user name 103 | "pw VARCHAR(20) NOT NULL," // password 104 | "su_t DATETIME," // sign up time 105 | "sd_t DATETIME," // sign down time 106 | "role VARCHAR(5) NOT NULL);" // user role ('root', 'admin', 'user') 107 | ); 108 | if (query.lastError().isValid()) { 109 | qDebug() << query.lastError(); 110 | return false; 111 | } 112 | 113 | // 创建聊天室表chatroom 114 | query.exec( 115 | "CREATE TABLE IF NOT EXISTS chatroom (" 116 | "c_id INT PRIMARY KEY," // chatroom id 117 | "c_name VARCHAR(20) UNIQUE NOT NULL," // chatroom name 118 | "c_u_id INT NOT NULL," // creator user id 119 | "cr_t DATETIME," // creation time 120 | "ds_t DATETIME);" // dissolution time 121 | ); 122 | if (query.lastError().isValid()) { 123 | qDebug() << query.lastError(); 124 | return false; 125 | } 126 | 127 | // 创建用户-聊天室关系表user_chatroom 128 | query.exec( 129 | "CREATE TABLE IF NOT EXISTS user_chatroom (" 130 | "u_id INT NOT NULL," // user id 131 | "c_id INT NOT NULL," // chatroom id 132 | "j_t DATETIME," // join time 133 | "q_t DATETIME," // quit time 134 | "role VARCHAR(5) NOT NULL," // user role in chatroom ("root", "admin", "user") 135 | "PRIMARY KEY(u_id, c_id)," 136 | "FOREIGN KEY(u_id) REFERENCES user(u_id)," 137 | "FOREIGN KEY(c_id) REFERENCES chatroom(c_id));" 138 | ); 139 | if (query.lastError().isValid()) { 140 | qDebug() << query.lastError(); 141 | return false; 142 | } 143 | 144 | // 创建消息表message 145 | query.exec( 146 | "CREATE TABLE IF NOT EXISTS message (" 147 | "m_id INT PRIMARY KEY," // message id 148 | "m_u_id INT NOT NULL," // user id 149 | "m_c_id INT NOT NULL," // chatroom id 150 | "m_t DATETIME," // message time 151 | "m_db_t DATETIME," // message drawback time 152 | "m_text TEXT," // message text 153 | "FOREIGN KEY(m_u_id) REFERENCES user(u_id)," 154 | "FOREIGN KEY(m_c_id) REFERENCES chatroom(c_id));" 155 | ); 156 | if (query.lastError().isValid()) { 157 | qDebug() << query.lastError(); 158 | return false; 159 | } 160 | 161 | // 创建触发器,限制用户名长度 162 | query.exec( 163 | "CREATE TRIGGER IF NOT EXISTS check_u_name_length " 164 | "BEFORE INSERT ON user " 165 | "FOR EACH ROW " 166 | "WHEN LENGTH(NEW.u_name) < 1 OR LENGTH(NEW.u_name) > 20 " 167 | "BEGIN " 168 | " SELECT RAISE(FAIL, 'u_name must be between 1 and 20 characters'); " 169 | "END; " 170 | ); 171 | if (query.lastError().isValid()) { 172 | qDebug() << query.lastError(); 173 | return false; 174 | } 175 | 176 | // 创建触发器,限制密码长度 177 | query.exec( 178 | "CREATE TRIGGER IF NOT EXISTS check_pw_length " 179 | "BEFORE INSERT ON user " 180 | "FOR EACH ROW " 181 | "WHEN LENGTH(NEW.pw) < 6 OR LENGTH(NEW.pw) > 20 " 182 | "BEGIN " 183 | " SELECT RAISE(FAIL, 'pw must be between 6 and 20 characters'); " 184 | "END; " 185 | ); 186 | if (query.lastError().isValid()) { 187 | qDebug() << query.lastError(); 188 | return false; 189 | } 190 | 191 | // 创建触发器强制要求密码包含数字、字母 192 | query.exec( 193 | "CREATE TRIGGER IF NOT EXISTS check_pw_format " 194 | "BEFORE INSERT ON user " 195 | "FOR EACH ROW " 196 | "WHEN NEW.pw NOT GLOB '*[0-9]*' OR NEW.pw NOT GLOB '*[a-zA-Z]*' " 197 | "BEGIN " 198 | " SELECT RAISE(FAIL, 'pw must contain at least one number and one letter'); " 199 | "END; " 200 | ); 201 | if (query.lastError().isValid()) { 202 | qDebug() << query.lastError(); 203 | return false; 204 | } 205 | 206 | // 创建触发器,限制用户角色 207 | query.exec( 208 | "CREATE TRIGGER IF NOT EXISTS check_role_value " 209 | "BEFORE INSERT ON user " 210 | "FOR EACH ROW " 211 | "WHEN NEW.role NOT IN ('root', 'admin', 'user') " 212 | "BEGIN " 213 | " SELECT RAISE(FAIL, 'role must be one of: root, admin, user'); " 214 | "END; " 215 | ); 216 | if (query.lastError().isValid()) { 217 | qDebug() << query.lastError(); 218 | return false; 219 | } 220 | 221 | // 创建触发器,root超级管理员不能被删除 222 | query.exec( 223 | "CREATE TRIGGER IF NOT EXISTS check_root " 224 | "BEFORE DELETE ON user " 225 | "FOR EACH ROW " 226 | "WHEN OLD.role = 'root' " 227 | "BEGIN " 228 | " SELECT RAISE(FAIL, 'root user cannot be deleted'); " 229 | "END; " 230 | ); 231 | if (query.lastError().isValid()) { 232 | qDebug() << query.lastError(); 233 | return false; 234 | } 235 | 236 | // 创建触发器,聊天室名长度限制1-20个字符之间 237 | query.exec( 238 | "CREATE TRIGGER IF NOT EXISTS check_c_name_length " 239 | "BEFORE INSERT ON chatroom " 240 | "FOR EACH ROW " 241 | "WHEN LENGTH(NEW.c_name) < 1 OR LENGTH(NEW.c_name) > 20 " 242 | "BEGIN " 243 | " SELECT RAISE(FAIL, 'c_name must be between 1 and 20 characters'); " 244 | "END; " 245 | ); 246 | if (query.lastError().isValid()) { 247 | qDebug() << query.lastError(); 248 | return false; 249 | } 250 | 251 | // 创建触发器,用户注册时间不能晚于注销时间 252 | query.exec( 253 | "CREATE TRIGGER IF NOT EXISTS check_u_t " 254 | "BEFORE INSERT ON user " 255 | "FOR EACH ROW " 256 | "WHEN NEW.su_t > NEW.sd_t " 257 | "BEGIN " 258 | " SELECT RAISE(FAIL, 'su_t must be earlier than sd_t'); " 259 | "END; " 260 | ); 261 | 262 | // 创建触发器,聊天室创建时间不能晚于注销时间 263 | query.exec( 264 | "CREATE TRIGGER IF NOT EXISTS check_c_t " 265 | "BEFORE INSERT ON chatroom " 266 | "FOR EACH ROW " 267 | "WHEN NEW.cr_t > NEW.ds_t " 268 | "BEGIN " 269 | " SELECT RAISE(FAIL, 'cr_t must be earlier than ds_t'); " 270 | "END; " 271 | ); 272 | 273 | // 创建触发器,消息发送时间不能晚于撤回时间 274 | query.exec( 275 | "CREATE TRIGGER IF NOT EXISTS check_m_t " 276 | "BEFORE INSERT ON message " 277 | "FOR EACH ROW " 278 | "WHEN NEW.m_t > NEW.m_db_t " 279 | "BEGIN " 280 | " SELECT RAISE(FAIL, 'm_t must be earlier than m_db_t'); " 281 | "END; " 282 | ); 283 | 284 | query.exec("SELECT MAX(u_id) FROM user;"); 285 | if (query.lastError().isValid()) { 286 | qDebug() << query.lastError(); 287 | return false; 288 | } 289 | if (query.next()) 290 | maxUserNumber = query.value(0).toInt(); // 获取用户号最大值 291 | 292 | query.exec("SELECT MAX(c_id) FROM chatroom;"); 293 | if (query.lastError().isValid()) { 294 | qDebug() << query.lastError(); 295 | return false; 296 | } 297 | if (query.next()) 298 | maxChatroomNumber = query.value(0).toInt(); // 获取聊天室号最大值 299 | 300 | query.exec("SELECT MAX(m_id) FROM message;"); 301 | if (query.lastError().isValid()) { 302 | qDebug() << query.lastError(); 303 | return false; 304 | } 305 | if (query.next()) 306 | maxMessageNumber = query.value(0).toInt(); // 获取消息号最大值 307 | 308 | // 如果不存在超级管理员,则创建超级管理员账号(role="root") 309 | if (registerAccount(rootUserName, password, "root")) { 310 | adminUserID = maxUserNumber; // 设置当前管理员用户ID 311 | qDebug() << "超级管理员账号创建成功!"; 312 | } else { 313 | // 若存在超级管理员,验证密码是否正确 314 | query.exec(QString("SELECT u_id FROM user WHERE u_name='%1' AND pw='%2' AND role='root';") 315 | .arg(rootUserName) 316 | .arg(password)); 317 | if (query.lastError().isValid()) { 318 | qDebug() << query.lastError(); 319 | return false; 320 | } 321 | if (!query.next()) { 322 | qDebug() << "超级管理员创建失败或账号密码错误!"; 323 | return false; 324 | } 325 | adminUserID = query.value(0).toInt(); // 设置当前管理员用户ID 326 | } 327 | 328 | adminUserName = rootUserName; // 设置当前管理员用户名 329 | qDebug() << "当前管理员用户ID:" << adminUserID << ";用户名:" << adminUserName; 330 | 331 | userTableModel = new QSqlTableModel; 332 | userTableModel->setTable("user"); // 替换为你的表名 333 | userTableModel->select(); // 执行查询以加载数据 334 | userTableModel->setHeaderData(0, Qt::Horizontal, "用户ID"); 335 | userTableModel->setHeaderData(1, Qt::Horizontal, "用户名"); 336 | userTableModel->setHeaderData(2, Qt::Horizontal, "密码"); 337 | userTableModel->setHeaderData(3, Qt::Horizontal, "注册时间"); 338 | userTableModel->setHeaderData(4, Qt::Horizontal, "注销时间"); 339 | userTableModel->setHeaderData(5, Qt::Horizontal, "用户角色"); 340 | 341 | chatTableModel = new QSqlTableModel; 342 | chatTableModel->setTable("chatroom"); // 替换为你的表名 343 | chatTableModel->select(); // 执行查询以加载数据 344 | chatTableModel->setHeaderData(0, Qt::Horizontal, "聊天室ID"); 345 | chatTableModel->setHeaderData(1, Qt::Horizontal, "聊天室名"); 346 | chatTableModel->setHeaderData(2, Qt::Horizontal, "创建者用户ID"); 347 | chatTableModel->setHeaderData(3, Qt::Horizontal, "创建时间"); 348 | chatTableModel->setHeaderData(4, Qt::Horizontal, "解散时间"); 349 | 350 | return true; 351 | } 352 | 353 | bool ServerCore::registerAccount(const QString &userName, const QString &password, const QString &role) { 354 | QSqlQuery query; 355 | 356 | // 检查用户名是否已存在 357 | query.exec(QString("SELECT u_name FROM user WHERE u_name = '%1';").arg(userName)); 358 | if (query.lastError().isValid()) { 359 | qDebug() << query.lastError(); 360 | return false; 361 | } 362 | if (query.next()) { 363 | qDebug() << "用户名已存在!"; 364 | return false; 365 | } 366 | 367 | // 插入新用户 368 | QString sql_statement = 369 | "INSERT INTO user (u_id, u_name, pw, su_t, sd_t, role) VALUES " + 370 | QString("(%1,'%2','%3','%4',%5, '%6');") 371 | .arg(++maxUserNumber) 372 | .arg(userName) 373 | .arg(password) 374 | .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")) 375 | .arg("NULL") 376 | .arg(role); 377 | 378 | // 执行SQL语句 379 | query.exec(sql_statement); 380 | if (query.lastError().isValid()) { 381 | --maxUserNumber; 382 | qDebug() << query.lastError(); 383 | return false; 384 | } 385 | // 更新界面显示的用户表 386 | if (userTableModel){ 387 | userTableModel->select(); 388 | } 389 | 390 | return true; 391 | } 392 | 393 | bool ServerCore::loginAccount(QSslSocket *socket, const QString &userName, const QString &password) { 394 | QSqlQuery query; 395 | 396 | // 检查用户名是否已存在 397 | query.exec(QString("SELECT u_name FROM user WHERE u_name = '%1' AND sd_t IS NULL;").arg(userName)); 398 | if (query.lastError().isValid()) { 399 | qDebug() << query.lastError(); 400 | return false; 401 | } 402 | query.next(); 403 | if (query.value(0).toString().isEmpty()) { 404 | qDebug() << "用户名不存在!"; 405 | return false; 406 | } 407 | 408 | // 检查密码是否正确 409 | query.exec(QString("SELECT pw FROM user WHERE u_name = '%1';").arg(userName)); 410 | if (query.lastError().isValid()) { 411 | qDebug() << query.lastError(); 412 | return false; 413 | } 414 | query.next(); 415 | if (query.value(0).toString() != password) { 416 | qDebug() << "密码错误!"; 417 | return false; 418 | } 419 | 420 | // 该账号与该连接绑定 421 | userSocketMap.insert(userName, socket); 422 | 423 | return true; 424 | } 425 | 426 | bool ServerCore::createChatroom(const QString &chatroomName, const QString &userName) { 427 | QSqlQuery query; 428 | 429 | // 检查聊天室名是否已存在 430 | query.exec(QString("SELECT c_id FROM chatroom WHERE c_name = '%1';").arg(chatroomName)); 431 | if (query.lastError().isValid()) { 432 | qDebug() << query.lastError(); 433 | return false; 434 | } 435 | if (query.next()) { 436 | qDebug() << "聊天室名已存在!"; 437 | return false; 438 | } 439 | 440 | // 检查用户名是否已存在 441 | query.exec(QString("SELECT u_id FROM user WHERE u_name = '%1' AND sd_t IS NULL;").arg(userName)); 442 | if (query.lastError().isValid()) { 443 | qDebug() << query.lastError(); 444 | return false; 445 | } 446 | if (!query.next()) { 447 | qDebug() << "用户名不存在!"; 448 | return false; 449 | } 450 | 451 | int userID = query.value(0).toInt(); 452 | 453 | // 插入新聊天室和新用户-聊天室关系,以事务的方式执行,保证原子性 454 | query.exec("BEGIN;"); 455 | query.exec(QString( 456 | "INSERT INTO chatroom (c_id, c_name, c_u_id, cr_t, ds_t) VALUES " 457 | "(%1,'%2',%3,'%4',%5);") 458 | .arg(++maxChatroomNumber) 459 | .arg(chatroomName) 460 | .arg(userID) 461 | .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")) 462 | .arg("NULL")); 463 | query.exec(QString( 464 | "INSERT INTO user_chatroom (u_id, c_id, j_t, q_t, role) VALUES " 465 | "(%1,%2,'%3',%4, '%5');") 466 | .arg(userID) 467 | .arg(maxChatroomNumber) 468 | .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")) 469 | .arg("NULL") 470 | .arg("root")); 471 | if (query.lastError().isValid()) { 472 | qDebug() << query.lastError(); 473 | query.exec("ROLLBACK;"); 474 | --maxChatroomNumber; 475 | return false; 476 | } 477 | query.exec("COMMIT;"); 478 | 479 | // 更新界面显示的聊天室表 480 | if (chatTableModel){ 481 | chatTableModel->select(); 482 | } 483 | 484 | return true; 485 | } 486 | 487 | bool ServerCore::joinChatroom(const QString &chatroomName, const QString &userName) { 488 | QSqlQuery query; 489 | 490 | // 检查聊天室名是否已存在 491 | query.exec(QString("SELECT c_id FROM chatroom WHERE c_name = '%1' AND ds_t IS NULL;").arg(chatroomName)); 492 | if (query.lastError().isValid()) { 493 | qDebug() << query.lastError(); 494 | return false; 495 | } 496 | if (!query.next()) { 497 | qDebug() << "聊天室名不存在!"; 498 | return false; 499 | } 500 | 501 | int chatroomID = query.value(0).toInt(); 502 | 503 | // 检查用户名是否已存在 504 | query.exec(QString("SELECT u_id FROM user WHERE u_name = '%1' AND sd_t IS NULL;").arg(userName)); 505 | if (query.lastError().isValid()) { 506 | qDebug() << query.lastError(); 507 | return false; 508 | } 509 | if (!query.next()) { 510 | qDebug() << "用户名不存在!"; 511 | return false; 512 | } 513 | 514 | int userID = query.value(0).toInt(); 515 | 516 | // 插入新用户-聊天室关系 517 | query.exec(QString( 518 | "INSERT INTO user_chatroom (u_id, c_id, j_t, q_t, role) VALUES " 519 | "(%1,%2,'%3',%4,'%5');") 520 | .arg(userID) 521 | .arg(chatroomID) 522 | .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")) 523 | .arg("NULL") 524 | .arg("user")); 525 | if (query.lastError().isValid()) { 526 | qDebug() << query.lastError(); 527 | return false; 528 | } 529 | 530 | return true; 531 | } 532 | 533 | QJsonArray ServerCore::getJoinedChatList(const QString &userName) { 534 | QJsonArray chatList; 535 | QSqlQuery query; 536 | 537 | // 查询该用户已加入的所有聊天室 538 | query.exec(QString( 539 | "SELECT c_name FROM user_chatroom uc " 540 | "INNER JOIN chatroom c ON uc.c_id = c.c_id " 541 | "INNER JOIN user u ON uc.u_id = u.u_id " 542 | "WHERE u_name = '%1' AND q_t IS NULL AND ds_t IS NULL;") 543 | .arg(userName)); 544 | if (query.lastError().isValid()) { 545 | qDebug() << "When getJoinedChatList: " << query.lastError(); 546 | return chatList; 547 | } 548 | 549 | while (query.next()) { 550 | chatList.append(query.value(0).toString()); 551 | } 552 | 553 | return chatList; 554 | } 555 | 556 | QJsonArray ServerCore::getUnjoinedChatList(const QString &userName) { 557 | QJsonArray chatList; 558 | QSqlQuery query; 559 | 560 | // 查询该用户未加入的所有聊天室 561 | query.exec(QString( 562 | "SELECT c_name FROM chatroom " 563 | "WHERE c_id NOT IN " 564 | "(SELECT uc.c_id FROM user_chatroom uc " 565 | "INNER JOIN chatroom c ON uc.c_id = c.c_id " 566 | "INNER JOIN user u ON uc.u_id = u.u_id " 567 | "WHERE u_name = '%1' AND q_t IS NULL AND ds_t IS NULL AND sd_t IS NULL) " 568 | "AND ds_t IS NULL;") 569 | .arg(userName)); 570 | if (query.lastError().isValid()) { 571 | qDebug() << "When getUnjoinedChatList: " << query.lastError(); 572 | return chatList; 573 | } 574 | 575 | while (query.next()) { 576 | chatList.append(query.value(0).toString()); 577 | } 578 | 579 | return chatList; 580 | } 581 | 582 | QJsonArray ServerCore::getChatUserList(const QString &chatName) { 583 | QJsonArray userList; 584 | QSqlQuery query; 585 | 586 | // 查询该聊天室的所有用户 587 | query.exec(QString( 588 | "SELECT u_name FROM user_chatroom " 589 | "INNER JOIN chatroom ON user_chatroom.c_id = chatroom.c_id " 590 | "INNER JOIN user ON user_chatroom.u_id = user.u_id " 591 | "WHERE c_name = '%1' AND ds_t IS NULL AND sd_t IS NULL;") 592 | .arg(chatName)); 593 | if (query.lastError().isValid()) { 594 | qDebug() << query.lastError(); 595 | return userList; 596 | } 597 | 598 | while (query.next()) { 599 | userList.append(query.value(0).toString()); 600 | } 601 | 602 | return userList; 603 | } 604 | 605 | QJsonArray ServerCore::getMessage(const QString &chatName, const int latestMessageID, const QString &lastTime) { 606 | QJsonArray messageList; 607 | QSqlQuery query; 608 | 609 | // 查询该聊天室 ID > latestMessageID 的所有消息(每条聊天记录包含id,name,content,time) 610 | query.exec(QString( 611 | "SELECT m_id, u_name, m_text, m_t FROM message " 612 | "INNER JOIN user ON message.m_u_id = user.u_id " 613 | "INNER JOIN chatroom ON message.m_c_id = chatroom.c_id " 614 | "WHERE c_name = '%1' AND m_id > %2 AND m_db_t IS NULL AND ds_t IS NULL " 615 | "ORDER BY message.m_t ASC;") 616 | .arg(chatName) 617 | .arg(latestMessageID)); 618 | if (query.lastError().isValid()) { 619 | qDebug() << query.lastError(); 620 | return QJsonArray(); 621 | } 622 | 623 | while (query.next()) { 624 | QJsonObject message; 625 | message.insert("id", query.value(0).toInt()); 626 | message.insert("name", query.value(1).toString()); 627 | message.insert("content", query.value(2).toString()); 628 | message.insert("time", query.value(3).toString()); 629 | messageList.append(message); 630 | } 631 | 632 | // 查询被撤回的消息 633 | query.exec(QString( 634 | "SELECT m_id FROM message " 635 | "INNER JOIN chatroom ON message.m_c_id = chatroom.c_id " 636 | "WHERE c_name = '%1' AND m_db_t IS NOT NULL AND m_db_t >= '%2' AND ds_t IS NULL;") 637 | .arg(chatName) 638 | .arg(lastTime)); 639 | if (query.lastError().isValid()) { 640 | qDebug() << query.lastError(); 641 | return QJsonArray(); 642 | } 643 | 644 | while (query.next()) { 645 | QJsonObject message; 646 | message.insert("id", query.value(0).toInt()); 647 | message.insert("time", ""); 648 | messageList.append(message); 649 | } 650 | 651 | return messageList; 652 | } 653 | 654 | bool ServerCore::sendMessage(const QString &chatName, const QString &senderName, const QString &message) { 655 | QSqlQuery query; 656 | 657 | // 查询该聊天室的 ID 658 | query.exec(QString( 659 | "SELECT c_id FROM chatroom WHERE c_name = '%1';") 660 | .arg(chatName)); 661 | if (query.lastError().isValid()) { 662 | qDebug() << query.lastError(); 663 | return false; 664 | } 665 | if (!query.next()) { 666 | qDebug() << "聊天室不存在!"; 667 | return false; 668 | } 669 | int chatID = query.value(0).toInt(); 670 | 671 | // 查询该用户的 ID 672 | query.exec(QString( 673 | "SELECT u_id FROM user WHERE u_name = '%1' AND sd_t IS NULL;") 674 | .arg(senderName)); 675 | if (query.lastError().isValid()) { 676 | qDebug() << query.lastError(); 677 | return false; 678 | } 679 | if (!query.next()) { 680 | qDebug() << "用户不存在!"; 681 | return false; 682 | } 683 | int senderID = query.value(0).toInt(); 684 | 685 | // 插入消息 m_id, m_u_id, m_c_id, m_t, m_text 686 | query.exec(QString( 687 | "INSERT INTO message (m_id, m_u_id, m_c_id, m_t, m_db_t, m_text) " 688 | "VALUES (%1,%2,%3,'%4',%5,'%6');") 689 | .arg(++maxMessageNumber) 690 | .arg(senderID) 691 | .arg(chatID) 692 | .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")) 693 | .arg("NULL") 694 | .arg(message)); 695 | if (query.lastError().isValid()) { 696 | --maxMessageNumber; 697 | qDebug() << query.lastError(); 698 | return false; 699 | } 700 | 701 | synchronizationRemind(chatName, senderName); 702 | 703 | return true; 704 | } 705 | 706 | bool ServerCore::synchronizationRemind(const QString &chatName, const QString &senderName) { 707 | return true; 708 | // 发送同步提醒消息,提醒聊天室中所有在线用户更新信息 709 | QJsonObject remindJsonObj = baseJsonObj("synchronization", "remind"); 710 | 711 | // 编辑数据字段 712 | QJsonObject dataObj; 713 | dataObj["chatName"] = chatName; 714 | remindJsonObj["data"] = dataObj; 715 | 716 | // 发送消息给该聊天室的在线用户(从数据库中选出该聊天室用户,再用userSocketMap尝试发送) 717 | QJsonArray userList = getChatUserList(chatName); 718 | qDebug() << "发送同步提醒报文:" << userList; 719 | for (int i = 0; i < userList.size(); i++) { 720 | QString userName = userList.at(i).toString(); 721 | if (userName == senderName) { // 不给消息发送者发送同步提醒 722 | continue; 723 | } 724 | if (userSocketMap.contains(userName)) { 725 | sendJsonObj(userSocketMap[userName], remindJsonObj); 726 | } 727 | } 728 | 729 | return true; 730 | } 731 | 732 | void ServerCore::processReadMessage(QSslSocket *socket, const QString &message) { 733 | // 解析JSON字符串 734 | QJsonDocument jsonDoc = QJsonDocument::fromJson(message.toUtf8()); 735 | 736 | if (jsonDoc.isNull() || !jsonDoc.isObject()) { 737 | qDebug() << "数据报文解析失败!"; 738 | return; 739 | } 740 | QJsonObject jsonObj = jsonDoc.object(); 741 | 742 | if (jsonObj["state"].toString() != "request") { 743 | qDebug() << "该数据报文不是请求报文!"; 744 | return; 745 | } 746 | 747 | QString type = jsonObj["type"].toString(); 748 | QJsonObject dataObj = jsonObj["data"].toObject(); 749 | 750 | if (type == "test") { 751 | if (dataObj["test"] == "test") { 752 | qDebug() << "测试成功"; 753 | QJsonObject resJsonObj = baseJsonObj(type, "success"); 754 | sendJsonObj(socket, resJsonObj); 755 | } else { 756 | qDebug() << "测试失败"; 757 | QJsonObject resJsonObj = baseJsonObj(type, "failed"); 758 | sendJsonObj(socket, resJsonObj); 759 | return; 760 | } 761 | } else if (type == "register") { 762 | if (registerAccount(dataObj["userName"].toString(), dataObj["password"].toString(), dataObj["role"].toString())) { 763 | qDebug() << "新账号注册成功"; 764 | QJsonObject resJsonObj = baseJsonObj(type, "success"); 765 | 766 | // 编辑数据字段 767 | QJsonObject resDataObj = resJsonObj["data"].toObject(); 768 | resDataObj["userName"] = dataObj["userName"].toString(); 769 | resJsonObj["data"] = resDataObj; 770 | 771 | sendJsonObj(socket, resJsonObj); 772 | } else { 773 | qDebug() << "新账号注册失败"; 774 | QJsonObject resJsonObj = baseJsonObj(type, "failed"); 775 | sendJsonObj(socket, resJsonObj); 776 | return; 777 | } 778 | } else if (type == "login") { 779 | if (loginAccount(socket, dataObj["userName"].toString(), dataObj["password"].toString())) { 780 | qDebug() << "用户登录成功"; 781 | QJsonObject resJsonObj = baseJsonObj(type, "success"); 782 | 783 | // 编辑数据字段 784 | QJsonObject resDataObj = resJsonObj["data"].toObject(); 785 | resDataObj["userName"] = dataObj["userName"].toString(); 786 | resJsonObj["data"] = resDataObj; 787 | 788 | sendJsonObj(socket, resJsonObj); 789 | }else { 790 | qDebug() << "用户登录失败"; 791 | QJsonObject resJsonObj = baseJsonObj(type, "failed"); 792 | sendJsonObj(socket, resJsonObj); 793 | return; 794 | } 795 | } else if (type == "createChatroom") { 796 | if (createChatroom(dataObj["chatName"].toString(), dataObj["creatorName"].toString())) { 797 | qDebug() << "创建聊天室成功"; 798 | QJsonObject resJsonObj = baseJsonObj(type, "success"); 799 | 800 | // 编辑数据字段 801 | QJsonObject resDataObj = resJsonObj["data"].toObject(); 802 | resDataObj["chatName"] = dataObj["chatName"].toString(); 803 | resJsonObj["data"] = resDataObj; 804 | 805 | sendJsonObj(socket, resJsonObj); 806 | } else { 807 | qDebug() << "创建聊天室失败"; 808 | QJsonObject resJsonObj = baseJsonObj(type, "failed"); 809 | sendJsonObj(socket, resJsonObj); 810 | return; 811 | } 812 | } else if (type == "joinChatroom") { 813 | if (joinChatroom(dataObj["chatName"].toString(), dataObj["userName"].toString())) { 814 | qDebug() << "加入聊天室成功"; 815 | QJsonObject resJsonObj = baseJsonObj(type, "success"); 816 | 817 | // 编辑数据字段 818 | QJsonObject resDataObj = resJsonObj["data"].toObject(); 819 | resDataObj["chatName"] = dataObj["chatName"].toString(); 820 | resDataObj["userName"] = dataObj["userName"].toString(); 821 | resJsonObj["data"] = resDataObj; 822 | 823 | sendJsonObj(socket, resJsonObj); 824 | } else { 825 | qDebug() << "加入聊天室失败"; 826 | QJsonObject resJsonObj = baseJsonObj(type, "failed"); 827 | sendJsonObj(socket, resJsonObj); 828 | return; 829 | } 830 | } else if (type == "getChatList") { 831 | QJsonArray joinedChatList = getJoinedChatList(dataObj["userName"].toString()); 832 | QJsonArray unjoinedChatList = getUnjoinedChatList(dataObj["userName"].toString()); 833 | 834 | qDebug() << "获取用户可访问的聊天室列表成功"; 835 | QJsonObject resJsonObj = baseJsonObj(type, "success"); 836 | 837 | // 编辑数据字段 838 | QJsonObject resDataObj = resJsonObj["data"].toObject(); 839 | resDataObj["userName"] = dataObj["userName"].toString(); 840 | resDataObj["joinedChatList"] = joinedChatList; 841 | resDataObj["unjoinedChatList"] = unjoinedChatList; 842 | resJsonObj["data"] = resDataObj; 843 | 844 | sendJsonObj(socket, resJsonObj); 845 | } else if (type == "getChatUserList") { 846 | QJsonArray chatUserList = getChatUserList(dataObj["chatName"].toString()); 847 | 848 | qDebug() << "获取聊天室用户列表成功"; 849 | QJsonObject resJsonObj = baseJsonObj(type, "success"); 850 | 851 | // 编辑数据字段 852 | QJsonObject resDataObj = resJsonObj["data"].toObject(); 853 | resDataObj["chatName"] = dataObj["chatName"].toString(); 854 | resDataObj["chatUserList"] = chatUserList; 855 | resJsonObj["data"] = resDataObj; 856 | 857 | sendJsonObj(socket, resJsonObj); 858 | } else if (type == "getMessage") { 859 | QJsonArray messageList = getMessage( 860 | dataObj["chatName"].toString(), 861 | dataObj["latestMessageID"].toInt(), 862 | dataObj["lastTime"].toString() 863 | ); 864 | 865 | qDebug() << "获取聊天室消息成功"; 866 | QJsonObject resJsonObj = baseJsonObj(type, "success"); 867 | 868 | // 编辑数据字段 869 | QJsonObject resDataObj = resJsonObj["data"].toObject(); 870 | resDataObj["chatName"] = dataObj["chatName"].toString(); 871 | resDataObj["latestMessageID"] = dataObj["latestMessageID"].toInt(); 872 | resDataObj["lastTime"] = dataObj["lastTime"].toString(); 873 | resDataObj["messageList"] = messageList; 874 | resJsonObj["data"] = resDataObj; 875 | 876 | sendJsonObj(socket, resJsonObj); 877 | qDebug() << "已发送聊天室消息到ip地址和端口" << socket->peerAddress().toString() << socket->peerPort(); 878 | } else if (type == "sendMessage") { 879 | if (sendMessage(dataObj["chatName"].toString(), dataObj["senderName"].toString(), dataObj["message"].toString())) { 880 | qDebug() << "发送消息成功"; 881 | QJsonObject resJsonObj = baseJsonObj(type, "success"); 882 | 883 | // 编辑数据字段 884 | QJsonObject resDataObj = resJsonObj["data"].toObject(); 885 | resDataObj["chatName"] = dataObj["chatName"].toString(); 886 | resDataObj["senderName"] = dataObj["senderName"].toString(); 887 | resDataObj["message"] = dataObj["message"].toString(); 888 | resJsonObj["data"] = resDataObj; 889 | 890 | sendJsonObj(socket, resJsonObj); 891 | } else { 892 | qDebug() << "发送消息失败"; 893 | QJsonObject resJsonObj = baseJsonObj(type, "failed"); 894 | sendJsonObj(socket, resJsonObj); 895 | return; 896 | } 897 | } else { 898 | qDebug() << "数据报文类型不正确!"; 899 | return; 900 | } 901 | } 902 | 903 | void ServerCore::onReceiveMessage(QSslSocket *socket, const QString &message) { 904 | // 如果包含多个报文,需要分割 905 | QStringList messageList = message.split("}{"); 906 | 907 | for (int i = 0; i < messageList.size(); i++) { 908 | // 如果不是第一个报文,需要加上 { 909 | if (i != 0) { 910 | messageList[i] = "{" + messageList[i]; 911 | } 912 | 913 | // 如果不是最后一个报文,需要加上 } 914 | if (i != messageList.size() - 1) { 915 | messageList[i] = messageList[i] + "}"; 916 | } 917 | 918 | processReadMessage(socket, messageList[i]); 919 | } 920 | } 921 | 922 | ServerCore::ServerCore() { 923 | maxUserNumber = 0; 924 | maxChatroomNumber = 0; 925 | maxMessageNumber = 0; 926 | connect(&server, &Server::receiveMessage, this, &ServerCore::onReceiveMessage); 927 | } // 私有构造函数,确保单例 928 | 929 | ServerCore::~ServerCore() { 930 | if (userTableModel) delete userTableModel; 931 | if (chatTableModel) delete chatTableModel; 932 | } 933 | 934 | QJsonObject ServerCore::baseJsonObj(const QString &type, const QString &state) { 935 | // 创建一个 JSON 对象 936 | QJsonObject jsonObj; 937 | jsonObj["type"] = type; 938 | jsonObj["state"] = state; 939 | 940 | // 创建一个嵌套的JSON数据对象 941 | QJsonObject dataObj; 942 | jsonObj["data"] = dataObj; 943 | 944 | return jsonObj; 945 | } 946 | 947 | void ServerCore::sendJsonObj(QSslSocket *socket, const QJsonObject &jsonObj) { 948 | // 使用 QJsonDocument 生成 JSON 字符串,并发送报文 949 | QJsonDocument jsonDoc(jsonObj); 950 | QString message = jsonDoc.toJson(QJsonDocument::Compact); 951 | // qDebug() << "发送报文到ip地址和端口" << socket->peerAddress().toString() << socket->peerPort(); 952 | // qDebug() << socket->write(message.toUtf8()); 953 | server.sendMessage(socket, message); 954 | } 955 | -------------------------------------------------------------------------------- /server/src/servercore.h: -------------------------------------------------------------------------------- 1 | #ifndef SERVERCORE_H 2 | #define SERVERCORE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "server.h" 10 | 11 | class ServerCore : public QObject 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | static ServerCore& getInstance() { 17 | static ServerCore instance; // 单例对象 18 | return instance; 19 | } 20 | 21 | public: 22 | bool createServer(const QHostAddress &address, quint16 port, const QString &rootUserName, const QString &password); 23 | 24 | bool generateCertAndKey(const QString &certPath, const QString &keyPath); 25 | 26 | bool createDatabase(const QString &rootUserName, const QString &password); 27 | 28 | bool registerAccount(const QString &userName, const QString &password, const QString &role); 29 | 30 | bool loginAccount(QSslSocket *socket, const QString &userName, const QString &password); 31 | 32 | bool createChatroom(const QString &chatroomName, const QString &userName); 33 | 34 | bool joinChatroom(const QString &chatroomName, const QString &userName); 35 | 36 | QJsonArray getJoinedChatList(const QString &userName); 37 | 38 | QJsonArray getUnjoinedChatList(const QString &userName); 39 | 40 | QJsonArray getChatUserList(const QString &chatName); 41 | 42 | QJsonArray getMessage(const QString &chatName, const int latestMessageID, const QString &lastTime); 43 | 44 | bool sendMessage(const QString &chatName, const QString &senderName, const QString &message); 45 | 46 | bool synchronizationRemind(const QString &chatName, const QString &senderName); 47 | 48 | void processReadMessage(QSslSocket *socket, const QString &message); 49 | 50 | private slots: 51 | void onReceiveMessage(QSslSocket *socket, const QString &message); 52 | 53 | private: 54 | ServerCore(); // 私有构造函数,确保单例 55 | 56 | ~ServerCore(); 57 | 58 | QJsonObject baseJsonObj(const QString &type, const QString &state); 59 | 60 | void sendJsonObj(QSslSocket *socket, const QJsonObject &jsonObj); 61 | 62 | public: 63 | Server server; // 服务器 64 | QHostAddress serverAddress; // 服务器IP地址 65 | quint16 serverPort; // 服务器端口 66 | 67 | QSqlDatabase db; // 数据库 68 | QSqlTableModel* userTableModel; 69 | QSqlTableModel* chatTableModel; 70 | 71 | int adminUserID; // 目前登录服务器的管理员用户ID 72 | QString adminUserName; // 目前登录服务器的管理员用户名 73 | 74 | QMap userSocketMap; // 用户名与socket的映射 75 | 76 | int maxUserNumber; // 用于计数累计用户数量,从而确定新建u_id 77 | int maxChatroomNumber; // 用于计数累计聊天室数量,从而确定新建c_id 78 | int maxMessageNumber; // 用于计数累计消息数量,从而确定新建m_id 79 | }; 80 | 81 | #endif // SERVERCORE_H 82 | -------------------------------------------------------------------------------- /server/src/usermanage.cpp: -------------------------------------------------------------------------------- 1 | #include "usermanage.h" 2 | #include "ui_usermanage.h" 3 | #include 4 | UserManage::UserManage(const QString &name, const QString &id, QWidget *parent) : 5 | QWidget(parent), 6 | ui(new Ui::UserManage) { 7 | ui->setupUi(this); 8 | 9 | Roomname = name; 10 | RoomID = id; 11 | ui->ChatName->setText("聊天名称:"+name); 12 | ui->ChatID->setText("聊天ID:"+id); 13 | 14 | QSqlQuery query; 15 | query.exec(QString( 16 | "CREATE VIEW IF NOT EXISTS chat_user_%1 AS " 17 | "SELECT u.u_id, u.u_name, uc.j_t, uc.q_t, uc.role FROM user_chatroom uc " 18 | "JOIN user u ON uc.u_id = u.u_id " 19 | "WHERE uc.c_id = %1 " 20 | "ORDER BY u.u_id ASC;" 21 | ).arg(RoomID)); 22 | if (query.lastError().isValid()) { 23 | qDebug() << query.lastError(); 24 | return; 25 | } 26 | 27 | chatUserTableModel = new QSqlTableModel(this); 28 | chatUserTableModel->setTable(QString("chat_user_%1").arg(RoomID)); 29 | chatUserTableModel->select(); 30 | chatUserTableModel->setHeaderData(0, Qt::Horizontal, "用户ID"); 31 | chatUserTableModel->setHeaderData(1, Qt::Horizontal, "用户名"); 32 | chatUserTableModel->setHeaderData(2, Qt::Horizontal, "加入时间"); 33 | chatUserTableModel->setHeaderData(3, Qt::Horizontal, "退出时间"); 34 | chatUserTableModel->setHeaderData(4, Qt::Horizontal, "群聊中角色"); 35 | 36 | ui->UserTable->setModel(chatUserTableModel); 37 | ui->UserTable->setColumnWidth(3, 160); 38 | ui->UserTable->setColumnWidth(4, 160); 39 | 40 | ui->DeleteButton->setEnabled(false); 41 | 42 | // 如果ui->UserTable至少选中了一行,则激活ui->DeleteButton 43 | connect(ui->UserTable->selectionModel(), &QItemSelectionModel::selectionChanged, [=](){ 44 | ui->DeleteButton->setEnabled(!ui->UserTable->selectionModel()->selectedRows().isEmpty()); 45 | }); 46 | } 47 | 48 | UserManage::~UserManage() { 49 | delete ui; 50 | } 51 | 52 | void UserManage::on_FindButton_clicked() { 53 | QString userName = ui->ContentInput->text(); 54 | chatUserTableModel->setFilter(QString("u_name LIKE '%%1%'").arg(userName)); 55 | chatUserTableModel->select(); 56 | } 57 | 58 | void UserManage::on_DeleteButton_clicked() { 59 | QItemSelectionModel *selectionModel = ui->UserTable->selectionModel(); 60 | QModelIndexList selectedRows = selectionModel->selectedRows(); 61 | QModelIndex index_0; 62 | int userID; 63 | QSqlQuery query; 64 | // 事务开始,保证原子性 65 | query.exec("BEGIN"); 66 | // 删除所有选中的行的用户 67 | for (const QModelIndex &index : selectedRows) { 68 | index_0 = ui->UserTable->model()->index(index.row(), 0); 69 | userID = ui->UserTable->model()->data(index_0).toInt(); 70 | query.prepare("UPDATE user_chatroom SET q_t = :quit_time WHERE u_id = :user_id AND c_id = :chatroom_id AND q_t IS NULL;"); 71 | query.bindValue(":quit_time", QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")); 72 | query.bindValue(":user_id", userID); 73 | query.bindValue(":chatroom_id", RoomID); 74 | query.exec(); 75 | if (query.lastError().isValid()) { 76 | qDebug() << query.lastError(); 77 | query.exec("ROLLBACK"); 78 | chatUserTableModel->select(); 79 | return; 80 | } 81 | } 82 | // 事务提交 83 | query.exec("COMMIT"); 84 | // 刷新用户表 85 | chatUserTableModel->select(); 86 | } 87 | -------------------------------------------------------------------------------- /server/src/usermanage.h: -------------------------------------------------------------------------------- 1 | #ifndef USERMANAGE_H 2 | #define USERMANAGE_H 3 | 4 | #include 5 | #include 6 | 7 | namespace Ui { 8 | class UserManage; 9 | } 10 | 11 | class UserManage : public QWidget 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | explicit UserManage(const QString &name, const QString &id, QWidget *parent = nullptr); 17 | ~UserManage(); 18 | 19 | private slots: 20 | void on_FindButton_clicked(); 21 | 22 | void on_DeleteButton_clicked(); 23 | 24 | private: 25 | QString Roomname; 26 | QString RoomID; 27 | Ui::UserManage *ui; 28 | QSqlTableModel* chatUserTableModel; 29 | }; 30 | 31 | #endif // USERMANAGE_H 32 | -------------------------------------------------------------------------------- /server/src/usermanage.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | UserManage 4 | 5 | 6 | 7 | 0 8 | 0 9 | 783 10 | 548 11 | 12 | 13 | 14 | 群聊用户管理 15 | 16 | 17 | 18 | :/icon/manage.png 19 | :/icon/manage.png:/icon/manage.png 20 | 21 | 22 | QPushButton { 23 | background-color:#030408; 24 | color:#db9a45; 25 | font-size: 20px; 26 | font:10pt "Microsoft YaHei UI"; 27 | border-radius:8px; 28 | font-weight:700; 29 | height:30px; 30 | width: 100%; 31 | } 32 | QPushButton:disabled { 33 | background-color: #787a7e; 34 | } 35 | QPushButton:hover { 36 | background-color: #383a3e; 37 | } 38 | QLineEdit{ 39 | border: 1px solid rgb(41, 57, 85); 40 | border-radius: 3px; 41 | border-color:#dcdee0; 42 | height:36px; 43 | background: white; 44 | padding-left: 12px; 45 | padding-right: 12px; 46 | border-color:#e4e5eb; 47 | } 48 | QLineEdit:hover { 49 | border-color: #db9a45; 50 | } 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 300 60 | 16777215 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 查询 69 | 70 | 71 | 72 | 73 | 74 | 75 | 移出选中用户 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | TextLabel 87 | 88 | 89 | 90 | 91 | 92 | 93 | Qt::Horizontal 94 | 95 | 96 | QSizePolicy::Fixed 97 | 98 | 99 | 100 | 20 101 | 20 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | TextLabel 110 | 111 | 112 | 113 | 114 | 115 | 116 | Qt::Horizontal 117 | 118 | 119 | 120 | 40 121 | 20 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | ContentInput 135 | FindButton 136 | DeleteButton 137 | UserTable 138 | 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /server/src/widget.cpp: -------------------------------------------------------------------------------- 1 | #include "widget.h" 2 | #include "ui_widget.h" 3 | #include "adminmanagement.h" 4 | #include 5 | #include 6 | #include 7 | Widget::Widget(QWidget *parent) 8 | : QWidget(parent) 9 | , ui(new Ui::Widget) 10 | { 11 | ui->setupUi(this); 12 | this->setWindowFlags(Qt::FramelessWindowHint);//去除标题栏 13 | this->setAttribute(Qt::WA_TranslucentBackground);//透明 14 | //绘制阴影 15 | QGraphicsDropShadowEffect * shadowEffect = new QGraphicsDropShadowEffect(); 16 | shadowEffect->setOffset(0, 0); 17 | QColor color = Qt::black; 18 | color.setAlpha(64); 19 | shadowEffect->setColor(color); 20 | shadowEffect->setBlurRadius(20); 21 | ui->frame->setGraphicsEffect(shadowEffect); 22 | // 获取核心实例 23 | core = &ServerCore::getInstance(); 24 | } 25 | 26 | Widget::~Widget() 27 | { 28 | delete ui; 29 | } 30 | 31 | 32 | void Widget::on_CloseButton_clicked() 33 | { 34 | this->close(); 35 | } 36 | 37 | void Widget::on_LoginButton_clicked() 38 | { 39 | if (core->createServer(QHostAddress(ui->IPInput->text()), ui->PortInput->text().toInt(), 40 | ui->AdminInput->text(), ui->PassWordInput->text())) { 41 | //跳转界面 42 | AdminManagement * adminManageWindow = new AdminManagement; 43 | this->close(); 44 | adminManageWindow->show(); 45 | } 46 | } 47 | void Widget::mousePressEvent(QMouseEvent * event) 48 | { 49 | diff_pos = this->pos()-event->globalPos(); 50 | } 51 | void Widget::mouseMoveEvent(QMouseEvent *event) 52 | { 53 | this->move(event->globalPos()+diff_pos); 54 | } 55 | -------------------------------------------------------------------------------- /server/src/widget.h: -------------------------------------------------------------------------------- 1 | #ifndef WIDGET_H 2 | #define WIDGET_H 3 | 4 | #include 5 | #include "servercore.h" 6 | 7 | QT_BEGIN_NAMESPACE 8 | namespace Ui { class Widget; } 9 | QT_END_NAMESPACE 10 | 11 | class Widget : public QWidget 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | Widget(QWidget *parent = nullptr); 17 | ~Widget(); 18 | 19 | private slots: 20 | void on_CloseButton_clicked(); 21 | 22 | void on_LoginButton_clicked(); 23 | 24 | private: 25 | void mousePressEvent(QMouseEvent *event) override; 26 | void mouseMoveEvent(QMouseEvent *event) override; 27 | QPoint diff_pos; 28 | Ui::Widget *ui; 29 | ServerCore *core; 30 | }; 31 | #endif // WIDGET_H 32 | -------------------------------------------------------------------------------- /server/src/widget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Widget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | 15 | 400 16 | 300 17 | 18 | 19 | 20 | 21 | 400 22 | 300 23 | 24 | 25 | 26 | 服务端登录 27 | 28 | 29 | QPushButton { 30 | background-color:#030408; 31 | color:#db9a45; 32 | font-size: 20px; 33 | font:10pt "Microsoft YaHei UI"; 34 | border-radius:8px; 35 | font-weight:700; 36 | height:44px; 37 | width: 100%; 38 | } 39 | QPushButton:disabled { 40 | background-color: #787a7e; 41 | } 42 | QPushButton:hover { 43 | background-color: #383a3e; 44 | } 45 | QFrame{ 46 | margin:2px; 47 | border: 1px solid #dcdee0;; 48 | border-radius: 24px; 49 | background-color:rgb(255, 255, 255); 50 | padding:10px; 51 | } 52 | QLineEdit{ 53 | border: 1px solid rgb(41, 57, 85); 54 | border-radius: 3px; 55 | border-color:#dcdee0; 56 | height:36px; 57 | background: white; 58 | padding-left: 12px; 59 | padding-right: 12px; 60 | border-color:#e4e5eb; 61 | } 62 | QLineEdit:hover { 63 | border-color: #db9a45; 64 | } 65 | 66 | 67 | 68 | 69 | 70 | QFrame::StyledPanel 71 | 72 | 73 | QFrame::Raised 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 12345 82 | 83 | 84 | 端口 85 | 86 | 87 | 88 | 89 | 90 | 91 | 127.0.0.1 92 | 93 | 94 | IP地址 95 | 96 | 97 | 98 | 99 | 100 | 101 | Qt::Horizontal 102 | 103 | 104 | QSizePolicy::Fixed 105 | 106 | 107 | 108 | 40 109 | 20 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 超级管理员账号 118 | 119 | 120 | 121 | 122 | 123 | 124 | Qt::Horizontal 125 | 126 | 127 | QSizePolicy::Fixed 128 | 129 | 130 | 131 | 40 132 | 20 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | QLineEdit::Password 141 | 142 | 143 | 密码 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | Qt::Horizontal 155 | 156 | 157 | 158 | 40 159 | 20 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 建立 168 | 169 | 170 | 171 | 172 | 173 | 174 | Qt::Horizontal 175 | 176 | 177 | QSizePolicy::Fixed 178 | 179 | 180 | 181 | 40 182 | 20 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 关闭 191 | 192 | 193 | 194 | 195 | 196 | 197 | Qt::Horizontal 198 | 199 | 200 | 201 | 40 202 | 20 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | IPInput 216 | PortInput 217 | AdminInput 218 | PassWordInput 219 | LoginButton 220 | CloseButton 221 | 222 | 223 | 224 | 225 | --------------------------------------------------------------------------------