├── .gitignore ├── ChatServer.pro ├── README.md ├── image ├── 9.jpg └── ButtonImage │ ├── add_hover.png │ ├── add_normal.png │ ├── close_down.png │ ├── close_hover.png │ ├── close_normal.png │ ├── menu_down.png │ ├── menu_hover.png │ ├── menu_normal.png │ ├── min_down.png │ ├── min_hover.png │ ├── min_normal.png │ ├── minus_hover.png │ └── minus_normal.png ├── qml.qrc ├── qml ├── CusButton.qml ├── MoveMouseArea.qml ├── ResizeMouseArea.qml └── main.qml ├── src ├── chatsocket.cpp ├── chatsocket.h ├── chattcpserver.cpp ├── chattcpserver.h ├── database.cpp ├── database.h ├── framelesswindow.cpp ├── framelesswindow.h ├── main.cpp ├── protocol.cpp └── protocol.h ├── test.sql └── users ├── 843261040 └── headImage │ └── head1.jpg └── users.db /.gitignore: -------------------------------------------------------------------------------- 1 | users/ 2 | debug/ 3 | release/ 4 | *.user 5 | *.Debug 6 | *.Release 7 | Makefile 8 | *.stash 9 | *.rc 10 | *.exe 11 | *.qmlc -------------------------------------------------------------------------------- /ChatServer.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT += qml quick network sql 4 | 5 | CONFIG += c++11 6 | 7 | HEADERS += \ 8 | src/chattcpserver.h \ 9 | src/protocol.h \ 10 | src/chatsocket.h \ 11 | src/framelesswindow.h \ 12 | src/database.h 13 | 14 | SOURCES += \ 15 | src/main.cpp \ 16 | src/chattcpserver.cpp \ 17 | src/chatsocket.cpp \ 18 | src/framelesswindow.cpp \ 19 | src/database.cpp \ 20 | src/protocol.cpp 21 | 22 | RESOURCES += qml.qrc 23 | 24 | # Additional import path used to resolve QML modules in Qt Creator's code model 25 | QML_IMPORT_PATH = 26 | 27 | # Additional import path used to resolve QML modules just for Qt Quick Designer 28 | QML_DESIGNER_IMPORT_PATH = 29 | 30 | # The following define makes your compiler emit warnings if you use 31 | # any feature of Qt which as been marked deprecated (the exact warnings 32 | # depend on your compiler). Please consult the documentation of the 33 | # deprecated API in order to know how to port your code away from it. 34 | DEFINES += QT_DEPRECATED_WARNINGS 35 | 36 | # You can also make your code fail to compile if you use deprecated APIs. 37 | # In order to do so, uncomment the following line. 38 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 39 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 40 | 41 | # Default rules for deployment. 42 | qnx: target.path = /tmp/$${TARGET}/bin 43 | else: unix:!android: target.path = /opt/$${TARGET}/bin 44 | !isEmpty(target.path): INSTALLS += target 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengps/MChatServer/5409a4577492260210535b60770e4fdf37243c40/README.md -------------------------------------------------------------------------------- /image/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengps/MChatServer/5409a4577492260210535b60770e4fdf37243c40/image/9.jpg -------------------------------------------------------------------------------- /image/ButtonImage/add_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengps/MChatServer/5409a4577492260210535b60770e4fdf37243c40/image/ButtonImage/add_hover.png -------------------------------------------------------------------------------- /image/ButtonImage/add_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengps/MChatServer/5409a4577492260210535b60770e4fdf37243c40/image/ButtonImage/add_normal.png -------------------------------------------------------------------------------- /image/ButtonImage/close_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengps/MChatServer/5409a4577492260210535b60770e4fdf37243c40/image/ButtonImage/close_down.png -------------------------------------------------------------------------------- /image/ButtonImage/close_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengps/MChatServer/5409a4577492260210535b60770e4fdf37243c40/image/ButtonImage/close_hover.png -------------------------------------------------------------------------------- /image/ButtonImage/close_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengps/MChatServer/5409a4577492260210535b60770e4fdf37243c40/image/ButtonImage/close_normal.png -------------------------------------------------------------------------------- /image/ButtonImage/menu_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengps/MChatServer/5409a4577492260210535b60770e4fdf37243c40/image/ButtonImage/menu_down.png -------------------------------------------------------------------------------- /image/ButtonImage/menu_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengps/MChatServer/5409a4577492260210535b60770e4fdf37243c40/image/ButtonImage/menu_hover.png -------------------------------------------------------------------------------- /image/ButtonImage/menu_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengps/MChatServer/5409a4577492260210535b60770e4fdf37243c40/image/ButtonImage/menu_normal.png -------------------------------------------------------------------------------- /image/ButtonImage/min_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengps/MChatServer/5409a4577492260210535b60770e4fdf37243c40/image/ButtonImage/min_down.png -------------------------------------------------------------------------------- /image/ButtonImage/min_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengps/MChatServer/5409a4577492260210535b60770e4fdf37243c40/image/ButtonImage/min_hover.png -------------------------------------------------------------------------------- /image/ButtonImage/min_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengps/MChatServer/5409a4577492260210535b60770e4fdf37243c40/image/ButtonImage/min_normal.png -------------------------------------------------------------------------------- /image/ButtonImage/minus_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengps/MChatServer/5409a4577492260210535b60770e4fdf37243c40/image/ButtonImage/minus_hover.png -------------------------------------------------------------------------------- /image/ButtonImage/minus_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengps/MChatServer/5409a4577492260210535b60770e4fdf37243c40/image/ButtonImage/minus_normal.png -------------------------------------------------------------------------------- /qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | qml/main.qml 4 | qml/CusButton.qml 5 | qml/MoveMouseArea.qml 6 | qml/ResizeMouseArea.qml 7 | image/9.jpg 8 | image/ButtonImage/add_hover.png 9 | image/ButtonImage/add_normal.png 10 | image/ButtonImage/close_down.png 11 | image/ButtonImage/close_hover.png 12 | image/ButtonImage/close_normal.png 13 | image/ButtonImage/menu_down.png 14 | image/ButtonImage/menu_hover.png 15 | image/ButtonImage/menu_normal.png 16 | image/ButtonImage/min_down.png 17 | image/ButtonImage/min_hover.png 18 | image/ButtonImage/min_normal.png 19 | image/ButtonImage/minus_hover.png 20 | image/ButtonImage/minus_normal.png 21 | 22 | 23 | -------------------------------------------------------------------------------- /qml/CusButton.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | 3 | Rectangle 4 | { 5 | id: root 6 | color: hovered ? "#9AFFFFFF" : "transparent"; 7 | 8 | property string buttonNormalImage: ""; 9 | property string buttonPressedImage: ""; 10 | property string buttonHoverImage: ""; 11 | property bool hovered: false; 12 | 13 | signal pressed(); 14 | signal released(); 15 | signal clicked(); 16 | signal exited(); 17 | signal entered(); 18 | 19 | Image 20 | { 21 | id: image 22 | anchors.fill: parent 23 | fillMode: Image.PreserveAspectFit 24 | antialiasing: true 25 | mipmap: true 26 | source: root.buttonNormalImage 27 | 28 | MouseArea 29 | { 30 | anchors.fill: parent 31 | hoverEnabled: true 32 | 33 | onEntered: 34 | { 35 | root.entered(); 36 | root.hovered = true; 37 | image.source = buttonHoverImage; 38 | } 39 | onPressed: 40 | { 41 | root.pressed(); 42 | root.clicked(); 43 | image.source = buttonPressedImage; 44 | } 45 | onReleased: 46 | { 47 | root.released(); 48 | image.source = buttonNormalImage; 49 | } 50 | onExited: 51 | { 52 | root.exited(); 53 | root.hovered = false; 54 | image.source = buttonNormalImage; 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /qml/MoveMouseArea.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | 3 | MouseArea 4 | { 5 | hoverEnabled: true 6 | 7 | property var target: undefined; 8 | property point startPoint: Qt.point(0, 0); 9 | property point offsetPoint: Qt.point(0, 0); 10 | 11 | onPressed: 12 | { 13 | cursorShape = Qt.SizeAllCursor; 14 | startPoint = Qt.point(mouseX, mouseY); 15 | } 16 | onPositionChanged: 17 | { 18 | if(pressed) 19 | { 20 | offsetPoint = Qt.point(mouse.x - startPoint.x, mouse.y - startPoint.y); 21 | target.x = target.x + offsetPoint.x; 22 | target.y = target.y + offsetPoint.y; 23 | } 24 | } 25 | onReleased: 26 | { 27 | cursorShape = Qt.ArrowCursor; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /qml/ResizeMouseArea.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.7 2 | 3 | /* 4 | ↑ ↑ ↑ 5 | ←\1\ \2\ \3\→ 6 | ←\4\ \5\ \6\→ 7 | ←\7\ \8\ \9\→ 8 | ↓ ↓ ↓ 9 | 分8个缩放区域 10 | \5\为拖动区域 11 | target 缩放目标 12 | startPoint 鼠标起始点 13 | fixedPont 用于固定窗口的点 14 | 每一个area 大小 8 x 8 15 | */ 16 | 17 | Item 18 | { 19 | id: area 20 | 21 | property var target: undefined; 22 | property point startPoint: Qt.point(0, 0); 23 | property point fixedPont: Qt.point(0, 0); 24 | 25 | MouseArea 26 | { 27 | id: area1 28 | x: 0 29 | y: 0 30 | width: 8 31 | height: 8 32 | hoverEnabled: true 33 | 34 | onEntered: cursorShape = Qt.SizeFDiagCursor; 35 | onExited: cursorShape = Qt.ArrowCursor; 36 | onPressed: startPoint = Qt.point(mouseX, mouseY); 37 | onPositionChanged: 38 | { 39 | if(pressed) 40 | { 41 | var offsetX = mouse.x - startPoint.x; 42 | var offsetY = mouse.y - startPoint.y; 43 | if ((target.width - offsetX) >= target.minimumWidth) //如果本次调整小于最小限制,则调整为最小 44 | { 45 | target.width -= offsetX; 46 | target.x += offsetX; 47 | } 48 | else 49 | { 50 | target.x += (target.width - target.minimumWidth); 51 | target.width -= (target.width - target.minimumWidth); 52 | } 53 | 54 | if ((target.height - offsetY) >= target.minimumHeight && offsetY != 0) 55 | { 56 | target.height -= offsetY; 57 | target.y += offsetY; 58 | } 59 | } 60 | } 61 | } 62 | 63 | MouseArea 64 | { 65 | id: area2 66 | x: 8 67 | y: 0 68 | width: target.width - 16 69 | height: 8 70 | hoverEnabled: true 71 | 72 | onEntered: cursorShape = Qt.SizeVerCursor; 73 | onExited: cursorShape = Qt.ArrowCursor; 74 | onPressed: startPoint = Qt.point(mouseX, mouseY); 75 | onPositionChanged: 76 | { 77 | if(pressed) 78 | { 79 | var offsetY = mouse.y - startPoint.y; 80 | if ((target.height - offsetY) >= target.minimumHeight && offsetY != 0) 81 | { 82 | target.height -= offsetY; 83 | target.y += offsetY; 84 | } 85 | } 86 | } 87 | } 88 | 89 | MouseArea 90 | { 91 | id: area3 92 | x: target.width - 8 93 | y: 0 94 | width: 8 95 | height: 8 96 | hoverEnabled: true 97 | 98 | onEntered: cursorShape = Qt.SizeBDiagCursor; 99 | onExited: cursorShape = Qt.ArrowCursor 100 | onPressed: 101 | { 102 | startPoint = Qt.point(mouseX, mouseY); 103 | fixedPont = Qt.point(target.x, target.y) 104 | } 105 | onPositionChanged: 106 | { 107 | if(pressed) 108 | { 109 | var offsetX = mouse.x - startPoint.x; 110 | var offsetY = mouse.y - startPoint.y; 111 | if ((target.width + offsetX) >= target.minimumWidth && offsetX != 0) 112 | { 113 | target.width += offsetX; 114 | target.x = fixedPont.x; 115 | } 116 | if ((target.height - offsetY) >= target.minimumHeight && offsetY != 0) 117 | { 118 | target.height -= offsetY; 119 | target.y += offsetY; 120 | } 121 | } 122 | } 123 | } 124 | 125 | MouseArea 126 | { 127 | id: area4 128 | x: 0 129 | y: 8 130 | width: 8 131 | height: target.height - 16 132 | hoverEnabled: true 133 | 134 | onEntered: cursorShape = Qt.SizeHorCursor; 135 | onExited: cursorShape = Qt.ArrowCursor; 136 | onPressed: 137 | { 138 | startPoint = Qt.point(mouseX, mouseY); 139 | } 140 | onPositionChanged: 141 | { 142 | if(pressed) 143 | { 144 | var offsetX = mouse.x - startPoint.x; 145 | if ((target.width - offsetX) >= target.minimumWidth) 146 | { 147 | target.width -= offsetX; 148 | target.x += offsetX; 149 | } 150 | } 151 | } 152 | } 153 | 154 | MoveMouseArea 155 | { 156 | id: area5 157 | x: 8 158 | y: 8 159 | width: area.target.width - 16 160 | height: area.target.height - 16 161 | target: area.target 162 | } 163 | 164 | MouseArea 165 | { 166 | id: area6 167 | x: target.width - 8 168 | y: 8 169 | width: 8 170 | height: target.height - 16 171 | hoverEnabled: true 172 | property real fixedX: 0; 173 | 174 | onEntered: cursorShape = Qt.SizeHorCursor; 175 | onExited: cursorShape = Qt.ArrowCursor; 176 | onPressed: 177 | { 178 | startPoint = Qt.point(mouseX, mouseY); 179 | fixedPont = Qt.point(target.x, target.y) 180 | } 181 | onPositionChanged: 182 | { 183 | if(pressed) 184 | { 185 | var offsetX = mouse.x - startPoint.x; 186 | if ((target.width + offsetX) >= target.minimumWidth && offsetX != 0) 187 | { 188 | target.width += offsetX; 189 | target.x = fixedPont.x; 190 | } 191 | } 192 | } 193 | } 194 | 195 | MouseArea 196 | { 197 | id: area7 198 | x: 0 199 | y: target.height - 8 200 | width: 8 201 | height: target.height - 16 202 | hoverEnabled: true 203 | property real fixedX: 0; 204 | 205 | onEntered: cursorShape = Qt.SizeBDiagCursor; 206 | onExited: cursorShape = Qt.ArrowCursor; 207 | onPressed: 208 | { 209 | startPoint = Qt.point(mouseX, mouseY); 210 | fixedPont = Qt.point(target.x, target.y) 211 | } 212 | onPositionChanged: 213 | { 214 | if (pressed) 215 | { 216 | var offsetX = mouse.x - startPoint.x; 217 | var offsetY = mouse.y - startPoint.y; 218 | if ((target.width - offsetX) >= target.minimumWidth && offsetX != 0) 219 | { 220 | target.width -= offsetX; 221 | target.x += offsetX; 222 | } 223 | if ((target.height + offsetY) >= target.minimumHeight && offsetY != 0) 224 | { 225 | target.height += offsetY; 226 | target.y = fixedPont.y; 227 | } 228 | } 229 | } 230 | } 231 | 232 | MouseArea 233 | { 234 | id: area8 235 | x: 8 236 | y: target.height - 8 237 | width: target.height - 16 238 | height: 8 239 | hoverEnabled: true 240 | property real fixedX: 0; 241 | 242 | onEntered: cursorShape = Qt.SizeVerCursor; 243 | onExited: cursorShape = Qt.ArrowCursor; 244 | onPressed: 245 | { 246 | startPoint = Qt.point(mouseX, mouseY); 247 | fixedPont = Qt.point(target.x, target.y) 248 | } 249 | onPositionChanged: 250 | { 251 | if (pressed) 252 | { 253 | var offsetY = mouse.y - startPoint.y; 254 | if ((target.height + offsetY) >= target.minimumHeight && offsetY != 0) 255 | { 256 | target.height += offsetY; 257 | target.y = fixedPont.y; 258 | } 259 | } 260 | } 261 | } 262 | 263 | MouseArea 264 | { 265 | id: area9 266 | x: target.width - 8 267 | y: target.height - 8 268 | width: 8 269 | height: 8 270 | hoverEnabled: true 271 | 272 | onEntered: cursorShape = Qt.SizeFDiagCursor; 273 | onExited: cursorShape = Qt.ArrowCursor 274 | onPressed: 275 | { 276 | startPoint = Qt.point(mouseX, mouseY); 277 | fixedPont = Qt.point(target.x, target.y) 278 | } 279 | onPositionChanged: 280 | { 281 | if(pressed) 282 | { 283 | var offsetX = mouse.x - startPoint.x; 284 | var offsetY = mouse.y - startPoint.y; 285 | if ((target.width + offsetX) >= target.minimumWidth && offsetX != 0) 286 | { 287 | target.width += offsetX; 288 | target.x = fixedPont.x; 289 | } 290 | if ((target.height + offsetY) >= target.minimumHeight && offsetY != 0) 291 | { 292 | target.height += offsetY; 293 | target.y = fixedPont.y; 294 | } 295 | } 296 | } 297 | } 298 | } 299 | 300 | -------------------------------------------------------------------------------- /qml/main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtQuick.Window 2.2 3 | import QtQuick.Controls 1.4 4 | import QtQuick.Controls 2.2 5 | import an.framelessWindow 1.0 6 | 7 | FramelessWindow 8 | { 9 | id: root 10 | visible: true 11 | width: 640 12 | height: 480 13 | x: (Screen.desktopAvailableWidth - actualWidth) / 2 14 | y: (Screen.desktopAvailableHeight - actualHeight) / 2 15 | actualHeight: height 16 | actualWidth: width 17 | title: qsTr("MPS Chat 服务器") 18 | taskbarHint: true 19 | windowIcon: "qrc:/image/winIcon.png" 20 | 21 | property var usersArray: new Array; 22 | 23 | function displayServerInfo(ip, port) 24 | { 25 | serverIP.text = "服务器 IP : " + ip; 26 | serverPort.text = "服务器 端口 : " + port; 27 | } 28 | 29 | function addMessage(msg) 30 | { 31 | messageText.text += "\n" + msg; 32 | } 33 | 34 | function retState(state) 35 | { 36 | switch (state) 37 | { 38 | case 0: 39 | return "在线"; 40 | case 1: 41 | return "隐身"; 42 | case 2: 43 | return "忙碌"; 44 | case 3: 45 | return "离线"; 46 | } 47 | } 48 | 49 | function addNewClient(username, ip, state) 50 | { 51 | usersArray.push(username); 52 | myModel.append({ "ip": ip, 53 | "username": username, 54 | "state": retState(state) }); 55 | addMessage(username + "已经连接..."); 56 | } 57 | 58 | function stateChange(username, state) 59 | { 60 | var index = usersArray.indexOf(username); 61 | if (index != -1) 62 | { 63 | myModel.setProperty(index, "state", retState(state)); 64 | } 65 | } 66 | 67 | function removeClient(username) 68 | { 69 | var index = usersArray.indexOf(username); 70 | if (index != -1) 71 | { 72 | usersArray.splice(index, 1); 73 | myModel.remove(index); 74 | } 75 | } 76 | 77 | /*Image 78 | { 79 | id: background 80 | source: "qrc:/image/9.jpg" 81 | }*/ 82 | 83 | Rectangle 84 | { 85 | x: 0 86 | y: 0 87 | width: parent.width 88 | height: parent.height 89 | radius: consoleWindow.radius 90 | 91 | Rectangle 92 | { 93 | anchors.fill: parent 94 | radius: parent.radius 95 | gradient: Gradient 96 | { 97 | GradientStop 98 | { 99 | position: 0.000 100 | color: "#88BBEEFA" 101 | } 102 | GradientStop 103 | { 104 | position: 0.500 105 | color: "#8800EA75" 106 | } 107 | GradientStop 108 | { 109 | position: 1.000 110 | color: "#88BBEEFA" 111 | } 112 | } 113 | } 114 | 115 | ResizeMouseArea 116 | { 117 | id: resizeMouseArea 118 | target: root 119 | } 120 | 121 | Row 122 | { 123 | width: 102 124 | height: 40 125 | anchors.right: parent.right 126 | anchors.rightMargin: 6 127 | anchors.top: parent.top 128 | anchors.topMargin: 6 129 | 130 | CusButton 131 | { 132 | id: menuButton 133 | width: 32 134 | height: 32 135 | 136 | onClicked: 137 | { 138 | } 139 | Component.onCompleted: 140 | { 141 | buttonNormalImage = "qrc:/image/ButtonImage/menu_normal.png"; 142 | buttonPressedImage = "qrc:/image/ButtonImage/menu_down.png"; 143 | buttonHoverImage = "qrc:/image/ButtonImage/menu_hover.png"; 144 | } 145 | } 146 | 147 | CusButton 148 | { 149 | id: minButton 150 | width: 32 151 | height: 32 152 | 153 | onClicked: 154 | { 155 | root.showMinimized(); 156 | } 157 | Component.onCompleted: 158 | { 159 | buttonNormalImage = "qrc:/image/ButtonImage/min_normal.png"; 160 | buttonPressedImage = "qrc:/image/ButtonImage/min_down.png"; 161 | buttonHoverImage = "qrc:/image/ButtonImage/min_hover.png"; 162 | } 163 | } 164 | 165 | CusButton 166 | { 167 | id: closeButton 168 | width: 32 169 | height: 32 170 | 171 | onClicked: 172 | { 173 | root.close(); 174 | } 175 | Component.onCompleted: 176 | { 177 | buttonNormalImage = "qrc:/image/ButtonImage/close_normal.png"; 178 | buttonPressedImage = "qrc:/image/ButtonImage/close_down.png"; 179 | buttonHoverImage = "qrc:/image/ButtonImage/close_hover.png"; 180 | } 181 | } 182 | } 183 | 184 | ListModel 185 | { 186 | id: myModel 187 | } 188 | 189 | Row 190 | { 191 | id: serverRect 192 | anchors.horizontalCenter: parent.horizontalCenter 193 | width: 400 194 | height: 45 195 | 196 | Text 197 | { 198 | id: serverIP 199 | height: 45 200 | width: 200 201 | color: "red" 202 | verticalAlignment: Text.AlignVCenter 203 | horizontalAlignment: Text.AlignHCenter 204 | font.family: "微软雅黑" 205 | font.pointSize: 11 206 | } 207 | 208 | Text 209 | { 210 | id: serverPort 211 | height: 45 212 | width: 200 213 | color: "red" 214 | verticalAlignment: Text.AlignVCenter 215 | horizontalAlignment: Text.AlignHCenter 216 | font.family: "微软雅黑" 217 | font.pointSize: 11 218 | } 219 | } 220 | 221 | TableView 222 | { 223 | id: tableView 224 | anchors.left: parent.left 225 | anchors.right: parent.right 226 | anchors.top: serverRect.bottom 227 | anchors.bottom: consoleWindow.top 228 | anchors.bottomMargin: 20 229 | model: myModel 230 | backgroundVisible: false 231 | headerDelegate: Component 232 | { 233 | id: headerDelegate 234 | 235 | Rectangle 236 | { 237 | id: headerRect; 238 | height: 30 239 | width: 100 240 | border.color: "#400040" 241 | color: styleData.selected ? "gray" : "transparent" 242 | radius: 3 243 | 244 | Text 245 | { 246 | text: styleData.value 247 | anchors.centerIn: parent 248 | font.family: "微软雅黑" 249 | font.pointSize: 10 250 | color: "red" 251 | } 252 | } 253 | } 254 | rowDelegate: Component 255 | { 256 | id: rowDelegate 257 | Rectangle 258 | { 259 | color: "transparent" 260 | height: 42 261 | } 262 | } 263 | itemDelegate: Component 264 | { 265 | id: itemDelegate 266 | 267 | Rectangle 268 | { 269 | id: tableCell 270 | anchors.fill: parent 271 | anchors.topMargin: 4 272 | anchors.leftMargin: 1 273 | border.color: "#400040" 274 | radius: 3 275 | color: styleData.selected ? "#44EEEEEE" : "#66B5E61D" 276 | 277 | Text 278 | { 279 | id: textID 280 | text: styleData.value 281 | font.family: "微软雅黑" 282 | anchors.fill: parent 283 | color: "black" 284 | elide: Text.ElideRight 285 | verticalAlignment: Text.AlignVCenter 286 | horizontalAlignment: Text.AlignHCenter 287 | } 288 | } 289 | } 290 | 291 | 292 | TableViewColumn 293 | { 294 | role: "username" 295 | title: "帐号" 296 | width: 180 297 | } 298 | 299 | TableViewColumn 300 | { 301 | role: "ip" 302 | title: "连接IP" 303 | width: 180 304 | } 305 | 306 | TableViewColumn 307 | { 308 | role: "state" 309 | title: "状态" 310 | width: 100 311 | } 312 | } 313 | 314 | Rectangle 315 | { 316 | id: consoleWindow 317 | opacity: 0.9 318 | radius: 10 319 | clip: true 320 | height: 160 321 | anchors.left: parent.left 322 | anchors.right: parent.right 323 | anchors.bottom: parent.bottom 324 | 325 | Flickable 326 | { 327 | id: flick 328 | //interactive: false 329 | anchors.fill: parent 330 | anchors.margins: 15 331 | contentHeight: messageText.contentHeight 332 | contentWidth: messageText.contentWidth 333 | 334 | TextEdit 335 | { 336 | id: messageText 337 | width: flick.width 338 | height: flick.height 339 | wrapMode: Text.WrapAnywhere 340 | font.family: "微软雅黑" 341 | //textFormat: Text.RichText 342 | text: "服务器运行中..." 343 | color: "#400040" 344 | onTextChanged: flick.contentY = Math.max(0, contentHeight - height); 345 | } 346 | 347 | ScrollBar.vertical: ScrollBar 348 | { 349 | width: 12 350 | policy: ScrollBar.AlwaysOn 351 | } 352 | } 353 | } 354 | } 355 | } 356 | -------------------------------------------------------------------------------- /src/chatsocket.cpp: -------------------------------------------------------------------------------- 1 | #include "chatsocket.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | ChatSocket::ChatSocket(qintptr socketDescriptor, QObject *parent) 12 | : QTcpSocket(parent) 13 | , m_status(Offline) 14 | { 15 | if (!setSocketDescriptor(socketDescriptor)) 16 | { 17 | emit logMessage(errorString()); 18 | deleteLater(); 19 | } 20 | 21 | m_username = CLIENT_ID; 22 | m_heartbeat = new QTimer(this); 23 | m_heartbeat->setInterval(60000); 24 | m_sendDataBytes = 0; 25 | m_sendData = QByteArray(); 26 | m_recvData = QByteArray(); 27 | m_lastTime = QDateTime::currentDateTime(); 28 | 29 | connect(this, &ChatSocket::readyRead, this, &ChatSocket::heartbeat); 30 | connect(this, &ChatSocket::bytesWritten, this, &ChatSocket::continueWrite, Qt::DirectConnection); 31 | connect(this, &ChatSocket::readyRead, this, [this]() 32 | { 33 | m_recvData += readAll(); 34 | processRecvMessage(); 35 | }); 36 | connect(this, &ChatSocket::disconnected, this, &ChatSocket::onDisconnected); 37 | connect(m_heartbeat, &QTimer::timeout, this, &ChatSocket::checkHeartbeat); 38 | 39 | m_heartbeat->start(); //开始心跳 40 | } 41 | 42 | ChatSocket::~ChatSocket() 43 | { 44 | 45 | } 46 | 47 | void ChatSocket::heartbeat() 48 | { 49 | if (!m_heartbeat->isActive()) 50 | m_heartbeat->start(); 51 | m_lastTime = QDateTime::currentDateTime(); 52 | } 53 | 54 | void ChatSocket::continueWrite(qint64 sentSize) 55 | { 56 | static int sentBytes = 0; 57 | sentBytes += sentSize; 58 | 59 | if (sentBytes >= m_sendDataBytes) 60 | { 61 | m_sendDataBytes = 0; 62 | sentBytes = 0; 63 | m_sendData.clear(); 64 | m_hasMessageProcessing = false; 65 | if (!m_messageQueue.isEmpty()) 66 | processNextSendMessage(); //如果消息队列不为空,则继续处理下一条待发送消息 67 | } 68 | } 69 | 70 | void ChatSocket::checkHeartbeat() 71 | { 72 | if (m_lastTime.secsTo(QDateTime::currentDateTime()) >= 60) //检测掉线,停止心跳 73 | { 74 | qDebug() << "heartbeat 超时, 即将断开连接"; 75 | m_heartbeat->stop(); 76 | disconnectFromHost(); 77 | } 78 | } 79 | 80 | void ChatSocket::onDisconnected() 81 | { 82 | emit clientDisconnected(m_username); 83 | emit logMessage(peerAddress().toString() + " 断开连接.."); 84 | deleteLater(); 85 | } 86 | 87 | void ChatSocket::processNextSendMessage() 88 | { 89 | if (!m_hasMessageProcessing && !m_messageQueue.isEmpty()) 90 | { 91 | QByteArray block; 92 | QDataStream out(&block, QIODevice::WriteOnly); 93 | out.setVersion(QDataStream::Qt_5_9); 94 | Message *message = m_messageQueue.dequeue(); 95 | out << *message; 96 | m_sendData = block; 97 | m_sendDataBytes = block.size(); 98 | m_hasMessageProcessing = true; 99 | delete message; 100 | 101 | write(block); 102 | flush(); //立即发送消息 103 | } 104 | } 105 | 106 | void ChatSocket::toJsonAndSend(const UserInfo &info, const QMap > &friends) 107 | { 108 | QJsonObject object; 109 | object.insert("Username", info.username); 110 | object.insert("Nickname", info.nickname); 111 | object.insert("HeadImage", info.headImage); 112 | object.insert("Background", info.background); 113 | object.insert("Gender", info.gender); 114 | object.insert("Birthday", info.birthday); 115 | object.insert("Signature", info.signature); 116 | object.insert("UnreadMessage", 0); 117 | object.insert("Level", info.level); 118 | QJsonArray friendList; 119 | for (auto it = friends.constBegin(); it != friends.constEnd(); it++) 120 | { 121 | QJsonArray array; 122 | for (auto it2 : it.value()) 123 | { 124 | UserInfo info2 = m_database->getUserInfo(it2.friendname); 125 | QJsonObject object2; 126 | object2.insert("Username", info2.username); 127 | object2.insert("Nickname", info2.nickname); 128 | object2.insert("HeadImage", info2.headImage); 129 | object2.insert("Gender", info2.gender); 130 | object2.insert("Birthday", info2.birthday); 131 | object2.insert("Signature", info2.signature); 132 | object2.insert("UnreadMessage", it2.unreadMessage); 133 | object2.insert("Level", info2.level); 134 | array.append(object2); 135 | } 136 | QJsonObject obj; 137 | obj.insert("Group", it.key()); 138 | obj.insert("Friend", array); 139 | friendList.append(obj); 140 | } 141 | object.insert("FriendList", friendList); 142 | writeClientData(SERVER_ID, MT_USERINFO, MO_NULL, QJsonDocument(object).toJson()); 143 | qDebug() << object; 144 | } 145 | 146 | void ChatSocket::updateInfomation(const QByteArray &infoJson) 147 | { 148 | UserInfo info = jsonToInfo(infoJson); 149 | qDebug() << info; 150 | m_database->setUserInfo(info); 151 | } 152 | 153 | UserInfo ChatSocket::jsonToInfo(const QByteArray &json) 154 | { 155 | UserInfo info; 156 | QJsonParseError error; 157 | QJsonDocument doc = QJsonDocument::fromJson(json, &error); 158 | if (!doc.isNull() && (error.error == QJsonParseError::NoError)) 159 | { 160 | if (doc.isObject()) 161 | { 162 | QJsonObject object = doc.object(); 163 | QJsonValue value = object.value("Username"); 164 | if (value.isString()) 165 | info.username = value.toString(); 166 | value = object.value("Password"); 167 | if (value.isString()) 168 | info.password = value.toString(); 169 | value = object.value("Nickname"); 170 | if (value.isString()) 171 | info.nickname = value.toString(); 172 | value = object.value("HeadImage"); 173 | if (value.isString()) 174 | info.headImage = value.toString(); 175 | value = object.value("Background"); 176 | if (value.isString()) 177 | info.background = value.toString(); 178 | value = object.value("Gender"); 179 | if (value.isString()) 180 | info.gender = value.toString(); 181 | value = object.value("Signature"); 182 | if (value.isString()) 183 | info.signature = value.toString(); 184 | value = object.value("Birthday"); 185 | if (value.isString()) 186 | info.birthday = value.toString(); 187 | value = object.value("Level"); 188 | if (value.isDouble()) 189 | info.level = value.toInt(); 190 | } 191 | } 192 | else qDebug() << error.errorString(); 193 | 194 | return info; 195 | } 196 | 197 | QByteArray ChatSocket::infoToJson(const UserInfo &info) 198 | { 199 | QJsonObject object; 200 | object.insert("Username", info.username); 201 | object.insert("Nickname", info.nickname); 202 | object.insert("HeadImage", info.headImage); 203 | object.insert("Gender", info.gender); 204 | object.insert("Birthday", info.birthday); 205 | object.insert("Signature", info.signature); 206 | object.insert("Level", info.level); 207 | 208 | return QJsonDocument(object).toJson(); 209 | } 210 | 211 | void ChatSocket::writeClientData(const QByteArray &sender, msg_t type, msg_option_t option, const QByteArray &data) 212 | { 213 | QByteArray base64 = data.toBase64(); 214 | QByteArray md5 = QCryptographicHash::hash(base64, QCryptographicHash::Md5); 215 | 216 | MessageHeader header = { MSG_FLAG, type, msg_size_t(base64.size()), option, sender, m_username, md5 }; 217 | Message *message = new Message(header, base64); 218 | m_messageQueue.enqueue(message); 219 | processNextSendMessage(); 220 | } 221 | 222 | void ChatSocket::processRecvMessage() 223 | { 224 | //尝试读取一个完整的消息头 225 | if (m_recvHeader.isEmpty() && m_recvData.size() > 0) 226 | { 227 | MessageHeader header; 228 | QDataStream in(&m_recvData, QIODevice::ReadOnly); 229 | in.setVersion(QDataStream::Qt_5_9); 230 | in >> header; 231 | 232 | if (header.isEmpty()) return; 233 | qDebug() << header; 234 | m_recvHeader = header; 235 | m_recvData.remove(0, header.getSize() + 4); //data为QByteArray,前面有4字节的大小 236 | 237 | //如果成功读取了一个完整的消息头,但flag不一致(即:不是我的消息) 238 | if (get_flag(m_recvHeader) != MSG_FLAG) 239 | { 240 | m_recvHeader = MessageHeader(); 241 | return; 242 | } 243 | } 244 | 245 | //如果数据大小不足一条消息 246 | int size = int(get_size(m_recvHeader)); 247 | if (m_recvData.size() < size) 248 | return; 249 | 250 | auto rawData = m_recvData.left(size); 251 | m_recvData = m_recvData.mid(size); 252 | 253 | auto md5 = QCryptographicHash::hash(rawData, QCryptographicHash::Md5); 254 | auto data = QByteArray::fromBase64(rawData); 255 | if (md5 != get_md5(m_recvHeader)) return; 256 | 257 | qDebug() << "md5 一致,消息为:" + data; 258 | qDebug() << "消息大小:" + QString::number(data.size()); 259 | 260 | switch (get_type(m_recvHeader)) 261 | { 262 | case MT_HEARTBEAT: 263 | break; 264 | case MT_CHECK: 265 | { 266 | QString str = QString::fromLocal8Bit(data); 267 | QStringList list = str.split("%%"); 268 | qDebug() << "登录信息:" << list; 269 | m_username = list.at(0).toLatin1(); //记录该socket的帐号 270 | m_database = new Database(m_username + QString::number(qintptr(QThread::currentThreadId()), 16), this); 271 | UserInfo info = m_database->getUserInfo(list.at(0)); 272 | if (info.password == list.at(1)) 273 | { 274 | writeClientData(SERVER_ID, MT_CHECK, MO_NULL, CHECK_SUCCESS); 275 | emit clientLoginSuccess(m_username, peerAddress().toString()); 276 | } 277 | else 278 | { 279 | writeClientData(SERVER_ID, MT_CHECK, MO_NULL, CHECK_FAILURE); 280 | } 281 | break; 282 | } 283 | case MT_USERINFO: 284 | { 285 | qDebug() << "MT_USERINFO" << get_option(m_recvHeader); 286 | if (get_option(m_recvHeader) == MO_DOWNLOAD) 287 | { 288 | auto info = m_database->getUserInfo(m_username); 289 | auto friendsInfo = m_database->getUserFriendsInfo(m_username); 290 | toJsonAndSend(info, friendsInfo); 291 | } 292 | else if (get_option(m_recvHeader) == MO_UPLOAD) 293 | { 294 | updateInfomation(data); 295 | } 296 | break; 297 | } 298 | case MT_STATECHANGE: 299 | { 300 | m_status = ChatStatus(data.toInt()); 301 | emit hasNewMessage(m_username, get_receiver(m_recvHeader), MT_STATECHANGE, MO_NULL, data); 302 | break; 303 | } 304 | case MT_SEARCH: 305 | { 306 | QString username = QString::fromLocal8Bit(data); 307 | qDebug() << "获取" << username << "的信息"; 308 | UserInfo info = m_database->getUserInfo(username); 309 | 310 | QByteArray sendData = infoToJson(info); 311 | writeClientData(SERVER_ID, MT_SEARCH, MO_NULL, sendData); 312 | break; 313 | } 314 | case MT_SHAKE: 315 | { 316 | QString str = QString::fromLocal8Bit(data); 317 | qDebug() << "发送给" << get_receiver(m_recvHeader) << str; 318 | emit hasNewMessage(m_username, get_receiver(m_recvHeader), MT_SHAKE, get_option(m_recvHeader), data); 319 | break; 320 | } 321 | case MT_TEXT: 322 | { 323 | QString str = QString::fromLocal8Bit(data); 324 | qDebug() << "发送给" << get_receiver(m_recvHeader) << "的消息:" << str; 325 | emit hasNewMessage(m_username, get_receiver(m_recvHeader), MT_TEXT, get_option(m_recvHeader), data); 326 | break; 327 | } 328 | 329 | case MT_IMAGE: 330 | break; 331 | 332 | case MT_FILE: 333 | break; 334 | 335 | case MT_ADDFRIEND: 336 | { 337 | qDebug() << "发送给" << get_receiver(m_recvHeader) << "的添加好友请求。"; 338 | QString addStr = QString::fromLocal8Bit(data); 339 | if (addStr == ADD_SUCCESS) //添加好友成功 340 | { 341 | m_database->addFriend(get_sender(m_recvHeader), get_receiver(m_recvHeader)); 342 | //将好友信息发送回去 343 | data = infoToJson(m_database->getUserInfo(get_receiver(m_recvHeader))); 344 | writeClientData(get_receiver(m_recvHeader), MT_ADDFRIEND, MO_NULL, data); 345 | //发送给另一个好友 346 | data = infoToJson(m_database->getUserInfo(get_sender(m_recvHeader))); 347 | } 348 | emit hasNewMessage(m_username, get_receiver(m_recvHeader), MT_ADDFRIEND, MO_NULL, data); 349 | break; 350 | } 351 | 352 | case MT_REGISTER: 353 | { 354 | qDebug() << "来自" << get_sender(m_recvHeader) << "的注册请求。"; 355 | UserInfo info = jsonToInfo(data); 356 | m_database = new Database(m_username + QString::number(qintptr(QThread::currentThreadId()), 16), this); 357 | if (!info.isEmpty()) 358 | { 359 | if (m_database->createUser(info)) 360 | writeClientData(SERVER_ID, MT_REGISTER, MO_NULL, REG_SUCCESS); 361 | else writeClientData(SERVER_ID, MT_REGISTER, MO_NULL, REG_FAILURE); 362 | } 363 | break; 364 | } 365 | 366 | case MT_UNKNOW: 367 | break; 368 | 369 | default: 370 | break; 371 | } 372 | //处理结束,清空消息头 373 | m_recvHeader = MessageHeader(); 374 | } 375 | -------------------------------------------------------------------------------- /src/chatsocket.h: -------------------------------------------------------------------------------- 1 | #ifndef CHATSOCKET_H 2 | #define CHATSOCKET_H 3 | 4 | #include "database.h" 5 | #include "protocol.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | class QTimer; 12 | class ChatSocket : public QTcpSocket 13 | { 14 | Q_OBJECT 15 | 16 | public: 17 | enum ChatStatus 18 | { 19 | Online = 0, //在线 20 | Stealth, //隐身 21 | Busy, //忙碌 22 | Offline //离线 23 | }; 24 | 25 | public: 26 | ChatSocket(qintptr socketDescriptor, QObject *parent = nullptr); 27 | ~ChatSocket(); 28 | 29 | int status() const { return m_status; } 30 | 31 | signals: 32 | void clientLoginSuccess(const QString &username, const QString &ip); 33 | void clientDisconnected(const QString &username); 34 | void hasNewMessage(const QByteArray &sender, const QByteArray &receiver, 35 | msg_t type, msg_option_t option, const QByteArray &data); 36 | void logMessage(const QString &message); 37 | 38 | public slots: 39 | void writeClientData(const QByteArray &sender, msg_t type, msg_option_t option, 40 | const QByteArray &data); 41 | 42 | private slots: 43 | void heartbeat(); 44 | void continueWrite(qint64 sentSize); 45 | void checkHeartbeat(); 46 | void onDisconnected(); 47 | void processNextSendMessage(); 48 | void processRecvMessage(); 49 | 50 | private: 51 | //将查询到的数据转换成JSON并发送回客户端 52 | void toJsonAndSend(const UserInfo &info, const QMap > &friends); 53 | //将json转换成info并更新数据库 54 | void updateInfomation(const QByteArray &infoJson); 55 | //将json装换成info 56 | UserInfo jsonToInfo(const QByteArray &json); 57 | //将info装换成json 58 | QByteArray infoToJson(const UserInfo &info); 59 | 60 | private: 61 | ChatStatus m_status; 62 | QTimer *m_heartbeat; 63 | QDateTime m_lastTime; 64 | qint64 m_sendDataBytes; 65 | QByteArray m_sendData; 66 | QByteArray m_recvData; 67 | MessageHeader m_recvHeader; 68 | 69 | QByteArray m_username; 70 | QQueue m_messageQueue; 71 | bool m_hasMessageProcessing; //指示是否有消息在处理中 72 | Database *m_database; 73 | }; 74 | 75 | #endif // CHATSOCKET_H 76 | -------------------------------------------------------------------------------- /src/chattcpserver.cpp: -------------------------------------------------------------------------------- 1 | #include "chattcpserver.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | ChatTcpServer::ChatTcpServer(QQmlEngine *engine, QObject *parent) 9 | : QTcpServer(parent) 10 | , m_qmlengine(engine) 11 | { 12 | m_database = new Database("ServerConnection", this); 13 | } 14 | 15 | ChatTcpServer::~ChatTcpServer() 16 | { 17 | 18 | } 19 | 20 | void ChatTcpServer::loadWindow() 21 | { 22 | QQmlComponent component(m_qmlengine, "qrc:/qml/main.qml"); 23 | QObject *obj = component.create(); 24 | m_window = qobject_cast(obj); 25 | m_window->requestActivate(); 26 | m_window->show(); 27 | 28 | QMetaObject::invokeMethod(m_window, "displayServerInfo", 29 | Q_ARG(QVariant, QVariant(server_ip)), Q_ARG(QVariant, QVariant(43800))); 30 | } 31 | 32 | void ChatTcpServer::incomingConnection(qintptr socketDescriptor) 33 | { 34 | QThread *thread = new QThread; 35 | ChatSocket *socket = new ChatSocket(socketDescriptor); 36 | 37 | connect(thread, &QThread::finished, thread, &QThread::deleteLater); 38 | connect(socket, &ChatSocket::hasNewMessage, this, &ChatTcpServer::disposeMessage); 39 | connect(socket, &ChatSocket::logMessage, this, [this](const QString &message) 40 | { 41 | QMetaObject::invokeMethod(m_window, "addMessage", Q_ARG(QVariant, QVariant(message))); 42 | }); 43 | connect(socket, &ChatSocket::clientLoginSuccess, this, [this](const QString &username, const QString &ip) 44 | { 45 | ChatSocket *socket = qobject_cast(sender()); 46 | m_users[username] = socket; 47 | QMetaObject::invokeMethod(m_window, "addNewClient", Q_ARG(QVariant, username), Q_ARG(QVariant, ip), 48 | Q_ARG(QVariant, socket->status())); 49 | }); 50 | connect(socket, &ChatSocket::clientDisconnected, this, [this](const QString &username) 51 | { 52 | m_users.remove(username); 53 | QMetaObject::invokeMethod(m_window, "removeClient", Q_ARG(QVariant, QVariant(username))); 54 | qDebug() << m_users; 55 | }); 56 | 57 | socket->moveToThread(thread); 58 | thread->start(); 59 | } 60 | 61 | void ChatTcpServer::saveRecord(const QByteArray &sender, const QByteArray &receiver, const QByteArray &data) 62 | { 63 | //以后会设计一个专门格式化存储聊天记录的工具 64 | QFile file("users/" + QString(sender) + "/messageText/MSG" + QString(receiver) + ".txt"); 65 | file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text); 66 | QTextStream out(&file); 67 | out << "[time:" << QDateTime::currentDateTime().toString("yyyyMMdd hhmmss") << "]" << endl 68 | << "[data:" << QString::fromLocal8Bit(data) << "]" << endl; 69 | file.close(); 70 | 71 | file.setFileName("users/" + QString(receiver) + "/messageText/MSG" + QString(sender) + ".txt"); 72 | file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text); 73 | out << "[time:" << QDateTime::currentDateTime().toString("yyyyMMdd hhmmss") << "]" << endl 74 | << "[data:" << QString::fromLocal8Bit(data) << "]" << endl; 75 | file.close(); 76 | } 77 | 78 | void ChatTcpServer::writeDataToClient(const QByteArray &sender, const QByteArray &receiver, msg_t type, const QByteArray &data) 79 | { 80 | //因为在不同的线程中,使用invokeMethod调用 81 | //此函数简化了操作 82 | QMetaObject::invokeMethod(m_users[QString(receiver)], "writeClientData", Q_ARG(QByteArray, sender), 83 | Q_ARG(msg_t, type), Q_ARG(msg_option_t, MO_NULL), Q_ARG(QByteArray, data)); 84 | } 85 | 86 | void ChatTcpServer::disposeMessage(const QByteArray &sender, const QByteArray &receiver, msg_t type, msg_option_t option, const QByteArray &data) 87 | { 88 | switch (type) 89 | { 90 | case MT_SHAKE: 91 | case MT_TEXT: 92 | { 93 | //将双方的消息存入 94 | saveRecord(sender, receiver, data); 95 | if (m_users.contains(QString(receiver))) //如果另一方在线 96 | writeDataToClient(sender, receiver, type, data); 97 | else m_database->addUnreadMessage(QString(sender), QString(receiver)); //不在线则unreadMessage+1,下次登录时发送 98 | break; 99 | } 100 | case MT_STATECHANGE: 101 | { 102 | QMetaObject::invokeMethod(m_window, "stateChange", Q_ARG(QVariant, QString(sender)), Q_ARG(QVariant, m_users[sender]->status())); 103 | QStringList friends = m_database->getUserFriends(sender); 104 | for (auto it : friends) 105 | { 106 | if (m_users.contains(it)) //如果好友在线,则为其发送状态更新 107 | writeDataToClient(sender, it.toLatin1(), MT_STATECHANGE, data); 108 | } 109 | break; 110 | } 111 | case MT_ADDFRIEND: 112 | { 113 | if (m_users.contains(QString(receiver))) //如果另一方在线 114 | { 115 | writeDataToClient(sender, receiver, MT_ADDFRIEND, data); 116 | } 117 | } 118 | 119 | default: 120 | break; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/chattcpserver.h: -------------------------------------------------------------------------------- 1 | #ifndef CHATTCPSERVER_H 2 | #define CHATTCPSERVER_H 3 | 4 | #include "chatsocket.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | class ChatSocket; 11 | class QQmlEngine; 12 | class QQuickWindow; 13 | class ChatTcpServer : public QTcpServer 14 | { 15 | Q_OBJECT 16 | 17 | public: 18 | ChatTcpServer(QQmlEngine *engine, QObject *parent = nullptr); 19 | ~ChatTcpServer() override; 20 | 21 | void loadWindow(); 22 | 23 | protected: 24 | void incomingConnection(qintptr socketDescriptor) override; 25 | 26 | private slots: 27 | //保存聊天记录 28 | void saveRecord(const QByteArray &sender, const QByteArray &receiver, const QByteArray &data); 29 | //写入数据到指定客户端连接 30 | void writeDataToClient(const QByteArray &sender, const QByteArray &receiver, msg_t type, const QByteArray &data); 31 | //处理消息 32 | void disposeMessage(const QByteArray &sender, const QByteArray &receiver, msg_t type, msg_option_t option, const QByteArray &data); 33 | 34 | private: 35 | Database *m_database; 36 | QQmlEngine *m_qmlengine; 37 | QPointer m_window; 38 | QMap m_users; 39 | }; 40 | 41 | #endif // CHATTCPSERVER_H 42 | -------------------------------------------------------------------------------- /src/database.cpp: -------------------------------------------------------------------------------- 1 | #include "database.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | extern QDebug operator<<(QDebug debug, const UserInfo &info) 8 | { 9 | QDebugStateSaver saver(debug); 10 | debug << "username: " << info.username << "password: " << info.password << "nickname: " << info.nickname 11 | << "headImage: " << info.headImage << "background" << info.background << "gende: " << info.gender 12 | << "birthday: " << info.birthday << "signature: " << info.signature << "level: " << info.level; 13 | return debug; 14 | } 15 | 16 | Database::Database(const QString &connectionName, QObject *parent) 17 | : QObject(parent) 18 | , m_connectionName(connectionName) 19 | { 20 | QMutexLocker locker(&m_mutex); 21 | m_database = QSqlDatabase::addDatabase("QSQLITE", connectionName); 22 | m_database.setDatabaseName("users/users.db"); 23 | m_database.setHostName("localhost"); 24 | m_database.setUserName("MChatServer"); 25 | m_database.setPassword("123456"); 26 | m_database.open(); 27 | } 28 | 29 | Database::~Database() 30 | { 31 | closeDatabase(); 32 | //QSqlDatabase::removeDatabase(m_connectionName); 33 | } 34 | 35 | bool Database::openDatabase() 36 | { 37 | if (!m_database.isOpen()) 38 | { 39 | QMutexLocker locker(&m_mutex); 40 | return m_database.open(); 41 | } 42 | 43 | return true; 44 | } 45 | 46 | void Database::closeDatabase() 47 | { 48 | if (m_database.isOpen()) 49 | m_database.close(); 50 | } 51 | 52 | bool Database::createUser(const UserInfo &info) 53 | { 54 | if (!tableExists()) 55 | return false; 56 | 57 | QString user_dir = "users/" + info.username; 58 | if (!QFile::exists(user_dir)) //如果该用户不存在,则创建一系列文件夹 59 | { 60 | QDir dir; 61 | dir.mkpath(user_dir); 62 | dir.mkpath(user_dir + "/headImage"); 63 | dir.mkpath(user_dir + "/messageImage"); 64 | dir.mkpath(user_dir + "/messageText"); 65 | } 66 | 67 | QString insert = "INSERT INTO info VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?);"; 68 | QSqlQuery query(m_database); 69 | query.prepare(insert); 70 | query.addBindValue(info.username); 71 | query.addBindValue(info.password); 72 | query.addBindValue(info.nickname); 73 | query.addBindValue(info.headImage); 74 | query.addBindValue(info.background); 75 | query.addBindValue(info.gender); 76 | query.addBindValue(info.birthday); 77 | query.addBindValue(info.signature); 78 | query.addBindValue(info.level); 79 | 80 | if (query.exec()) 81 | { 82 | return true; 83 | } 84 | else 85 | { 86 | qDebug() << __func__ << query.lastError().text(); 87 | closeDatabase(); 88 | return false; 89 | } 90 | } 91 | 92 | UserInfo Database::getUserInfo(const QString &username) 93 | { 94 | if (!tableExists()) 95 | return UserInfo(); 96 | 97 | QSqlQuery query(m_database); 98 | if (!query.exec("SELECT * FROM info WHERE user_username = '" + username + "';")) 99 | qDebug() << __func__ << query.lastError().text(); 100 | query.next(); 101 | 102 | UserInfo info; 103 | if (query.isValid()) 104 | { 105 | info.username = query.value(0).toString(); 106 | info.password = query.value(1).toString(); 107 | info.nickname = query.value(2).toString(); 108 | info.headImage = query.value(3).toString(); 109 | info.background = query.value(4).toString(); 110 | info.gender = query.value(5).toString(); 111 | info.birthday = query.value(6).toString(); 112 | info.signature = query.value(7).toString(); 113 | info.level = query.value(8).toInt(); 114 | } 115 | else qDebug() << __func__ << "未找到" + username + "的信息。"; 116 | 117 | return info; 118 | } 119 | 120 | bool Database::addFriend(const QString &username, const QString &friendname) 121 | { 122 | /*if (username > friendname) 123 | qSwap(username, friendname);*/ 124 | QString insert = "INSERT INTO users VALUES(?, ?, ?, ?, ?, ?);"; 125 | QSqlQuery query(m_database); 126 | query.prepare(insert); 127 | query.addBindValue(username); 128 | query.addBindValue(friendname); 129 | query.addBindValue("我的好友"); 130 | query.addBindValue("我的好友"); 131 | query.addBindValue(0); 132 | query.addBindValue(0); 133 | 134 | if (query.exec()) 135 | { 136 | return true; 137 | } 138 | else 139 | { 140 | qDebug() << __func__ << query.lastError().text();; 141 | closeDatabase(); 142 | return false; 143 | } 144 | } 145 | 146 | void Database::setUserInfo(const UserInfo &info) 147 | { 148 | QString query_update = "UPDATE info " 149 | "SET user_password = '" + info.password + 150 | "', user_nickname = '" + info.nickname + 151 | "', user_headImage = '" + info.headImage + 152 | "', user_background = '" + info.background + 153 | "', user_gender = '" + info.gender + 154 | "', user_birthday = '" + info.birthday + 155 | "', user_signature = '" + info.signature + 156 | "', user_level = " + QString::number(info.level) + 157 | " WHERE user_username = '" + info.username + "';"; 158 | QSqlQuery query(m_database); 159 | if (query.exec(query_update)) 160 | { 161 | qDebug() << "user info 更新成功"; 162 | } 163 | else 164 | { 165 | qDebug() << __func__ << query.lastError().text(); 166 | closeDatabase(); 167 | } 168 | } 169 | 170 | QStringList Database::getUserFriends(const QString &username) 171 | { 172 | QString query_friends = "SELECT user_friend AS user_friend " 173 | "FROM users " 174 | "WHERE user_username = '" + username + "' " 175 | "UNION ALL " 176 | "SELECT user_username AS user_friend " 177 | "FROM users " 178 | "WHERE user_friend = '" + username + "'; "; 179 | 180 | QSqlQuery query(m_database); 181 | QStringList friends; 182 | if (query.exec(query_friends)) 183 | { 184 | while (query.next()) 185 | { 186 | if (query.isValid()) 187 | friends << query.value(0).toString(); 188 | } 189 | } 190 | else 191 | { 192 | qDebug() << __func__ << query.lastError().text(); 193 | closeDatabase(); 194 | } 195 | 196 | return friends; 197 | } 198 | 199 | QMap > Database::getUserFriendsInfo(const QString &username) 200 | { 201 | QString query_friends = "SELECT user_group AS user_group, user_friend AS user_friend, user_unread AS unreadMessage " 202 | "FROM users " 203 | "WHERE user_username = '" + username + "' " 204 | "UNION ALL " 205 | "SELECT friend_group AS user_group, user_username AS user_friend, friend_unread AS unreadMessage " 206 | "FROM users " 207 | "WHERE user_friend = '" + username + "';"; 208 | QSqlQuery query(m_database); 209 | QMap > friendsInfo; 210 | if (query.exec(query_friends)) 211 | { 212 | while (query.next()) 213 | { 214 | if (query.isValid()) 215 | { 216 | QString user_group = query.value(0).toString(); 217 | QString user_friend = query.value(1).toString(); 218 | int user_unread = query.value(2).toInt(); 219 | FriendInfo info = { user_friend, user_unread }; 220 | friendsInfo[user_group].append(info); 221 | } 222 | } 223 | } 224 | else 225 | { 226 | qDebug() << __func__ << query.lastError().text(); 227 | closeDatabase(); 228 | } 229 | 230 | return friendsInfo; 231 | } 232 | 233 | bool Database::addUnreadMessage(const QString &sender, const QString &receiver) 234 | { 235 | QString query_unread = "SELECT user_unread AS unreadMessage " 236 | "FROM users " 237 | "WHERE user_username = '" + receiver + "' " 238 | "UNION ALL " 239 | "SELECT friend_unread AS unreadMessage " 240 | "FROM users " 241 | "WHERE user_friend = '" + receiver + "';"; 242 | 243 | QSqlQuery query(m_database); 244 | 245 | if (query.exec(query_unread)) 246 | { 247 | query.next(); 248 | int unreadMessage = query.value(0).toInt(); 249 | QString query_update1 = "UPDATE users " 250 | "SET user_unread = " + QString::number(unreadMessage + 1) + 251 | " WHERE user_username = '" + receiver + "' AND user_friend = '" + sender + "';"; 252 | 253 | QString query_update2 = "UPDATE users " 254 | "SET friend_unread = " + QString::number(unreadMessage + 1) + 255 | " WHERE user_username = '" + sender + "' AND user_friend = '" + receiver + "';"; 256 | if (query.exec(query_update1) && query.exec(query_update2)) 257 | { 258 | return true; 259 | } 260 | else 261 | { 262 | qDebug() << __func__ << query.lastError().text(); 263 | closeDatabase(); 264 | return false; 265 | } 266 | } 267 | else 268 | { 269 | qDebug() << __func__ << query.lastError().text(); 270 | closeDatabase(); 271 | return false; 272 | } 273 | } 274 | 275 | bool Database::tableExists() 276 | { 277 | if (!openDatabase()) 278 | return false; 279 | 280 | QString query_create_users = "CREATE TABLE IF NOT EXISTS users" 281 | "(" 282 | " user_username varchar(10) NOT NULL," 283 | " user_friend varchar(10) NOT NULL," 284 | " user_group varchar(16) DEFAULT '我的好友'," 285 | " friend_group varchar(16) DEFAULT '我的好友'," 286 | " user_unread int DEFAULT 0," 287 | " friend_unread int DEFAULT 0" 288 | ");"; 289 | QString query_create_info = "CREATE TABLE IF NOT EXISTS info" 290 | "(" 291 | " user_username varchar(10) NOT NULL PRIMARY KEY," 292 | " user_password varchar(32) NOT NULL," 293 | " user_nickname varchar(32) DEFAULT 'USER'," 294 | " user_headImage varchar(32) DEFAULT 'qrc:/image/winIcon.png'," 295 | " user_background varchar(32) DEFAULT 'qrc:/image/Background/7.jpg'," 296 | " user_gender varchar(2) DEFAULT '男'," 297 | " user_birthday text DEFAULT '2019-01-01'," 298 | " user_signature varchar(64) DEFAULT NULL," 299 | " user_level int DEFAULT 1" 300 | ");"; 301 | QSqlQuery query(m_database); 302 | if (query.exec(query_create_users)) 303 | { 304 | if (query.exec(query_create_info)) 305 | { 306 | return true; 307 | } 308 | else 309 | { 310 | qDebug() << __func__ << query.lastError().text(); 311 | closeDatabase(); 312 | return false; 313 | } 314 | } 315 | else 316 | { 317 | qDebug() << __func__ << query.lastError().text(); 318 | closeDatabase(); 319 | return false; 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /src/database.h: -------------------------------------------------------------------------------- 1 | #ifndef DATABASE_H 2 | #define DATABASE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | struct UserInfo 10 | { 11 | QString username; 12 | QString password; 13 | QString nickname; 14 | QString headImage; 15 | QString background; 16 | QString gender; 17 | QString birthday; 18 | QString signature; 19 | int level; 20 | 21 | UserInfo() 22 | : username(""), password(""), nickname("") , headImage("") 23 | , background(""), gender(""), birthday(""), signature(""), level(0) 24 | { 25 | 26 | } 27 | 28 | bool isEmpty() const { return username.isEmpty(); } 29 | }; 30 | 31 | struct FriendInfo 32 | { 33 | QString friendname; 34 | int unreadMessage; 35 | }; 36 | 37 | QDebug operator<<(QDebug debug, const UserInfo &info); 38 | 39 | class Database : public QObject 40 | { 41 | Q_OBJECT 42 | 43 | public: 44 | Database(const QString &connectionName, QObject *parent = nullptr); 45 | ~Database(); 46 | 47 | QString name() const { return m_database.connectionName(); } 48 | bool openDatabase(); 49 | void closeDatabase(); 50 | 51 | bool createUser(const UserInfo &info); 52 | bool addFriend(const QString &username, const QString &friendname); 53 | 54 | void setUserInfo(const UserInfo &info); 55 | UserInfo getUserInfo(const QString &username); 56 | 57 | QStringList getUserFriends(const QString &username); 58 | QMap > getUserFriendsInfo(const QString &username); 59 | 60 | //增加一条未读记录 61 | bool addUnreadMessage(const QString &sender, const QString &receiver); 62 | 63 | private: 64 | bool tableExists(); 65 | 66 | private: 67 | QMutex m_mutex; 68 | QString m_connectionName; 69 | QSqlDatabase m_database; 70 | }; 71 | 72 | #endif // DATABASE_H 73 | -------------------------------------------------------------------------------- /src/framelesswindow.cpp: -------------------------------------------------------------------------------- 1 | #include "framelesswindow.h" 2 | #include 3 | 4 | #ifdef Q_OS_WIN 5 | #include 6 | #endif 7 | 8 | FramelessWindow::FramelessWindow(QQuickWindow *parent) 9 | : QQuickWindow(parent) 10 | { 11 | m_minimumWidth = 0; 12 | m_minimumHeight = 0; 13 | m_maximumWidth = 9999; 14 | m_maximumHeight = 9999; 15 | m_mousePenetrate = false; 16 | m_topHint = false; 17 | m_taskbarHint = false; 18 | 19 | setFlags(flags() | Qt::Tool | Qt::FramelessWindowHint); 20 | setColor(Qt::transparent); 21 | 22 | /*#if defined(Q_OS_WIN) 23 | HWND hwnd = reinterpret_cast(winId()); 24 | DWORD class_style = ::GetClassLong(hwnd, GCL_STYLE); 25 | class_style &= ~CS_DROPSHADOW; 26 | ::SetClassLong(hwnd, GCL_STYLE, class_style); // windows系统函数 27 | #endif*/ 28 | } 29 | 30 | FramelessWindow::~FramelessWindow() 31 | { 32 | 33 | } 34 | 35 | QString FramelessWindow::windowIcon() const 36 | { 37 | return m_windowIcon; 38 | } 39 | 40 | QPoint FramelessWindow::coord() const 41 | { 42 | return QPoint(x(), y()); 43 | } 44 | 45 | int FramelessWindow::width() const 46 | { 47 | return m_width; 48 | } 49 | 50 | int FramelessWindow::height() const 51 | { 52 | return m_height; 53 | } 54 | 55 | int FramelessWindow::actualWidth() const 56 | { 57 | return QQuickWindow::width(); 58 | } 59 | 60 | int FramelessWindow::actualHeight() const 61 | { 62 | return QQuickWindow::height(); 63 | } 64 | 65 | int FramelessWindow::minimumWidth() const 66 | { 67 | return m_minimumWidth; 68 | } 69 | 70 | int FramelessWindow::minimumHeight() const 71 | { 72 | return m_minimumHeight; 73 | } 74 | 75 | int FramelessWindow::maximumWidth() const 76 | { 77 | return m_maximumWidth; 78 | } 79 | 80 | int FramelessWindow::maximumHeight() const 81 | { 82 | return m_maximumHeight; 83 | } 84 | 85 | bool FramelessWindow::mousePenetrate() const 86 | { 87 | return m_mousePenetrate; 88 | } 89 | 90 | bool FramelessWindow::topHint() const 91 | { 92 | return m_topHint; 93 | } 94 | 95 | bool FramelessWindow::taskbarHint() const 96 | { 97 | return m_taskbarHint; 98 | } 99 | 100 | void FramelessWindow::setWindowIcon(const QString &arg) 101 | { 102 | if (m_windowIcon != arg) 103 | { 104 | setIcon(QIcon(arg)); 105 | m_windowIcon = arg; 106 | emit windowIconChanged(arg); 107 | } 108 | } 109 | 110 | void FramelessWindow::setCoord(const QPoint &arg) 111 | { 112 | if (coord() != arg) 113 | { 114 | setX(arg.x()); 115 | setY(arg.y()); 116 | emit coordChanged(arg); 117 | } 118 | } 119 | 120 | void FramelessWindow::setWidth(int arg) 121 | { 122 | if (arg <= m_maximumWidth && arg >= m_minimumWidth) 123 | { 124 | m_width = arg; 125 | contentItem()->setWidth(arg); 126 | emit widthChanged(arg); 127 | } 128 | } 129 | 130 | void FramelessWindow::setHeight(int arg) 131 | { 132 | if (arg <= m_maximumHeight && arg >= m_minimumHeight) 133 | { 134 | m_height = arg; 135 | contentItem()->setHeight(arg); 136 | emit heightChanged(arg); 137 | } 138 | } 139 | 140 | void FramelessWindow::setActualWidth(int arg) 141 | { 142 | if (actualWidth() != arg) 143 | { 144 | QQuickWindow::setWidth(arg); 145 | emit actualWidthChanged(arg); 146 | } 147 | } 148 | 149 | void FramelessWindow::setActualHeight(int arg) 150 | { 151 | if (actualHeight() != arg) 152 | { 153 | QQuickWindow::setHeight(arg); 154 | emit actualHeightChanged(arg); 155 | } 156 | } 157 | 158 | void FramelessWindow::setMinimumWidth(int arg) 159 | { 160 | if (m_minimumWidth != arg) 161 | { 162 | m_minimumWidth = arg; 163 | emit minimumWidthChanged(arg); 164 | } 165 | } 166 | 167 | void FramelessWindow::setMinimumHeight(int arg) 168 | { 169 | if (m_minimumHeight != arg) 170 | { 171 | m_minimumHeight = arg; 172 | emit minimumHeightChanged(arg); 173 | } 174 | } 175 | 176 | void FramelessWindow::setMaximumWidth(int arg) 177 | { 178 | if (m_maximumWidth != arg) 179 | { 180 | m_maximumWidth = arg; 181 | emit maximumWidthChanged(arg); 182 | } 183 | } 184 | 185 | void FramelessWindow::setMaximumHeight(int arg) 186 | { 187 | if (m_maximumHeight != arg) 188 | { 189 | m_maximumHeight = arg; 190 | emit maximumHeightChanged(arg); 191 | } 192 | } 193 | 194 | void FramelessWindow::setMousePenetrate(bool arg) 195 | { 196 | if (m_mousePenetrate != arg) 197 | { 198 | #if defined(Q_OS_WIN) 199 | HWND my_hwnd = (HWND)this->winId (); 200 | if(arg) 201 | { 202 | SetWindowLong(my_hwnd, GWL_EXSTYLE, 203 | GetWindowLong(my_hwnd, GWL_EXSTYLE) | WS_EX_TRANSPARENT); 204 | } 205 | else 206 | { 207 | SetWindowLong(my_hwnd, GWL_EXSTYLE, 208 | GetWindowLong(my_hwnd, GWL_EXSTYLE)&(~WS_EX_TRANSPARENT)); 209 | } 210 | #endif 211 | m_mousePenetrate = arg; 212 | emit mousePenetrateChanged(); 213 | } 214 | } 215 | 216 | void FramelessWindow::setTopHint(bool arg) 217 | { 218 | if (m_topHint != arg) 219 | { 220 | if (arg) 221 | setFlags(flags() | Qt::WindowStaysOnTopHint); 222 | else 223 | setFlags(flags() & ~Qt::WindowStaysOnTopHint); 224 | m_topHint = arg; 225 | emit topHintChanged(); 226 | } 227 | } 228 | 229 | void FramelessWindow::setTaskbarHint(bool arg) 230 | { 231 | if (m_taskbarHint != arg) 232 | { 233 | if(arg) 234 | setFlags(flags() & (~Qt::Tool) | Qt::Window); 235 | else 236 | setFlags(flags() | Qt::Tool); 237 | m_taskbarHint = arg; 238 | emit taskbarHintChanged(); 239 | } 240 | } 241 | 242 | void FramelessWindow::close() 243 | { 244 | emit closed(); 245 | deleteLater(); 246 | } 247 | 248 | bool FramelessWindow::event(QEvent *ev) 249 | { 250 | if (ev->type() == QEvent::Enter) //鼠标进入 251 | { 252 | emit entered(); 253 | } 254 | else if (ev->type() == QEvent::Leave) //鼠标离开 255 | { 256 | emit exited(); 257 | } 258 | 259 | return QQuickWindow::event(ev); 260 | } 261 | -------------------------------------------------------------------------------- /src/framelesswindow.h: -------------------------------------------------------------------------------- 1 | #ifndef FRAMELESSWINDOW_H 2 | #define FRAMELESSWINDOW_H 3 | 4 | #include 5 | 6 | class FramelessWindow : public QQuickWindow 7 | { 8 | Q_OBJECT 9 | 10 | Q_PROPERTY(QString windowIcon READ windowIcon WRITE setWindowIcon NOTIFY windowIconChanged) 11 | Q_PROPERTY(QPoint coord READ coord WRITE setCoord NOTIFY coordChanged) 12 | Q_PROPERTY(int width READ width WRITE setWidth NOTIFY widthChanged) 13 | Q_PROPERTY(int height READ height WRITE setHeight NOTIFY heightChanged) 14 | Q_PROPERTY(int actualWidth READ actualWidth WRITE setActualWidth NOTIFY actualWidthChanged) 15 | Q_PROPERTY(int actualHeight READ actualHeight WRITE setActualHeight NOTIFY actualHeightChanged) 16 | Q_PROPERTY(int minimumWidth READ minimumWidth WRITE setMinimumWidth NOTIFY minimumWidthChanged) 17 | Q_PROPERTY(int minimumHeight READ minimumHeight WRITE setMinimumHeight NOTIFY minimumHeightChanged) 18 | Q_PROPERTY(int maximumWidth READ maximumWidth WRITE setMaximumWidth NOTIFY maximumWidthChanged) 19 | Q_PROPERTY(int maximumHeight READ maximumHeight WRITE setMaximumHeight NOTIFY maximumHeightChanged) 20 | //是否穿透鼠标 21 | Q_PROPERTY(bool mousePenetrate READ mousePenetrate WRITE setMousePenetrate NOTIFY mousePenetrateChanged) 22 | //是否显示在最前 23 | Q_PROPERTY(bool topHint READ topHint WRITE setTopHint NOTIFY topHintChanged) 24 | Q_PROPERTY(bool taskbarHint READ taskbarHint WRITE setTaskbarHint NOTIFY taskbarHintChanged) 25 | 26 | public: 27 | FramelessWindow(QQuickWindow *parent = nullptr); 28 | ~FramelessWindow(); 29 | 30 | QString windowIcon() const; 31 | void setWindowIcon(const QString &arg); 32 | 33 | QPoint coord() const; 34 | void setCoord(const QPoint &arg); 35 | 36 | int width() const; 37 | void setWidth(int arg); 38 | 39 | int height() const; 40 | void setHeight(int arg); 41 | 42 | int actualWidth() const; 43 | void setActualWidth(int arg); 44 | 45 | int actualHeight() const; 46 | void setActualHeight(int arg); 47 | 48 | int minimumWidth() const; 49 | void setMinimumWidth(int arg); 50 | 51 | int minimumHeight() const; 52 | void setMinimumHeight(int arg); 53 | 54 | int maximumWidth() const; 55 | void setMaximumWidth(int arg); 56 | 57 | int maximumHeight() const; 58 | void setMaximumHeight(int arg); 59 | 60 | bool mousePenetrate() const; 61 | void setMousePenetrate(bool arg); 62 | 63 | bool topHint() const; 64 | void setTopHint(bool arg); 65 | 66 | bool taskbarHint() const; 67 | void setTaskbarHint(bool arg); 68 | 69 | signals: 70 | void windowIconChanged(const QString &arg); 71 | void coordChanged(const QPoint &arg); 72 | void widthChanged(int arg); 73 | void heightChanged(int arg); 74 | void actualWidthChanged(int arg); 75 | void actualHeightChanged(int arg); 76 | void minimumWidthChanged(int arg); 77 | void minimumHeightChanged(int arg); 78 | void maximumWidthChanged(int arg); 79 | void maximumHeightChanged(int arg); 80 | void mousePenetrateChanged(); 81 | void topHintChanged(); 82 | void taskbarHintChanged(); 83 | 84 | void entered(); 85 | void exited(); 86 | void closed(); 87 | 88 | public slots: 89 | void close(); 90 | 91 | protected: 92 | bool event(QEvent *ev); 93 | 94 | private: 95 | QString m_windowIcon; 96 | int m_width; 97 | int m_height; 98 | int m_minimumWidth; 99 | int m_minimumHeight; 100 | int m_maximumWidth; 101 | int m_maximumHeight; 102 | bool m_mousePenetrate; 103 | bool m_topHint; 104 | bool m_taskbarHint; 105 | }; 106 | 107 | #endif // FRAMELESSWINDOW_H 108 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "chattcpserver.h" 2 | #include "framelesswindow.h" 3 | 4 | #include 5 | #include 6 | 7 | int main(int argc, char *argv[]) 8 | { 9 | QGuiApplication app(argc, argv); 10 | 11 | qmlRegisterType("an.framelessWindow", 1, 0, "FramelessWindow"); 12 | qRegisterMetaType("msg_t"); 13 | qRegisterMetaType("msg_option_t"); 14 | 15 | QQmlApplicationEngine engine; 16 | ChatTcpServer server(&engine); 17 | if (!server.listen(QHostAddress::AnyIPv4, server_port)) 18 | { 19 | QGuiApplication::exit(1); 20 | } 21 | else server.loadWindow(); 22 | 23 | return app.exec(); 24 | } 25 | -------------------------------------------------------------------------------- /src/protocol.cpp: -------------------------------------------------------------------------------- 1 | #include "protocol.h" 2 | #include 3 | 4 | extern QDebug operator<<(QDebug debug, const MessageHeader &header) 5 | { 6 | debug << hex << header.flag 7 | << header.type 8 | << header.size 9 | << header.option 10 | << header.sender 11 | << header.receiver 12 | << header.md5; 13 | 14 | return debug; 15 | } 16 | 17 | extern QDebug operator<<(QDebug debug, const Message &message) 18 | { 19 | debug << message.header << message.data; 20 | 21 | return debug; 22 | } 23 | 24 | extern QDataStream& operator<<(QDataStream &out, const MessageHeader &header) 25 | { 26 | out << header.flag 27 | << header.type 28 | << header.size 29 | << header.option 30 | << header.sender 31 | << header.receiver 32 | << header.md5; 33 | 34 | return out; 35 | } 36 | 37 | extern QDataStream& operator>>(QDataStream &in, MessageHeader &header) 38 | { 39 | in >> header.flag 40 | >> header.type 41 | >> header.size 42 | >> header.option 43 | >> header.sender 44 | >> header.receiver 45 | >> header.md5; 46 | 47 | return in; 48 | } 49 | 50 | extern QDataStream& operator<<(QDataStream &out, const Message &message) 51 | { 52 | out << message.header 53 | << message.data; 54 | 55 | return out; 56 | } 57 | 58 | extern QDataStream& operator>>(QDataStream &in, Message &message) 59 | { 60 | in >> message.header 61 | >> message.data; 62 | 63 | return in; 64 | } 65 | -------------------------------------------------------------------------------- /src/protocol.h: -------------------------------------------------------------------------------- 1 | #ifndef PROTOCOL_H 2 | #define PROTOCOL_H 3 | 4 | #include 5 | #include 6 | 7 | typedef quint32 msg_flag_t; 8 | typedef quint8 msg_t; 9 | typedef quint32 msg_size_t; 10 | typedef quint8 msg_option_t; 11 | 12 | //用于测试 13 | #define server_ip "10.220.3.25" 14 | #define server_port 43800 15 | 16 | //消息头的标志 17 | #define MSG_FLAG 0xF8AD951A 18 | 19 | //消息类型 20 | //系统类型 0x10 ~ 0x40 21 | #define MSG_IS_SYSTEM(x) (x <= 0x40) 22 | #define MT_CHECK 0x10 //验证 23 | #define MT_HEARTBEAT 0x11 //心跳 24 | #define MT_USERINFO 0x12 //用户信息 25 | #define MT_STATECHANGE 0x13 //状态变化 26 | #define MT_SEARCH 0x14 //获取信息 27 | #define MT_ADDFRIEND 0x15 //添加好友 28 | #define MT_REGISTER 0x16 //注册账户 29 | #define MT_UNKNOW 0x30 //未知 30 | 31 | #define CHECK_SUCCESS "SUCCESS" //验证成功 32 | #define CHECK_FAILURE "FAILURE" //验证失败 33 | 34 | #define CLIENT_ID "CLIENT" //客户端默认ID 35 | #define SERVER_ID "SERVER" //服务器ID 36 | #define HEARTBEAT "HEARTBEAT" //心跳数据 37 | #define USERINFO "USERINFO" //用户信息 38 | 39 | #define ADDFRIEND "ADDFRIEND" //添加好友 40 | //添加成功将发送其信息 41 | #define ADD_SUCCESS "SUCCESS" //添加成功 42 | #define ADD_FAILURE "FAILURE" //添加失败 43 | 44 | //注册成功/失败消息 45 | #define REG_SUCCESS "SUCCESS" //注册成功 46 | #define REG_FAILURE "FAILURE" //注册失败 47 | 48 | //用户类型 0x41 ~ 0xFF 49 | #define MSG_IS_USER(x) (!(MSG_IS_SYSTEM(x))) 50 | #define MT_SHAKE 0x41 //窗口抖动 51 | #define MT_TEXT 0x42 //普通文本 52 | #define MT_IMAGE 0x43 //图像 53 | #define MT_FILE 0x44 //文件 54 | 55 | //选项类型 56 | #define MO_NULL 0x10 //无类型 57 | #define MO_UPLOAD 0x11 //上传 58 | #define MO_DOWNLOAD 0x12 //下载 59 | 60 | /* 61 | * \ 消息标志flag \\ 消息类型type \\ 消息大小size \\ 选项类型option \\ 发送者ID \\ 接收者ID \\ MD5验证 \ 62 | * { 消息头 } 63 | * \ 数据data \ ... \ 数据data \ 64 | * { 数据 } 65 | */ 66 | 67 | struct MessageHeader 68 | { 69 | MessageHeader() 70 | : flag(0), type(0), size(0), option(0), 71 | sender(QByteArray()), receiver(QByteArray()), md5(QByteArray()) {} 72 | MessageHeader(msg_flag_t f, msg_t t, msg_size_t s, msg_option_t o, 73 | const QByteArray &sr, const QByteArray &rr, const QByteArray &m) 74 | : flag(f), type(t), size(s), option(o), sender(sr), receiver(rr), md5(m) {} 75 | 76 | bool isEmpty() const 77 | { 78 | return flag == 0 || 79 | type == 0 || 80 | size == 0 || 81 | option == 0 || 82 | sender.isEmpty() || 83 | receiver.isEmpty() || 84 | md5.isEmpty(); 85 | } 86 | 87 | int getSize() const 88 | { 89 | return int(sizeof(flag)) + 90 | int(sizeof(type)) + 91 | int(sizeof(size)) + 92 | int(sizeof(option)) + 93 | sender.size() + 94 | receiver.size() + 95 | md5.size() + 96 | 3 * 4; //有三个QByteArray,每个会在前面加4字节大小 97 | } 98 | 99 | msg_flag_t flag; 100 | msg_t type; 101 | msg_size_t size; 102 | msg_option_t option; 103 | QByteArray sender; //Latin-1 104 | QByteArray receiver; //Latin-1 105 | QByteArray md5; //md5(base64 data) 106 | }; 107 | 108 | struct Message 109 | { 110 | Message() : header(MessageHeader()), data(QByteArray()) {} 111 | Message(const MessageHeader &h, const QByteArray &d) : header(h), data(d) {} 112 | bool isEmpty() const { return header.isEmpty() || data.isEmpty(); } 113 | 114 | MessageHeader header; 115 | QByteArray data; //Local8Bit 116 | }; 117 | 118 | msg_flag_t inline get_flag(MessageHeader &header) { return header.flag; } 119 | msg_flag_t inline get_flag(Message &message) { return get_flag(message.header); } 120 | msg_t inline get_type(MessageHeader &header) { return header.type; } 121 | msg_t inline get_type(Message &message) { return get_type(message.header); } 122 | msg_size_t inline get_size(MessageHeader &header) { return header.size; } 123 | msg_size_t inline get_size(Message &message) { return get_size(message.header); } 124 | msg_option_t inline get_option(MessageHeader &header) { return header.option; } 125 | msg_option_t inline get_option(Message &message) { return get_option(message.header); } 126 | QByteArray inline get_sender(MessageHeader &header) { return header.sender; } 127 | QByteArray inline get_sender(Message &message) { return get_sender(message.header); } 128 | QByteArray inline get_receiver(MessageHeader &header) { return header.receiver; } 129 | QByteArray inline get_receiver(Message &message) { return get_receiver(message.header); } 130 | QByteArray inline get_md5(MessageHeader &header) { return header.md5; } 131 | QByteArray inline get_md5(Message &message) { return get_md5(message.header); } 132 | 133 | QDebug operator<<(QDebug debug, const MessageHeader &header); 134 | QDebug operator<<(QDebug debug, const Message &message); 135 | QDataStream& operator<<(QDataStream &out, const MessageHeader &header); 136 | QDataStream& operator>>(QDataStream &in, MessageHeader &header); 137 | QDataStream& operator<<(QDataStream &out, const Message &message); 138 | QDataStream& operator>>(QDataStream &in, Message &message); 139 | 140 | #endif // PROTOCOL_H 141 | -------------------------------------------------------------------------------- /test.sql: -------------------------------------------------------------------------------- 1 | SELECT user_group AS user_group, user_friend AS user_friend, user_unread AS unreadMessage 2 | FROM users.users 3 | WHERE user_username = "843261040" 4 | UNION ALL 5 | SELECT friend_group AS user_group, user_username AS user_friend, friend_unread AS unreadMessage 6 | FROM users.users 7 | WHERE user_friend = "843261040"; 8 | 9 | SELECT user_unread AS unreadMessage 10 | FROM users.users 11 | WHERE user_username ="843261040" 12 | UNION ALL 13 | SELECT friend_unread AS unreadMessage 14 | FROM users.users 15 | WHERE user_friend ="843261040"; 16 | 17 | 18 | /*UPDATE users.users 19 | SET user_unread = 3 20 | WHERE user_username = "00000001" AND user_friend = "843261040"; 21 | UPDATE users.users 22 | SET friend_unread = 3 23 | WHERE user_username = "843261040" AND user_friend = "00000001";*/ 24 | 25 | /*UPDATE users.info 26 | SET user_password = '00000000000', user_gender = '男' 27 | WHERE user_username = '843261040';*/ 28 | 29 | 30 | INSERT INTO users.info 31 | VALUES("843261040", "00000000000", "MPS", "qrc:/image/head1.jpg", "qrc:/image/Background/7.jpg", "男", "1996-07-27", "嗷嗷嗷嗷嗷嗷嗷", 999); 32 | INSERT INTO users.info 33 | VALUES("00000001", "00000000000", "mps1", "qrc:/image/HeadImage/head1.jpg", "qrc:/image/Background/7.jpg","男", "1995-01-23", "我不爱你了、", 37); 34 | INSERT INTO users.info 35 | VALUES("00000002", "00000000000", "mps2", "qrc:/image/HeadImage/head2.jpg", "qrc:/image/Background/7.jpg","男", "1998-10-12", "哎 o(︶︿︶)o ", 16); 36 | INSERT INTO users.info 37 | VALUES("00000003", "00000000000", "mps3", "qrc:/image/HeadImage/head3.jpg", "qrc:/image/Background/7.jpg","女", "1997-08-08", "累了、", 23); -------------------------------------------------------------------------------- /users/843261040/headImage/head1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengps/MChatServer/5409a4577492260210535b60770e4fdf37243c40/users/843261040/headImage/head1.jpg -------------------------------------------------------------------------------- /users/users.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengps/MChatServer/5409a4577492260210535b60770e4fdf37243c40/users/users.db --------------------------------------------------------------------------------