服务器源码地址:https://github.com/ermu592275254/chat-socket 网页源码地址:https://github.com/ermu592275254/chat-socket
使用nodejs搭建后台,由于是一个单页应用,而且先后端通讯使用了webSocket,全部只用http
模块搭建一个简单的服务器,未使用koa、express
等web框架。前端
使用socket.io
实现webSocket,前端经过import socket.io 的方式会出现不断重连的状况,因而使用script
方式实现。vue
const io = require('socket.io-client'); // or with import syntax import io from 'socket.io-client'; // or script <script src="/socket.io/socket.io.js"></script> <script> const socket = io('http://localhost'); </script>
使用mongoose
操做mongodb。mongodb这类非关系型数据库,功能较关系型数据库阉割了许多。主要表如今复杂的sql语句、事务支持等。node
使用vue以及vue的衍生产品,同时用到bootstarp做为样式框架。简单兼容了PC和移动。(PC仅支持chrome,在firefox、ie等浏览器中,会出现样式、布局混乱的状况)。git
经过用户名和socketId进行匹配。保存用户每次登陆的socketId,当对方在线时,将此信息经过socketId发送给对方。不在线仅保存到数据库,用户上线便可在私聊中查看。目前不支持消息通知,也不支持未读消息github
...// 每次登陆都将socketId替换为当前登陆的socketId userModel.update({username: data.username}, {socketId: socket.id}).then(res => { socket.emit('login', user); }).catch(err => { console.log(err); socket.emit('err', 'update user socketId was failed'); }); ...
chatModel.findOne({sendTime: time}).populate('sender receiver').then(newChat=>{ let receiverData = { receiver: data.sender, data: newChat }; // 若是对方在线就发送给对方 if (io.sockets.connected[user.socketId]) { io.sockets.connected[user.socketId].emit('newMessage', receiverData); } let senderData = { receiver: data.receiver, data: newChat }; // 同时也发送给本身(也可直接在前端添加,后端不发送) io.sockets.connected[socket.id].emit('newMessage', senderData); }).catch(err=>{ io.sockets.connected[socket.id].emit('err', 'can`t find the newMessage') })
经过broadcast
实现组发送。将群、群对应的聊天记录保存在数据库。用户进入群聊,则将其加入到对应的broadcast中。web
socket.on('joinRoom', function(data) { if (!common.checkData(data)) { io.sockets.connected[socket.id].emit('err', 'request params Can`t be empty'); return; } // 加入对应的群聊 socket.join(data.groupName, function() { let roomName = Object.keys(socket.rooms); io.to(data.groupName, `${data.username} has joined the room`); socket.broadcast.in('data.groupName').emit('newUserJoin', { groupName: data.groupName, username: data.username }) }); })
groupChatModel.findOne({'sendTime': time}).populate('sender').then(res=>{ if(res){ // 发送给本身 io.sockets.connected[socket.id].emit('newMsgOfGroup', res); // 将消息发送给群里的全部人除了本身 socket.broadcast.in(data.groupName).emit('newMsgOfGroup', res); } else { io.sockets.connected[socket.id].emit('err', 'the message data is null'); }
一样使用webSocket,将头像ID保存在用户信息表中,将图片文件保存在服务器static文件夹中。sql
uploadIcon(){ let file = this.$refs.uploadEl.files[0]; console.log(file); if(file.size > 100000){ this.Toast('文件大小不能超过1M'); this.$refs.uploadEl.value = ''; return; } let data = { username: this.user.username, file: file, type: file.type.split('/')[1] }; socket.emit('uploadUserIcon', data); this.$refs.uploadEl.value = ''; }
socket.on('uploadUserIcon', function(data) { let time = new Date().getTime(); let savePath = `/static/userIcon/${time}.${data.type}`; let hostPath = 'http://' + host + ':' + port; // 经过fs模块操做 fs.writeFile('.'+ savePath, data.file, function(err) { if (err) { console.log(err); io.sockets.connected[socket.id].emit('err', 'save userIcon failed'); } else { userModel.update({username: data.username}, {$set: {userIcon: hostPath + savePath}}).then(res => { userModel.findOne({username: data.username}).then(user=>{ io.sockets.connected[socket.id].emit('uploadUserIcon', { user: user, message: 'upload userIcon success' }); }).catch(err =>{ io.sockets.connected[socket.id].emit('err', 'find userInfo failed'); }); }).catch(err => { io.sockets.connected[socket.id].emit('err', 'save userIcon path failed'); }) } }) });
将用户名做为惟一值。注册时不能注册已存在的用户名。登陆支持自动登陆,将密码保存在localStorage中。mongodb
最后: 这是本菜鸡陆陆续续作了一年的项目,屡次放弃又从新拾起。代码写得不堪入目,没有精力和激情再去作优化了。暂时先这样吧......chrome