图片描述图片描述图片描述图片描述图片描述

项目地址

服务器源码地址:https://github.com/ermu592275254/chat-socket
网页源码地址:https://github.com/ermu592275254/chat-socket

项目设计概述

相关技术

nodejs

使用nodejs搭建后台,因为是一个单页应用,并且前后端通信使用了webSocket,所有只用http模块搭建一个简单的服务器,未使用koa、express等web框架。

webSocket

使用socket.io实现webSocket,前端通过import socket.io 的方式会出现不断重连的情况,于是使用script方式实现。

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>

mongodb

使用mongoose操作mongodb。mongodb这类非关系型数据库,功能较关系型数据库阉割了许多。主要表现在复杂的sql语句、事务支持等。

vue

使用vue以及vue的衍生产品,同时用到bootstarp作为样式框架。简单兼容了PC和移动。(PC仅支持chrome,在firefox、ie等浏览器中,会出现样式、布局混乱的情况)。

功能点实现

私聊

通过用户名和socketId进行匹配。保存用户每次登录的socketId,当对方在线时,将此信息通过socketId发送给对方。不在线仅保存到数据库,用户上线即可在私聊中查看。目前不支持消息通知,也不支持未读消息

...// 每次登录都将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中。

 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文件夹中。

 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中。

待处理bug以及优化

打包后静态资源路径有问题(有没有大神能帮帮我QAQ)

需要未读消息小红点

增加表情、图片发送

最后: 这是本菜鸡陆陆续续做了一年的项目,多次放弃又重新拾起。代码写得不堪入目,没有精力和激情再去做优化了。暂时先这样吧......


ermu
500 声望117 粉丝

不行啊,要上班