[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-9064":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":16,"subscribersCount":16,"size":16,"stars1d":16,"stars7d":16,"stars30d":16,"stars90d":16,"forks30d":16,"starsTrendScore":16,"compositeScore":17,"rankGlobal":10,"rankLanguage":10,"license":10,"archived":18,"fork":18,"defaultBranch":19,"hasWiki":20,"hasPages":18,"topics":21,"createdAt":10,"pushedAt":10,"updatedAt":28,"readmeContent":29,"aiSummary":30,"trendingCount":16,"starSnapshotCount":16,"syncStatus":15,"lastSyncTime":31,"discoverSource":32},9064,"Vchat","wuyawei\u002FVchat","wuyawei","💘🍦🙈Vchat — 从头到脚，撸一个社交聊天系统（vue + node + mongodb）","",null,"Vue",939,231,20,2,0,48.1,false,"master",true,[22,23,24,25,26,27],"chat","javascipt","mongodb","node","socket","vue","2026-06-12 04:00:42","﻿## 【从头到脚】撸一个社交聊天系统（vue + node + mongodb）- 💘🍦🙈Vchat \n\n### 说明\n> 不再维护\n\n> 该项目是自己学习的时候写的，有些地方可读性较差（如 mongo 部分大量回调），有需要的同学审慎阅读，谢谢。\n\n> 分支代码为线上版，做了相应改动。如默认加入聊天群、后端为适应linux部分代码调整等，建议拉取master。\n### 项目启动\n> 注意必须要有node、npm以及mongodb，项目默认mongodb IP地址为127.0.0.1:27017，可以在配置文件中修改。（chatServer\\utils\\database.js）\n```\n    cd chatRoom\n    npm install 安装前端依赖\n    npm run build 编译前端代码\n    cd ..\n    cd chatServer\n    npm install 安装后端依赖\n    npm run create 初始化数据库（号码池、表情包）\n    npm start 启动服务\n    在浏览器中打开 localhost:9988 即可\n```\n### 前言\n***\n> 项目开始是因为工作需要一个聊天室功能，但是因为某些原因最终选用的是基于xmpp协议的Strophe.js写的。于是就想用node自己写一套，本来只是想简单的写个聊天页面，但是写完了又不满意，所以不断的重构（似乎可以理解产品经理为什么老是改需求了๑乛◡乛๑）。\n\n>很多东西，比如mongodb，我也是第一次用，以前只接触过mysql。所以都是一边学一边写，利用工作之余的时间，断断续续的写了几个月（这次讲的是V0.9.0版本，项目还在更新中···），包含了一整套的前后端交互。uI是按照自己的感觉来的，没有设计天分（话说主题切换到现在还只有一套主题，实在是不好设计啊~），轻喷---。项目还有很多需要优化完善的地方，欢迎大家提到issues（文末有q群，欢迎一起学习交流）。\n\n> 闲话少说，本文主要讲项目的设计流程，以及部分功能实现思路。对项目感兴趣的同学请移步源码 [Vchat — 从头到脚，撸一个在线聊天的web应用（vue + node + mongodb)](https:\u002F\u002Fgithub.com\u002Fwuyawei\u002FVchat)。\n\n**这是分隔线---------------------------------------深夜码字，最近真冷*\n\n### 相关地址\n\n* [掘金](https:\u002F\u002Fjuejin.im\u002Fpost\u002F5c0a00fb6fb9a049d4419d3a)\n* [知乎](https:\u002F\u002Fzhuanlan.zhihu.com\u002Fp\u002F51963164)\n\n### 项目架构\n* 技术栈\n![](https:\u002F\u002Fuser-gold-cdn.xitu.io\u002F2018\u002F12\u002F9\u002F16791dc82a7ede84?w=1006&h=604&f=png&s=71877)\n>  前端主要采用了vue全家桶，没什么多说的，脚手架构建项目，vuex状态管理，vue-router控制路由，axios进行前后端交互。后端是基于node搭的服务，用的是express。我为什么不用koa呢，纯粹是图方便，因为koa不熟（捂脸）。聊天最重要的当然是通信，项目用[socket.io](https:\u002F\u002Fwww.w3cschool.cn\u002Fsocket\u002Fsocket-1olq2egc.html)来进行前后端通信。\n\n> 数据库是mongoDB，主要有用户、好友、群聊、消息、表情、号码池等。\n\n* 功能概览\n![](https:\u002F\u002Fuser-gold-cdn.xitu.io\u002F2018\u002F12\u002F9\u002F16791b1fce406602?w=1319&h=986&f=png&s=81227)\n\n* 目录结构\n\n```javascript\n    \u002F\u002F 前端\n    ├─build\n    ├─config\n    ├─src\n    │  ├─api                                  \u002F\u002F 接口api\n    │  ├─assets                               \u002F\u002F 静态资源\n    │  │  └─img\n    │  ├─directives                           \u002F\u002F 全局指令\n    │  ├─libs                                 \u002F\u002F 全局组件\n    │  │  ├─bscroll\n    │  │  ├─dropdown\n    │  │  ├─icon\n    │  │  ├─nodata\n    │  │  ├─PhotoSwipe\n    │  │  ├─uploadPopover\n    │  │  └─vScroll\n    │  ├─router                                \u002F\u002F 路由\n    │  ├─store                                 \u002F\u002F 状态管理\n    │  ├─utils                                 \u002F\u002F 方法\n    │  └─views\n    │      ├─applicationModel\n    │      │  ├─games                          \u002F\u002F 游戏\n    │      │  │\n    │      │  └─sub                            \u002F\u002F 应用\n    │      ├─components                        \u002F\u002F 组件\n    │      │  ├─APlayer\n    │      │  ├─chat\n    │      │  ├─cropper\n    │      │  ├─DPlayer\n    │      │  └─header\n    │      ├─personalModel                     \u002F\u002F 主页\n    │      │  ├─appModel                       \u002F\u002F 天气等\n    │      │  ├─friendModel                    \u002F\u002F 好友\n    │      │  └─groupModel                     \u002F\u002F 群聊\n    │      └─settingModel                      \u002F\u002F 设置\n    └─static\n        ├─css                                  \u002F\u002F 样式文件\n        ├─font                                 \u002F\u002F 字体文件\n        └─theme                                \u002F\u002F 主题\n            └─vchat\n```\n### 功能设计\n* 登录注册\n\n![](https:\u002F\u002Fuser-gold-cdn.xitu.io\u002F2018\u002F12\u002F9\u002F167920107fc245f8?w=959&h=559&f=gif&s=411226)\n\n>Vchat中用户注册时，会随机指定一个code号码，而这个code号是从预先生成的一个号码池（号码池存在mongodb）中取的。初始指定10000001-10001999的号码段为用户code, 100001-100999的号码段为群聊code。用户可以凭借code号或者账号登录。\n```javascript\n    \u002F\u002F 号码池设计\n    * code 号码\n    * status 1 已使用 0 未使用\n    * type  1 用户 2 群聊\n    * random   随机数索引，用于随机查找某一条\n    \u002F\u002F user表主要字段\n    * name 账号\n    * pass 密码\n    * avatar 头像\n    * signature 个性签名\n    * nickname 昵称\n    * email 邮件\n    * phone 手机\n    * sex 性别\n    * bubble 气泡\n    * projectTheme 项目主题\n    * wallpaper 聊天壁纸\n    * signUpTime 注册时间\n    * lastLoginTime 最后一次登录时间\n    * chatColor 聊天文字颜色\n    * province 省\n    * city 市\n    * town 县\n    * conversationsList 会话列表\n    * cover 封面列表\n```\n> 注册时，需要判断账号是否已存在，以及随机取得的code需要在号码池中标记为已被使用，用户密码用md5加密等。\n``` javascript\n    \u002F\u002F md5 密码加密\n    const md5 = pass => { \u002F\u002F 避免多次调用MD5报错\n        let md5 = crypto.createHash('md5');\n        return md5.update(pass).digest(\"hex\");\n    };\n```\n> 登录同样需要判断用户是否已注册，以及支持账号和code两种方式登录。\n``` javascript\n    const login = (params, callback) => { \u002F\u002F 登录\n        baseList.users\n            .find({ \u002F\u002F mongodb中可以直接用$or表示或关系\n                $or: [{\"name\": params.name}, {\"code\": params.name}]\n            })\n            .then(r => {\n                if (r.length) {\n                    let pass = md5(params.pass);\n                    if (r[0]['pass'] === pass) {\n                        \u002F\u002F更新最后一次登录时间 此处直接写Date.now 会报错 需要Date.now()!!!;\n                        baseList.users.update({name: params.name}, {lastLoginTime: Date.now()}).then(raw => {\n                            console.log(raw);\n                        });\n                        callback({code: 0, data: {name: r[0].name, photo: r[0].photo}});\n                    } else {\n                        callback({code: -1});\n                    }\n                } else {\n                    callback({code: -1});\n                }\n        })\n    };\n```\n> 登录权限管理\n\n1. 后端设置全局中间件，将没有登录的api请求统一返回status: 0\n``` javascript\n    app.use('\u002Fv*', (req, res, next) => {\n        if (req.session.login) {\n            next();\n        } else {\n            if (req.originalUrl === '\u002Fv\u002Fuser\u002Flogin' || req.originalUrl === '\u002Fv\u002Fuser\u002FsignUp') {\n                next();\n            } else {\n                res.json({\n                    status: 0\n                });\n            }\n        }\n    });\n```\n2. 前端用axios统一设置拦截器\n```javascript\n\u002F\u002F http response 服务器响应拦截器，这里拦截未登录和401错误，并重新跳入登页重新获取token\ninstance.interceptors.response.use(\n    response => { \u002F\u002F 拦截未登录\n        if (response.data.status === 0) {\n            router.replace('\u002F');\n        }\n        return response;\n    },\n    error => {\n        if (error.response) {\n            switch (error.response.status) {\n                case 401:\n                    \u002F\u002F 这里写清除token的代码\n                    router.replace('\u002F');\n            }\n        }\n        return Promise.reject(error.response.data)\n    });\n```\n* 消息\n> vchat中，消息种类包括好友或者加群申请、回复申请（同意or拒绝）、入群通知、聊天消息（文字、图片、表情、文件）\n\n![](https:\u002F\u002Fuser-gold-cdn.xitu.io\u002F2018\u002F12\u002F9\u002F16792620c25958ab?w=930&h=583&f=gif&s=1868555)\n\n![](https:\u002F\u002Fuser-gold-cdn.xitu.io\u002F2018\u002F12\u002F9\u002F16792629f85ee6cd?w=930&h=583&f=gif&s=2184961)\n\n> 在实现消息发送之前，需要大体的了解一些`socket.io`的api。详细api文档可以查看[socket.io](https:\u002F\u002Fwww.w3cschool.cn\u002Fsocket\u002Fsocket-1olq2egc.html)\n```javascript\n    \u002F\u002F 所有的消息请求都是建立在已连接的基础上的\n    io.on('connect', onConnect);\n    \u002F\u002F 发送给当前客户端\n    socket.emit('hello', 'can you hear me?', 1, 2, 'abc');\n    \u002F\u002F 发送给所有客户端，除了发送者\n    socket.broadcast.emit('broadcast', 'hello friends!');\n    \u002F\u002F 发送给同在 'game' 房间的所有客户端，除了发送者\n    socket.to('game').emit('nice game', \"let's play a game\");\n    \u002F\u002F 发送给同在 'game' 房间的所有客户端，包括发送者\n    io.in('game').emit('big-announcement', 'the game will start soon');\n```\n\n1. 加入房间\n> 加入会话列表中的房间，会话列表在好友申请成功或者加群成功时会自动添加。但是你也可以手动移除或添加，移除后将不会再收到被移除会话的消息（类似于屏蔽）。\n``` javascript\n    \u002F\u002F 前端 发起加入房间的请求\n    this.conversationsList.forEach(v => {\n        let val = {\n            name: this.user.name,\n            time: utils.formatTime(new Date()),\n            avatar: this.user.photo,\n            roomid: v.id\n        };\n        this.$socket.emit('join', val);\n    });\n    \u002F\u002F 后端 接受请求后执行加入操作，记录每个房间加入的成员，以及回信告知指定房间已上线成员\n    socket.on('join', (val) => {\n        socket.join(val.roomid, () => {\n            if (OnlineUser[val.name]) {\n                return;\n            }\n            OnlineUser[val.name] = socket.id;\n            io.in(val.roomid).emit('joined', OnlineUser); \u002F\u002F 包括发送者\n        });\n    });\n```\n2. 多房间\n> 同时加入多个聊天房间会出现一个问题，socket可以加入多个房间并给指定房间发送消息，但是接受消息的时候并不会区分房间。换句话说，所有房间的消息，会一起发送给客户端。所以我们需要自己区分哪条消息是哪个房间的并进行分发。这样就需要一个房间标识来过滤，Vchat用的是房间id。\n\n```javascript\n    mes(r) { \u002F\u002F 只有本房间的消息才展示\n        if (r.roomid === this.currSation.id) {\n            this.chatList.push(Object.assign({}, r, {type: 'other'}));\n        }\n    }\n```\n3. 发消息\n```\n    \u002F\u002F 前端\n    send(params, type = 'mess') { \u002F\u002F 发送消息\n        if (!this.message && !params) {\n            return;\n        }\n        let val = {\n            name: this.user.name,\n            mes: this.message,\n            time: utils.formatTime(new Date()),\n            avatar: this.user.photo,\n            nickname: this.user.nickname,\n            read: [this.user.name],\n            roomid: this.currSation.id,\n            style: 'mess',\n            userM: this.user.id\n        };\n        this.chatList.push(Object.assign({},val,{type: 'mine'})); \u002F\u002F 更新视图\n        this.$socket.emit('mes', val);\n        this.message = '';\n    }\n    \u002F\u002F 后端 接收消息后存储到数据库，并转发给房间内其他成员，不包括发送者。\n    socket.on('mes', (val) => { \u002F\u002F 聊天消息\n        apiList.saveMessage(val);\n        socket.to(val.roomid).emit('mes', val);\n    });\n```\n\n4. 消息记录\n> 所有的消息都会存到mongodb中，当切换房间的时候，会获取历史消息。而处在当前房间时，只会把最新消息追加到dom中，不会从数据库获取。聊天窗口默认只展示最新100条消息，更多消息可在聊天记录中查看。\n```javascript\n    \u002F\u002F 前端 获取指定房间的历史消息\n    this.$socket.emit('getHistoryMessages', {roomid: v.id, offset: 1, limit: 100});\n    \u002F\u002F 后端 关联表、分页、排序\n    messages.find({roomid: params.roomid})\n        .populate({path: 'userM', select: 'signature photo nickname'}) \u002F\u002F 关联用户基本信息\n        .sort({'time': -1})\n        .skip((params.offset - 1) * params.limit)\n        .limit(params.limit)\n        .then(r => {\n            r.forEach(v => { \u002F\u002F 防止用户修改资料后，信息未更新\n                if (v.userM) {\n                    v.nickname = v.userM.nickname;\n                    v.photo = v.userM.photo;\n                    v.signature = v.userM.signature;\n                }\n            });\n            r.reverse();\n            callback({code: 0, data: r, count: count});\n        }).catch(err => {\n        console.log(err);\n        callback({code: -1});\n    });\n```\n### 项目展示\n> 主页\n\n![](https:\u002F\u002Fuser-gold-cdn.xitu.io\u002F2018\u002F12\u002F9\u002F167929c9dbd6d170?w=1000&h=474&f=jpeg&s=90518)\n> 聊天窗口，可拖拽或缩放，聊天壁纸及文字颜色设置。\n\n![](https:\u002F\u002Fuser-gold-cdn.xitu.io\u002F2018\u002F12\u002F9\u002F16792a73d68eb06d?w=1000&h=471&f=jpeg&s=113520)\n\n> 个人设置\n\n![](https:\u002F\u002Fuser-gold-cdn.xitu.io\u002F2018\u002F12\u002F9\u002F16792a7eea5b2b57?w=1000&h=471&f=png&s=48638)\n\n> 应用空间\n\n![](https:\u002F\u002Fuser-gold-cdn.xitu.io\u002F2018\u002F12\u002F9\u002F16792a85f6d708cd?w=1000&h=474&f=png&s=687355)\n\n### 相关阅读\n* [Mongoose基础入门](http:\u002F\u002Fwww.cnblogs.com\u002Fxiaohuochai\u002Fp\u002F7215067.html?utm_source=itdadao&utm_medium=referral)\n* [socket.io文档](https:\u002F\u002Fwww.w3cschool.cn\u002Fsocket\u002Fsocket-buvk2eib.html)\n* Vchat主题切换实现方案来自于 [d2-admin](https:\u002F\u002Fgithub.com\u002Fd2-projects\u002Fd2-admin)\n\n### 写在后面\n> 本文主要讲了Vchat的整体设计以及一些主要功能的实现，其实写项目过程中坑还是挺多的，比如mongoose联表查询、文件上传等等，这里就不在细说，以后有时间再更新。如果Vchat对你有帮助，记得star一下哟^_^。\n \n","Vchat 是一个基于 Vue、Node 和 MongoDB 构建的社交聊天系统。该项目通过前端 Vue 框架配合 Vuex 状态管理和 Vue Router 路由控制，后端采用 Node.js 和 Express 搭建服务，并使用 Socket.IO 实现即时通信功能，数据库选用 MongoDB 存储用户信息、好友关系、群聊及消息等数据。Vchat 适用于需要快速搭建基础聊天应用的场景，如小型团队协作工具或个人学习项目。尽管作者声明不再维护且代码可读性有待提高，但其完整的前后端实现为开发者提供了宝贵的参考案例。","2026-06-11 03:21:00","top_language"]