├── QQClient ├── QQClient.pro ├── QQClient.pro.user ├── account.cpp ├── account.h ├── account.ui ├── addfriend.cpp ├── addfriend.h ├── addfriend.ui ├── changedata.cpp ├── changedata.h ├── changedata.ui ├── chatwindow.cpp ├── chatwindow.h ├── chatwindow.ui ├── findfriends.cpp ├── findfriends.h ├── findfriends.ui ├── global.cpp ├── global.h ├── lib.qrc ├── lib │ ├── Accont.png │ ├── Big.png │ ├── BigToNormal.png │ ├── Chat.png │ ├── FDownArrow.png │ ├── FLeftArrow.png │ ├── Greyguanbi.png │ ├── Greyzuixiaohua.png │ ├── Keyboard.png │ ├── Loge.png │ ├── MainMenu.png │ ├── MoveF.png │ ├── QQ.png │ ├── System.png │ ├── add.png │ ├── addfriend.png │ ├── backg.jpeg │ ├── biaoqing.png │ ├── close.png │ ├── cut.png │ ├── default.jpg │ ├── delete.png │ ├── fileIcon.png │ ├── fileIcons │ │ ├── PDF.png │ │ ├── Recv.png │ │ ├── WindowsWhite.png │ │ ├── excel.png │ │ ├── mp3.png │ │ ├── mp4.png │ │ ├── photo.png │ │ ├── ppt.png │ │ ├── send.png │ │ ├── txt.png │ │ ├── word.png │ │ └── zip.png │ ├── find.gif │ ├── gantanhao.png │ ├── gengduo.png │ ├── guanbi.png │ ├── guanbi2.png │ ├── main.gif │ ├── nicheng.png │ ├── picIcon.png │ ├── pwd.png │ ├── quit.png │ ├── search.png │ ├── shangjiantou.png │ ├── tishi.png │ ├── xiajiantou.png │ ├── xingbie.png │ ├── zhanghao.png │ └── zuixiaohua.png ├── login.cpp ├── login.h ├── login.ui ├── main.cpp ├── maininterface.cpp ├── maininterface.h ├── maininterface.ui ├── personaldata.cpp ├── personaldata.h ├── personaldata.ui ├── tcpthread.cpp └── tcpthread.h ├── QQServer ├── QQServer.pro ├── QQServer.pro.user ├── global.cpp ├── global.h ├── lib.qrc ├── lib │ ├── DefaultHead │ │ ├── touxiang1.jpg │ │ ├── touxiang10.jpg │ │ ├── touxiang11.jpg │ │ ├── touxiang12.jpg │ │ ├── touxiang13.jpg │ │ ├── touxiang14.jpg │ │ ├── touxiang15.jpg │ │ ├── touxiang16.jpg │ │ ├── touxiang17.jpg │ │ ├── touxiang2.jpg │ │ ├── touxiang3.jpg │ │ ├── touxiang4.jpg │ │ ├── touxiang5.jpg │ │ ├── touxiang6.jpg │ │ ├── touxiang7.jpg │ │ ├── touxiang8.jpg │ │ └── touxiang9.jpg │ ├── QQ.png │ └── back.jpg ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h ├── mainwindow.ui ├── qqsqldata.cpp ├── qqsqldata.h ├── sqldata.cpp ├── sqldata.h ├── tcpthread.cpp ├── tcpthread.h ├── workthread.cpp └── workthread.h ├── QQSqlData.sql └── README.md /QQClient/QQClient.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 | account.cpp \ 20 | addfriend.cpp \ 21 | changedata.cpp \ 22 | chatwindow.cpp \ 23 | findfriends.cpp \ 24 | global.cpp \ 25 | main.cpp \ 26 | login.cpp \ 27 | maininterface.cpp \ 28 | personaldata.cpp \ 29 | tcpthread.cpp 30 | 31 | HEADERS += \ 32 | account.h \ 33 | addfriend.h \ 34 | changedata.h \ 35 | chatwindow.h \ 36 | findfriends.h \ 37 | global.h \ 38 | login.h \ 39 | maininterface.h \ 40 | personaldata.h \ 41 | tcpthread.h 42 | 43 | FORMS += \ 44 | account.ui \ 45 | addfriend.ui \ 46 | changedata.ui \ 47 | chatwindow.ui \ 48 | findfriends.ui \ 49 | login.ui \ 50 | maininterface.ui \ 51 | personaldata.ui 52 | 53 | # Default rules for deployment. 54 | qnx: target.path = /tmp/$${TARGET}/bin 55 | else: unix:!android: target.path = /opt/$${TARGET}/bin 56 | !isEmpty(target.path): INSTALLS += target 57 | 58 | RESOURCES += \ 59 | lib.qrc 60 | -------------------------------------------------------------------------------- /QQClient/account.cpp: -------------------------------------------------------------------------------- 1 | #include "account.h" 2 | #include "ui_account.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | Account* Account::m_Acc = nullptr; 9 | QMutex Account::m_mutex; 10 | 11 | Account::Account(bool find, QWidget* parent) : 12 | QWidget(parent), 13 | ui(new Ui::Account) 14 | { 15 | ui->setupUi(this); 16 | //设置标题 17 | setWindowTitle("注册账号"); 18 | //账户AccountLine只能输入数字 19 | ui->lineEdit->setValidator(new QRegExpValidator(QRegExp("[0-9]+$"))); 20 | //密码PwdLineEdit不允许输入# 21 | ui->lineEdit_2->setValidator(new QRegExpValidator(QRegExp("[^#]*"))); 22 | //初始化背景动态图 23 | m_movie = new QMovie(":/lib/find.gif"); 24 | m_movie->setScaledSize(QSize(800, 600)); 25 | m_movie->setSpeed(85); 26 | ui->AcMovieLabel->setMovie(m_movie); 27 | m_movie->start(); 28 | //是找回密码则更改界面 29 | isFind(find); 30 | } 31 | 32 | Account* Account::GetAccount(bool find) 33 | { 34 | //二重锁懒汉模式 35 | if(m_Acc == nullptr) 36 | { 37 | m_mutex.lock(); 38 | 39 | if(m_Acc == nullptr) 40 | { 41 | m_Acc = new Account(find); 42 | } 43 | 44 | m_mutex.unlock(); 45 | } 46 | else 47 | { 48 | //如果指针不为空但与此次请求的界面不同则重新构造,相同则直接返回 49 | if(m_Acc->m_find != find); 50 | 51 | { 52 | m_mutex.lock(); 53 | 54 | if(m_Acc != nullptr) 55 | { 56 | delete m_Acc; 57 | m_Acc = new Account(find); 58 | } 59 | 60 | m_mutex.unlock(); 61 | } 62 | } 63 | 64 | return m_Acc; 65 | } 66 | 67 | void Account::isFind(bool find) 68 | { 69 | //若为找回密码则更改界面 70 | if(find == true) 71 | { 72 | //设置标题 73 | setWindowTitle("找回密码"); 74 | ui->label_2->hide(); 75 | ui->lineEdit_2->hide(); 76 | ui->label_3->setText("找回密码"); 77 | ui->Again->hide(); 78 | ui->AgainLine->hide(); 79 | ui->pushButton->setText("找回"); 80 | } 81 | 82 | m_find = find; 83 | } 84 | 85 | Account::~Account() 86 | { 87 | delete m_movie; 88 | delete ui; 89 | } 90 | 91 | void Account::closeEvent(QCloseEvent* event) 92 | { 93 | this->hide(); 94 | emit BacktoLogin(true); 95 | //忽略要关闭这个窗口的事件.当前窗口不会被关闭 96 | event->ignore(); 97 | } 98 | 99 | void Account::recvResultFromTcp(int type, QString pwd, QString result) 100 | { 101 | if(type == FindPwd) 102 | { 103 | if(pwd == "") 104 | { 105 | QMessageBox::warning(this, "警告", "该账号不存在请重新输入!"); 106 | } 107 | else 108 | { 109 | QMessageBox::information(this, "密码已找回", "您的密码为:" + pwd + ",请牢记您的密码"); 110 | } 111 | 112 | //服务器回应后恢复按钮 113 | ui->pushButton->setEnabled(true); 114 | ui->pushButton->setStyleSheet("font-size:15px;color:white;border-style:none;border-radius:8px 8px;background-color:rgb(0, 170, 255);"); 115 | return; 116 | } 117 | else 118 | { 119 | if(result == "注册成功") 120 | { 121 | QMessageBox::information(this, "注册成功", "您的账号注册成功!"); 122 | } 123 | else 124 | { 125 | QMessageBox::warning(this, "注册失败", "注册失败!该账号已存在,请重新输入新的账号或点击找回密码。"); 126 | } 127 | 128 | ui->pushButton->setEnabled(true); 129 | ui->pushButton->setStyleSheet("font-size:15px;color:white;border-style:none;border-radius:8px 8px;background-color:rgb(0, 170, 255);"); 130 | return; 131 | } 132 | } 133 | 134 | void Account::DisConnectedFromSer(bool onl) 135 | { 136 | //如果信息为断开连接且用户当前停留在注册界面则提示并返回 137 | if(onl == false && this->isVisible()) 138 | { 139 | QMessageBox::warning(this, "警告", "您已断开连接!"); 140 | this->hide(); 141 | emit BacktoLogin(true); 142 | } 143 | } 144 | 145 | void Account::on_pushButton_2_clicked() 146 | { 147 | this->hide(); 148 | emit BacktoLogin(true); 149 | } 150 | 151 | void Account::on_pushButton_clicked() 152 | { 153 | //检查账号密码合法性 154 | if(ui->lineEdit->text().isEmpty()) 155 | { 156 | QMessageBox::warning(this, "警告", "请输入账号!"); 157 | return; 158 | } 159 | 160 | if(ui->lineEdit->text().length() > 9 || ui->lineEdit->text().length() < 4) 161 | { 162 | QMessageBox::warning(this, "警告", "账号位数为4到9位!"); 163 | return; 164 | } 165 | 166 | if(m_find == false) 167 | { 168 | if(ui->lineEdit_2->text().isEmpty()) 169 | { 170 | QMessageBox::warning(this, "警告", "请输入密码!"); 171 | return; 172 | } 173 | 174 | if(ui->lineEdit_2->text().length() < 6) 175 | { 176 | QMessageBox::information(this, "提示", "密码位数至少为6位"); 177 | return; 178 | } 179 | 180 | if(ui->lineEdit_2->text() != ui->AgainLine->text()) 181 | { 182 | QMessageBox::warning(this, "警告", "两次输入的密码不一致!"); 183 | return; 184 | } 185 | } 186 | 187 | //获取账号密码并发送,如果为找回密码则只发送账号 188 | int acc = ui->lineEdit->text().toInt(); 189 | QString pwd = ui->lineEdit_2->text(); 190 | 191 | if(m_find) 192 | { 193 | emit AccountReq(FindPwd, acc); 194 | //在服务器返回结果前关闭按钮 195 | ui->pushButton->setEnabled(false); 196 | ui->pushButton->setStyleSheet("font-size:15px;color:black;border-style:none;border-radius:8px 8px;background-color:rgb(170,170,170);"); 197 | //qDebug() << "找回密码"; 198 | } 199 | else 200 | { 201 | emit AccountReq(Registration, acc, pwd); 202 | ui->pushButton->setEnabled(false); 203 | ui->pushButton->setStyleSheet("font-size:15px;color:black;border-style:none;border-radius:8px 8px;background-color:rgb(170,170,170);"); 204 | //qDebug() << "注册"; 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /QQClient/account.h: -------------------------------------------------------------------------------- 1 | #ifndef ACCOUNT_H 2 | #define ACCOUNT_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "global.h" 9 | 10 | namespace Ui 11 | { 12 | class Account; 13 | } 14 | 15 | class Account : public QWidget 16 | { 17 | Q_OBJECT 18 | 19 | public: 20 | static Account* GetAccount(bool find); //单例模式 21 | ~Account(); 22 | Account& operator=(const Account&) = delete; //单例模式(删除拷贝构造和赋值构造) 23 | Account(Account&) = delete; 24 | void closeEvent(QCloseEvent* event) override; //重写关闭事件,退出则返回登录界面 25 | 26 | public slots: 27 | void recvResultFromTcp(int type, QString pwd, QString result); //接收tcp套接字传回的结果 28 | void DisConnectedFromSer(bool onl); //断开连接 29 | 30 | protected: 31 | void isFind(bool find); //为找回密码需隐藏密码输入栏 32 | 33 | signals: 34 | void BacktoLogin(bool fromAcc); //返回登录界面 35 | void AccountReq(int type, int acc, QString pwd = ""); //发送账号密码信息 36 | 37 | private slots: 38 | void on_pushButton_2_clicked(); //返回 39 | 40 | void on_pushButton_clicked(); //注册/找回 41 | 42 | private: 43 | explicit Account(bool find, QWidget* parent = nullptr); //单例模式(将构造函数私有) 44 | Ui::Account* ui; 45 | bool m_find = false; //是否为找回密码 46 | QMovie* m_movie; //动态图 47 | static Account* m_Acc; //单例模式 48 | static QMutex m_mutex; //锁 49 | }; 50 | 51 | #endif // ACCOUNT_H 52 | -------------------------------------------------------------------------------- /QQClient/account.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Account 4 | 5 | 6 | 7 | 0 8 | 0 9 | 800 10 | 600 11 | 12 | 13 | 14 | 15 | 800 16 | 600 17 | 18 | 19 | 20 | 21 | 800 22 | 600 23 | 24 | 25 | 26 | 找回密码 27 | 28 | 29 | 30 | :/lib/System.png:/lib/System.png 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 0 39 | 0 40 | 800 41 | 600 42 | 43 | 44 | 45 | 46 | 800 47 | 600 48 | 49 | 50 | 51 | 52 | 800 53 | 600 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 0 64 | 0 65 | 800 66 | 600 67 | 68 | 69 | 70 | 71 | 800 72 | 600 73 | 74 | 75 | 76 | 77 | 800 78 | 600 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 0 87 | 88 | 89 | 90 | 91 | Qt::Horizontal 92 | 93 | 94 | 95 | 222 96 | 20 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 0 108 | 0 109 | 110 | 111 | 112 | 113 | 16777215 114 | 150 115 | 116 | 117 | 118 | QLabel 119 | { 120 | font-size:30px; 121 | color:white; 122 | } 123 | 124 | 125 | 注册账号 126 | 提示:账号由纯数字组成 127 | 128 | 129 | false 130 | 131 | 132 | Qt::AlignCenter 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 0 141 | 0 142 | 143 | 144 | 145 | 146 | 16777215 147 | 16777215 148 | 149 | 150 | 151 | 152 | 0 153 | 154 | 155 | 80 156 | 157 | 158 | 159 | 160 | QLabel 161 | { 162 | font-size:20px; 163 | color:white; 164 | } 165 | 166 | 167 | 账号: 168 | 169 | 170 | 171 | 172 | 173 | 174 | QLabel 175 | { 176 | font-size:20px; 177 | color:white; 178 | } 179 | 180 | 181 | 密码: 182 | 183 | 184 | 185 | 186 | 187 | 188 | QLineEdit 189 | { 190 | color:white; 191 | background-color:transparent; 192 | border-style:solid; 193 | border-color:rgb(200,200,200); 194 | border-top-width:0px; 195 | border-left-width:0px; 196 | border-right-width:0px; 197 | border-bottom-width:1px; 198 | } 199 | QLineEdit:hover 200 | { 201 | color:white; 202 | background-color:transparent; 203 | border-style:solid; 204 | border-color:rgb(170,170,170); 205 | border-top-width:0px; 206 | border-left-width:0px; 207 | border-right-width:0px; 208 | border-bottom-width:1px; 209 | } 210 | QLineEdit:focus 211 | { 212 | color:white; 213 | background-color:transparent; 214 | border-style:solid; 215 | border-color:rgb(0, 170, 255); 216 | border-top-width:0px; 217 | border-left-width:0px; 218 | border-right-width:0px; 219 | border-bottom-width:1px; 220 | } 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | QLineEdit 229 | { 230 | color:white; 231 | background-color:transparent; 232 | border-style:solid; 233 | border-color:rgb(200,200,200); 234 | border-top-width:0px; 235 | border-left-width:0px; 236 | border-right-width:0px; 237 | border-bottom-width:1px; 238 | } 239 | QLineEdit:hover 240 | { 241 | color:white; 242 | background-color:transparent; 243 | border-style:solid; 244 | border-color:rgb(170,170,170); 245 | border-top-width:0px; 246 | border-left-width:0px; 247 | border-right-width:0px; 248 | border-bottom-width:1px; 249 | } 250 | QLineEdit:focus 251 | { 252 | color:white; 253 | background-color:transparent; 254 | border-style:solid; 255 | border-color:rgb(0, 170, 255); 256 | border-top-width:0px; 257 | border-left-width:0px; 258 | border-right-width:0px; 259 | border-bottom-width:1px; 260 | } 261 | 262 | 263 | 264 | QLineEdit::PasswordEchoOnEdit 265 | 266 | 267 | 268 | 269 | 270 | 271 | QLineEdit 272 | { 273 | color:white; 274 | background-color:transparent; 275 | border-style:solid; 276 | border-color:rgb(200,200,200); 277 | border-top-width:0px; 278 | border-left-width:0px; 279 | border-right-width:0px; 280 | border-bottom-width:1px; 281 | } 282 | QLineEdit:hover 283 | { 284 | color:white; 285 | background-color:transparent; 286 | border-style:solid; 287 | border-color:rgb(170,170,170); 288 | border-top-width:0px; 289 | border-left-width:0px; 290 | border-right-width:0px; 291 | border-bottom-width:1px; 292 | } 293 | QLineEdit:focus 294 | { 295 | color:white; 296 | background-color:transparent; 297 | border-style:solid; 298 | border-color:rgb(0, 170, 255); 299 | border-top-width:0px; 300 | border-left-width:0px; 301 | border-right-width:0px; 302 | border-bottom-width:1px; 303 | } 304 | 305 | 306 | 307 | QLineEdit::PasswordEchoOnEdit 308 | 309 | 310 | 311 | 312 | 313 | 314 | QLabel 315 | { 316 | font-size:20px; 317 | color:white; 318 | } 319 | 320 | 321 | 重复密码: 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 0 333 | 40 334 | 335 | 336 | 337 | 338 | 16777215 339 | 40 340 | 341 | 342 | 343 | QPushButton 344 | { 345 | font-size:15px; 346 | color:white; 347 | border-style:none; 348 | border-radius:8px 8px; 349 | background-color:rgb(0, 170, 255); 350 | } 351 | QPushButton:hover 352 | { 353 | font-size:15px; 354 | color:white; 355 | border-style:none; 356 | border-radius:8px 8px; 357 | background-color:rgb(0, 213, 255); 358 | } 359 | QPushButton:pressed 360 | { 361 | font-size:15px; 362 | color:white; 363 | border-style:none; 364 | border-radius:8px 8px; 365 | background-color:rgb(0, 85, 255); 366 | } 367 | 368 | 369 | 注册 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | Qt::Horizontal 379 | 380 | 381 | 382 | 222 383 | 20 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 800 396 | 30 397 | 398 | 399 | 400 | 401 | 800 402 | 30 403 | 404 | 405 | 406 | 407 | 0 408 | 409 | 410 | 0 411 | 412 | 413 | 414 | 415 | Qt::Horizontal 416 | 417 | 418 | 419 | 652 420 | 20 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 75 430 | 20 431 | 432 | 433 | 434 | 435 | 75 436 | 20 437 | 438 | 439 | 440 | QPushButton 441 | { 442 | font-size:15px; 443 | color:white; 444 | border-style:none; 445 | border-radius:8px 8px; 446 | background-color:rgb(0, 170, 255); 447 | } 448 | QPushButton:hover 449 | { 450 | font-size:15px; 451 | color:white; 452 | border-style:none; 453 | border-radius:8px 8px; 454 | background-color:rgb(0, 213, 255); 455 | } 456 | QPushButton:pressed 457 | { 458 | font-size:15px; 459 | color:white; 460 | border-style:none; 461 | border-radius:8px 8px; 462 | background-color:rgb(0, 85, 255); 463 | } 464 | 465 | 466 | 返回 467 | 468 | 469 | 470 | 471 | 472 | 473 | Qt::Horizontal 474 | 475 | 476 | QSizePolicy::Fixed 477 | 478 | 479 | 480 | 40 481 | 20 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | -------------------------------------------------------------------------------- /QQClient/addfriend.cpp: -------------------------------------------------------------------------------- 1 | #include "addfriend.h" 2 | #include "ui_addfriend.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | AddFriend::AddFriend(bool type, QStringList gn, QStringList umsg, QString yanzheng, QWidget* parent) : 9 | QWidget(parent), 10 | ui(new Ui::AddFriend) 11 | { 12 | ui->setupUi(this); 13 | 14 | m_type = type; 15 | 16 | //设置背景透明 17 | setAttribute(Qt::WA_TranslucentBackground); 18 | //设置阴影边框 19 | QGraphicsDropShadowEffect* shadow = new QGraphicsDropShadowEffect(ui->frame); 20 | shadow->setOffset(0, 0); 21 | shadow->setColor(Qt::black); 22 | shadow->setBlurRadius(10); 23 | ui->frame->setGraphicsEffect(shadow); 24 | //安装事件过滤器,处理绘画事件 25 | ui->frame->installEventFilter(this); 26 | update(); 27 | 28 | //true为发送好友申请界面,false为接收界面 29 | if(type == false) 30 | { 31 | //若有验证消息则添加验证消息 32 | if(yanzheng != "") 33 | { 34 | ui->Yanzheng->setPlainText(yanzheng); 35 | } 36 | 37 | //更改按钮文字及提示信息 38 | ui->YanzhengLab->setText("验证信息"); 39 | ui->Yanzheng->setReadOnly(true); 40 | ui->OkBtn->setText("同意"); 41 | ui->NoBtn->setText("拒绝"); 42 | } 43 | 44 | //设置分组名 45 | ui->FriCombo->addItems(gn); 46 | 47 | //设置用户信息 48 | m_acc = umsg.at(enacc).toInt(); 49 | m_nickname = umsg.at(ennickname); 50 | m_sex = umsg.at(ensex); 51 | m_age = umsg.at(enage); 52 | m_location = umsg.at(enlocation); 53 | 54 | //将用户信息显示到界面上 55 | QPixmap pix = Global::CreateHeadShot(Global::AppAllUserPath() + "/" + QString::number(m_acc) + ".jpg"); 56 | setWindowTitle("添加好友 " + m_nickname); 57 | ui->HeadshotLab->setPixmap(pix); 58 | ui->AccLab->setText(QString::number(m_acc)); 59 | ui->NickNameLab->setText(m_nickname); 60 | ui->SexLab->setText("性别:" + m_sex); 61 | ui->AgeLab->setText("年龄:" + m_age); 62 | ui->LocLab->setText("所在地:" + m_location); 63 | 64 | //设置窗口图标 65 | setWindowIcon(QIcon(":/lib/QQ.png")); 66 | //设置窗口无边框 67 | setWindowFlags(Qt::FramelessWindowHint); 68 | //设置退出该窗口时不退出主程序 69 | setAttribute(Qt::WA_QuitOnClose, false); 70 | 71 | //最小化 72 | connect(ui->MiniBtn, &QToolButton::clicked, this, &AddFriend::showMinimized); 73 | //若为收到好友申请则关闭等同忽略该好友申请 74 | connect(ui->CloseBtn, &QToolButton::clicked, this, [ = ]() 75 | { 76 | emit CloseAddFriend(); 77 | }); 78 | } 79 | 80 | AddFriend::~AddFriend() 81 | { 82 | delete ui; 83 | } 84 | 85 | void AddFriend::initShadow() 86 | { 87 | QPainter painter(ui->frame); 88 | painter.fillRect(ui->frame->rect().adjusted(-10, -10, 10, 10), QColor(220, 220, 220)); 89 | } 90 | 91 | bool AddFriend::eventFilter(QObject* w, QEvent* e) 92 | { 93 | if((w == ui->frame) && (e->type() == QEvent::Paint)) 94 | { 95 | initShadow(); 96 | return true; 97 | } 98 | 99 | return QWidget::eventFilter(w, e); 100 | } 101 | 102 | void AddFriend::mousePressEvent(QMouseEvent* e) 103 | { 104 | if(e->button() == Qt::LeftButton) 105 | { 106 | isPressed = true; 107 | m_point = e->globalPos() - pos(); 108 | } 109 | } 110 | 111 | void AddFriend::mouseMoveEvent(QMouseEvent* e) 112 | { 113 | if(e->buttons() & Qt::LeftButton && isPressed) 114 | { 115 | move(e->globalPos() - m_point); 116 | } 117 | } 118 | 119 | void AddFriend::mouseReleaseEvent(QMouseEvent* event) 120 | { 121 | Q_UNUSED(event); 122 | //释放时将bool值恢复false 123 | isPressed = false; 124 | } 125 | 126 | void AddFriend::on_NoBtn_clicked() 127 | { 128 | emit CloseAddFriend(); 129 | } 130 | 131 | void AddFriend::on_OkBtn_clicked() 132 | { 133 | if(ui->OkBtn->text() == "完成") 134 | { 135 | QMessageBox::information(this, "提示", "您的好友添加请求已发送,请等待对方确认"); 136 | emit CloseAddFriend("完成", m_acc, ui->FriCombo->currentText(), ui->Yanzheng->toPlainText()); 137 | } 138 | else 139 | { 140 | emit CloseAddFriend("同意", m_acc, ui->FriCombo->currentText()); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /QQClient/addfriend.h: -------------------------------------------------------------------------------- 1 | #ifndef ADDFRIEND_H 2 | #define ADDFRIEND_H 3 | 4 | #include 5 | #include 6 | #include "global.h" 7 | 8 | namespace Ui 9 | { 10 | class AddFriend; 11 | } 12 | 13 | class AddFriend : public QWidget 14 | { 15 | Q_OBJECT 16 | 17 | public: 18 | explicit AddFriend(bool type, QStringList gn, QStringList umsg, QString yanzheng = "", QWidget* parent = nullptr); 19 | ~AddFriend(); 20 | protected: 21 | void initShadow(); //初始化窗口边框阴影 22 | bool eventFilter(QObject* w, QEvent* e) override; 23 | void mousePressEvent(QMouseEvent* e) override; 24 | void mouseMoveEvent(QMouseEvent* e) override; 25 | void mouseReleaseEvent(QMouseEvent* event) override; 26 | signals: 27 | void CloseAddFriend(QString type = "", int acc = -1, QString GpNa = "", QString yanzheng = ""); //向主窗口发送好友申请操作信息 28 | 29 | private slots: 30 | void on_NoBtn_clicked(); //取消/拒绝 31 | 32 | void on_OkBtn_clicked(); //完成/同意 33 | 34 | private: 35 | Ui::AddFriend* ui; 36 | 37 | //添加好友窗口用户资料编号 38 | enum umsg { enacc = 0, ennickname, ensex, enage, enlocation }; 39 | bool m_type; //true为发送好友申请界面,false为接收界面 40 | bool isPressed = false; //记录是否按下鼠标 41 | QPoint m_point; //记录鼠标点下位置 42 | 43 | /* 想添加的用户信息 */ 44 | int m_acc; //账号 45 | QString m_nickname; //昵称 46 | QString m_sex; //性别 47 | QString m_age; //年龄 48 | QString m_location; //所在地 49 | }; 50 | 51 | #endif // ADDFRIEND_H 52 | -------------------------------------------------------------------------------- /QQClient/changedata.cpp: -------------------------------------------------------------------------------- 1 | #include "changedata.h" 2 | #include "ui_changedata.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | ChangeData::ChangeData(QStringList uD, QWidget* parent) : 9 | QWidget(parent), 10 | ui(new Ui::ChangeData) 11 | { 12 | ui->setupUi(this); 13 | 14 | //初始未更改 15 | isChanged = false; 16 | 17 | //设置窗口无边框 18 | setWindowFlags(Qt::FramelessWindowHint); 19 | 20 | //设置背景透明 21 | setAttribute(Qt::WA_TranslucentBackground); 22 | //设置阴影边框 23 | QGraphicsDropShadowEffect* shadow = new QGraphicsDropShadowEffect(ui->frame); 24 | shadow->setOffset(0, 0); 25 | shadow->setColor(Qt::black); 26 | shadow->setBlurRadius(10); 27 | ui->frame->setGraphicsEffect(shadow); 28 | 29 | //安装事件过滤器,处理绘画事件 30 | ui->frame->installEventFilter(this); 31 | update(); 32 | 33 | //初始化窗口信息 34 | setWindowTitle("修改资料"); 35 | setWindowIcon(QIcon(":/lib/QQ.png")); 36 | //设置退出该窗口时不退出主程序 37 | setAttribute(Qt::WA_QuitOnClose, false); 38 | 39 | //初始化用户资料 40 | ui->nickname->setText(uD.at(Dnickname)); 41 | ui->sex->addItem("男"); 42 | ui->sex->addItem("女"); 43 | ui->sex->setCurrentText(uD.at(Dsex)); 44 | QDate date = QDate::fromString(uD.at(Dbirthday), "yyyy-MM-dd"); 45 | ui->birthday->setDate(date ); 46 | ui->signature->setText(uD.at(Dsignature)); 47 | 48 | ui->location->setText(uD.at(Dlocation)); 49 | ui->work->setText(uD.at(Dwork)); 50 | ui->school->setText(uD.at(Dsch_comp)); 51 | 52 | ui->blood->addItem("A型"); 53 | ui->blood->addItem("B型"); 54 | ui->blood->addItem("O型"); 55 | ui->blood->addItem("AB型"); 56 | ui->blood->addItem("未知"); 57 | ui->blood->setCurrentText(uD.at(Dblood_type)); 58 | 59 | //限制用户输入# 60 | QRegExp rx = QRegExp("[^#]*"); 61 | QRegExpValidator* validator = new QRegExpValidator(rx); 62 | ui->nickname->setValidator(validator); 63 | ui->signature->setValidator(validator); 64 | ui->work->setValidator(validator); 65 | ui->school->setValidator(validator); 66 | ui->location->setValidator(validator); 67 | 68 | connect(ui->MiniBtn, &QToolButton::clicked, this, &ChangeData::showMinimized); 69 | connect(ui->Close, &QToolButton::clicked, this, &ChangeData::CloseEdit); 70 | connect(ui->CloseBtn, &QPushButton::clicked, this, &ChangeData::CloseEdit); 71 | 72 | //若用户改变资料则保存按钮变为可用 73 | connect(ui->nickname, &QLineEdit::textChanged, this, &ChangeData::TextChanged); 74 | connect(ui->sex, &QComboBox::currentTextChanged, this, &ChangeData::TextChanged); 75 | connect(ui->birthday, &QDateEdit::dateChanged, this, &ChangeData::TextChanged); 76 | connect(ui->signature, &QLineEdit::textChanged, this, &ChangeData::TextChanged); 77 | connect(ui->work, &QLineEdit::textChanged, this, &ChangeData::TextChanged); 78 | connect(ui->school, &QLineEdit::textChanged, this, &ChangeData::TextChanged); 79 | connect(ui->blood, &QComboBox::currentTextChanged, this, &ChangeData::TextChanged); 80 | connect(ui->location, &QLineEdit::textChanged, this, &ChangeData::TextChanged); 81 | 82 | //未更改时不能点击保存 83 | ui->Save->setEnabled(false); 84 | } 85 | 86 | ChangeData::~ChangeData() 87 | { 88 | delete ui; 89 | } 90 | 91 | bool ChangeData::eventFilter(QObject* w, QEvent* e) 92 | { 93 | if((w == ui->frame) && (e->type() == QEvent::Paint)) 94 | { 95 | initShadow(); 96 | return true; 97 | } 98 | 99 | return QWidget::eventFilter(w, e); 100 | } 101 | 102 | void ChangeData::mousePressEvent(QMouseEvent* e) 103 | { 104 | if(e->button() == Qt::LeftButton) 105 | { 106 | if(e->pos().y() < 35 || e->pos().y() > 690) 107 | { 108 | isPressed = true; 109 | } 110 | 111 | m_point = e->globalPos() - this->frameGeometry().topLeft(); 112 | } 113 | } 114 | 115 | void ChangeData::mouseMoveEvent(QMouseEvent* e) 116 | { 117 | if(e->buttons() & Qt::LeftButton && isPressed) 118 | { 119 | move(e->globalPos() - m_point); 120 | } 121 | } 122 | 123 | void ChangeData::mouseReleaseEvent(QMouseEvent* event) 124 | { 125 | Q_UNUSED(event); 126 | isPressed = false; 127 | } 128 | 129 | void ChangeData::initShadow() 130 | { 131 | QPainter painter(ui->frame); 132 | painter.fillRect(ui->frame->rect().adjusted(-10, -10, 10, 10), QColor(220, 220, 220)); 133 | } 134 | 135 | void ChangeData::TextChanged() 136 | { 137 | qDebug() << "已更改资料"; 138 | isChanged = true; 139 | ui->Save->setEnabled(true); 140 | 141 | //更改资料后取消绑定 142 | disconnect(ui->nickname, &QLineEdit::textChanged, this, &ChangeData::TextChanged); 143 | disconnect(ui->sex, &QComboBox::currentTextChanged, this, &ChangeData::TextChanged); 144 | disconnect(ui->birthday, &QDateEdit::dateChanged, this, &ChangeData::TextChanged); 145 | disconnect(ui->signature, &QLineEdit::textChanged, this, &ChangeData::TextChanged); 146 | disconnect(ui->work, &QLineEdit::textChanged, this, &ChangeData::TextChanged); 147 | disconnect(ui->school, &QLineEdit::textChanged, this, &ChangeData::TextChanged); 148 | disconnect(ui->blood, &QComboBox::currentTextChanged, this, &ChangeData::TextChanged); 149 | disconnect(ui->location, &QLineEdit::textChanged, this, &ChangeData::TextChanged); 150 | } 151 | 152 | void ChangeData::on_Save_clicked() 153 | { 154 | //输入有效性检查 155 | if(ui->nickname->text().length() > 15) 156 | { 157 | QMessageBox::warning(this, "警告", "昵称不能超过15个字!"); 158 | return; 159 | } 160 | 161 | if(ui->signature->text().length() > 25) 162 | { 163 | QMessageBox::warning(this, "警告", "个性签名不能超过25个字!"); 164 | return; 165 | } 166 | 167 | QString uDatas = ui->nickname->text() + "##"; 168 | uDatas += ui->signature->text() + "##"; 169 | uDatas += ui->sex->currentText() + "##"; 170 | QString birthday = ui->birthday->text(); 171 | //当前年份减去生日年份,计算用户年龄 172 | int age = QDate::currentDate().toString("yyyy/MM/dd").split("/").at(0).toInt() - birthday.split("/").at(0).toInt(); 173 | uDatas += QString::number(age) + "##"; 174 | uDatas += birthday + "##"; 175 | uDatas += ui->location->text() + "##"; 176 | uDatas += ui->blood->currentText() + "##"; 177 | uDatas += ui->work->text() + "##"; 178 | uDatas += ui->school->text(); 179 | 180 | qDebug() << "Datas: " << uDatas; 181 | emit ChangeUserDatas(uDatas); 182 | emit CloseEdit(); 183 | } 184 | -------------------------------------------------------------------------------- /QQClient/changedata.h: -------------------------------------------------------------------------------- 1 | #ifndef CHANGEDATA_H 2 | #define CHANGEDATA_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "global.h" 9 | 10 | namespace Ui 11 | { 12 | class ChangeData; 13 | } 14 | 15 | class ChangeData : public QWidget 16 | { 17 | Q_OBJECT 18 | 19 | public: 20 | explicit ChangeData(QStringList uD, QWidget* parent = nullptr); 21 | ~ChangeData(); 22 | 23 | signals: 24 | void CloseEdit(); //关闭更改窗口 25 | void ChangeUserDatas(QString); //更改用户资料 26 | 27 | protected: 28 | bool eventFilter(QObject* w, QEvent* e) override; 29 | void mousePressEvent(QMouseEvent* e) override; 30 | void mouseMoveEvent(QMouseEvent* e) override; 31 | void mouseReleaseEvent(QMouseEvent* event) override; 32 | void initShadow(); //初始化窗口边框阴影 33 | 34 | private slots: 35 | void TextChanged(); //文本改变 36 | 37 | void on_Save_clicked(); //保存 38 | 39 | private: 40 | Ui::ChangeData* ui; 41 | 42 | QPoint m_point; //记录点击位置 43 | bool isPressed = false; //是否点下 44 | bool isChanged; //是否更改信息 45 | }; 46 | 47 | #endif // CHANGEDATA_H 48 | -------------------------------------------------------------------------------- /QQClient/changedata.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | ChangeData 4 | 5 | 6 | 7 | 0 8 | 0 9 | 380 10 | 750 11 | 12 | 13 | 14 | 15 | 380 16 | 750 17 | 18 | 19 | 20 | 21 | 380 22 | 750 23 | 24 | 25 | 26 | Form 27 | 28 | 29 | 30 | 0 31 | 32 | 33 | 10 34 | 35 | 36 | 10 37 | 38 | 39 | 10 40 | 41 | 42 | 10 43 | 44 | 45 | 46 | 47 | QFrame::StyledPanel 48 | 49 | 50 | QFrame::Raised 51 | 52 | 53 | 54 | 0 55 | 56 | 57 | 0 58 | 59 | 60 | 0 61 | 62 | 63 | 0 64 | 65 | 66 | 0 67 | 68 | 69 | 70 | 71 | 72 | 0 73 | 35 74 | 75 | 76 | 77 | 78 | 16777215 79 | 35 80 | 81 | 82 | 83 | QWidget 84 | { 85 | background-color:rgb(18,183,245); 86 | } 87 | 88 | 89 | 90 | 8 91 | 92 | 93 | 0 94 | 95 | 96 | 0 97 | 98 | 99 | 0 100 | 101 | 102 | 103 | 104 | 105 | 0 106 | 0 107 | 108 | 109 | 110 | 111 | 25 112 | 25 113 | 114 | 115 | 116 | 117 | 25 118 | 25 119 | 120 | 121 | 122 | 123 | 124 | 125 | :/lib/Loge.png 126 | 127 | 128 | true 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 65 137 | 25 138 | 139 | 140 | 141 | 142 | 65 143 | 25 144 | 145 | 146 | 147 | color:white; 148 | 149 | 150 | 编辑资料 151 | 152 | 153 | 154 | 155 | 156 | 157 | Qt::Horizontal 158 | 159 | 160 | 161 | 165 162 | 20 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 35 172 | 35 173 | 174 | 175 | 176 | 177 | 35 178 | 35 179 | 180 | 181 | 182 | Qt::ClickFocus 183 | 184 | 185 | 最小化 186 | 187 | 188 | -1 189 | 190 | 191 | QToolButton:hover 192 | { 193 | background-color:rgba(200, 200, 200,85); 194 | } 195 | QToolButton:pressed 196 | { 197 | background-color:rgba(220,220,220,85); 198 | } 199 | 200 | 201 | 202 | ... 203 | 204 | 205 | 206 | :/lib/zuixiaohua.png:/lib/zuixiaohua.png 207 | 208 | 209 | true 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 35 218 | 35 219 | 220 | 221 | 222 | 223 | 35 224 | 35 225 | 226 | 227 | 228 | Qt::ClickFocus 229 | 230 | 231 | 关闭 232 | 233 | 234 | QToolButton:hover 235 | { 236 | background-color:rgb(255, 0, 0); 237 | } 238 | QToolButton:pressed 239 | { 240 | background-color:rgb(170, 0, 0); 241 | } 242 | 243 | 244 | 245 | ... 246 | 247 | 248 | 249 | :/lib/guanbi.png:/lib/guanbi.png 250 | 251 | 252 | true 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | QGroupBox 263 | { 264 | font-size:20px; 265 | font-weight:bold; 266 | background-color:white; 267 | } 268 | 269 | 270 | 基础信息 271 | 272 | 273 | 274 | 275 | 276 | QLabel 277 | { 278 | color:rgb(127,127,127); 279 | } 280 | 281 | 282 | 昵 称: 283 | 284 | 285 | 286 | 287 | 288 | 289 | QLineEdit 290 | { 291 | border-color:rgb(127,127,127); 292 | border-style:solid; 293 | border-width:1px; 294 | } 295 | QLineEdit:hover 296 | { 297 | border-color:rgb(18,183,245); 298 | border-style:solid; 299 | border-width:1px; 300 | } 301 | QLineEdit:press 302 | { 303 | border-color:rgb(0, 255, 255); 304 | border-style:solid; 305 | border-width:1px; 306 | } 307 | 308 | 309 | 310 | 311 | 312 | 313 | QLabel 314 | { 315 | color:rgb(127,127,127); 316 | } 317 | 318 | 319 | 性 别: 320 | 321 | 322 | 323 | 324 | 325 | 326 | QComboBox 327 | { 328 | background-color:white; 329 | border-style:solid; 330 | border-width:1px; 331 | border-color:rgb(127,127,127); 332 | } 333 | QComboBox:hover 334 | { 335 | background-color:white; 336 | border-style:solid; 337 | border-width:1px; 338 | border-color:rgb(18,183,245); 339 | } 340 | QComboBox:press 341 | { 342 | background-color:white; 343 | border-style:solid; 344 | border-width:1px; 345 | border-color:rgb(0,255,255); 346 | } 347 | 348 | 349 | 350 | 351 | 352 | 353 | QLabel 354 | { 355 | color:rgb(127,127,127); 356 | } 357 | 358 | 359 | 生 日: 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | QLabel 370 | { 371 | color:rgb(127,127,127); 372 | } 373 | 374 | 375 | 签 名: 376 | 377 | 378 | 379 | 380 | 381 | 382 | QLineEdit 383 | { 384 | border-color:rgb(127,127,127); 385 | border-style:solid; 386 | border-width:1px; 387 | } 388 | QLineEdit:hover 389 | { 390 | border-color:rgb(18,183,245); 391 | border-style:solid; 392 | border-width:1px; 393 | } 394 | QLineEdit:press 395 | { 396 | border-color:rgb(0, 255, 255); 397 | border-style:solid; 398 | border-width:1px; 399 | } 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | QGroupBox 410 | { 411 | font-size:20px; 412 | font-weight:bold; 413 | background-color:white; 414 | } 415 | 416 | 417 | 更多资料 418 | 419 | 420 | 421 | 422 | 423 | QLabel 424 | { 425 | color:rgb(127,127,127); 426 | } 427 | 428 | 429 | 职 业: 430 | 431 | 432 | 433 | 434 | 435 | 436 | QLineEdit 437 | { 438 | border-color:rgb(127,127,127); 439 | border-style:solid; 440 | border-width:1px; 441 | } 442 | QLineEdit:hover 443 | { 444 | border-color:rgb(18,183,245); 445 | border-style:solid; 446 | border-width:1px; 447 | } 448 | QLineEdit:press 449 | { 450 | border-color:rgb(0, 255, 255); 451 | border-style:solid; 452 | border-width:1px; 453 | } 454 | 455 | 456 | 457 | 458 | 459 | 460 | QLabel 461 | { 462 | color:rgb(127,127,127); 463 | } 464 | 465 | 466 | 公司/学校: 467 | 468 | 469 | 470 | 471 | 472 | 473 | QLineEdit 474 | { 475 | border-color:rgb(127,127,127); 476 | border-style:solid; 477 | border-width:1px; 478 | } 479 | QLineEdit:hover 480 | { 481 | border-color:rgb(18,183,245); 482 | border-style:solid; 483 | border-width:1px; 484 | } 485 | QLineEdit:press 486 | { 487 | border-color:rgb(0, 255, 255); 488 | border-style:solid; 489 | border-width:1px; 490 | } 491 | 492 | 493 | 494 | 495 | 496 | 497 | QLabel 498 | { 499 | color:rgb(127,127,127); 500 | } 501 | 502 | 503 | 血 型: 504 | 505 | 506 | 507 | 508 | 509 | 510 | QComboBox 511 | { 512 | background-color:white; 513 | border-style:solid; 514 | border-width:1px; 515 | border-color:rgb(127,127,127); 516 | } 517 | QComboBox:hover 518 | { 519 | background-color:white; 520 | border-style:solid; 521 | border-width:1px; 522 | border-color:rgb(18,183,245); 523 | } 524 | QComboBox:press 525 | { 526 | background-color:white; 527 | border-style:solid; 528 | border-width:1px; 529 | border-color:rgb(0,255,255); 530 | } 531 | 532 | 533 | 534 | 535 | 536 | 537 | QLabel 538 | { 539 | color:rgb(127,127,127); 540 | } 541 | 542 | 543 | 所 在 地: 544 | 545 | 546 | 547 | 548 | 549 | 550 | QLineEdit 551 | { 552 | border-color:rgb(127,127,127); 553 | border-style:solid; 554 | border-width:1px; 555 | } 556 | QLineEdit:hover 557 | { 558 | border-color:rgb(18,183,245); 559 | border-style:solid; 560 | border-width:1px; 561 | } 562 | QLineEdit:press 563 | { 564 | border-color:rgb(0, 255, 255); 565 | border-style:solid; 566 | border-width:1px; 567 | } 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 0 579 | 50 580 | 581 | 582 | 583 | 584 | 16777215 585 | 50 586 | 587 | 588 | 589 | QWidget 590 | { 591 | background-color:rgb(221,221,221); 592 | } 593 | 594 | 595 | 596 | 0 597 | 598 | 599 | 0 600 | 601 | 602 | 15 603 | 604 | 605 | 0 606 | 607 | 608 | 609 | 610 | Qt::Horizontal 611 | 612 | 613 | 614 | 200 615 | 20 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 0 625 | 0 626 | 627 | 628 | 629 | 630 | 65 631 | 20 632 | 633 | 634 | 635 | 636 | 65 637 | 20 638 | 639 | 640 | 641 | Qt::ClickFocus 642 | 643 | 644 | QPushButton 645 | { 646 | background-color:rgb(235,242,250); 647 | border-style:solid; 648 | border-width:1px; 649 | border-color:rgb(0, 170, 255); 650 | } 651 | QPushButton:hover 652 | { 653 | background-color:rgba(0, 170, 255,50); 654 | border-style:solid; 655 | border-width:1px; 656 | border-color:rgb(116, 186, 220); 657 | } 658 | QPushButton:press 659 | { 660 | background-color:rgb(235,242,250); 661 | border-style:solid; 662 | border-width:1px; 663 | border-color:rgb(0, 170, 255); 664 | } 665 | 666 | 667 | 保存 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 65 676 | 20 677 | 678 | 679 | 680 | 681 | 65 682 | 20 683 | 684 | 685 | 686 | Qt::ClickFocus 687 | 688 | 689 | QPushButton 690 | { 691 | background-color:rgb(235,242,250); 692 | border-style:solid; 693 | border-width:1px; 694 | border-color:rgb(0, 170, 255); 695 | } 696 | QPushButton:hover 697 | { 698 | background-color:rgba(0, 170, 255,50); 699 | border-style:solid; 700 | border-width:1px; 701 | border-color:rgb(116, 186, 220); 702 | } 703 | QPushButton:press 704 | { 705 | background-color:rgb(235,242,250); 706 | border-style:solid; 707 | border-width:1px; 708 | border-color:rgb(0, 170, 255); 709 | } 710 | 711 | 712 | 关闭 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | -------------------------------------------------------------------------------- /QQClient/chatwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef CHATWINDOW_H 2 | #define CHATWINDOW_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "global.h" 11 | #include 12 | #include 13 | #include 14 | 15 | //信息类型 16 | enum MsgType { itsTime = 500, itsMsg, itsPicture, itsFile, RecvFile }; 17 | 18 | //记录鼠标位置 19 | enum Location 20 | { 21 | Top_Left, 22 | Top, 23 | Top_Right, 24 | Right, 25 | Bottom_Right, 26 | Bottom, 27 | Bottom_Left, 28 | Left, 29 | Center 30 | }; 31 | 32 | namespace Ui 33 | { 34 | class ChatWindow; 35 | } 36 | 37 | class ChatWindow : public QWidget 38 | { 39 | Q_OBJECT 40 | 41 | public: 42 | explicit ChatWindow(int targetAcc, QString nickN, QWidget* parent = nullptr); 43 | ~ChatWindow(); 44 | public slots: 45 | void FriendSendMsg(bool isMe, MsgType MsgType, QString Msg); //添加消息进消息框中 46 | void GetProgressInfo(int friAcc, QString fileName, qint64 speed, int value, QTime RestTime, bool isMe); //接收文件收发进度信息 47 | void SendOrRecvFile(bool isMe, QString fileName); //发送或接收文件 48 | protected: 49 | void initShadow(); //初始化窗口边框阴影 50 | bool eventFilter(QObject* w, QEvent* e) override; 51 | void paintEvent(QPaintEvent* event) override; 52 | void mousePressEvent(QMouseEvent* e) override; 53 | void mouseMoveEvent(QMouseEvent* e) override; 54 | void mouseReleaseEvent(QMouseEvent* event) override; 55 | void mouseDoubleClickEvent(QMouseEvent* event) override; 56 | QWidget* CreateWidget(bool isMe, MsgType MsgType, QString Msg); //创建自定义item 57 | int returnItemHeight(MsgType MsgType, int wLgh = -1, int heightPic = -1); //返回item高度 /* 若传入图片则wLgh为宽,heightPic为高,文字wLgh为文字长度 */ 58 | void ChangeCurSor(const QPoint& p); //更改鼠标样式 59 | QString GetFileSize(const qint64& size); //获取文件大小字符串 60 | QPixmap JudgeFileBack(QString fileName); //判断文件后缀,返回对应的文件类型图片 61 | void ChangefileListSta(bool isRecvOrSend); //文件接收栏状态,true为正在接收 62 | signals: 63 | void SendMsgToFri(int, MsgType, QString); //向朋友发送信息 64 | 65 | private slots: 66 | void on_SendBtn_clicked(); //发送文字 67 | 68 | void on_BigBtn_clicked(); //最大化 69 | 70 | void on_PicBtn_clicked(); //发送图片 71 | 72 | void on_FileBtn_clicked(); //发送文件 73 | 74 | private: 75 | Ui::ChatWindow* ui; 76 | bool isPressed = false; //记录鼠标是否按下 77 | QPoint m_point; //记录鼠标点下位置 78 | Location m_loc; //记录鼠标当前位置 79 | 80 | //全局路径,防止鼠标移动事件穿透 81 | QPixmap m_pixmap; 82 | QPainterPath m_globalPath; 83 | 84 | bool isMaxed = false; //是最大化状态,默认不是 85 | 86 | int m_targetAcc; //好友账号 87 | QString m_nickname; //好友昵称 88 | QString m_FriHeadShot; //好友的头像地址 89 | 90 | QDateTime LastMsgTime = QDateTime::fromString("2023-11-25 18:00:00", "yyyy-MM-dd hh:mm:ss"); //最后一次发送消息的时间,初始化为较早值,这样第一次消息便直接输出时间 91 | 92 | QMap m_fileItems; //保存文件收发界面item 93 | QMap m_progressBars; //保存进度条 94 | QMap m_speedLab; //保存速度label 95 | QMap m_resttimeLab; //保存剩余时间label 96 | }; 97 | 98 | #endif // CHATWINDOW_H 99 | -------------------------------------------------------------------------------- /QQClient/findfriends.cpp: -------------------------------------------------------------------------------- 1 | #include "findfriends.h" 2 | #include "ui_findfriends.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | FindFriends* FindFriends::m_Fri = nullptr; 9 | QMutex FindFriends::m_mutex; 10 | 11 | FindFriends::FindFriends(QWidget* parent) : 12 | QWidget(parent), 13 | ui(new Ui::FindFriends) 14 | { 15 | ui->setupUi(this); 16 | ui->checkBox->setCheckable(false); 17 | 18 | setWindowTitle("查找好友"); 19 | //设置背景透明 20 | setAttribute(Qt::WA_TranslucentBackground); 21 | //设置阴影边框 22 | QGraphicsDropShadowEffect* shadow = new QGraphicsDropShadowEffect(ui->frame); 23 | shadow->setOffset(0, 0); 24 | shadow->setColor(Qt::black); 25 | shadow->setBlurRadius(10); 26 | ui->frame->setGraphicsEffect(shadow); 27 | 28 | ui->frame->installEventFilter(this); 29 | update(); 30 | 31 | //设置消息提示框样式 32 | MsgBox.setWindowIcon(QIcon(":/lib/QQ.png")); 33 | MsgBox.setIcon(QMessageBox::Information); 34 | MsgBox.setWindowTitle("提示"); 35 | MsgBox.setStyleSheet("QMessageBox{background-color:rgb(235,242,249);}" 36 | "QMessageBox QPushButton{background-color:rgb(235,242,250);border-style:solid;border-width:1px;border-color:rgb(0, 170, 255);min-width:70px;min-height:20px;}" 37 | "QMessageBox QPushButton:hover{background-color:rgb(0, 170, 255);border-style:solid;border-width:1px;border-color:rgb(116, 186, 220);}" 38 | "QPushButton:press{background-color:rgb(235,242,250);border-style:solid;border-width:1px;border-color:rgb(0, 170, 255);}"); 39 | 40 | //设置窗口无边框 41 | setWindowFlags(Qt::FramelessWindowHint); 42 | 43 | //设置窗口图标 44 | setWindowIcon(QIcon(":/lib/QQ.png")); 45 | 46 | //查找文本栏只能输入数字 47 | ui->lineEdit->setValidator(new QRegExpValidator(QRegExp("[0-9]+$"))); 48 | 49 | connect(ui->MiniBtn, &QToolButton::clicked, this, &FindFriends::showMinimized); 50 | connect(ui->CloseBtn, &QToolButton::clicked, this, &FindFriends::hide); 51 | } 52 | 53 | FindFriends* FindFriends::createFindFriends() 54 | { 55 | if(m_Fri == nullptr) 56 | { 57 | m_mutex.lock(); 58 | 59 | if(m_Fri == nullptr) 60 | { 61 | m_Fri = new FindFriends; 62 | } 63 | 64 | m_mutex.unlock(); 65 | } 66 | 67 | return m_Fri; 68 | } 69 | 70 | FindFriends::~FindFriends() 71 | { 72 | delete ui; 73 | } 74 | 75 | void FindFriends::initShadow() 76 | { 77 | QPainter painter(ui->frame); 78 | painter.fillRect(ui->frame->rect().adjusted(-10, -10, 10, 10), QColor(220, 220, 220)); 79 | } 80 | 81 | bool FindFriends::eventFilter(QObject* w, QEvent* e) 82 | { 83 | if((w == ui->frame) && (e->type() == QEvent::Paint)) 84 | { 85 | initShadow(); 86 | return true; 87 | } 88 | 89 | return QWidget::eventFilter(w, e); 90 | } 91 | 92 | void FindFriends::mousePressEvent(QMouseEvent* e) 93 | { 94 | if(e->button() == Qt::LeftButton) 95 | { 96 | isPressed = true; 97 | m_point = e->globalPos() - pos(); 98 | } 99 | } 100 | 101 | void FindFriends::mouseMoveEvent(QMouseEvent* e) 102 | { 103 | if(e->buttons() & Qt::LeftButton && isPressed) 104 | { 105 | move(e->globalPos() - m_point); 106 | } 107 | } 108 | 109 | void FindFriends::mouseReleaseEvent(QMouseEvent* event) 110 | { 111 | //释放时将bool值恢复false 112 | isPressed = false; 113 | event->accept(); 114 | } 115 | 116 | void FindFriends::closeEvent(QCloseEvent* event) 117 | { 118 | //关闭事件改为隐藏此窗口 119 | hide(); 120 | event->ignore(); 121 | } 122 | 123 | void FindFriends::GetReply(bool type) 124 | { 125 | //返回正确为已存在该好友,返回错误为该账号未在线或不存在该账号 126 | if(type) 127 | { 128 | MsgBox.setText("您已添加该好友,不能重复添加!"); 129 | MsgBox.exec(); 130 | return; 131 | } 132 | else 133 | { 134 | MsgBox.setText("该账号未上线或不存在该账号,请确认后重新输入!"); 135 | MsgBox.exec(); 136 | return; 137 | } 138 | } 139 | 140 | void FindFriends::on_MiniBtn_clicked() 141 | { 142 | this->hide(); 143 | } 144 | 145 | void FindFriends::on_pushButton_clicked() 146 | { 147 | //查看是否输入账号 148 | QString text = ui->lineEdit->text(); 149 | 150 | if(text == "") 151 | { 152 | MsgBox.setText("未输入账号,请输入账号后再搜索"); 153 | MsgBox.exec(); 154 | return; 155 | } 156 | 157 | emit SearchingAcc(text); 158 | ui->lineEdit->clear(); 159 | } 160 | -------------------------------------------------------------------------------- /QQClient/findfriends.h: -------------------------------------------------------------------------------- 1 | #ifndef FINDFRIENDS_H 2 | #define FINDFRIENDS_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace Ui 11 | { 12 | class FindFriends; 13 | } 14 | 15 | class FindFriends : public QWidget 16 | { 17 | Q_OBJECT 18 | 19 | public: 20 | static FindFriends* createFindFriends(); //单例模式 21 | ~FindFriends(); 22 | FindFriends(FindFriends&) = delete; //单例模式(删除拷贝构造和赋值构造) 23 | FindFriends& operator=(const FindFriends&) = delete; 24 | protected: 25 | void initShadow(); //初始化窗口边框阴影 26 | bool eventFilter(QObject* w, QEvent* e) override; 27 | void mousePressEvent(QMouseEvent* e) override; 28 | void mouseMoveEvent(QMouseEvent* e) override; 29 | void mouseReleaseEvent(QMouseEvent* event) override; 30 | void closeEvent(QCloseEvent* event) override; 31 | 32 | public slots: 33 | void GetReply(bool type); //返回搜索结果 34 | 35 | signals: 36 | void SearchingAcc(QString acc); //发送要查找的好友账号 37 | 38 | private slots: 39 | void on_MiniBtn_clicked(); //最小化 40 | 41 | void on_pushButton_clicked(); //搜索账号 42 | 43 | private: 44 | Ui::FindFriends* ui; 45 | explicit FindFriends(QWidget* parent = nullptr); //单例模式 46 | static FindFriends* m_Fri; //单例模式 47 | static QMutex m_mutex; //锁 48 | bool isPressed = false; //记录是否点下 49 | QPoint m_point; //记录鼠标点下位置 50 | QMessageBox MsgBox; //消息提示框 51 | }; 52 | 53 | #endif // FINDFRIENDS_H 54 | -------------------------------------------------------------------------------- /QQClient/findfriends.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | FindFriends 4 | 5 | 6 | 7 | 0 8 | 0 9 | 800 10 | 500 11 | 12 | 13 | 14 | 15 | 800 16 | 500 17 | 18 | 19 | 20 | 21 | 800 22 | 500 23 | 24 | 25 | 26 | Form 27 | 28 | 29 | 30 | 0 31 | 32 | 33 | 10 34 | 35 | 36 | 10 37 | 38 | 39 | 10 40 | 41 | 42 | 10 43 | 44 | 45 | 46 | 47 | QFrame::StyledPanel 48 | 49 | 50 | QFrame::Raised 51 | 52 | 53 | 54 | 0 55 | 56 | 57 | 0 58 | 59 | 60 | 0 61 | 62 | 63 | 0 64 | 65 | 66 | 0 67 | 68 | 69 | 70 | 71 | .QWidget 72 | { 73 | background-color:rgb(0,155,219); 74 | } 75 | 76 | 77 | 78 | 0 79 | 80 | 81 | 0 82 | 83 | 84 | 0 85 | 86 | 87 | 0 88 | 89 | 90 | 91 | 92 | 93 | 20 94 | 20 95 | 96 | 97 | 98 | 99 | 20 100 | 20 101 | 102 | 103 | 104 | 105 | 106 | 107 | :/lib/Loge.png 108 | 109 | 110 | true 111 | 112 | 113 | 114 | 115 | 116 | 117 | QLabel 118 | { 119 | color:white; 120 | font-size:15px; 121 | } 122 | 123 | 124 | 查找 125 | 126 | 127 | 128 | 129 | 130 | 131 | Qt::Horizontal 132 | 133 | 134 | 135 | 40 136 | 20 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 30 146 | 30 147 | 148 | 149 | 150 | 151 | 30 152 | 30 153 | 154 | 155 | 156 | Qt::ClickFocus 157 | 158 | 159 | 160 | 161 | 162 | -1 163 | 164 | 165 | QToolButton:hover 166 | { 167 | background-color:rgba(200, 200, 200,85); 168 | } 169 | QToolButton:pressed 170 | { 171 | background-color:rgba(220,220,220,85); 172 | } 173 | 174 | 175 | 176 | ... 177 | 178 | 179 | 180 | :/lib/zuixiaohua.png:/lib/zuixiaohua.png 181 | 182 | 183 | true 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 30 192 | 30 193 | 194 | 195 | 196 | 197 | 30 198 | 30 199 | 200 | 201 | 202 | Qt::ClickFocus 203 | 204 | 205 | 206 | 207 | 208 | QToolButton:hover 209 | { 210 | background-color:rgb(255, 0, 0); 211 | } 212 | QToolButton:pressed 213 | { 214 | background-color:rgb(170, 0, 0); 215 | } 216 | 217 | 218 | 219 | ... 220 | 221 | 222 | 223 | :/lib/guanbi.png:/lib/guanbi.png 224 | 225 | 226 | true 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | QTabBar::tab 237 | { 238 | font:20px; 239 | width:389px; 240 | height:30px; 241 | border-style:solid; 242 | border-top-width:0px; 243 | border-left-width:0px; 244 | border-right-width:0px; 245 | border-bottom-width:1px; 246 | border-color:rgb(200,200,200); 247 | background-color:rgb(0,155,219); 248 | } 249 | QTabBar::tab:selected 250 | { 251 | border-bottom-width:5px; 252 | border-color:blue; 253 | } 254 | QWidget 255 | { 256 | background-color:rgb(235,242,249); 257 | } 258 | 259 | 260 | 0 261 | 262 | 263 | 264 | 找人 265 | 266 | 267 | 268 | 269 | 270 | 271 | 0 272 | 80 273 | 274 | 275 | 276 | 277 | 16777215 278 | 80 279 | 280 | 281 | 282 | 283 | 284 | 285 | Qt::Horizontal 286 | 287 | 288 | QSizePolicy::Fixed 289 | 290 | 291 | 292 | 10 293 | 20 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 350 303 | 25 304 | 305 | 306 | 307 | 308 | 350 309 | 25 310 | 311 | 312 | 313 | QLineEdit 314 | { 315 | background-color:white; 316 | border-style:solid; 317 | border-width:1px; 318 | border-color:rgb(0, 170, 255); 319 | } 320 | 321 | 322 | 请输入要添加的账号 323 | 324 | 325 | 326 | 327 | 328 | 329 | 在线 330 | 331 | 332 | true 333 | 334 | 335 | true 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 110 344 | 40 345 | 346 | 347 | 348 | 349 | 110 350 | 40 351 | 352 | 353 | 354 | QPushButton 355 | { 356 | font-size:18px; 357 | color:white; 358 | border-style:none; 359 | border-radius:8px 8px; 360 | background-color:rgb(0, 170, 255); 361 | } 362 | QPushButton:hover 363 | { 364 | font-size:18px; 365 | color:white; 366 | border-style:none; 367 | border-radius:8px 8px; 368 | background-color:rgb(0, 213, 255); 369 | } 370 | QPushButton:pressed 371 | { 372 | font-size:18px; 373 | color:white; 374 | border-style:none; 375 | border-radius:8px 8px; 376 | background-color:rgb(0, 85, 255); 377 | } 378 | 379 | 380 | 查找 381 | 382 | 383 | 384 | 385 | 386 | 387 | Qt::Horizontal 388 | 389 | 390 | 391 | 211 392 | 20 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 找群 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | -------------------------------------------------------------------------------- /QQClient/global.cpp: -------------------------------------------------------------------------------- 1 | #include "global.h" 2 | #include 3 | #include 4 | #include 5 | 6 | bool Global::isFirstLogin = false; 7 | bool Global::isRemember = false; 8 | bool Global::isConnecting = false; 9 | QString Global::WorkPath = ""; 10 | QString Global::AllUserPath = ""; 11 | 12 | QString Global::LoginUserPath = ""; 13 | QString Global::LoginUserFileRecvPath = ""; 14 | 15 | int Global::m_UserAccount = -1; 16 | QString Global::m_UserPwd = ""; 17 | QString Global::m_UserHeadShot = ""; 18 | QString Global::m_nickName = ""; 19 | QString Global::m_signature = ""; 20 | QString Global::scrollbarStyle = "QScrollBar{width:8px;background:white;margin:0px,0px,0px,0px;}" 21 | "QScrollBar::handle{width:8px;background:rgb(235,235,235);border-radius:4px;height:20px;}" 22 | "QScrollBar::handle:hover{width:8px;background:rgb(139,139,139);border-radius:4px;height:20px;}"; 23 | 24 | void Global::GetWorkPath(QString path) 25 | { 26 | WorkPath = path; 27 | AllUserPath = path + "/allusers"; 28 | } 29 | 30 | QString Global::AppWorkPath() 31 | { 32 | return WorkPath; 33 | } 34 | 35 | QString Global::AppAllUserPath() 36 | { 37 | return AllUserPath; 38 | } 39 | 40 | bool Global::CreateWorkPath() 41 | { 42 | QDir dir; 43 | 44 | if(!dir.exists(WorkPath)) 45 | { 46 | qDebug() << "未创建用户数据文件夹"; 47 | 48 | if(!dir.mkdir(WorkPath)) 49 | { 50 | qDebug() << "创建用户数据文件夹失败"; 51 | return false; 52 | } 53 | } 54 | 55 | if(!dir.exists(AllUserPath)) 56 | { 57 | qDebug() << "allusers数据文件夹未创建"; 58 | 59 | if(!dir.mkdir(AllUserPath)) 60 | { 61 | qDebug() << "allusers数据文件夹创建失败!"; 62 | return false; 63 | } 64 | } 65 | 66 | return true; 67 | } 68 | 69 | void Global::InitLoginUserInfo(int account, QString pwd) 70 | { 71 | LoginUserPath = WorkPath + "/" + QString::number(account); 72 | LoginUserFileRecvPath = LoginUserPath + "/FileRecv/"; 73 | 74 | m_UserAccount = account; 75 | m_UserPwd = pwd; 76 | m_UserHeadShot = LoginUserPath + "/" + QString::number(account) + ".jpg"; 77 | } 78 | 79 | void Global::InitUserNameAndSig(QString name, QString sig) 80 | { 81 | m_nickName = name; 82 | m_signature = sig; 83 | } 84 | 85 | QString Global::UserPath() 86 | { 87 | return LoginUserPath; 88 | } 89 | 90 | QString Global::UserLoginFile() 91 | { 92 | return LoginUserPath + "/login.txt"; 93 | } 94 | 95 | QString Global::UserHeadShot() 96 | { 97 | return m_UserHeadShot; 98 | } 99 | 100 | QString Global::UserFileRecvPath() 101 | { 102 | return LoginUserFileRecvPath; 103 | } 104 | 105 | int Global::UserAccount() 106 | { 107 | return m_UserAccount; 108 | } 109 | 110 | QString Global::UserPwd() 111 | { 112 | return m_UserPwd; 113 | } 114 | 115 | QString Global::UserNickName() 116 | { 117 | return m_nickName; 118 | } 119 | 120 | QString Global::UserSignature() 121 | { 122 | return m_signature; 123 | } 124 | 125 | QPixmap Global::CreateHeadShot(QString picPath) 126 | { 127 | QPixmap src(picPath); 128 | QPixmap pix(src.width(), src.height()); 129 | 130 | //设置图片透明 131 | pix.fill(Qt::transparent); 132 | 133 | QPainter painter(&pix); 134 | //设置图片边缘抗锯齿,指示引擎应使用平滑像素图变换算法绘制图片 135 | painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); 136 | 137 | QPainterPath path; 138 | //设置圆形半径,取图片较小边长作为裁切半径 139 | int radius = src.width() > src.height() ? src.height() / 2 : src.width() / 2; 140 | //绘制裁切区域的大小 141 | path.addEllipse(src.rect().center(), radius, radius); 142 | //设置裁切区域 143 | painter.setClipPath(path); 144 | //把源图片的内容绘制到创建的pixmap上,非裁切区域内容不显示 145 | painter.drawPixmap(pix.rect(), src); 146 | 147 | return pix; 148 | } 149 | 150 | QString Global::IsFileExist(QString filepath) 151 | { 152 | QFileInfo info; 153 | 154 | if(!info.exists(filepath)) //不存在直接返回 155 | { 156 | return filepath; 157 | } 158 | else 159 | { 160 | int num = 1; 161 | //存在添加后缀 162 | QString fileName = filepath.split(".").first(); //文件名 163 | QString fileBack = filepath.split(".").last(); //文件后缀名 164 | 165 | while(true) 166 | { 167 | QString FileP = fileName + "(" + QString::number(num) + ")" + "." + fileBack; 168 | 169 | if(!info.exists(FileP)) 170 | { 171 | qDebug() << "接收重复文件,文件名为: " << FileP; 172 | return FileP; 173 | } 174 | 175 | num++; 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /QQClient/global.h: -------------------------------------------------------------------------------- 1 | #ifndef GLOBAL_H 2 | #define GLOBAL_H 3 | 4 | #include 5 | #include 6 | 7 | #define BufferSize 1024 8 | #define HeadSize 10 //包头大小 9 | #define NoHeadBufSize 1014 //去掉包头数据包剩余大小 10 | 11 | //包头类型 12 | enum RecvType 13 | { 14 | JsonDataHead = 50, 15 | FileInfoHead, 16 | FileDataHead, 17 | FileEndDataHead 18 | }; 19 | 20 | //Json数据中信息类型 21 | enum InforType 22 | { 23 | Registration = 1125, 24 | FindPwd, 25 | LoginAcc, 26 | SearchFri, 27 | AddFri, 28 | ChangeOnlSta, 29 | SendMsg, 30 | AskForData, 31 | UserChangeData, 32 | UpdateHeadShot, 33 | SendFileToFri 34 | }; 35 | 36 | //保存用户资料编号 37 | enum UserMsg 38 | { 39 | Dnickname = 0, 40 | Dsignature, 41 | Dsex, 42 | Dage, 43 | Dbirthday, 44 | Dlocation, 45 | Dblood_type, 46 | Dwork, 47 | Dsch_comp 48 | }; 49 | 50 | class Global 51 | { 52 | public: 53 | /* 用户数据文件夹目录 */ 54 | static void GetWorkPath(QString path); //获取工作目录用户数据文件夹 55 | static QString AppWorkPath(); //返回工作目录 56 | static QString AppAllUserPath(); //返回好友头像目录 57 | static bool CreateWorkPath(); //创建工作目录用户数据文件夹 58 | 59 | //设置用户信息 60 | static void InitLoginUserInfo(int account, QString pwd); //设置用户数据 61 | static void InitUserNameAndSig(QString name, QString sig); //设置用户名和个签 62 | 63 | /* 登录用户数据文件夹 */ 64 | static QString UserPath(); //返回登录用户文件夹位置 65 | static QString UserLoginFile(); //返回用户登录初始化文件 66 | static QString UserFileRecvPath(); //返回登录用户文件接收文件夹位置 67 | static QString UserHeadShot(); //登录用户头像位置 68 | 69 | /* 登录用户信息 */ 70 | static int UserAccount(); //返回登录用户账号 71 | static QString UserPwd(); //返回登录用户密码 72 | static QString UserNickName(); //返回用户昵称 73 | static QString UserSignature(); //返回用户个签 74 | 75 | static QPixmap CreateHeadShot(QString picPath); //返回无锯齿圆形头像 76 | static QString IsFileExist(QString filepath); //查看文件是否存在,不存在直接返回,存在则添加后缀 77 | 78 | static QString scrollbarStyle; 79 | 80 | static bool isFirstLogin; //是否是第一次登录 81 | static bool isRemember; //是否记住密码 82 | static bool isConnecting; //是否已连接到服务器 83 | private: 84 | static QString WorkPath; //工作目录下用户数据文件夹 85 | static QString AllUserPath; //好友头像文件夹 86 | 87 | static QString LoginUserPath; //登录账号用户文件夹 88 | static QString LoginUserFileRecvPath; //登录账号用户文件存放文件夹 89 | 90 | static int m_UserAccount; //登录账号 91 | static QString m_UserPwd; //登录密码 92 | static QString m_UserHeadShot; //登录账号头像 93 | static QString m_nickName; //昵称 94 | static QString m_signature; //个性签名 95 | }; 96 | 97 | #endif // GLOBAL_H 98 | -------------------------------------------------------------------------------- /QQClient/lib.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | lib/Greyzuixiaohua.png 4 | lib/Greyguanbi.png 5 | lib/backg.jpeg 6 | lib/gengduo.png 7 | lib/xingbie.png 8 | lib/nicheng.png 9 | lib/zhanghao.png 10 | lib/BigToNormal.png 11 | lib/cut.png 12 | lib/Big.png 13 | lib/picIcon.png 14 | lib/fileIcon.png 15 | lib/biaoqing.png 16 | lib/Chat.png 17 | lib/delete.png 18 | lib/FLeftArrow.png 19 | lib/FDownArrow.png 20 | lib/search.png 21 | lib/close.png 22 | lib/addfriend.png 23 | lib/quit.png 24 | lib/MainMenu.png 25 | lib/add.png 26 | lib/tishi.png 27 | lib/gantanhao.png 28 | lib/xiajiantou.png 29 | lib/shangjiantou.png 30 | lib/default.jpg 31 | lib/guanbi2.png 32 | lib/Keyboard.png 33 | lib/QQ.png 34 | lib/System.png 35 | lib/find.gif 36 | lib/pwd.png 37 | lib/Accont.png 38 | lib/Loge.png 39 | lib/main.gif 40 | lib/guanbi.png 41 | lib/zuixiaohua.png 42 | lib/fileIcons/excel.png 43 | lib/fileIcons/mp3.png 44 | lib/fileIcons/mp4.png 45 | lib/fileIcons/PDF.png 46 | lib/fileIcons/ppt.png 47 | lib/fileIcons/txt.png 48 | lib/fileIcons/WindowsWhite.png 49 | lib/fileIcons/word.png 50 | lib/fileIcons/zip.png 51 | lib/fileIcons/photo.png 52 | lib/fileIcons/Recv.png 53 | lib/fileIcons/send.png 54 | lib/MoveF.png 55 | 56 | 57 | -------------------------------------------------------------------------------- /QQClient/lib/Accont.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/Accont.png -------------------------------------------------------------------------------- /QQClient/lib/Big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/Big.png -------------------------------------------------------------------------------- /QQClient/lib/BigToNormal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/BigToNormal.png -------------------------------------------------------------------------------- /QQClient/lib/Chat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/Chat.png -------------------------------------------------------------------------------- /QQClient/lib/FDownArrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/FDownArrow.png -------------------------------------------------------------------------------- /QQClient/lib/FLeftArrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/FLeftArrow.png -------------------------------------------------------------------------------- /QQClient/lib/Greyguanbi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/Greyguanbi.png -------------------------------------------------------------------------------- /QQClient/lib/Greyzuixiaohua.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/Greyzuixiaohua.png -------------------------------------------------------------------------------- /QQClient/lib/Keyboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/Keyboard.png -------------------------------------------------------------------------------- /QQClient/lib/Loge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/Loge.png -------------------------------------------------------------------------------- /QQClient/lib/MainMenu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/MainMenu.png -------------------------------------------------------------------------------- /QQClient/lib/MoveF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/MoveF.png -------------------------------------------------------------------------------- /QQClient/lib/QQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/QQ.png -------------------------------------------------------------------------------- /QQClient/lib/System.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/System.png -------------------------------------------------------------------------------- /QQClient/lib/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/add.png -------------------------------------------------------------------------------- /QQClient/lib/addfriend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/addfriend.png -------------------------------------------------------------------------------- /QQClient/lib/backg.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/backg.jpeg -------------------------------------------------------------------------------- /QQClient/lib/biaoqing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/biaoqing.png -------------------------------------------------------------------------------- /QQClient/lib/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/close.png -------------------------------------------------------------------------------- /QQClient/lib/cut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/cut.png -------------------------------------------------------------------------------- /QQClient/lib/default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/default.jpg -------------------------------------------------------------------------------- /QQClient/lib/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/delete.png -------------------------------------------------------------------------------- /QQClient/lib/fileIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/fileIcon.png -------------------------------------------------------------------------------- /QQClient/lib/fileIcons/PDF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/fileIcons/PDF.png -------------------------------------------------------------------------------- /QQClient/lib/fileIcons/Recv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/fileIcons/Recv.png -------------------------------------------------------------------------------- /QQClient/lib/fileIcons/WindowsWhite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/fileIcons/WindowsWhite.png -------------------------------------------------------------------------------- /QQClient/lib/fileIcons/excel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/fileIcons/excel.png -------------------------------------------------------------------------------- /QQClient/lib/fileIcons/mp3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/fileIcons/mp3.png -------------------------------------------------------------------------------- /QQClient/lib/fileIcons/mp4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/fileIcons/mp4.png -------------------------------------------------------------------------------- /QQClient/lib/fileIcons/photo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/fileIcons/photo.png -------------------------------------------------------------------------------- /QQClient/lib/fileIcons/ppt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/fileIcons/ppt.png -------------------------------------------------------------------------------- /QQClient/lib/fileIcons/send.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/fileIcons/send.png -------------------------------------------------------------------------------- /QQClient/lib/fileIcons/txt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/fileIcons/txt.png -------------------------------------------------------------------------------- /QQClient/lib/fileIcons/word.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/fileIcons/word.png -------------------------------------------------------------------------------- /QQClient/lib/fileIcons/zip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/fileIcons/zip.png -------------------------------------------------------------------------------- /QQClient/lib/find.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/find.gif -------------------------------------------------------------------------------- /QQClient/lib/gantanhao.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/gantanhao.png -------------------------------------------------------------------------------- /QQClient/lib/gengduo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/gengduo.png -------------------------------------------------------------------------------- /QQClient/lib/guanbi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/guanbi.png -------------------------------------------------------------------------------- /QQClient/lib/guanbi2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/guanbi2.png -------------------------------------------------------------------------------- /QQClient/lib/main.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/main.gif -------------------------------------------------------------------------------- /QQClient/lib/nicheng.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/nicheng.png -------------------------------------------------------------------------------- /QQClient/lib/picIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/picIcon.png -------------------------------------------------------------------------------- /QQClient/lib/pwd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/pwd.png -------------------------------------------------------------------------------- /QQClient/lib/quit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/quit.png -------------------------------------------------------------------------------- /QQClient/lib/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/search.png -------------------------------------------------------------------------------- /QQClient/lib/shangjiantou.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/shangjiantou.png -------------------------------------------------------------------------------- /QQClient/lib/tishi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/tishi.png -------------------------------------------------------------------------------- /QQClient/lib/xiajiantou.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/xiajiantou.png -------------------------------------------------------------------------------- /QQClient/lib/xingbie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/xingbie.png -------------------------------------------------------------------------------- /QQClient/lib/zhanghao.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/zhanghao.png -------------------------------------------------------------------------------- /QQClient/lib/zuixiaohua.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQClient/lib/zuixiaohua.png -------------------------------------------------------------------------------- /QQClient/login.cpp: -------------------------------------------------------------------------------- 1 | #include "login.h" 2 | #include "ui_login.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | Login::Login(QWidget* parent) 17 | : QWidget(parent) 18 | , ui(new Ui::Login) 19 | { 20 | ui->setupUi(this); 21 | //设置无边框窗口,任务栏不显示 22 | setWindowFlags(Qt::FramelessWindowHint | Qt::SplashScreen | Qt::WindowStaysOnTopHint); 23 | 24 | //设置背景透明 25 | setAttribute(Qt::WA_TranslucentBackground); 26 | 27 | //初始化主界面动态图 28 | m_movie = new QMovie(":/lib/main.gif"); 29 | m_movie->setScaledSize(QSize(430, 130)); 30 | ui->MovieLabel->setMovie(m_movie); 31 | m_movie->start(); 32 | 33 | //设置阴影边框 34 | QGraphicsDropShadowEffect* shadow = new QGraphicsDropShadowEffect(ui->frame); 35 | shadow->setOffset(0, 0); 36 | shadow->setColor(Qt::black); 37 | shadow->setBlurRadius(10); 38 | ui->frame->setGraphicsEffect(shadow); 39 | //安装事件过滤器,处理frame的绘画事件 40 | ui->frame->installEventFilter(this); 41 | update(); 42 | 43 | //初始化用户数据 44 | initUserData(); 45 | 46 | //账户AccountLine只能输入数字 47 | ui->AccountLine->setValidator(new QRegExpValidator(QRegExp("[0-9]+$"))); 48 | //密码PwdLineEdit不能输入# 49 | ui->PwdLine->setValidator(new QRegExpValidator(QRegExp("[^#]*"))); 50 | //密码PwdLineEdit输入时显示密码文 51 | ui->PwdLine->setEchoMode(QLineEdit::Password); 52 | 53 | //设置账号密码栏通过鼠标单击和tab键获取焦点 54 | ui->AccountLine->setFocus(); 55 | ui->AccountLine->setFocusPolicy(Qt::StrongFocus); 56 | ui->PwdLine->setFocusPolicy(Qt::StrongFocus); 57 | 58 | //添加文本框图标 59 | QAction* AcAction = new QAction(ui->AccountLine); 60 | AcAction->setIcon(QIcon(":/lib/Accont.png")); 61 | ui->AccountLine->addAction(AcAction, QLineEdit::LeadingPosition); 62 | 63 | QAction* PwdAction = new QAction(ui->PwdLine); 64 | PwdAction->setIcon(QIcon(":/lib/pwd.png")); 65 | ui->PwdLine->addAction(PwdAction, QLineEdit::LeadingPosition); 66 | //初始化系统托盘 67 | InitSysTrayicon(); 68 | 69 | ui->TipsWidget->hide(); 70 | ui->PwdErr->hide(); 71 | ui->shuruacc->hide(); 72 | ui->shurupwd->hide(); 73 | } 74 | 75 | Login::~Login() 76 | { 77 | delete m_movie; 78 | delete ui; 79 | } 80 | 81 | void Login::initShadow() 82 | { 83 | QPainter painter(ui->frame); 84 | painter.fillRect(ui->frame->rect().adjusted(-10, -10, 10, 10), QColor(220, 220, 220)); 85 | } 86 | 87 | bool Login::eventFilter(QObject* w, QEvent* e) 88 | { 89 | if((w == ui->frame) && (e->type() == QEvent::Paint)) 90 | { 91 | initShadow(); 92 | return true; 93 | } 94 | 95 | return QWidget::eventFilter(w, e); 96 | } 97 | 98 | void Login::mousePressEvent(QMouseEvent* e) 99 | { 100 | if(e->button() == Qt::LeftButton) 101 | { 102 | //点下时在控件上 103 | isMainWidget = true; 104 | m_point = e->globalPos() - pos(); 105 | } 106 | } 107 | 108 | void Login::mouseMoveEvent(QMouseEvent* e) 109 | { 110 | if(e->buttons() & Qt::LeftButton && isMainWidget) 111 | { 112 | move(e->globalPos() - m_point); 113 | } 114 | } 115 | 116 | void Login::mouseReleaseEvent(QMouseEvent* event) 117 | { 118 | //释放时将bool值恢复false 119 | isMainWidget = false; 120 | event->accept(); 121 | } 122 | 123 | //初始化系统托盘 124 | void Login::InitSysTrayicon() 125 | { 126 | //创建托盘菜单 127 | m_show = new QAction("打开主面板", this); 128 | connect(m_show, &QAction::triggered, this, &Login::ShowWindow); 129 | 130 | m_quit = new QAction("退出", this); 131 | connect(m_quit, &QAction::triggered, this, &Login::on_CloseToolBtn_clicked); 132 | m_menu = new QMenu(this); 133 | 134 | m_menu->addAction(m_show); 135 | m_menu->addSeparator(); 136 | m_menu->addAction(m_quit); 137 | 138 | //初始化系统托盘 139 | QIcon icon(":/lib/System.png"); 140 | m_sysIcon = new QSystemTrayIcon(this); 141 | m_sysIcon->setIcon(icon); 142 | m_sysIcon->setToolTip("QQ"); 143 | m_sysIcon->setContextMenu(m_menu); 144 | connect(m_sysIcon, &QSystemTrayIcon::activated, this, &Login::on_activatedSysTrayIcon); 145 | m_sysIcon->show(); 146 | } 147 | 148 | void Login::on_CloseToolBtn_clicked() 149 | { 150 | //qDebug() << "正常退出"; 151 | emit LoginClose(); //发送关闭信号 152 | QThread::msleep(25); //等待25毫秒等待线程定时器暂停 153 | QApplication::quit(); 154 | } 155 | 156 | void Login::on_MiniToolBtn_clicked() 157 | { 158 | this->hide(); 159 | } 160 | 161 | void Login::on_activatedSysTrayIcon(QSystemTrayIcon::ActivationReason reason) 162 | { 163 | switch(reason) 164 | { 165 | case QSystemTrayIcon::Trigger: //单击恢复 166 | ShowWindow(); 167 | break; 168 | 169 | case QSystemTrayIcon::DoubleClick: //双击恢复 170 | ShowWindow(); 171 | break; 172 | 173 | default: 174 | break; 175 | } 176 | } 177 | 178 | void Login::on_FindBtn_clicked() 179 | { 180 | if(!Global::isConnecting) 181 | { 182 | //如果当前未显示提示信息则显示提示五秒钟 183 | if(ui->TipsWidget->isHidden()) 184 | { 185 | ui->tipsLabel->setText("登录超时,请检查网络或本机防火墙设置。"); 186 | ui->TipsWidget->show(); 187 | QTimer::singleShot(5000, ui->TipsWidget, SLOT(hide())); 188 | } 189 | } 190 | else 191 | { 192 | emit ToAccount(true); 193 | Registering = true; 194 | } 195 | } 196 | 197 | void Login::on_NewAcBtn_clicked() 198 | { 199 | if(!Global::isConnecting) 200 | { 201 | if(ui->TipsWidget->isHidden()) 202 | { 203 | ui->tipsLabel->setText("登录超时,请检查网络或本机防火墙设置。"); 204 | ui->TipsWidget->show(); 205 | QTimer::singleShot(5000, ui->TipsWidget, SLOT(hide())); 206 | } 207 | } 208 | else 209 | { 210 | emit ToAccount(false); 211 | Registering = true; 212 | } 213 | } 214 | 215 | void Login::ShowWindow(bool fromAcc) 216 | { 217 | //判断是否是从注册界面发回的信号 218 | if(fromAcc == false) 219 | { 220 | //如果正在注册则不返回 221 | if(Registering == true) 222 | { 223 | qDebug() << "请从注册/找回窗口返回!"; 224 | return; 225 | } 226 | else 227 | { 228 | this->show(); 229 | } 230 | } 231 | else 232 | { 233 | Registering = false; 234 | this->show(); 235 | } 236 | } 237 | 238 | void Login::initUserData() 239 | { 240 | QDir dir; 241 | QFile file; 242 | //查找本地保存账号 243 | dir.setPath(Global::AppWorkPath()); 244 | QStringList dirNames = dir.entryList(QDir::Dirs); 245 | dirNames.removeOne("."); 246 | dirNames.removeOne(".."); 247 | dirNames.removeOne("allusers"); 248 | 249 | //若无已保存账号则头像显示默认头像 250 | if(dirNames.isEmpty()) 251 | { 252 | QPixmap pix = Global::CreateHeadShot(":/lib/default.jpg"); 253 | ui->label->setPixmap(pix); 254 | } 255 | 256 | for(auto dirName : dirNames) 257 | { 258 | m_Accs.append(dirName); 259 | file.setFileName(Global::AppWorkPath() + "/" + dirName + "/login.txt"); 260 | 261 | //qDebug() << file.fileName() << file.exists(); 262 | if(!file.open(QFile::ReadOnly)) 263 | { 264 | qDebug() << "打开用户login文件失败"; 265 | } 266 | 267 | QByteArray data = file.readLine(); 268 | file.close(); 269 | QString userMsg = QString(data); 270 | //逗号拆分用户登录信息 271 | /* 272 | * 用户登录文件格式 273 | * 昵称 头像位置 记住/不记住 记住为密码,不记住则为空 274 | */ 275 | QStringList Msgs = userMsg.split("##"); 276 | m_Names.append(Msgs.at(0)); 277 | m_Icons.append(Global::AppWorkPath() + "/" + dirName + "/" + Msgs.at(1)); 278 | QString isRem = Msgs.at(2); 279 | 280 | if(isRem == "记住") 281 | { 282 | isRemember.append(true); 283 | m_Pwds.append(Msgs.at(3)); 284 | } 285 | else 286 | { 287 | isRemember.append(false); 288 | m_Pwds.append(""); 289 | } 290 | } 291 | 292 | initComboBox(); 293 | } 294 | 295 | void Login::initComboBox() 296 | { 297 | //加载自定义的下拉列表框 298 | QListWidget* lwidget = new QListWidget(this); 299 | //不获得焦点,删除虚线边框 300 | lwidget->setFocusPolicy(Qt::NoFocus); 301 | lwidget->setStyleSheet("QListWidget{outline:0px;}"); 302 | ui->comboBox->setModel(lwidget->model()); 303 | ui->comboBox->setView(lwidget); 304 | //设置comboBox最大项数 305 | ui->comboBox->setMaxVisibleItems(3); 306 | 307 | for(int i = 0; i < m_Accs.size(); i++) 308 | { 309 | //存放此用户数据的标号 310 | m_dataLoc.push_back(i); 311 | 312 | QHBoxLayout* layout = new QHBoxLayout; 313 | 314 | //设置头像 315 | QLabel* label1 = new QLabel; 316 | QPixmap pix = Global::CreateHeadShot(m_Icons.at(i)); 317 | label1->setPixmap(pix); 318 | label1->setFixedSize(40, 40); 319 | label1->setScaledContents(true); 320 | 321 | //设置用户名和账号 322 | QVBoxLayout* vlayout = new QVBoxLayout; 323 | QLabel* label2 = new QLabel; 324 | label2->setText(QString("%1").arg(m_Names.at(i))); 325 | label2->setStyleSheet("QLabel{color:black;font-size:13pt;}"); 326 | QLabel* label3 = new QLabel; 327 | label3->setText(QString("%2").arg(m_Accs.at(i))); 328 | label3->setStyleSheet("QLabel{color:grey;font-size:10pt;}"); 329 | vlayout->addWidget(label2); 330 | vlayout->addWidget(label3); 331 | 332 | //添加删除按钮 333 | QPushButton* btn = new QPushButton; 334 | btn->setFixedSize(32, 32); 335 | btn->setStyleSheet("QPushButton{background-color:transparent;border-image:url(:/lib/guanbi.png);}" 336 | "QPushButton:hover{background-color:transparent;border-image:url(:/lib/guanbi2.png);}"); 337 | 338 | layout->addWidget(label1); 339 | layout->addLayout(vlayout); 340 | layout->addWidget(btn); 341 | QWidget* widget = new QWidget(this); 342 | widget->setLayout(layout); 343 | QListWidgetItem* lwitem = new QListWidgetItem(lwidget); 344 | //绑定删除按钮信号槽 345 | connect(btn, &QPushButton::clicked, this, [ = ]() 346 | { 347 | switch(QMessageBox::warning(this, "警告", "确认要删除该账号吗?", QMessageBox::Ok | QMessageBox::No)) 348 | { 349 | case QMessageBox::Ok: 350 | { 351 | /* 352 | * 由于此处删除后容器会重新排序,因此如果直接使用标号删除只有第一次删除能够正常删除, 353 | * 之后标号则会大于容器的大小,不与容器内容相对应了,所以此处将标号放入QVector容器中 354 | * 查找QVector容器中该标号的位置,用这个位置来删除用户数据,并同步删除QVector中的标号, 355 | * 才能够与要删除的用户数据同步 356 | */ 357 | deleteUserData(m_Accs.at(m_dataLoc.indexOf(i))); 358 | m_Accs.removeAt(m_dataLoc.indexOf(i)); 359 | m_Pwds.removeAt(m_dataLoc.indexOf(i)); 360 | m_Icons.removeAt(m_dataLoc.indexOf(i)); 361 | isRemember.removeAt(m_dataLoc.indexOf(i)); 362 | m_dataLoc.removeOne(i); 363 | lwidget->removeItemWidget(lwitem); 364 | delete lwitem; 365 | 366 | //如果将账号全部删除则设置为初始状态 367 | if(m_dataLoc.length() == 0) 368 | { 369 | QPixmap pix = Global::CreateHeadShot(":/lib/default.jpg"); 370 | ui->label->setPixmap(pix); 371 | ui->AccountLine->setText(""); 372 | ui->PwdLine->setText(""); 373 | ui->RememberCheck->setChecked(false); 374 | } 375 | 376 | break; 377 | } 378 | 379 | case QMessageBox::No: 380 | break; 381 | 382 | default: 383 | break; 384 | } 385 | }); 386 | lwidget->setItemWidget(lwitem, widget); 387 | } 388 | } 389 | 390 | void Login::deleteUserData(QString acc) 391 | { 392 | QString userPath = Global::AppWorkPath() + "/" + acc; 393 | QDir dir(userPath); 394 | 395 | if(!dir.removeRecursively()) 396 | { 397 | qDebug() << "删除用户文件失败"; 398 | } 399 | 400 | qDebug() << "若账户中已添加好友则删除本地账号后重新登录会导致好友丢失," 401 | "在每次下线检查好友列表是否改动时若有改动则同步向服务器发送改动后的好友列表文件即可解决"; 402 | } 403 | 404 | void Login::closeSystemIcon() 405 | { 406 | m_sysIcon->hide(); 407 | } 408 | 409 | void Login::GetResultFromSer(QString result) 410 | { 411 | if(result == "账号密码错误") 412 | { 413 | qDebug() << "账号密码错误"; 414 | ui->PwdErr->show(); 415 | } 416 | else if(result == "重复登录") 417 | { 418 | qDebug() << "重复登录"; 419 | 420 | if(ui->TipsWidget->isHidden()) 421 | { 422 | qDebug() << "changing tips text"; 423 | ui->tipsLabel->setText("你已在QQ登录了" + ui->AccountLine->text() + ",不能重复登录。"); 424 | ui->TipsWidget->show(); 425 | QTimer::singleShot(5000, ui->TipsWidget, SLOT(hide())); 426 | } 427 | } 428 | } 429 | 430 | void Login::on_pushButton_clicked() 431 | { 432 | //获取用户输入账号密码 433 | QString acc = ui->AccountLine->text(); 434 | QString pwd = ui->PwdLine->text(); 435 | qDebug() << acc << pwd; 436 | 437 | //检查是否输入账号 438 | if(acc == "") 439 | { 440 | if(ui->shuruacc->isHidden()) 441 | { 442 | ui->shuruacc->show(); 443 | QTimer::singleShot(3000, ui->shuruacc, SLOT(hide())); 444 | } 445 | 446 | return; 447 | } 448 | 449 | //检查是否输入密码 450 | if(pwd == "") 451 | { 452 | if(ui->shurupwd->isHidden()) 453 | { 454 | ui->shurupwd->show(); 455 | QTimer::singleShot(3000, ui->shurupwd, SLOT(hide())); 456 | } 457 | 458 | return; 459 | } 460 | 461 | //检查账号密码合法性 462 | if(acc.length() < 4 || acc.length() > 9) 463 | { 464 | QMessageBox::information(this, "提示", "账号为4至9位!"); 465 | return; 466 | } 467 | 468 | if(pwd.length() < 6) 469 | { 470 | QMessageBox::information(this, "提示", "密码至少6位"); 471 | return; 472 | } 473 | 474 | //检查是否登录服务器 475 | if(!Global::isConnecting) 476 | { 477 | if(ui->TipsWidget->isHidden()) 478 | { 479 | ui->tipsLabel->setText("登录超时,请检查网络或本机防火墙设置。"); 480 | ui->TipsWidget->show(); 481 | QTimer::singleShot(5000, ui->TipsWidget, SLOT(hide())); 482 | } 483 | 484 | return; 485 | } 486 | 487 | //如果用户数据中无该账号记录则为第一次登录 488 | if(m_Accs.indexOf(acc) == -1) 489 | { 490 | Global::isFirstLogin = true; 491 | } 492 | 493 | //设置账号密码和是否记住密码 494 | Global::isRemember = ui->RememberCheck->isChecked(); 495 | Global::InitLoginUserInfo(acc.toInt(), pwd); 496 | 497 | emit LoginToServer(); 498 | } 499 | 500 | //选择ComboBox时改变账号头像等信息 501 | void Login::on_comboBox_currentIndexChanged(int index) 502 | { 503 | if(index >= 0) 504 | { 505 | //点击已存储账号后设置为非第一次登录 506 | Global::isFirstLogin = false; 507 | 508 | //qDebug() << index; 509 | QPixmap pix = Global::CreateHeadShot(m_Icons.at(index)); 510 | ui->label->setPixmap(pix); 511 | ui->AccountLine->setText(m_Accs.at(index)); 512 | ui->PwdLine->setText(m_Pwds.at(index)); 513 | ui->RememberCheck->setChecked(isRemember.at(index)); 514 | } 515 | } 516 | 517 | //忘记密码提示框出现后点击确定返回 518 | void Login::on_pushButton_2_clicked() 519 | { 520 | ui->PwdErr->hide(); 521 | } 522 | 523 | //用户改变账号文本栏中文本时,若本地用户数据无该账号记录则显示默认头像,有则显示对应头像 524 | void Login::on_AccountLine_textChanged(const QString& arg1) 525 | { 526 | //qDebug() << arg1; 527 | //若用户本地数据中有该账号信息则显示该账号头像,否则显示默认头像 528 | int loc = m_Accs.indexOf(arg1); 529 | 530 | if(loc == -1) 531 | { 532 | QPixmap pix = Global::CreateHeadShot(":/lib/default.jpg"); 533 | ui->label->setPixmap(pix); 534 | } 535 | else 536 | { 537 | QPixmap pix = Global::CreateHeadShot(m_Icons.at(loc)); 538 | ui->label->setPixmap(pix); 539 | } 540 | } 541 | -------------------------------------------------------------------------------- /QQClient/login.h: -------------------------------------------------------------------------------- 1 | #ifndef LOGIN_H 2 | #define LOGIN_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "account.h" 9 | #include "global.h" 10 | 11 | QT_BEGIN_NAMESPACE 12 | namespace Ui 13 | { 14 | class Login; 15 | } 16 | QT_END_NAMESPACE 17 | 18 | class Login : public QWidget 19 | { 20 | Q_OBJECT 21 | 22 | public: 23 | Login(QWidget* parent = nullptr); 24 | ~Login(); 25 | public slots: 26 | void ShowWindow(bool fromAcc = false); //显示主界面 27 | void closeSystemIcon(); //关闭托盘 28 | void GetResultFromSer(QString result); //接收服务器传回的结果 29 | 30 | signals: 31 | void ToAccount(bool); //转到注册界面 32 | void LoginClose(); //发送关闭信号给tcp线程 33 | void LoginToServer(); //登录 34 | 35 | protected: 36 | void initShadow(); //初始化窗口边框阴影 37 | bool eventFilter(QObject* w, QEvent* e) override; 38 | void mousePressEvent(QMouseEvent* e) override; 39 | void mouseMoveEvent(QMouseEvent* e) override; 40 | void mouseReleaseEvent(QMouseEvent* event) override; 41 | void InitSysTrayicon(); //初始化系统托盘 42 | 43 | void initUserData(); //初始化用户数据 44 | void initComboBox(); //初始化账户comboBox 45 | void deleteUserData(QString acc); //删除账号数据 46 | 47 | private slots: 48 | void on_activatedSysTrayIcon(QSystemTrayIcon::ActivationReason reason); //点击托盘图标 49 | 50 | void on_CloseToolBtn_clicked(); //关闭 51 | 52 | void on_MiniToolBtn_clicked(); //最小化 53 | 54 | void on_FindBtn_clicked(); //找回密码 55 | 56 | void on_NewAcBtn_clicked(); //注册新用户 57 | 58 | void on_pushButton_clicked(); //登录 59 | 60 | void on_comboBox_currentIndexChanged(int index); //当前选择账号更改 61 | 62 | void on_pushButton_2_clicked(); //从忘记密码提示框返回 63 | 64 | void on_AccountLine_textChanged(const QString& arg1); //账号栏文本改变 65 | 66 | private: 67 | Ui::Login* ui; 68 | bool isMainWidget = false; //记录点下时是否在主窗口上而非内部控件上 69 | QPoint m_point; //记录点击时位置 70 | QMovie* m_movie; //加载动态图 71 | bool Registering = false; //是否正在注册界面 72 | 73 | /* 托盘菜单 */ 74 | QSystemTrayIcon* m_sysIcon; //托盘功能 75 | QAction* m_quit; //退出 76 | QAction* m_show; //恢复 77 | QMenu* m_menu; //托盘菜单 78 | 79 | /* 已保存账号内容 */ 80 | QVector m_dataLoc; //存放添加入comboBox的用户数据标号 81 | QStringList m_Accs; //存放用户账号 82 | QStringList m_Names; //存放用户名 83 | QStringList m_Pwds; //存放用户密码 84 | QStringList m_Icons; //存放用户头像位置 85 | QList isRemember; //记录是否记住密码00 86 | }; 87 | #endif // LOGIN_H 88 | -------------------------------------------------------------------------------- /QQClient/main.cpp: -------------------------------------------------------------------------------- 1 | #include "maininterface.h" 2 | #include "global.h" 3 | #include 4 | 5 | int main(int argc, char* argv[]) 6 | { 7 | QApplication a(argc, argv); 8 | Global::GetWorkPath(QApplication::applicationDirPath() + "/userdata"); 9 | MainInterface m; 10 | return a.exec(); 11 | } 12 | -------------------------------------------------------------------------------- /QQClient/maininterface.h: -------------------------------------------------------------------------------- 1 | #ifndef MAININTERFACE_H 2 | #define MAININTERFACE_H 3 | 4 | #include 5 | #include "login.h" 6 | #include "account.h" 7 | #include "tcpthread.h" 8 | #include "addfriend.h" 9 | #include 10 | #include 11 | #include 12 | #include "findfriends.h" 13 | #include 14 | #include "chatwindow.h" 15 | #include 16 | #include 17 | #include "personaldata.h" 18 | #include "changedata.h" 19 | #include "global.h" 20 | 21 | namespace Ui 22 | { 23 | class MainInterface; 24 | } 25 | 26 | class MainInterface : public QWidget 27 | { 28 | Q_OBJECT 29 | 30 | public: 31 | explicit MainInterface(QWidget* parent = nullptr); 32 | ~MainInterface(); 33 | public slots: 34 | void ShowAccount(bool); //显示注册界面 35 | 36 | /* 网络相关 */ 37 | void GetResultFromSer(int type, int targetAcc, QString uData, QString Msg, QString MsgType); //获取服务器返回的数据 38 | void Reconnection(bool onl); //掉线时重新连接上线 39 | 40 | /* 好友添加相关 */ 41 | void ShowFindFri(); //显示查找好友窗口 42 | void SearchingAcc(QString acc); //查找账号信息 43 | void AddFriendClosed(QString type, int targetacc, QString GpNa, QString yanzheng); //接收添加好友界面关闭信号 44 | 45 | /* 好友聊天相关 */ 46 | ChatWindow* showFriChatWindow(int acc); //显示好友聊天窗口 47 | void SendMsgToFri(int, MsgType, QString); //发送信息给好友 48 | 49 | /* 更改用户信息 */ 50 | void ChangingLoginFile(QString NkN = "", QString pwd = ""); //更改登录文件 51 | void ChangingHeadShot(); //修改头像 52 | 53 | /* 个人资料 */ 54 | void ClosePerData(int); //关闭个人资料窗口 55 | void EditPersonalData(QStringList); //修改个人资料窗口 56 | void ChangeUserDatas(QString); //更改用户资料 57 | void CloseEdit(); //关闭编辑窗口 58 | protected: 59 | /* 系统托盘 */ 60 | void initSystemIcon(); //初始化系统托盘 61 | void on_activatedSysTrayIcon(QSystemTrayIcon::ActivationReason reason); //点击托盘图标 62 | 63 | //界面相关 64 | void ChangeCurSor(const QPoint& p); //更改鼠标样式 65 | void initShadow(); //初始化窗口边框阴影 66 | bool eventFilter(QObject* w, QEvent* e) override; 67 | void paintEvent(QPaintEvent* event) override; 68 | void mousePressEvent(QMouseEvent* e) override; 69 | void mouseMoveEvent(QMouseEvent* e) override; 70 | void mouseReleaseEvent(QMouseEvent* event) override; 71 | 72 | /* 好友列表 */ 73 | void GetFriendsData(); //从本地数据文件中获取好友列表信息 74 | void InitTreeWidget(); //初始化好友列表TreeWidget 75 | void InitFriRitBtnMenu(); //初始化好友列表右键菜单 76 | void UpdateTreeWidget(); //更新好友列表TreeWidget 77 | QTreeWidgetItem* CreateTreeWidgetItem(QString fenzuming, int acc = -1); //创建好友列表节点指针 78 | void DelFri(); //删除好友 79 | signals: 80 | void StartConnecting(); //连接服务器 81 | void MainInterfaceClose(); //主窗口退出 82 | void SendReplyToFindFri(bool type); //将回应发送回查找好友界面 83 | 84 | //发送信息给服务器 85 | void SendMsgToServer(int type, int acc = -1, int targetacc = -1, QString Msg = "", QString MsgType = ""); 86 | private slots: 87 | void on_CloseBtn_clicked(); //关闭 88 | 89 | void on_MiniBtn_clicked(); //最小化 90 | 91 | void onTreeWidgetClicked(QTreeWidgetItem* item); //单击好友栏 92 | 93 | void onTreeWidgetDoubleClicked(QTreeWidgetItem* item); //双击好友栏 94 | 95 | void onItemExpended(QTreeWidgetItem* item); //好友列表展开 96 | 97 | void onItemCollapsed(QTreeWidgetItem* item); //好友列表收起 98 | 99 | void FriRightBtnMenu(const QPoint& pos); //好友列表右键菜单 100 | 101 | void AddGroup(); //添加分组 102 | 103 | void ReNameGroup(); //重命名分组 104 | 105 | void RemoveGroup(); //删除分组 106 | 107 | void ItemNameChanged(QTreeWidgetItem* item); //分组名改变 108 | 109 | void on_HeadShotBtn_clicked(); //点击自己的头像 110 | 111 | void MoveFriend(); //移动好友 112 | 113 | private: 114 | Ui::MainInterface* ui; 115 | 116 | TcpThread* m_mytcp; //网络连接类 117 | QThread* thread; //tcp线程 118 | bool isLogined = false; //是否已登录到主界面 119 | 120 | /* 窗口跟随鼠标移动拖动 */ 121 | QPoint m_point; //记录鼠标点下位置 122 | bool isPressed = false; //记录是否点下鼠标 123 | Location m_loc; //记录鼠标当前位置 124 | int TabWidgetWidth; //记录TabWidget的宽度 125 | //全局路径,防止鼠标移动事件穿透 126 | QPixmap m_pixmap; 127 | QPainterPath m_globalPath; 128 | 129 | /* 其他界面 */ 130 | Login* m_log; //登录界面 131 | Account* m_accClass; //注册界面 132 | FindFriends* m_FindFri; //查找好友界面 133 | QList m_addfri; //添加好友界面 134 | QHash m_chatWindows; //聊天界面 135 | QHash m_PersonData; //个人资料界面 136 | ChangeData* m_editData = nullptr; //修改个人资料界面 137 | 138 | /* 托盘 */ 139 | QSystemTrayIcon* m_sysIcon; //托盘功能 140 | QAction* m_quit; //退出 141 | QAction* m_show; //恢复 142 | QMenu* m_menu; //托盘菜单signature; //个性签名 143 | 144 | /* 存储好友信息 */ 145 | QStringList m_groupNames; //存放分组名称 146 | QMultiMap m_friends; //保存好友信息,Key为分组名,value为账号 147 | QHash m_frinicknames; //保存好友昵称 148 | QHash m_frisignatures; //保存好友个性签名 149 | 150 | QMap m_waitFriReply; //存放添加好友的分组信息,等待好友同意后加入该分组 151 | 152 | /* 结点类型 */ 153 | enum itemtype { engroup = 1001, enfriend }; 154 | 155 | /* 好友列表右键菜单 */ 156 | QMenu* m_frimenu; //好友右键菜单 157 | QAction* m_Chat; //与好友聊天 158 | QAction* m_FriData; //查看好友资料 159 | QMenu* m_moveFri; //移动联系人 160 | QMap m_movegroups; //可移动的分组 161 | QAction* m_delete; //删除好友 162 | 163 | /* 好友分组右键菜单 */ 164 | QMenu* m_grpmenu; //分组右键菜单 165 | QAction* m_addgrp; //添加分组 166 | QAction* m_renamegrp; //重命名分组 167 | QAction* m_removegrp; //删除分组 168 | 169 | /* 存储更改分组时的信息 */ 170 | QTreeWidgetItem* m_chaggrpitem = nullptr; //要更改的分组item 171 | QString m_chagName; //要更改的分组名 172 | bool isaddgrp; //是否是添加好友分组 173 | bool FriIsChanged = false; //好友列表是否已更改 174 | 175 | /* 保存选择的好友item */ 176 | QTreeWidgetItem* m_friItem = nullptr; //选择的好友item 177 | }; 178 | 179 | #endif // MAININTERFACE_H 180 | -------------------------------------------------------------------------------- /QQClient/personaldata.cpp: -------------------------------------------------------------------------------- 1 | #include "personaldata.h" 2 | #include "ui_personaldata.h" 3 | #include 4 | #include 5 | #include 6 | 7 | PersonalData::PersonalData(bool isMe, int acc, QStringList UserData, QWidget* parent) : 8 | QWidget(parent), 9 | ui(new Ui::PersonalData) 10 | { 11 | ui->setupUi(this); 12 | isPressed = false; 13 | //设置窗口无边框 14 | setWindowFlags(Qt::FramelessWindowHint); 15 | 16 | //若不是自己则不显示编辑按钮且不能点击头像 17 | if(!isMe) 18 | { 19 | m_isMe = isMe; 20 | ui->EditBtn->hide(); 21 | } 22 | else 23 | { 24 | m_isMe = true; 25 | } 26 | 27 | m_acc = acc; 28 | m_UserData = UserData; 29 | 30 | //设置窗口信息 31 | setWindowIcon(QIcon(":/lib/QQ.png")); 32 | setWindowTitle(m_UserData.at(Dnickname) + "的资料"); 33 | 34 | //设置背景透明 35 | setAttribute(Qt::WA_TranslucentBackground); 36 | 37 | //设置阴影边框 38 | QGraphicsDropShadowEffect* shadow = new QGraphicsDropShadowEffect(ui->frame); 39 | shadow->setOffset(0, 0); 40 | shadow->setColor(Qt::black); 41 | shadow->setBlurRadius(10); 42 | ui->frame->setGraphicsEffect(shadow); 43 | 44 | //安装事件过滤器,处理绘画事件 45 | ui->frame->installEventFilter(this); 46 | update(); 47 | 48 | /* 目前需要显示的信息并不多,需隐藏滚动条 */ 49 | ui->scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 50 | ui->scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 51 | 52 | //设置退出该窗口时不退出主程序 53 | setAttribute(Qt::WA_QuitOnClose, false); 54 | 55 | //设置头像 56 | QPixmap pix; 57 | 58 | if(isMe) 59 | { 60 | pix = Global::CreateHeadShot(Global::UserHeadShot()); 61 | } 62 | else 63 | { 64 | pix = Global::CreateHeadShot(Global::AppAllUserPath() + "/" + QString::number(acc) + ".jpg"); 65 | } 66 | 67 | ui->HeadShotBtn->setIcon(QIcon(pix)); 68 | ui->HeadShotBtn->setIconSize(QSize(65, 65)); 69 | 70 | //设置昵称签名 71 | ui->nameLab->setText(m_UserData.at(Dnickname)); 72 | ui->signatureLab->setText(m_UserData.at(Dsignature)); 73 | 74 | //设置资料 75 | ui->acc->setText(QString::number(m_acc)); 76 | ui->nickname->setText(m_UserData.at(Dnickname)); 77 | QString sex_age_birthday = m_UserData.at(Dsex) + " " + m_UserData.at(Dage) + " " + m_UserData.at(Dbirthday); 78 | ui->sex_age_birthday->setText(sex_age_birthday); 79 | 80 | ui->Loc->setText(m_UserData.at(Dlocation)); 81 | ui->blood->setText(m_UserData.at(Dblood_type)); 82 | ui->work->setText(m_UserData.at(Dwork)); 83 | ui->school->setText(m_UserData.at(Dsch_comp)); 84 | 85 | connect(ui->MiniBtn, &QToolButton::clicked, this, &PersonalData::showMinimized); 86 | } 87 | 88 | PersonalData::~PersonalData() 89 | { 90 | delete ui; 91 | } 92 | 93 | bool PersonalData::eventFilter(QObject* w, QEvent* e) 94 | { 95 | if((w == ui->frame) && (e->type() == QEvent::Paint)) 96 | { 97 | initShadow(); 98 | return true; 99 | } 100 | 101 | return QWidget::eventFilter(w, e); 102 | } 103 | 104 | void PersonalData::mousePressEvent(QMouseEvent* e) 105 | { 106 | if(e->button() == Qt::LeftButton) 107 | { 108 | isPressed = true; 109 | m_point = e->globalPos() - this->frameGeometry().topLeft(); 110 | } 111 | } 112 | 113 | void PersonalData::mouseMoveEvent(QMouseEvent* e) 114 | { 115 | if(e->buttons() & Qt::LeftButton && isPressed) 116 | { 117 | move(e->globalPos() - m_point); 118 | } 119 | } 120 | 121 | void PersonalData::mouseReleaseEvent(QMouseEvent* event) 122 | { 123 | Q_UNUSED(event); 124 | isPressed = false; 125 | } 126 | 127 | void PersonalData::initShadow() 128 | { 129 | QPainter painter(ui->frame); 130 | painter.fillRect(ui->frame->rect().adjusted(-10, -10, 10, 10), QColor(220, 220, 220)); 131 | } 132 | 133 | void PersonalData::CutPhoto(QString path) 134 | { 135 | QImage img; 136 | 137 | if(!img.load(path)) 138 | { 139 | qDebug() << "图片加载失败: " << path; 140 | return; 141 | } 142 | 143 | int pwidth = img.width(); 144 | int phigh = img.height(); 145 | qDebug() << "图片高为:" << pwidth << "宽" << phigh; 146 | QImage cimg; 147 | 148 | if(pwidth == phigh) 149 | { 150 | cimg = img.copy(); 151 | qDebug() << "图片宽高:" << cimg.width() << "," << cimg.height(); 152 | } 153 | else if(pwidth > phigh) 154 | { 155 | cimg = img.copy(QRect((pwidth - phigh) / 2, 0, phigh, phigh)); 156 | qDebug() << "截取横屏图片,图片宽高:" << cimg.width() << "," << cimg.height(); 157 | } 158 | else 159 | { 160 | cimg = img.copy(QRect(0, (phigh - pwidth) / 2, pwidth, pwidth)); 161 | qDebug() << "截取竖屏图片,图片宽高:" << cimg.width() << "," << cimg.height(); 162 | } 163 | 164 | //头像统一设置为350*350 165 | cimg = cimg.scaled(350, 350, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); 166 | qDebug() << "图片宽高:" << cimg.width() << "," << cimg.height(); 167 | 168 | //更新图片 169 | QString fileName = Global::UserHeadShot(); 170 | 171 | //删除原来的头像 172 | bool ok = QFile::remove(fileName); 173 | 174 | if(!ok) 175 | { 176 | qDebug() << "删除头像失败"; 177 | } 178 | 179 | cimg.save(fileName); 180 | } 181 | 182 | void PersonalData::on_CloseBtn_clicked() 183 | { 184 | emit ClosePerData(m_acc); 185 | } 186 | 187 | void PersonalData::on_EditBtn_clicked() 188 | { 189 | emit ChangingData(m_UserData); 190 | emit ClosePerData(m_acc); 191 | } 192 | 193 | void PersonalData::on_HeadShotBtn_clicked() 194 | { 195 | if(m_isMe) 196 | { 197 | QString hsFile = QFileDialog::getOpenFileName(this, "更改头像", "", "Picture Files(*.jpg;*.jpeg;*.png)"); 198 | 199 | if(hsFile == "") 200 | { 201 | qDebug() << "未选择图像"; 202 | return; 203 | } 204 | 205 | //qDebug() << "修改头像; " << hsFile; 206 | CutPhoto(hsFile); 207 | emit ChangingHeadShot(); 208 | emit ClosePerData(m_acc); 209 | } 210 | else 211 | { 212 | //qDebug() << "点击好友头像"; 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /QQClient/personaldata.h: -------------------------------------------------------------------------------- 1 | #ifndef PERSONALDATA_H 2 | #define PERSONALDATA_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "global.h" 9 | 10 | namespace Ui 11 | { 12 | class PersonalData; 13 | } 14 | 15 | class PersonalData : public QWidget 16 | { 17 | Q_OBJECT 18 | 19 | public: 20 | explicit PersonalData(bool isMe, int acc, QStringList UserData, QWidget* parent = nullptr); 21 | ~PersonalData(); 22 | signals: 23 | void ClosePerData(int); //关闭个人资料界面 24 | void ChangingData(QStringList); //更改个人资料 25 | void ChangingHeadShot(); //更改头像 26 | 27 | protected: 28 | bool eventFilter(QObject* w, QEvent* e) override; 29 | void mousePressEvent(QMouseEvent* e) override; 30 | void mouseMoveEvent(QMouseEvent* e) override; 31 | void mouseReleaseEvent(QMouseEvent* event) override; 32 | void initShadow(); //初始化窗口边框阴影 33 | void CutPhoto(QString path); //剪切图片 34 | 35 | private slots: 36 | void on_CloseBtn_clicked(); //关闭 37 | 38 | void on_EditBtn_clicked(); //编辑 39 | 40 | void on_HeadShotBtn_clicked(); //点击头像 41 | 42 | private: 43 | Ui::PersonalData* ui; 44 | 45 | QPoint m_point; //记录点击位置 46 | bool isPressed; //是否点下 47 | 48 | bool m_isMe; //是否是自己的资料 49 | int m_acc; //账号 50 | QStringList m_UserData; //用户资料 51 | }; 52 | 53 | #endif // PERSONALDATA_H 54 | -------------------------------------------------------------------------------- /QQClient/tcpthread.h: -------------------------------------------------------------------------------- 1 | #ifndef TCPTHREAD_H 2 | #define TCPTHREAD_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "global.h" 10 | 11 | class TcpThread : public QObject 12 | { 13 | Q_OBJECT 14 | public: 15 | explicit TcpThread(QObject* parent = nullptr); 16 | 17 | //发送接收信息 18 | void SendToServer(QByteArray jsondata, QString fileName, InforType type, int fileNums, int RecvAccount); //发送数据给服务器 19 | void ReadMsgFromServer(); //接收服务器传来的数据 20 | 21 | //解析和转换信息 22 | void MsgToJson(int type, int acc = -1, int targetacc = -1, QString Msg = "", QString MsgType = ""); //将数据转换为Json数据 23 | void ParseMsg(QByteArray); //解析传回的数据 24 | 25 | bool isCloseing = false; //是否准备退出 26 | public slots: 27 | //连接服务器端 28 | void StartConnect(); //开始连接,连接失败则自动重连 29 | void GetClose(); //接收关闭信号 30 | 31 | //收到注册界面信息 32 | void recvAccMsg(int type, int acc, QString pwd); //获取注册界面返回的信息 33 | //收到登录信号 34 | void LoginToServer(); 35 | signals: 36 | //发送结果回注册界面和登录界面 37 | void sendResultToAccMsg(int type, QString pwd, QString result); 38 | void sendResultToLogin(QString result); 39 | //连接成功时发出true,服务器断开连接发送false 40 | void isConnectingWithServer(bool onl); 41 | //发送结果回主界面 42 | void sendResultToMainInterFace(int type, int targetAcc, QString uData = "", QString Msg = "", QString MsgType = ""); 43 | //发送文件收发进度信息 44 | void SendProgressInfo(int friAcc, QString fileName, qint64 speed, int value, QTime RestTime, bool isMe); 45 | protected: 46 | //连接服务器及自动重连操作 47 | void connectToServer(); //连接服务器 48 | void ConnectSuccess(); //连接服务器成功 49 | void DisconnectFromServer(); //服务器断开连接 50 | void AutoConnect(); //自动重连 51 | 52 | //网络传输相关 53 | void SplitDataPackAge(QByteArray); //拆分数据包 54 | void SendFile(QString fileName, quint32 type, int RecvAccount); //发送文件 55 | void SendJson(QByteArray jsonData); //发送Json数据 56 | private: 57 | QTcpSocket* m_tcp = nullptr; //tcp套接字 58 | quint32 port = 9000; //端口号 59 | QString address = "127.0.0.1"; //ip地址 60 | 61 | /* 自动重连 */ 62 | QTimer* m_timer = nullptr; //定时器 63 | 64 | /* 接收 */ 65 | QByteArray m_buffer; //保存数据 66 | 67 | /* 接收文件 */ 68 | QFile m_file; //操作文件 69 | qint64 m_fileSize; //文件大小 70 | int FilePackAgeCount; //已接收文件数据包个数 71 | 72 | /* 接收好友文件 */ 73 | int m_InfoType; //发送类型 74 | int m_SendAcc; //发送方账号 75 | double m_lastsize; //上次已接收大小 76 | double m_nowsize; //本次已接收大小 77 | QTime m_lastT; //上次接收时间 78 | QTime m_nowT; //本次接收时间 79 | }; 80 | 81 | #endif // TCPTHREAD_H 82 | -------------------------------------------------------------------------------- /QQServer/QQServer.pro: -------------------------------------------------------------------------------- 1 | QT += core gui sql network 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 | global.cpp \ 20 | main.cpp \ 21 | mainwindow.cpp \ 22 | qqsqldata.cpp \ 23 | tcpthread.cpp \ 24 | workthread.cpp 25 | 26 | HEADERS += \ 27 | global.h \ 28 | mainwindow.h \ 29 | qqsqldata.h \ 30 | tcpthread.h \ 31 | workthread.h 32 | 33 | FORMS += \ 34 | mainwindow.ui 35 | 36 | # Default rules for deployment. 37 | qnx: target.path = /tmp/$${TARGET}/bin 38 | else: unix:!android: target.path = /opt/$${TARGET}/bin 39 | !isEmpty(target.path): INSTALLS += target 40 | 41 | RESOURCES += \ 42 | lib.qrc 43 | -------------------------------------------------------------------------------- /QQServer/global.cpp: -------------------------------------------------------------------------------- 1 | #include "global.h" 2 | #include 3 | #include 4 | 5 | QString Global::WorkPath = ""; 6 | 7 | void Global::GetWorkPath(QString path) 8 | { 9 | WorkPath = path; 10 | } 11 | 12 | bool Global::CreateWorkPath() 13 | { 14 | QDir dir; 15 | 16 | if(!dir.exists(WorkPath)) 17 | { 18 | qDebug() << "未创建用户数据文件夹"; 19 | 20 | if(!dir.mkdir(WorkPath)) 21 | { 22 | qDebug() << "创建用户数据文件夹失败"; 23 | return false; 24 | } 25 | } 26 | 27 | return true; 28 | } 29 | 30 | QString Global::UserPath(int account) 31 | { 32 | return WorkPath + "/" + QString::number(account); 33 | } 34 | 35 | QString Global::UserHeadShot(int account) 36 | { 37 | return UserPath(account) + "/" + QString::number(account) + ".jpg"; 38 | } 39 | 40 | QString Global::UserFilePath(int account) 41 | { 42 | return UserPath(account) + "/FileRecv/"; 43 | } 44 | -------------------------------------------------------------------------------- /QQServer/global.h: -------------------------------------------------------------------------------- 1 | #ifndef GLOBAL_H 2 | #define GLOBAL_H 3 | 4 | #include 5 | 6 | #define BufferSize 1024 7 | #define HeadSize 10 8 | #define NoHeadBufSize 1014 9 | 10 | /* 11 | 文件取消传送可添加一个文件取消包头,收到后删除该端口号已接收的文件数据 12 | 或者直接发送文件尾,判断如果接收文件大小不等于客户端发送来的文件总大小也可 13 | */ 14 | //包头类型 15 | enum RecvType 16 | { 17 | JsonDataHead = 50, 18 | FileInfoHead, 19 | FileDataHead, 20 | FileEndDataHead 21 | }; 22 | 23 | //Json数据中信息类型 24 | enum InforType 25 | { 26 | Registration = 1125, 27 | FindPwd, 28 | LoginAcc, 29 | SearchFri, 30 | AddFri, 31 | ChangeOnlSta, 32 | SendMsg, 33 | AskForData, 34 | UserChangeData, 35 | UpdateHeadShot, 36 | SendFileToFri 37 | }; 38 | 39 | //保存用户资料编号 40 | enum UserMsg 41 | { 42 | ennickname = 0, 43 | ensignature, 44 | ensex, 45 | enage, 46 | enbirthday, 47 | enlocation, 48 | enblood_type, 49 | enwork, ensch_comp 50 | }; 51 | 52 | class Global 53 | { 54 | public: 55 | static void GetWorkPath(QString path); //获取工作目录用户数据文件夹 56 | static bool CreateWorkPath(); //创建工作目录用户数据文件夹 57 | static QString UserPath(int account); //返回该用户数据文件夹位置 58 | static QString UserHeadShot(int account); //返回该用户头像位置 59 | static QString UserFilePath(int account); //返回该用户文件接收文件夹位置 60 | private: 61 | static QString WorkPath; //工作目录用户数据文件夹 62 | }; 63 | 64 | #endif // GLOBAL_H 65 | -------------------------------------------------------------------------------- /QQServer/lib.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | lib/QQ.png 4 | lib/DefaultHead/touxiang1.jpg 5 | lib/DefaultHead/touxiang2.jpg 6 | lib/DefaultHead/touxiang3.jpg 7 | lib/DefaultHead/touxiang4.jpg 8 | lib/DefaultHead/touxiang5.jpg 9 | lib/DefaultHead/touxiang6.jpg 10 | lib/DefaultHead/touxiang7.jpg 11 | lib/back.jpg 12 | lib/DefaultHead/touxiang8.jpg 13 | lib/DefaultHead/touxiang9.jpg 14 | lib/DefaultHead/touxiang10.jpg 15 | lib/DefaultHead/touxiang11.jpg 16 | lib/DefaultHead/touxiang12.jpg 17 | lib/DefaultHead/touxiang13.jpg 18 | lib/DefaultHead/touxiang14.jpg 19 | lib/DefaultHead/touxiang15.jpg 20 | lib/DefaultHead/touxiang16.jpg 21 | lib/DefaultHead/touxiang17.jpg 22 | 23 | 24 | -------------------------------------------------------------------------------- /QQServer/lib/DefaultHead/touxiang1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQServer/lib/DefaultHead/touxiang1.jpg -------------------------------------------------------------------------------- /QQServer/lib/DefaultHead/touxiang10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQServer/lib/DefaultHead/touxiang10.jpg -------------------------------------------------------------------------------- /QQServer/lib/DefaultHead/touxiang11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQServer/lib/DefaultHead/touxiang11.jpg -------------------------------------------------------------------------------- /QQServer/lib/DefaultHead/touxiang12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQServer/lib/DefaultHead/touxiang12.jpg -------------------------------------------------------------------------------- /QQServer/lib/DefaultHead/touxiang13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQServer/lib/DefaultHead/touxiang13.jpg -------------------------------------------------------------------------------- /QQServer/lib/DefaultHead/touxiang14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQServer/lib/DefaultHead/touxiang14.jpg -------------------------------------------------------------------------------- /QQServer/lib/DefaultHead/touxiang15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQServer/lib/DefaultHead/touxiang15.jpg -------------------------------------------------------------------------------- /QQServer/lib/DefaultHead/touxiang16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQServer/lib/DefaultHead/touxiang16.jpg -------------------------------------------------------------------------------- /QQServer/lib/DefaultHead/touxiang17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQServer/lib/DefaultHead/touxiang17.jpg -------------------------------------------------------------------------------- /QQServer/lib/DefaultHead/touxiang2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQServer/lib/DefaultHead/touxiang2.jpg -------------------------------------------------------------------------------- /QQServer/lib/DefaultHead/touxiang3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQServer/lib/DefaultHead/touxiang3.jpg -------------------------------------------------------------------------------- /QQServer/lib/DefaultHead/touxiang4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQServer/lib/DefaultHead/touxiang4.jpg -------------------------------------------------------------------------------- /QQServer/lib/DefaultHead/touxiang5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQServer/lib/DefaultHead/touxiang5.jpg -------------------------------------------------------------------------------- /QQServer/lib/DefaultHead/touxiang6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQServer/lib/DefaultHead/touxiang6.jpg -------------------------------------------------------------------------------- /QQServer/lib/DefaultHead/touxiang7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQServer/lib/DefaultHead/touxiang7.jpg -------------------------------------------------------------------------------- /QQServer/lib/DefaultHead/touxiang8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQServer/lib/DefaultHead/touxiang8.jpg -------------------------------------------------------------------------------- /QQServer/lib/DefaultHead/touxiang9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQServer/lib/DefaultHead/touxiang9.jpg -------------------------------------------------------------------------------- /QQServer/lib/QQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQServer/lib/QQ.png -------------------------------------------------------------------------------- /QQServer/lib/back.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fantasy714/-Qt5-C-S-QQ-/66fb6de7cfa0b54a383c12176ad3b52fde362577/QQServer/lib/back.jpg -------------------------------------------------------------------------------- /QQServer/main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include "global.h" 3 | #include 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | QApplication a(argc, argv); 8 | Global::GetWorkPath(QApplication::applicationDirPath() + "/usersdata"); //用户数据文件夹 9 | MainWindow w; 10 | w.show(); 11 | return a.exec(); 12 | } 13 | -------------------------------------------------------------------------------- /QQServer/mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include "ui_mainwindow.h" 3 | #include 4 | #include 5 | #include 6 | 7 | MainWindow::MainWindow(QWidget* parent) 8 | : QMainWindow(parent) 9 | , ui(new Ui::MainWindow) 10 | { 11 | ui->setupUi(this); 12 | 13 | //若无本地数据文件夹则创建 14 | Global::CreateWorkPath(); 15 | 16 | if(!m_sqldata.connectToSql()) 17 | { 18 | qDebug() << "数据库连接失败"; 19 | return; 20 | } 21 | 22 | //创建tcp线程和任务类并连接信号槽后开始监听 23 | m_TcpTd = new QThread; 24 | m_TcpTask = new TcpThread; 25 | connect(this, &MainWindow::StartListen, m_TcpTask, &TcpThread::StartListen); 26 | connect(m_TcpTask, &TcpThread::NewCltConnected, this, &MainWindow::ServerhasNewConnection); 27 | connect(m_TcpTask, &TcpThread::CltDisConnected, this, &MainWindow::CltDisConnected); 28 | m_TcpTask->moveToThread(m_TcpTd); 29 | m_TcpTd->start(); 30 | emit StartListen(); 31 | ui->PlainTextEdit->appendPlainText(QString("服务器开始监听...")); 32 | 33 | //创建工作线程及任务类 34 | m_WorkTd = new QThread; 35 | m_WorkTask = new WorkThread; 36 | connect(m_TcpTask, &TcpThread::RecvFinished, m_WorkTask, &WorkThread::SplitDataPackAge); 37 | connect(m_WorkTask, &WorkThread::ThreadbackMsg, this, &MainWindow::getThreadMsg); 38 | connect(m_WorkTask, &WorkThread::UserOnLine, m_TcpTask, &TcpThread::UserOnLine); 39 | connect(m_WorkTask, &WorkThread::SendMsgToClt, m_TcpTask, &TcpThread::SendReply); 40 | m_WorkTask->moveToThread(m_WorkTd); 41 | m_WorkTd->start(); 42 | } 43 | 44 | MainWindow::~MainWindow() 45 | { 46 | m_TcpTd->quit(); 47 | m_TcpTd->wait(); 48 | m_TcpTd->deleteLater(); 49 | m_TcpTask->~TcpThread(); 50 | m_TcpTask->deleteLater(); 51 | 52 | m_WorkTd->quit(); 53 | m_WorkTd->wait(); 54 | m_WorkTd->deleteLater(); 55 | m_WorkTask->~WorkThread(); 56 | m_WorkTask->deleteLater(); 57 | 58 | delete ui; 59 | } 60 | 61 | void MainWindow::ServerhasNewConnection(quint16 port) 62 | { 63 | ui->PlainTextEdit->appendPlainText(QString("有新客户端连接到服务器,服务器分配的端口号为: " + QString::number(port))); 64 | //已连接客户端数加一 65 | ui->Connect->setText(QString::number(ui->Connect->text().toInt() + 1)); 66 | } 67 | 68 | void MainWindow::CltDisConnected(quint16 port, int acc) 69 | { 70 | //若已登陆则将在线状态恢复为离线 71 | QString str = "客户端断开连接,断开连接的客户端端口号为: " + QString::number(port); 72 | 73 | if(acc != 0) 74 | { 75 | m_sqldata.ChangeOnlineSta(acc, "离线"); 76 | str += ", 该客户端账号为: " + QString::number(acc); 77 | ui->Online->setText(QString::number(ui->Online->text().toInt() - 1)); 78 | } 79 | 80 | ui->PlainTextEdit->appendPlainText(str); 81 | ui->Connect->setText(QString::number(ui->Connect->text().toInt() - 1)); 82 | } 83 | 84 | void MainWindow::getThreadMsg(int type, int account, QString msg) 85 | { 86 | //将客户端注册登录相关操作信息显示到服务端界面上 87 | switch(type) 88 | { 89 | case Registration: 90 | { 91 | QString res = "用户注册 >>> 账号: " + QString::number(account) + "," + msg; 92 | ui->PlainTextEdit->appendPlainText(res); 93 | break; 94 | } 95 | 96 | case FindPwd: 97 | { 98 | if(msg == "") 99 | { 100 | QString res = "用户找回密码 >>> 账号: " + QString::number(account) + ",找回密码失败"; 101 | ui->PlainTextEdit->appendPlainText(res); 102 | } 103 | else 104 | { 105 | QString res = "用户找回密码 >>> 账号: " + QString::number(account) + ",找回密码成功"; //+",密码为:" + msg; 106 | ui->PlainTextEdit->appendPlainText(res); 107 | } 108 | 109 | break; 110 | } 111 | 112 | case LoginAcc: 113 | { 114 | QString res = "用户登录 >>> 账号: " + QString::number(account) + "," + msg; 115 | ui->PlainTextEdit->appendPlainText(res); 116 | 117 | if(msg == "登录成功") 118 | { 119 | ui->Online->setText(QString::number(ui->Online->text().toInt() + 1)); 120 | } 121 | 122 | break; 123 | } 124 | 125 | case ChangeOnlSta: 126 | { 127 | QString res = "用户更新在线状态 >>> 账号:" + QString::number(account); 128 | ui->PlainTextEdit->appendPlainText(res); 129 | break; 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /QQServer/mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | #include 6 | #include "qqsqldata.h" 7 | #include "workthread.h" 8 | #include "tcpthread.h" 9 | #include "global.h" 10 | 11 | QT_BEGIN_NAMESPACE 12 | namespace Ui 13 | { 14 | class MainWindow; 15 | } 16 | QT_END_NAMESPACE 17 | 18 | class MainWindow : public QMainWindow 19 | { 20 | Q_OBJECT 21 | 22 | public: 23 | MainWindow(QWidget* parent = nullptr); 24 | ~MainWindow(); 25 | public slots: 26 | void ServerhasNewConnection(quint16); //服务器有新连接 27 | void CltDisConnected(quint16, int); //客户端断开连接 28 | public slots: 29 | void getThreadMsg(int type, int account, QString msg); //从工作线程中获取客户端的请求内容 30 | signals: 31 | void StartListen(); //开始监听 32 | private: 33 | Ui::MainWindow* ui; 34 | 35 | Qqsqldata m_sqldata; //数据库 36 | 37 | QThread* m_TcpTd; //套接字线程 38 | TcpThread* m_TcpTask; //套接字线程任务类 39 | 40 | QThread* m_WorkTd; //工作线程 41 | WorkThread* m_WorkTask; //工作线程任务类 42 | }; 43 | #endif // MAINWINDOW_H 44 | -------------------------------------------------------------------------------- /QQServer/mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 850 10 | 750 11 | 12 | 13 | 14 | 15 | 850 16 | 750 17 | 18 | 19 | 20 | 21 | 1300 22 | 750 23 | 24 | 25 | 26 | QQ服务器 27 | 28 | 29 | 30 | :/lib/QQ.png:/lib/QQ.png 31 | 32 | 33 | false 34 | 35 | 36 | 37 | 38 | 0 39 | 750 40 | 41 | 42 | 43 | 44 | 16777215 45 | 750 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 382 54 | 0 55 | 56 | 57 | 58 | 59 | 382 60 | 222222 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 380 69 | 650 70 | 71 | 72 | 73 | 74 | 380 75 | 650 76 | 77 | 78 | 79 | 80 | 81 | 82 | :/lib/back.jpg 83 | 84 | 85 | true 86 | 87 | 88 | 89 | 90 | 91 | 92 | 0 93 | 94 | 95 | 96 | 97 | 98 | 0 99 | 30 100 | 101 | 102 | 103 | 104 | 16777215 105 | 30 106 | 107 | 108 | 109 | 当前连接客户端数 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 0 118 | 30 119 | 120 | 121 | 122 | 123 | 16777215 124 | 30 125 | 126 | 127 | 128 | 0 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 0 137 | 30 138 | 139 | 140 | 141 | 142 | 16777215 143 | 30 144 | 145 | 146 | 147 | 当前已登录用户数: 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 0 156 | 30 157 | 158 | 159 | 160 | 161 | 16777215 162 | 30 163 | 164 | 165 | 166 | 0 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 0 180 | 0 181 | 182 | 183 | 184 | 185 | 16777215 186 | 16777215 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | -------------------------------------------------------------------------------- /QQServer/qqsqldata.cpp: -------------------------------------------------------------------------------- 1 | #include "qqsqldata.h" 2 | #include 3 | #include 4 | #include 5 | 6 | Qqsqldata::Qqsqldata(QObject* parent) : QObject(parent) 7 | { 8 | 9 | } 10 | 11 | bool Qqsqldata::connectToSql() 12 | { 13 | db = QSqlDatabase::addDatabase("QODBC"); 14 | db.setHostName("127.0.0.1"); 15 | db.setPort(3306); 16 | db.setDatabaseName("qqdata"); 17 | db.setUserName("root"); 18 | db.setPassword("123456"); 19 | bool ok = db.open(); 20 | 21 | if(!ok) 22 | { 23 | return false; 24 | } 25 | else 26 | { 27 | result = (QSqlQuery)db; //进行绑定 此后可以使用query对象对数据库进行操作 28 | return true; 29 | } 30 | } 31 | 32 | QString Qqsqldata::Addaccount(int account, QString pwd) 33 | { 34 | bool success = result.exec(QString("insert into qqaccount(account,pwd) value(%1,'%2');").arg(account).arg(pwd)); 35 | 36 | if(success) 37 | { 38 | //成功则创建数据库好友列表 39 | CreateFriends(account); 40 | //设置随机数种子 41 | qsrand(QTime(0, 0, 0).secsTo(QTime::currentTime())); 42 | int photoId = rand() % 17 + 1; //随机分配一个默认头像编号 43 | QString headshot = ":/lib/DefaultHead/touxiang" + QString::number(photoId) + ".jpg"; 44 | return headshot; 45 | } 46 | else 47 | { 48 | return ""; 49 | } 50 | } 51 | 52 | bool Qqsqldata::Deleteaccount(int account) 53 | { 54 | bool success = result.exec(QString("delete from qqaccount where account = %1;").arg(account)); 55 | return success; 56 | } 57 | 58 | QString Qqsqldata::FindPwd(int account) 59 | { 60 | QString pwd = ""; 61 | result.exec(QString("select pwd from qqaccount where account = %1;").arg(account)); 62 | 63 | if(!result.next()) 64 | { 65 | qDebug() << "找回密码失败"; 66 | } 67 | 68 | pwd = result.value("pwd").toString(); 69 | return pwd; 70 | } 71 | 72 | //验证登录账户密码是否错误,返回1为登录成功,返回0为重复登录,返回-1为账号密码错误 73 | int Qqsqldata::LoginVerification(int acc, QString pwd) 74 | { 75 | result.exec(QString("select pwd,onlinestatus from qqaccount where account = %1;").arg(acc)); 76 | 77 | if(!result.next()) 78 | { 79 | qDebug() << "账户错误"; 80 | return -1; 81 | } 82 | 83 | QString spwd = result.value("pwd").toString(); 84 | 85 | if(spwd != pwd) 86 | { 87 | qDebug() << "密码错误"; 88 | return -1; 89 | } 90 | 91 | QString isonl = result.value("onlinestatus").toString(); 92 | 93 | if(isonl != "离线") 94 | { 95 | qDebug() << "重复登录"; 96 | return 0; 97 | } 98 | 99 | bool suc = result.exec(QString("update qqaccount set onlinestatus = '在线' where account = %1;").arg(acc)); 100 | 101 | if(!suc) 102 | { 103 | qDebug() << "在线状态更新失败"; 104 | } 105 | 106 | return 1; 107 | } 108 | 109 | QStringList Qqsqldata::UserMessages(int acc) 110 | { 111 | QStringList userDatas; 112 | result.exec(QString("select nickname,signature,sex,age,birthday,location,blood_type,work,sch_comp from qqaccount where account = %1;").arg(acc)); 113 | 114 | if(!result.next()) 115 | { 116 | qDebug() << "查找资料失败"; 117 | return userDatas; 118 | } 119 | 120 | QString userD; 121 | qDebug() << result.size(); 122 | 123 | for(int i = 0; i < 9; i++) 124 | { 125 | userD = result.value(i).toString(); 126 | userDatas.append(userD); 127 | //qDebug() << userD; 128 | } 129 | 130 | return userDatas; 131 | } 132 | 133 | bool Qqsqldata::ChangeUserMessages(int acc, QStringList uD) 134 | { 135 | for(auto d : uD) 136 | { 137 | qDebug() << d; 138 | } 139 | 140 | bool suc = result.exec(QString("update qqaccount set nickname = '%1',signature = '%2',sex = '%3',age = %4,birthday = '%5',location = '%6',blood_type = '%7',work = '%8',sch_comp = '%9' " 141 | "where account = %10;").arg(uD.at(ennickname)).arg(uD.at(ensignature)).arg(uD.at(ensex)).arg(uD.at(enage).toInt()).arg(uD.at(enbirthday)).arg(uD.at(enlocation)).arg(uD.at(enblood_type)).arg(uD.at(enwork)).arg(uD.at(ensch_comp)).arg(acc)); 142 | return suc; 143 | } 144 | 145 | bool Qqsqldata::ChangeOnlineSta(int acc, QString sta) 146 | { 147 | bool suc = result.exec(QString("update qqaccount set onlinestatus = '%1' where account = %2;").arg(sta).arg(acc)); 148 | 149 | if(!suc) 150 | { 151 | qDebug() << "在线状态更新失败"; 152 | } 153 | 154 | return suc; 155 | } 156 | 157 | bool Qqsqldata::CreateFriends(int acc) 158 | { 159 | bool res = result.exec(QString("insert into qqfriends(account,friends)value(%1,'')").arg(acc)); 160 | 161 | if(!res) 162 | { 163 | qDebug() << "创建数据库好友列表失败"; 164 | } 165 | 166 | return res; 167 | } 168 | 169 | bool Qqsqldata::AddFriend(int acc1, int acc2) 170 | { 171 | //更新第一个用户的好友列表 172 | result.exec(QString("select friends from qqfriends where account = %1;").arg(acc1)); 173 | 174 | if(!result.next()) 175 | { 176 | qDebug() << "acc1查找好友失败!"; 177 | return false; 178 | } 179 | 180 | QString fris1 = result.value("friends").toString(); 181 | fris1 += QString::number(acc2) + ","; 182 | bool res = result.exec(QString("update qqfriends set friends = '%1' where account = %2;").arg(fris1).arg(acc1)); 183 | 184 | if(res == false) 185 | { 186 | qDebug() << "更改好友列表失败"; 187 | return false; 188 | } 189 | 190 | //更新第二个用户的好友列表 191 | result.exec(QString("select friends from qqfriends where account = %1;").arg(acc2)); 192 | 193 | if(!result.next()) 194 | { 195 | qDebug() << "acc1查找好友失败!"; 196 | return false; 197 | } 198 | 199 | QString fris2 = result.value("friends").toString(); 200 | fris2 += QString::number(acc1) + ","; 201 | res = result.exec(QString("update qqfriends set friends = '%1' where account = %2;").arg(fris2).arg(acc2)); 202 | 203 | if(res == false) 204 | { 205 | qDebug() << "更改好友列表失败"; 206 | return false; 207 | } 208 | 209 | return true; 210 | } 211 | 212 | bool Qqsqldata::DelFriend(int acc1, int acc2) 213 | { 214 | //更新第一个用户的好友列表 215 | result.exec(QString("select friends from qqfriends where account = %1;").arg(acc1)); 216 | 217 | if(!result.next()) 218 | { 219 | qDebug() << "acc1查找好友失败!"; 220 | return false; 221 | } 222 | 223 | //获取好友列表字符串 224 | QString fris1 = result.value("friends").toString(); 225 | //获取每个好友的账号 226 | QStringList friends1 = fris1.split(","); 227 | //删除好友的账号 228 | friends1.removeOne(QString::number(acc2)); 229 | 230 | //清空并重新将其他好友加入字符串 231 | fris1.clear(); 232 | 233 | for(auto f : friends1) 234 | { 235 | fris1.append(f); 236 | fris1.append(","); 237 | } 238 | 239 | //更新 240 | bool res = result.exec(QString("update qqfriends set friends = '%1' where account = %2;").arg(fris1).arg(acc1)); 241 | 242 | if(res == false) 243 | { 244 | qDebug() << "更改好友列表失败"; 245 | return false; 246 | } 247 | 248 | //更新第二个用户的好友列表 249 | result.exec(QString("select friends from qqfriends where account = %1;").arg(acc2)); 250 | 251 | if(!result.next()) 252 | { 253 | qDebug() << "acc2查找好友失败!"; 254 | return false; 255 | } 256 | 257 | //获取好友列表字符串 258 | QString fris2 = result.value("friends").toString(); 259 | //获取每个好友的账号 260 | QStringList friends2 = fris2.split(","); 261 | //删除好友的账号 262 | friends2.removeOne(QString::number(acc1)); 263 | 264 | //清空并重新将其他好友加入字符串 265 | fris2.clear(); 266 | 267 | for(auto f : friends2) 268 | { 269 | fris2.append(f); 270 | fris2.append(","); 271 | } 272 | 273 | //更新 274 | res = result.exec(QString("update qqfriends set friends = '%1' where account = %2;").arg(fris2).arg(acc2)); 275 | 276 | if(res == false) 277 | { 278 | qDebug() << "更改好友列表失败"; 279 | return false; 280 | } 281 | 282 | return true; 283 | } 284 | 285 | QVector Qqsqldata::ReturnFris(int acc) 286 | { 287 | result.exec(QString("select friends from qqfriends where account = %1;").arg(acc)); 288 | 289 | if(!result.next()) 290 | { 291 | qDebug() << "查找好友失败"; 292 | } 293 | 294 | QStringList frislist = result.value("friends").toString().split(","); 295 | QVector v; 296 | 297 | for(auto fri : frislist) 298 | { 299 | v.push_back(fri.toInt()); 300 | qDebug() << fri.toInt(); 301 | } 302 | 303 | return v; 304 | } 305 | 306 | QString Qqsqldata::OnLineSta(int acc) 307 | { 308 | result.exec(QString("select onlinestatus from qqaccount where account = %1;").arg(acc)); 309 | 310 | if(!result.next()) 311 | { 312 | qDebug() << "无该账号"; 313 | return ""; 314 | } 315 | 316 | QString sta = result.value("onlinestatus").toString(); 317 | return sta; 318 | } 319 | -------------------------------------------------------------------------------- /QQServer/qqsqldata.h: -------------------------------------------------------------------------------- 1 | #ifndef QQSQLDATA_H 2 | #define QQSQLDATA_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "global.h" 8 | 9 | class Qqsqldata : public QObject 10 | { 11 | Q_OBJECT 12 | public: 13 | explicit Qqsqldata(QObject* parent = nullptr); 14 | bool connectToSql(); //连接数据库 15 | 16 | /* 用户未登录时操作 */ 17 | QString Addaccount(int account, QString pwd); //注册新用户 18 | bool CreateFriends(int acc); //注册成功时在数据库创建该用户的好友列表 19 | QString FindPwd(int account); //找回密码 20 | int LoginVerification(int acc, QString pwd); //登录 21 | 22 | /* 用户登录后操作 */ 23 | QVector ReturnFris(int acc); //返回该用户的所有好友 24 | QStringList UserMessages(int acc); //返回用户资料 25 | bool ChangeUserMessages(int acc, QStringList uD); //更改用户资料 26 | bool AddFriend(int acc1, int acc2); //添加好友 27 | bool DelFriend(int acc1, int acc2); //删除好友 28 | 29 | /* 其他 */ 30 | bool ChangeOnlineSta(int acc, QString sta); //更改在线状态 31 | QString OnLineSta(int acc); //返回该用户的在线状态 32 | bool Deleteaccount(int account); //注销账户 33 | signals: 34 | 35 | private: 36 | QSqlDatabase db; //连接数据库 37 | QSqlQuery result; //操作数据库 38 | }; 39 | 40 | #endif // QQSQLDATA_H 41 | -------------------------------------------------------------------------------- /QQServer/sqldata.cpp: -------------------------------------------------------------------------------- 1 | #include "sqldata.h" 2 | #include 3 | 4 | SqlData::SqlData(QObject * parent) 5 | { 6 | 7 | } 8 | 9 | void SqlData::ConnectSql() 10 | { 11 | db = QSqlDatabase::addDatabase("QODBC"); 12 | db.setHostName("127.0.0.1"); 13 | db.setPort(3306); 14 | db.setDatabaseName("qqdata"); 15 | db.setUserName("root"); 16 | db.setPassword("123456"); 17 | bool ok = db.open(); 18 | if(!ok){ 19 | qDebug() << "数据库启动失败"; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /QQServer/sqldata.h: -------------------------------------------------------------------------------- 1 | #ifndef SQLDATA_H 2 | #define SQLDATA_H 3 | 4 | #include 5 | #include 6 | 7 | class SqlData 8 | { 9 | Q_OBJECT 10 | public: 11 | SqlData(QObject * parent); 12 | void ConnectSql(); 13 | private: 14 | QSqlDatabase db; 15 | }; 16 | 17 | #endif // SQLDATA_H 18 | -------------------------------------------------------------------------------- /QQServer/tcpthread.cpp: -------------------------------------------------------------------------------- 1 | #include "tcpthread.h" 2 | #include 3 | #include 4 | #include 5 | 6 | TcpThread::TcpThread(QObject* parent) : QObject(parent) 7 | { 8 | 9 | } 10 | 11 | TcpThread::~TcpThread() 12 | { 13 | for(auto onl : m_onlines) 14 | { 15 | int acc = m_onlines.key(onl); 16 | m_sql.ChangeOnlineSta(acc, "离线"); 17 | } 18 | 19 | m_onlines.clear(); 20 | 21 | for(auto sock : m_sockets) 22 | { 23 | sock->close(); 24 | sock->deleteLater(); 25 | } 26 | 27 | m_sockets.clear(); 28 | 29 | m_serv->close(); 30 | m_serv->deleteLater(); 31 | qDebug() << "套接字线程退出"; 32 | } 33 | 34 | void TcpThread::StartListen() 35 | { 36 | //服务器开始监听 37 | m_serv = new QTcpServer; 38 | m_serv->listen(QHostAddress::Any, m_port); 39 | connect(m_serv, &QTcpServer::newConnection, this, &TcpThread::ServerhasNewConnection); 40 | } 41 | 42 | void TcpThread::ServerhasNewConnection() 43 | { 44 | //获取套接字后加入以该套接字端口号为key的哈希表中 45 | QTcpSocket* sock = m_serv->nextPendingConnection(); 46 | quint16 port = sock->peerPort(); 47 | //qDebug() << "服务器分配的端口号为:" << port; 48 | emit NewCltConnected(port); 49 | connect(sock, &QTcpSocket::readyRead, this, &TcpThread::ReadDataFromClt); 50 | connect(sock, &QTcpSocket::disconnected, this, &TcpThread::CltDisconnected); 51 | m_sockets.insert(port, sock); 52 | } 53 | 54 | void TcpThread::CltDisconnected() 55 | { 56 | QTcpSocket* sock = (QTcpSocket*)sender(); 57 | int acc = m_onlines.key(sock); 58 | qDebug() << acc << "用户退出"; 59 | 60 | //从在线用户哈希表中删除 61 | if(acc != 0) 62 | { 63 | m_onlines.remove(acc); 64 | } 65 | 66 | emit CltDisConnected(sock->peerPort(), acc); 67 | m_sockets.remove(sock->peerPort()); 68 | sock->close(); 69 | sock->deleteLater(); 70 | } 71 | 72 | void TcpThread::UserOnLine(int acc, quint16 sockport) 73 | { 74 | QTcpSocket* thissock = m_sockets[sockport]; 75 | m_onlines.insert(acc, thissock); 76 | qDebug() << "账号" << acc << "成功加入在线用户哈希表"; 77 | } 78 | 79 | void TcpThread::ReadDataFromClt() 80 | { 81 | QTcpSocket* sock = (QTcpSocket*)sender(); 82 | 83 | //读取包头或若仍有未读取完毕的数据则跳过直接读取数据 84 | if(sock->bytesAvailable()) 85 | { 86 | quint16 port = sock->peerPort(); 87 | QByteArray RecvData = sock->readAll(); 88 | int dataSize = RecvData.size(); 89 | //qDebug() << "当前已存储数据大小: " << m_buffer.size() << "此次读取数据大小: " << dataSize; 90 | 91 | //刚好接收到数据包大小数据,且之前无未组成数据包的数据 92 | if(dataSize == BufferSize && m_buffer.size() == 0) 93 | { 94 | emit RecvFinished(RecvData, port); 95 | //qDebug() << "直接收到数据包,处理..."; 96 | return; 97 | } 98 | //刚好收到数据包大小数据但已保存有数据则拼接处理后再拼接 99 | else if(dataSize == BufferSize && m_buffer.size() > 0) 100 | { 101 | int MergeSize = BufferSize - m_buffer.size(); 102 | QByteArray MergeData = RecvData.left(MergeSize); 103 | RecvData.remove(0, MergeSize); 104 | 105 | m_buffer.append(MergeData); 106 | 107 | if(m_buffer.size() == BufferSize) 108 | { 109 | emit RecvFinished(m_buffer, port); 110 | m_buffer.clear(); 111 | //qDebug() << "已拼接数据,处理中..."; 112 | } 113 | 114 | m_buffer.append(RecvData); 115 | //qDebug() << "拼接后余下数据为: " << m_buffer.size(); 116 | return; 117 | } 118 | 119 | //两次数据拼接后刚好够数据包大小 120 | if((m_buffer.size() + dataSize) == BufferSize) 121 | { 122 | m_buffer.append(RecvData); 123 | emit RecvFinished(m_buffer, port); 124 | m_buffer.clear(); 125 | //qDebug() << "拼接后刚好够数据包大小"; 126 | return; 127 | } 128 | 129 | //两次数据相加不够一个数据包大小,直接拼接 130 | if((m_buffer.size() + dataSize) < BufferSize) 131 | { 132 | m_buffer.append(RecvData); 133 | //qDebug() << "拼接"; 134 | return; 135 | } 136 | 137 | //若两次数据相加超过单个数据包大小则分包处理 138 | if((m_buffer.size() + dataSize) > BufferSize) 139 | { 140 | //若无已保存数据且本次读取数据大于单个数据包大小则直接分包处理 141 | if((dataSize > BufferSize) && (m_buffer.size() == 0)) 142 | { 143 | while(dataSize / BufferSize) 144 | { 145 | QByteArray package = RecvData.left(BufferSize); 146 | RecvData.remove(0, BufferSize); 147 | 148 | emit RecvFinished(package, port); 149 | dataSize = RecvData.size(); 150 | 151 | if((dataSize / BufferSize == 0) && (dataSize != 0)) 152 | { 153 | m_buffer.append(RecvData); 154 | } 155 | } 156 | 157 | return; 158 | } 159 | 160 | //若本次读取数据大于单包大小且有已保存数据则先合并之前保存的数据后再分包处理 161 | if((dataSize > BufferSize) && (m_buffer.size() > 0)) 162 | { 163 | int MergeSize = BufferSize - m_buffer.size(); 164 | QByteArray MergeData = RecvData.left(MergeSize); 165 | RecvData.remove(0, MergeSize); 166 | 167 | m_buffer.append(MergeData); 168 | 169 | if(m_buffer.size() == BufferSize) 170 | { 171 | emit RecvFinished(m_buffer, port); 172 | m_buffer.clear(); 173 | //qDebug() << "已拼接数据,处理中..."; 174 | } 175 | 176 | dataSize = RecvData.size(); 177 | 178 | while(dataSize / BufferSize) 179 | { 180 | QByteArray package = RecvData.left(BufferSize); 181 | RecvData.remove(0, BufferSize); 182 | 183 | emit RecvFinished(package, port); 184 | dataSize = RecvData.size(); 185 | 186 | if((dataSize / BufferSize == 0) && (dataSize != 0)) 187 | { 188 | m_buffer.append(RecvData); 189 | } 190 | } 191 | 192 | return; 193 | } 194 | } 195 | } 196 | else 197 | { 198 | return; 199 | } 200 | } 201 | 202 | void TcpThread::SendReply(quint16 port, int type, int acc, int targetacc, QByteArray jsonData, QString msgtype, QString fileName) 203 | { 204 | //发送文件数量 205 | int fileNum = 0; 206 | //发送的账号,接收端依据账号和信息类型判断文件存放的文件夹 207 | int sendAcc = -1; 208 | QTcpSocket* sock; 209 | 210 | //若需要转发则要获取目标客户端套接字 211 | if(msgtype == "发送好友申请" || msgtype == "成功删除好友" || type == SendMsg || msgtype == "发送文件") 212 | { 213 | sock = m_onlines[targetacc]; 214 | } 215 | else 216 | { 217 | sock = m_sockets[port]; 218 | 219 | if(msgtype == "第一次登录") //第一次登录需发送头像和好友json文件 220 | { 221 | sendAcc = acc; 222 | fileNum = 2; 223 | } 224 | 225 | if(msgtype == "接收文件") 226 | { 227 | //此处将账号设置为好友账号,用于接收文件时发送进度信号给对应的好友窗口 228 | sendAcc = targetacc; 229 | } 230 | } 231 | 232 | if(sock == nullptr) 233 | { 234 | qDebug() << "好友未上线"; 235 | return; 236 | } 237 | 238 | if(msgtype == "需更新头像") 239 | { 240 | sendAcc = acc; 241 | } 242 | 243 | //若文件名不为空则为发送文件 244 | if(fileName != "") 245 | { 246 | if(fileNum > 1) 247 | { 248 | //用?号分割文件名,因为?号不能作为文件名 249 | QStringList fileNames = fileName.split("?"); 250 | 251 | for(auto fileName : fileNames) 252 | { 253 | SendFile(sock, fileName, type, sendAcc); 254 | } 255 | } 256 | else 257 | { 258 | SendFile(sock, fileName, type, sendAcc); 259 | } 260 | } 261 | 262 | if(jsonData.size() != 0) 263 | { 264 | SendJson(sock, jsonData); 265 | } 266 | } 267 | 268 | void TcpThread::SendFile(QTcpSocket* sock, QString fileName, int infotype, int RecvAccount) 269 | { 270 | QByteArray dataPackage; 271 | QDataStream out(&dataPackage, QIODevice::WriteOnly); 272 | //获取文件总大小 273 | QFile file(fileName); 274 | qint64 fileSize = file.size(); 275 | 276 | //获取文件名 277 | QString fName = fileName.split("/").last(); 278 | qDebug() << "正在发送文件, 文件名: " << fName << ", 接收方账号: " << RecvAccount; 279 | //发送的文件头包大小 (QString用QDataStream序列化其大小会*2后+4) 280 | quint32 size = sizeof(infotype) + sizeof(RecvAccount) + sizeof(fileSize) + fName.size() * 2 + 4; 281 | 282 | //发送文件头数据 283 | out << quint16(FileInfoHead); 284 | out << quint32(0); 285 | out << size; 286 | out << infotype << RecvAccount << fileSize << fName; 287 | dataPackage.resize(BufferSize); 288 | 289 | sock->write(dataPackage); 290 | sock->waitForBytesWritten(); 291 | dataPackage.clear(); 292 | 293 | file.open(QFile::ReadOnly); 294 | 295 | //发送小文件 296 | if(fileSize < NoHeadBufSize) 297 | { 298 | qDebug() << "发送小文件"; 299 | //读取文件数据 300 | QByteArray fileData = file.readAll(); 301 | file.close(); 302 | 303 | /* 304 | 将QDataStream对象所关联的QByteArray对象清空后,用QDataStream对象去继续写入数据到QByteArray对象中, 305 | 结果并不如预期那样从位置0开始写入,而是从之前的位置开始写入,前面的数据呈现未定义状态 306 | */ 307 | QDataStream fileEndOut(&dataPackage, QIODevice::WriteOnly); 308 | 309 | fileEndOut << quint16(FileEndDataHead); 310 | fileEndOut << quint32(0); 311 | fileEndOut << quint32(fileData.size()); 312 | dataPackage.append(fileData); 313 | dataPackage.resize(BufferSize); 314 | 315 | sock->write(dataPackage); 316 | sock->waitForBytesWritten(); 317 | } 318 | else 319 | { 320 | qint64 lastPackSize = fileSize % NoHeadBufSize; 321 | int SendTimes = fileSize / NoHeadBufSize; 322 | 323 | if(lastPackSize == 0) 324 | { 325 | //如果文件大小刚好为BufferSize的整数倍 326 | //将普通文件数据包发送次数减一,最后一次改为发送文件包尾,将最后一次发送数据的大小设置为数据包的包大小 327 | SendTimes -= 1; 328 | lastPackSize = NoHeadBufSize; 329 | qDebug() << "文件总大小: " << fileSize << "总共需要发送" << SendTimes << "个数据包,文件尾数据包大小为: " << lastPackSize; 330 | } 331 | else 332 | { 333 | qDebug() << "文件总大小: " << fileSize << "总共需要发送" << SendTimes + 1 << "个数据包,文件尾数据包大小为: " << lastPackSize; 334 | } 335 | 336 | for(int i = 0; i < SendTimes; i++) 337 | { 338 | QByteArray fileData = file.read(NoHeadBufSize); 339 | QDataStream fileDataOut(&dataPackage, QIODevice::WriteOnly); 340 | 341 | fileDataOut << quint16(FileDataHead); 342 | fileDataOut << quint32(0); 343 | fileDataOut << quint32(NoHeadBufSize); 344 | dataPackage.append(fileData); 345 | 346 | sock->write(dataPackage); 347 | sock->waitForBytesWritten(); 348 | dataPackage.clear(); 349 | } 350 | 351 | QByteArray fileEnd = file.read(lastPackSize); 352 | file.close(); 353 | 354 | QDataStream fileEndOut(&dataPackage, QIODevice::WriteOnly); 355 | fileEndOut << quint16(FileEndDataHead); 356 | fileEndOut << quint32(0); 357 | fileEndOut << quint32(fileEnd.size()); 358 | dataPackage.append(fileEnd); 359 | 360 | if(dataPackage.size() < BufferSize) 361 | { 362 | dataPackage.resize(BufferSize); 363 | } 364 | 365 | sock->write(dataPackage); 366 | sock->waitForBytesWritten(); 367 | } 368 | } 369 | 370 | void TcpThread::SendJson(QTcpSocket* sock, QByteArray jsonData) 371 | { 372 | qDebug() << "正在发送Json数据"; 373 | QByteArray dataPackage; 374 | QDataStream out(&dataPackage, QIODevice::WriteOnly); 375 | out << quint16(JsonDataHead); 376 | out << quint32(0); 377 | out << quint32(jsonData.size()); 378 | dataPackage.append(jsonData); 379 | dataPackage.resize(BufferSize); 380 | 381 | sock->write(dataPackage); 382 | sock->waitForBytesWritten(); 383 | } 384 | -------------------------------------------------------------------------------- /QQServer/tcpthread.h: -------------------------------------------------------------------------------- 1 | #ifndef TCPTHREAD_H 2 | #define TCPTHREAD_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "qqsqldata.h" 8 | #include "global.h" 9 | 10 | class TcpThread : public QObject 11 | { 12 | Q_OBJECT 13 | public: 14 | explicit TcpThread(QObject* parent = nullptr); 15 | ~TcpThread(); 16 | public slots: 17 | void StartListen(); //开始监听 18 | void UserOnLine(int acc, quint16 sockport); //用户上线则加入在线哈希表中 19 | void SendReply(quint16 port, int type, int acc, int targetacc, QByteArray jsondata, QString msgtype, QString fileName); //发送数据给客户端 20 | protected: 21 | /* 客户端连接和断开连接 */ 22 | void ServerhasNewConnection(); //有新的客户端连接 23 | void CltDisconnected(); //有客户端断开连接 24 | 25 | /* 读取和发送数据相关 */ 26 | void ReadDataFromClt(); //读取数据 27 | void SendFile(QTcpSocket*, QString fileName, int infotype, int RecvAccount); //发送文件 28 | void SendJson(QTcpSocket*, QByteArray); //发送Json数据 29 | signals: 30 | void NewCltConnected(quint16); //有客户端连接到服务器 31 | void CltDisConnected(quint16, int); //客户端断开连接 32 | void RecvFinished(QByteArray byteArray, quint16 port); //信息接收完成 33 | 34 | private: 35 | /* 服务器 */ 36 | quint16 m_port = 9000; //服务器监听端口号 37 | QTcpServer* m_serv; 38 | 39 | /* 在线用户套接字 */ 40 | QHash m_sockets; //tcp通信套接字 41 | QHash m_onlines; //在线用户哈希表 42 | 43 | /* 数据操作 */ 44 | QByteArray m_buffer; //保存数据 45 | 46 | /* 数据库操作 */ 47 | Qqsqldata m_sql; //操作数据库 48 | }; 49 | 50 | #endif // TCPTHREAD_H 51 | -------------------------------------------------------------------------------- /QQServer/workthread.cpp: -------------------------------------------------------------------------------- 1 | #include "workthread.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | WorkThread::WorkThread(QObject* parent) : QObject(parent) 14 | { 15 | 16 | } 17 | 18 | WorkThread::~WorkThread() 19 | { 20 | for(auto f : m_RecvFiles) 21 | { 22 | f->close(); 23 | f->remove(); 24 | f->deleteLater(); 25 | } 26 | 27 | qDebug() << "工作线程退出"; 28 | } 29 | 30 | void WorkThread::SplitDataPackAge(QByteArray data, quint16 port) 31 | { 32 | //取出包头并从数据包中删除包头 33 | QByteArray head = data.left(10); 34 | data.remove(0, 10); 35 | QDataStream headD(&head, QIODevice::ReadOnly); 36 | 37 | //取出包头内容 38 | quint16 headType; 39 | quint32 Empty; 40 | quint32 totalBytes; 41 | headD >> headType >> Empty >> totalBytes; 42 | //qDebug() << "接收到数据的包头类型" << headType << "总字节数:" << totalBytes; 43 | 44 | switch(headType) 45 | { 46 | case JsonDataHead: 47 | emit ParseMsg(port, data.left(totalBytes)); 48 | break; 49 | 50 | case FileInfoHead: 51 | { 52 | QByteArray filehead = data.left(totalBytes); 53 | QDataStream fHead(&filehead, QIODevice::ReadOnly); 54 | quint32 infotype; 55 | int RecvAccount; 56 | qint64 fSize; 57 | QString fName; 58 | fHead >> infotype >> RecvAccount >> fSize >> fName; 59 | qDebug() << "接收类型: " << infotype << "接收方账号: " << RecvAccount 60 | << "接收文件大小: " << fSize << "接收文件名称: " << fName; 61 | m_FileSizes.insert(port, fSize); 62 | FilePackAgeCount.insert(port, 0); 63 | 64 | QString SaveFilePath; 65 | 66 | switch(infotype) 67 | { 68 | case SendMsg: 69 | { 70 | SaveFilePath = Global::UserFilePath(RecvAccount) + fName; 71 | QFile* file = new QFile(SaveFilePath); 72 | m_RecvFiles.insert(port, file); //加入文件操作哈希表 73 | file->open(QFile::WriteOnly | QFile::Truncate); 74 | } 75 | break; 76 | 77 | case UpdateHeadShot: 78 | { 79 | SaveFilePath = Global::UserHeadShot(RecvAccount); 80 | QFile* file = new QFile(SaveFilePath); 81 | m_RecvFiles.insert(port, file); 82 | file->open(QFile::Truncate | QFile::WriteOnly); 83 | } 84 | break; 85 | 86 | case SendFileToFri: 87 | { 88 | SaveFilePath = Global::UserFilePath(RecvAccount) + fName; 89 | QFile* file = new QFile(SaveFilePath); 90 | m_RecvFiles.insert(port, file); //加入文件操作哈希表 91 | file->open(QFile::WriteOnly | QFile::Truncate); 92 | } 93 | break; 94 | 95 | default: 96 | break; 97 | } 98 | } 99 | break; 100 | 101 | case FileDataHead: 102 | { 103 | FilePackAgeCount[port]++; 104 | m_RecvFiles[port]->write(data); 105 | /* 106 | if(FilePackAgeCount % 1000 == 0) 107 | { 108 | qDebug() << "已接收的文件数据大小: " << m_RecvFiles[port]->size() 109 | << "文件总大小: " << m_fileSize << "已接收数据包个数: " << FilePackAgeCount; 110 | } 111 | */ 112 | } 113 | break; 114 | 115 | case FileEndDataHead: 116 | { 117 | QFile* file = m_RecvFiles[port]; 118 | file->write(data.left(totalBytes)); 119 | QString result = file->size() == m_FileSizes[port] ? "文件接收成功!" : "文件接收失败!"; 120 | qDebug() << "接收文件包尾数据: " << totalBytes << "接收文件总大小: " << file->size() 121 | << "已接收数据包个数: " << FilePackAgeCount[port] + 1 << ", " << result; 122 | file->close(); 123 | m_RecvFiles.remove(port); 124 | m_FileSizes.remove(port); 125 | FilePackAgeCount.remove(port); 126 | file->deleteLater(); 127 | } 128 | break; 129 | 130 | default: 131 | break; 132 | } 133 | } 134 | 135 | void WorkThread::ParseMsg(quint16 port, QByteArray data) 136 | { 137 | m_port = port; 138 | QJsonObject obj = QJsonDocument::fromJson(data).object(); 139 | int type = obj.value("type").toInt(); 140 | int account; 141 | int targetacc; 142 | 143 | switch(type) 144 | { 145 | case FindPwd: 146 | { 147 | account = obj.value("account").toInt(); 148 | qDebug() << "客户端找回密码,需找回密码账号为" << account; 149 | recvFind(account); 150 | break; 151 | } 152 | 153 | case Registration: 154 | { 155 | account = obj.value("account").toInt(); 156 | QString pwd = obj.value("pwd").toString(); 157 | qDebug() << "客户端注册,注册账号:" << account << "密码:" << pwd; 158 | recvRegistered(account, pwd); 159 | break; 160 | } 161 | 162 | case LoginAcc: 163 | { 164 | qDebug() << "客户端请求登录"; 165 | bool isFirst = obj.value("isfirstlogin").toBool(); 166 | account = obj.value("account").toInt(); 167 | QString pwd = obj.value("pwd").toString(); 168 | CltLogin(account, pwd, isFirst); 169 | break; 170 | } 171 | 172 | case SearchFri: 173 | { 174 | qDebug() << "用户查找好友中"; 175 | account = obj.value("account").toInt(); 176 | SearchingFri(account); 177 | break; 178 | } 179 | 180 | case AddFri: 181 | { 182 | qDebug() << "收到好友申请"; 183 | account = obj.value("account").toInt(); 184 | targetacc = obj.value("targetaccount").toInt(); 185 | QString msgType = obj.value("msgtype").toString(); 186 | QString yanzheng = obj.value("yanzheng").toString(); 187 | CltAddFri(account, targetacc, msgType, yanzheng); 188 | break; 189 | } 190 | 191 | case ChangeOnlSta: 192 | { 193 | qDebug() << "改变在线状态"; 194 | account = obj.value("account").toInt(); 195 | QString onlsta = obj.value("onlinestatus").toString(); 196 | CltChangeOnlSta(account, onlsta); 197 | break; 198 | } 199 | 200 | case SendMsg: 201 | { 202 | qDebug() << "转发信息"; 203 | int acc = obj.value("account").toInt(); 204 | int targetAcc = obj.value("targetacc").toInt(); 205 | QString msgType = obj.value("msgtype").toString(); 206 | 207 | if(msgType == "发送图片") 208 | { 209 | QString fileN = obj.value("msg").toString(); 210 | QString filePath = Global::UserFilePath(targetAcc) + fileN; 211 | qDebug() << "转发图片中...文件路径: " << filePath << "文件名: " << fileN; 212 | ReplyToJson(SendMsg, fileN, msgType, acc, targetAcc, filePath); 213 | break; 214 | } 215 | 216 | QString msg = obj.value("msg").toString(); 217 | ReplyToJson(SendMsg, msg, msgType, acc, targetAcc); 218 | break; 219 | } 220 | 221 | case AskForData: 222 | { 223 | qDebug() << "请求个人资料"; 224 | int acc = obj.value("account").toInt(); 225 | QString isSelf = obj.value("msgtype").toString(); 226 | 227 | if(isSelf == "请求自己的") 228 | { 229 | AskForUserData(acc, isSelf); 230 | } 231 | //若请求的好友的则需查看该用户本地的好友头像是否和服务器端好友大小相同 232 | //相同即为好友未更改头像,不相同则需更新好友头像 233 | else 234 | { 235 | int size = obj.value("headSize").toInt(); 236 | AskForUserData(acc, isSelf, size); 237 | } 238 | 239 | break; 240 | } 241 | 242 | case UserChangeData: 243 | { 244 | qDebug() << "更改用户资料"; 245 | int acc = obj.value("account").toInt(); 246 | QString datas = obj.value("userdatas").toString(); 247 | ChangingUserDatas(acc, datas); 248 | break; 249 | } 250 | 251 | case SendFileToFri: 252 | { 253 | qDebug() << "转发文件"; 254 | int acc = obj.value("account").toInt(); 255 | int targetAcc = obj.value("targetacc").toInt(); 256 | QString msgType = obj.value("msgtype").toString(); 257 | QString fName = obj.value("fileName").toString(); 258 | 259 | if(msgType == "发送文件") 260 | { 261 | ReplyToJson(SendFileToFri, fName, msgType, acc, targetAcc); 262 | } 263 | else 264 | { 265 | //获取该用户请求的文件路径,仅发送文件即可,不发送数据 266 | QString filePath = Global::UserFilePath(acc) + fName; 267 | ReplyToJson(SendFileToFri, "", "接收文件", acc, targetAcc, filePath); 268 | } 269 | 270 | break; 271 | } 272 | } 273 | } 274 | 275 | void WorkThread::ReplyToJson(InforType type, QString Msg, QString MsgType, int acc, int targetacc, QString fileName) 276 | { 277 | QJsonObject obj; 278 | obj.insert("type", type); 279 | 280 | switch(type) 281 | { 282 | 283 | case FindPwd: 284 | { 285 | obj.insert("pwd", Msg); 286 | break; 287 | } 288 | 289 | case Registration: 290 | { 291 | obj.insert("result", Msg); 292 | break; 293 | } 294 | 295 | case LoginAcc: 296 | { 297 | obj.insert("isfirstlogin", MsgType); 298 | obj.insert("result", Msg); 299 | 300 | if(Msg == "登录成功") 301 | { 302 | obj.insert("signature", m_userDatas.at(ensignature)); 303 | 304 | if(MsgType == "第一次登录") 305 | { 306 | //如果为第一次登录则发送账号昵称,头像和好友信息 307 | obj.insert("nickname", m_userDatas.at(ennickname)); 308 | } 309 | } 310 | 311 | break; 312 | } 313 | 314 | case SearchFri: 315 | { 316 | obj.insert("result", Msg); 317 | 318 | //如果查找成功则添加用户信息及头像 319 | if(Msg == "查找成功") 320 | { 321 | //将需要的几个用户资料添加到字符串中并用##隔开 322 | QString uData = QString::number(targetacc) + "##" + m_userDatas.at(ennickname) + "##" + 323 | m_userDatas.at(ensex) + "##" + m_userDatas.at(enage) + "##" + m_userDatas.at(enlocation); 324 | 325 | obj.insert("userData", uData); 326 | } 327 | 328 | break; 329 | } 330 | 331 | case AddFri: 332 | { 333 | if(Msg == "该好友已下线") 334 | { 335 | obj.insert("result", Msg); 336 | obj.insert("targetaccount", targetacc); 337 | } 338 | else 339 | { 340 | if(MsgType == "发送好友申请") 341 | { 342 | //将需要的几个用户资料添加到字符串中并用##隔开 343 | QString uData = QString::number(acc) + "##" + m_userDatas.at(ennickname) + "##" + 344 | m_userDatas.at(ensex) + "##" + m_userDatas.at(enage) + "##" + m_userDatas.at(enlocation); 345 | 346 | obj.insert("userData", uData); 347 | obj.insert("msgtype", MsgType); 348 | obj.insert("yanzheng", Msg); 349 | } 350 | else if(MsgType == "成功删除好友") 351 | { 352 | obj.insert("friacc", acc); 353 | obj.insert("msgtype", MsgType); 354 | } 355 | } 356 | 357 | break; 358 | } 359 | 360 | case SendMsg: 361 | { 362 | obj.insert("acc", acc); 363 | obj.insert("msgType", MsgType); 364 | obj.insert("msg", Msg); 365 | 366 | if(MsgType == "添加好友成功") 367 | { 368 | qDebug() << acc << "添加" << targetacc << "成功!"; 369 | QStringList userDatas = sql.UserMessages(acc); 370 | QString uD = userDatas.at(ennickname) + "##" + userDatas.at(ensignature); 371 | obj.insert("userData", uD); 372 | } 373 | 374 | break; 375 | } 376 | 377 | case AskForData: 378 | { 379 | if(MsgType == "请求自己的") 380 | { 381 | obj.insert("account", acc); 382 | obj.insert("msgtype", MsgType); 383 | obj.insert("userdatas", Msg); 384 | } 385 | else 386 | { 387 | obj.insert("account", acc); 388 | obj.insert("userdatas", Msg); 389 | 390 | //将拼接的查找类型和是否更新头像结果拆分 391 | QStringList res = MsgType.split("##"); 392 | QString isSelf = res.at(0); 393 | QString result = res.at(1); 394 | 395 | obj.insert("msgtype", isSelf); 396 | obj.insert("result", result); 397 | 398 | //将信息类型赋值为,需更新头像/不更新头像 399 | MsgType = result; 400 | qDebug() << MsgType; 401 | } 402 | 403 | break; 404 | } 405 | 406 | case SendFileToFri: 407 | { 408 | if(MsgType == "发送文件") 409 | { 410 | obj.insert("friacc", acc); 411 | obj.insert("msgType", MsgType); 412 | 413 | //获取文件大小添加在文件名后 414 | QFileInfo info(Global::UserFilePath(targetacc) + Msg); 415 | QString fileInfo = Msg + "?" + GetFileSize(info.size()); //此处用?隔开文件名和大小,方便接收端取出文件名 416 | obj.insert("fileInfo", fileInfo); 417 | } 418 | 419 | break; 420 | } 421 | 422 | default: 423 | break; 424 | } 425 | 426 | QJsonDocument doc(obj); 427 | QByteArray reply = doc.toJson(); 428 | 429 | emit SendMsgToClt(m_port, (int)type, acc, targetacc, reply, MsgType, fileName); 430 | } 431 | 432 | void WorkThread::recvRegistered(int acc, QString pwd) 433 | { 434 | QString iconName = sql.Addaccount(acc, pwd); 435 | 436 | //如果返回的头像名为空则为注册失败 437 | if(iconName == "") 438 | { 439 | //qDebug() << "账号重复,注册失败!"; 440 | ReplyToJson(Registration, "注册失败"); 441 | emit ThreadbackMsg((int)Registration, acc, "注册失败"); 442 | } 443 | else 444 | { 445 | //qDebug() << "注册成功!"; 446 | ReplyToJson(Registration, "注册成功"); 447 | emit ThreadbackMsg((int)Registration, acc, "注册成功"); 448 | //如果注册成功则创建以该用户账号为名的文件夹与好友列表文件和头像 449 | QString path = Global::UserPath(acc); 450 | QDir dir; 451 | dir.mkdir(path); 452 | //创建文件接收文件夹 453 | dir.mkdir(path + "/FileRecv"); 454 | //创建好友列表文件 455 | QString pathJ = path + "/friends.json"; 456 | QFile file(pathJ); 457 | file.open(QFile::WriteOnly); 458 | 459 | //创建默认好友分组 460 | QJsonArray jsarr; 461 | QJsonObject obj1; 462 | obj1.insert("name", "我的好友"); 463 | QJsonObject obj2; 464 | obj2.insert("name", "朋友"); 465 | QJsonObject obj3; 466 | obj3.insert("name", "家人"); 467 | QJsonObject obj4; 468 | obj4.insert("name", "同学"); 469 | jsarr.append(obj1); 470 | jsarr.append(obj2); 471 | jsarr.append(obj3); 472 | jsarr.append(obj4); 473 | 474 | QJsonDocument doc(jsarr); 475 | file.write(doc.toJson()); 476 | file.close(); 477 | 478 | //读取服务器随机分配的头像图片 479 | file.setFileName(iconName); 480 | file.open(QFile::ReadOnly); 481 | QByteArray pic = file.readAll(); 482 | file.close(); 483 | //创建用户头像 484 | file.setFileName(Global::UserHeadShot(acc)); 485 | file.open(QFile::WriteOnly); 486 | file.write(pic); 487 | file.close(); 488 | } 489 | } 490 | 491 | void WorkThread::recvFind(int acc) 492 | { 493 | QString rtpwd = sql.FindPwd(acc); 494 | 495 | if(rtpwd == "") 496 | { 497 | qDebug() << "无该账户!密码找回失败!"; 498 | ReplyToJson(FindPwd, ""); 499 | } 500 | else 501 | { 502 | qDebug() << "密码找回成功!"; 503 | ReplyToJson(FindPwd, rtpwd); 504 | } 505 | 506 | emit ThreadbackMsg((int)FindPwd, acc, rtpwd); 507 | } 508 | 509 | void WorkThread::CltLogin(int acc, QString pwd, bool isFirst) 510 | { 511 | int result = sql.LoginVerification(acc, pwd); 512 | 513 | if(result == -1) //账号或密码错误 514 | { 515 | qDebug() << "账号密码错误"; 516 | ReplyToJson(LoginAcc, "账号密码错误"); 517 | emit ThreadbackMsg((int)LoginAcc, acc, "账号或密码错误"); 518 | } 519 | else if(result == 0) //重复登录 520 | { 521 | qDebug() << "重复登录"; 522 | ReplyToJson(LoginAcc, "重复登录"); 523 | emit ThreadbackMsg((int)LoginAcc, acc, "重复登录"); 524 | } 525 | else //登录成功 526 | { 527 | qDebug() << "登录成功"; 528 | m_userDatas = sql.UserMessages(acc); //获取用户昵称和个性签名 529 | emit UserOnLine(acc, m_port); 530 | emit ThreadbackMsg((int)LoginAcc, acc, "登录成功"); 531 | 532 | if(isFirst) 533 | { 534 | //发送头像和好友信息 535 | QString fileN1 = Global::UserHeadShot(acc); //头像 536 | QString fileN2 = Global::UserPath(acc) + "/" + "friends.json"; //好友列表 537 | qDebug() << "filename1:" << fileN1 << "filename2:" << fileN2; 538 | 539 | //用?分割文件地址 540 | QString fPath = fileN1 + "?" + fileN2; 541 | 542 | ReplyToJson(LoginAcc, "登录成功", "第一次登录", acc, -1, fPath); 543 | return; 544 | } 545 | 546 | ReplyToJson(LoginAcc, "登录成功", "", acc); 547 | } 548 | } 549 | 550 | void WorkThread::SearchingFri(int acc) 551 | { 552 | QString res = sql.OnLineSta(acc); 553 | 554 | //如果返回的结果为空或离线则发送查找失败 555 | if(res == "离线" || res == "") 556 | { 557 | qDebug() << "查找好友失败,该账号不存在或未上线"; 558 | ReplyToJson(SearchFri, "查找失败"); 559 | } 560 | //否则查找成功,返回用户信息 561 | else 562 | { 563 | m_userDatas = sql.UserMessages(acc); 564 | ReplyToJson(SearchFri, "查找成功", "", -1, acc, Global::UserHeadShot(acc)); 565 | } 566 | } 567 | 568 | void WorkThread::CltAddFri(int acc, int targetacc, QString msgType, QString yanzheng) 569 | { 570 | QString res = sql.OnLineSta(targetacc); 571 | 572 | if(res == "离线") 573 | { 574 | qDebug() << "添加/删除好友失败,该账号已下线"; 575 | ReplyToJson(AddFri, "该好友已下线", "", -1, targetacc); 576 | return; 577 | } 578 | 579 | //若为发送好友申请则需发送申请方头像和申请方资料 580 | if(msgType == "发送好友申请") 581 | { 582 | qDebug() << "正在添加用户头像及资料"; 583 | m_userDatas = sql.UserMessages(acc); 584 | ReplyToJson(AddFri, yanzheng, msgType, acc, targetacc, Global::UserHeadShot(acc)); 585 | } 586 | else if(msgType == "同意好友申请") 587 | { 588 | //在数据库中更新这两个用户的好友列表 589 | sql.AddFriend(acc, targetacc); 590 | ReplyToJson(SendMsg, "我们已经是好友啦,一起来聊天吧!", "添加好友成功", acc, targetacc); 591 | ReplyToJson(SendMsg, "我们已经是好友啦,一起来聊天吧!", "添加好友成功", targetacc, acc); 592 | } 593 | else if(msgType == "删除好友") 594 | { 595 | //在数据库中更新这两个用户的好友列表 596 | sql.DelFriend(acc, targetacc); 597 | 598 | ReplyToJson(AddFri, "", "成功删除好友", acc, targetacc); 599 | ReplyToJson(AddFri, "", "成功删除好友", targetacc, acc); 600 | } 601 | } 602 | 603 | void WorkThread::CltChangeOnlSta(int acc, QString onlsta) 604 | { 605 | QString onlineSta = sql.OnLineSta(acc); 606 | 607 | if(onlineSta == "离线") 608 | { 609 | qDebug() << "用户掉线重连"; 610 | emit UserOnLine(acc, m_port); 611 | //ThreadbackMsg("用户掉线重连",acc,""); 612 | } 613 | 614 | sql.ChangeOnlineSta(acc, onlsta); 615 | } 616 | 617 | void WorkThread::AskForUserData(int acc, QString isSelf, int HeadShotSize) 618 | { 619 | m_userDatas = sql.UserMessages(acc); 620 | QString datas; 621 | 622 | for(auto uD : m_userDatas) 623 | { 624 | datas.append(uD); 625 | datas.append("##"); 626 | } 627 | 628 | qDebug() << "返回个人资料: " << datas; 629 | 630 | if(isSelf == "请求自己的") 631 | { 632 | ReplyToJson(AskForData, datas, isSelf, acc, -1); 633 | } 634 | else 635 | { 636 | QString hsPath = Global::UserHeadShot(acc); 637 | QFileInfo info(hsPath); 638 | int fHs = info.size(); 639 | qDebug() << "该用户本地的好友头像大小: " << HeadShotSize << "服务器保存的好友头像大小: " << fHs; 640 | 641 | if(fHs != HeadShotSize) //头像大小不同即为好友已更改头像 642 | { 643 | qDebug() << "需更新好友头像"; 644 | ReplyToJson(AskForData, datas, isSelf + "##" + "需更新头像", acc, -1, hsPath); 645 | return; 646 | } 647 | 648 | ReplyToJson(AskForData, datas, isSelf + "##" + "不更新头像", acc, -1); 649 | } 650 | } 651 | 652 | void WorkThread::ChangingUserDatas(int acc, QString datas) 653 | { 654 | QStringList UserDatas = datas.split("##"); 655 | 656 | bool changed = sql.ChangeUserMessages(acc, UserDatas); 657 | 658 | if(!changed) 659 | { 660 | qDebug() << "用户资料更改失败!"; 661 | } 662 | } 663 | 664 | QString WorkThread::GetFileSize(const qint64& size) 665 | { 666 | int integer = 0; //整数 667 | int decimal = 0; //小数 668 | QString unit = "B"; 669 | 670 | qint64 RealSize = size; //换算后大小 671 | qint64 dSize = size; //取小数位 672 | 673 | integer = RealSize; 674 | 675 | if(RealSize > 1024) 676 | { 677 | dSize = RealSize * 1000 / 1024; 678 | integer = dSize / 1000; //整数位 679 | decimal = dSize % 1000; //小数位 680 | RealSize /= 1024; 681 | unit = "KB"; 682 | 683 | if(RealSize > 1024) 684 | { 685 | dSize = RealSize * 1000 / 1024; 686 | integer = dSize / 1000; 687 | decimal = dSize % 1000; 688 | RealSize /= 1024; 689 | unit = "MB"; 690 | 691 | if(RealSize > 1024) 692 | { 693 | dSize = RealSize * 1000 / 1024; 694 | integer = dSize / 1000; 695 | decimal = dSize % 1000; 696 | RealSize /= 1024; 697 | unit = "GB"; 698 | } 699 | } 700 | } 701 | 702 | QString dec = ""; 703 | decimal /= 10; 704 | 705 | //保留两位小数 706 | if(decimal < 10) 707 | { 708 | dec = "0" + QString::number(decimal); 709 | } 710 | else 711 | { 712 | dec = QString::number(decimal); 713 | } 714 | 715 | QString FileSize = "(" + QString::number(integer) + "." + dec + unit + ")"; 716 | return FileSize; 717 | } 718 | -------------------------------------------------------------------------------- /QQServer/workthread.h: -------------------------------------------------------------------------------- 1 | #ifndef WORKTHREAD_H 2 | #define WORKTHREAD_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "qqsqldata.h" 8 | #include "global.h" 9 | 10 | class WorkThread : public QObject 11 | { 12 | Q_OBJECT 13 | public: 14 | explicit WorkThread(QObject* parent = nullptr); 15 | ~WorkThread(); 16 | public slots: 17 | void SplitDataPackAge(QByteArray, quint16); //拆分数据包 18 | protected: 19 | //解析和转换 20 | void ParseMsg(quint16, QByteArray); //解析收到的数据 21 | void ReplyToJson(InforType type, QString Msg, QString MsgType = "", int acc = -1, int targetacc = -1, QString fileName = ""); //将回应信息转为json格式 22 | 23 | /* 操作用户未登录 */ 24 | void recvRegistered(int, QString); //查找注册结果 25 | void recvFind(int); //查找找回密码结果 26 | void CltLogin(int, QString, bool); //客户端登录 27 | 28 | /* 操作用户已登录 */ 29 | //直接返回请求结果 30 | void SearchingFri(int acc); //查找好友 31 | void CltChangeOnlSta(int acc, QString onlsta); //用户改变在线状态 32 | void AskForUserData(int acc, QString isSelf, int HeadShotSize = -1); //请求个人资料 33 | void ChangingUserDatas(int acc, QString datas); //修改个人资料 34 | 35 | //需转发信息给其他客户端 36 | 37 | QString GetFileSize(const qint64& size); //获取文件大小字符串 38 | void CltAddFri(int acc, int targetacc, QString msgType, QString yanzheng); //处理好友申请信息 39 | signals: 40 | void ThreadbackMsg(int type, int account, QString msg); //传消息回服务器 41 | void UserOnLine(int acc, quint16 sockport); //用户上线则发送该tcp套接字加入服务器在线用户哈希表中 42 | void SendMsgToClt(quint16 port, int type, int acc, int targetacc, QByteArray jsondata, QString msgtype, QString fileName); //发送信息给客户端 43 | private: 44 | Qqsqldata sql; //连接数据库 45 | quint16 m_port; //客户端套接字端口号 46 | QStringList m_userDatas; //保存用户资料 47 | 48 | /* 文件操作 */ 49 | QHash FilePackAgeCount; //已接收数据包个数 50 | QHash m_RecvFiles; //操作文件,key为文件发送方端口号 51 | QHash m_FileSizes; //保存文件大小 52 | }; 53 | 54 | #endif // WORKTHREAD_H 55 | -------------------------------------------------------------------------------- /QQSqlData.sql: -------------------------------------------------------------------------------- 1 | create table qqaccount ( 2 | account int primary key comment '账号', 3 | pwd varchar(50) not null comment '密码', 4 | onlinestatus varchar(5) default '离线' comment '在线状态', 5 | nickname varchar(15) default 'QQ用户' comment '昵称', 6 | signature varchar(30) default '该用户很懒,暂无个性签名' comment '个性签名', 7 | sex char(1) default '男' check (sex = '男' or sex = '女') comment '性别', 8 | age tinyint unsigned default 18 check(age > 0 and age <= 130) comment '年龄', 9 | birthday date default '2006-11-15' comment '生日', 10 | location varchar(20) default '中国' comment '地址', 11 | blood_type varchar(5) default '未知' comment '血型', 12 | work varchar(10) default '未知' comment '职业', 13 | sch_comp varchar(20) default '未知' comment '学校/公司' 14 | ) comment '账户信息表'; 15 | 16 | -- 服务器数据库仅保存好友间关系,不保存用户将好友放在哪个分组下 17 | create table qqfriends ( 18 | account int primary key comment '账号', 19 | friends varchar(500) comment '好友列表' 20 | ) comment 'QQ好友列表'; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 使用Qt5开发的一个C/S架构多线程仿QQ聊天软件 2 | 3 | ### 开发环境 4 | 5 | Windows + Qt 5.14.2 MinGW 64-bit 6 | 7 | ### 主要内容 8 | 9 | 使用Qt完成了一个C/S架构的多线程仿QQ聊天软件,客户端使用Json格式向服务器端发送数据包,在每个数据包前添加包头以识别该数据包的操作类型和有效数据大小,服务器端负责读写套接字的线程接收数据后将数据包传给工作线程处理,处理完毕后再将处理结果发送回客户端或转发信息给其他客户端并将用户更改资料和添加好友的操作内容保存在MySql数据库中,客户端之间通过服务器转发来相互聊天或转发图片文件等操作 10 | 11 | ### 已实现功能: 12 | 13 | - 掉线自动重连 14 | - 注册及找回密码功能 15 | - 仿QQ的用户登录界面,保存账号,记住密码 16 | - 更换头像,更改个人昵称,签名和其他个人资料 17 | - 查看好友个人资料,移动好友到其他分组,添加好友,删除好友 18 | - 添加好友分组,更改分组名,删除分组 19 | - 发送消息,发送普通图片和Gif图片,发送大文件 20 | 21 | ### 项目介绍 22 | 23 | ##### 1.登录界面 24 | ![1](https://github.com/Fantasy714/MyQQ/assets/128826119/efb27f78-8990-4dff-998b-882c5fdaa40b) 25 | ##### 2.注册账号及找回密码界面 26 | ![2](https://github.com/Fantasy714/MyQQ/assets/128826119/5c2c99e2-48ec-4ec5-ae9f-a07fc5e7416b) 27 | ![3](https://github.com/Fantasy714/MyQQ/assets/128826119/61001d0f-23d3-4db6-b688-ebf8cc1a9693) 28 | ##### 3.掉线,重复登录及密码错误 29 | ![5](https://github.com/Fantasy714/MyQQ/assets/128826119/af98c2c7-0942-4fc9-88b3-3496beb41e40) 30 | ![4](https://github.com/Fantasy714/MyQQ/assets/128826119/8710cc67-9d17-4737-9cca-a716b360a429) 31 | ![6](https://github.com/Fantasy714/MyQQ/assets/128826119/d502401e-5ad2-4cb1-933d-22f63ffd9418) 32 | ##### 4.保存账号 33 | ![7](https://github.com/Fantasy714/MyQQ/assets/128826119/1b286fdd-56e1-451e-9592-1998d165dd5f) 34 | ##### 5.好友列表 35 | ![9](https://github.com/Fantasy714/MyQQ/assets/128826119/ddd8683a-520e-46d2-b182-4c481c740e40) 36 | ![10](https://github.com/Fantasy714/MyQQ/assets/128826119/18c8d5c7-c36d-4532-85e5-e5eddeab32fd) 37 | ![17](https://github.com/Fantasy714/MyQQ/assets/128826119/9f7fde0d-77f7-4ea2-ac6c-ff4817cfe985) 38 | ##### 6.个人资料 39 | ![11](https://github.com/Fantasy714/MyQQ/assets/128826119/8649b7a4-d958-491c-b13b-7aa513e50e57) 40 | ![12](https://github.com/Fantasy714/MyQQ/assets/128826119/50a7f26e-b47f-40fc-80e7-288ef01bea2e) 41 | ![13](https://github.com/Fantasy714/MyQQ/assets/128826119/fa990f4f-8b48-4752-ac30-5f072da60381) 42 | ##### 7.添加好友 43 | ![14](https://github.com/Fantasy714/MyQQ/assets/128826119/671cbe95-bd15-4a03-933a-cb9c1e38f0bc) 44 | ![15](https://github.com/Fantasy714/MyQQ/assets/128826119/c9957a4f-3360-419b-be8e-b9b4f0232942) 45 | ![16](https://github.com/Fantasy714/MyQQ/assets/128826119/f4c13364-de76-4066-a07c-ce769bbcf9c6) 46 | ##### 8.聊天及收发文件 47 | ![18](https://github.com/Fantasy714/MyQQ/assets/128826119/1575d7bd-725c-4acf-b91e-0b601075545b) 48 | ![19](https://github.com/Fantasy714/MyQQ/assets/128826119/78e8f2dc-58a4-433f-aa4e-dc0d046b3df4) 49 | ![20](https://github.com/Fantasy714/MyQQ/assets/128826119/046d9185-5279-4283-b5d6-a16ca42db3cf) 50 | ![21](https://github.com/Fantasy714/MyQQ/assets/128826119/f1e941b0-1cb7-4d4d-8333-71540afa371e) 51 | ![22](https://github.com/Fantasy714/MyQQ/assets/128826119/000f177f-83c2-405c-81b9-65e5e7d31b8c) 52 | ![23](https://github.com/Fantasy714/MyQQ/assets/128826119/abbda8e3-e74b-48b0-853a-c293bdd17a00) 53 | --------------------------------------------------------------------------------