├── .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 | 
35 |
36 | #### ER图
37 |
38 | 
39 |
40 | #### 网络通信示意图
41 |
42 | 
43 |
44 | #### 服务端用户逻辑
45 |
46 | 
47 |
48 | #### 客户端用户逻辑
49 |
50 | 
51 |
52 | #### 服务端界面截图
53 |
54 | 
55 |
56 | 
57 |
58 | 
59 |
60 | 
61 |
62 | 
63 |
64 | #### 客户端界面截图
65 |
66 | 
67 |
68 | 
69 |
70 | 
71 |
72 | 
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 |
--------------------------------------------------------------------------------