No longer maintained
This project was written when I was studying, and some places are poorly readable (such as a large number of callbacks in the mongo part). Students who need it read it carefully, thank you.
The branch code is online and has been changed accordingly. If you join a chat group by default, and the backend can adapt to some linux code adjustments, it is recommended to pull the master.
Note that there must be node, npm and mongodb. The default mongodb IP address of the project is 127.0.0.1:27017, which can be modified in the configuration file. (chatServerutilsdatabase.js)
cd chatRoom
npm install 安装前端依赖
npm run build 编译前端代码
cd ..
cd chatServer
npm install 安装后端依赖
npm run create 初始化数据库(号码池、表情包)
npm start 启动服务
在浏览器中打开 localhost:9988 即可
The project started because the work required a chat room function, but for some reasons, I finally chose Strophe.js based on the xmpp protocol. So I wanted to write a set of it myself using node. I originally wanted to simply write a chat page, but I was not satisfied after writing it, so I kept refactoring it (it seems that I can understand why the product manager always changed the needs).
Many things, such as mongodb, are also the first time I have used them. I have only been exposed to mysql before. So I learned and wrote at the same time. I used my spare time to write intermittently for several months (this time I talked about V0.9.0 version, and the project is still being updated...), which includes a complete set of front-end and back-end interactions. uI comes from my own feelings and has no design talent (then when it comes to switching themes, it is really difficult to design~), lightly spray--. There are still many areas that need to be optimized and improved in the project. Everyone is welcome to mention issues (there is a Q group at the end of the article, and you are welcome to learn and communicate together).
Less gossip, this article mainly talks about the project's design process and some functional implementation ideas. Students who are interested in the project please move to the source code Vchat - from head to toe toe toe toe toe toe toe toe toe toe toe toe toe toe toe toe toe toe toe toe toe toe toe toe toe toe to chat online.
* This is the divider --------------------------------------------------------------------------------------------------------------------------
The front-end mainly uses vue family buckets, and there is nothing to say, scaffolding construction projects, vuex state management, vue-router control routing, and axios for front-end interaction. The backend is a service based on node and uses express. Why don’t I use koa? It’s purely for convenience, because koa is not familiar with (covering her face). The most important thing in chat is of course communication. The project uses socket.io to communicate front and back end.
The database is mongoDB, which mainly includes users, friends, group chats, messages, expressions, number pools, etc.
Feature Overview
Directory structure
// 前端
├─build
├─config
├─src
│ ├─api // 接口api
│ ├─assets // 静态资源
│ │ └─img
│ ├─directives // 全局指令
│ ├─libs // 全局组件
│ │ ├─bscroll
│ │ ├─dropdown
│ │ ├─icon
│ │ ├─nodata
│ │ ├─PhotoSwipe
│ │ ├─uploadPopover
│ │ └─vScroll
│ ├─router // 路由
│ ├─store // 状态管理
│ ├─utils // 方法
│ └─views
│ ├─applicationModel
│ │ ├─games // 游戏
│ │ │
│ │ └─sub // 应用
│ ├─components // 组件
│ │ ├─APlayer
│ │ ├─chat
│ │ ├─cropper
│ │ ├─DPlayer
│ │ └─header
│ ├─personalModel // 主页
│ │ ├─appModel // 天气等
│ │ ├─friendModel // 好友
│ │ └─groupModel // 群聊
│ └─settingModel // 设置
└─static
├─css // 样式文件
├─font // 字体文件
└─theme // 主题
└─vchatWhen a user registers in Vchat, a code number will be randomly specified, and this code number is taken from a pre-generated number pool (the number pool exists in mongodb). The initial number segment specified as 10000001-10001999 is the user code, and the number segment of 100001-100999 is the group chat code. Users can log in with their code number or account.
// 号码池设计
* code 号码
* status 1 已使用 0 未使用
* type 1 用户 2 群聊
* random 随机数索引,用于随机查找某一条
// user表主要字段
* name 账号
* pass 密码
* avatar 头像
* signature 个性签名
* nickname 昵称
* email 邮件
* phone 手机
* sex 性别
* bubble 气泡
* projectTheme 项目主题
* wallpaper 聊天壁纸
* signUpTime 注册时间
* lastLoginTime 最后一次登录时间
* chatColor 聊天文字颜色
* province 省
* city 市
* town 县
* conversationsList 会话列表
* cover 封面列表When registering, you need to determine whether the account already exists, and the randomly obtained code needs to be marked as being used in the number pool, and the user password is encrypted with md5, etc.
// md5 密码加密
const md5 = pass => { // 避免多次调用MD5报错
let md5 = crypto . createHash ( 'md5' ) ;
return md5 . update ( pass ) . digest ( "hex" ) ;
} ;Login also requires determining whether the user has registered, and supporting account and code to log in.
const login = ( params , callback ) => { // 登录
baseList . users
. find ( { // mongodb中可以直接用$or表示或关系
$or : [ { "name" : params . name } , { "code" : params . name } ]
} )
. then ( r => {
if ( r . length ) {
let pass = md5 ( params . pass ) ;
if ( r [ 0 ] [ 'pass' ] === pass ) {
//更新最后一次登录时间 此处直接写Date.now 会报错 需要Date.now()!!!;
baseList . users . update ( { name : params . name } , { lastLoginTime : Date . now ( ) } ) . then ( raw => {
console . log ( raw ) ;
} ) ;
callback ( { code : 0 , data : { name : r [ 0 ] . name , photo : r [ 0 ] . photo } } ) ;
} else {
callback ( { code : - 1 } ) ;
}
} else {
callback ( { code : - 1 } ) ;
}
} )
} ;Login permission management
app . use ( '/v*' , ( req , res , next ) => {
if ( req . session . login ) {
next ( ) ;
} else {
if ( req . originalUrl === '/v/user/login' || req . originalUrl === '/v/user/signUp' ) {
next ( ) ;
} else {
res . json ( {
status : 0
} ) ;
}
}
} ) ; // http response 服务器响应拦截器,这里拦截未登录和401错误,并重新跳入登页重新获取token
instance . interceptors . response . use (
response => { // 拦截未登录
if ( response . data . status === 0 ) {
router . replace ( '/' ) ;
}
return response ;
} ,
error => {
if ( error . response ) {
switch ( error . response . status ) {
case 401 :
// 这里写清除token的代码
router . replace ( '/' ) ;
}
}
return Promise . reject ( error . response . data )
} ) ;In vchat, the types of messages include friends or group application, reply application (consent or rejection), group entry notification, chat message (text, pictures, expressions, files)
Before implementing message sending, you need to have a general understanding of some
socket.ioAPIs. For detailed API documentation, you can view socket.io
// 所有的消息请求都是建立在已连接的基础上的
io . on ( 'connect' , onConnect ) ;
// 发送给当前客户端
socket . emit ( 'hello' , 'can you hear me?' , 1 , 2 , 'abc' ) ;
// 发送给所有客户端,除了发送者
socket . broadcast . emit ( 'broadcast' , 'hello friends!' ) ;
// 发送给同在 'game' 房间的所有客户端,除了发送者
socket . to ( 'game' ) . emit ( 'nice game' , "let's play a game" ) ;
// 发送给同在 'game' 房间的所有客户端,包括发送者
io . in ( 'game' ) . emit ( 'big-announcement' , 'the game will start soon' ) ;Join the room in the session list. The session list will be automatically added when the friend application is successful or the group is successfully added. But you can also manually remove or add, and you will no longer receive messages about the removed session (similar to blocking).
// 前端 发起加入房间的请求
this . conversationsList . forEach ( v => {
let val = {
name : this . user . name ,
time : utils . formatTime ( new Date ( ) ) ,
avatar : this . user . photo ,
roomid : v . id
} ;
this . $socket . emit ( 'join' , val ) ;
} ) ;
// 后端 接受请求后执行加入操作,记录每个房间加入的成员,以及回信告知指定房间已上线成员
socket . on ( 'join' , ( val ) => {
socket . join ( val . roomid , ( ) => {
if ( OnlineUser [ val . name ] ) {
return ;
}
OnlineUser [ val . name ] = socket . id ;
io . in ( val . roomid ) . emit ( 'joined' , OnlineUser ) ; // 包括发送者
} ) ;
} ) ;There will be a problem with joining multiple chat rooms at the same time. The socket can join multiple rooms and send messages to the specified rooms, but it will not distinguish the rooms when accepting messages. In other words, all the messages from the room will be sent to the client together. So we need to distinguish which message is from which room and distribute it. This requires a room identifier to filter, and Vchat uses the room id.
mes ( r ) { // 只有本房间的消息才展示
if ( r . roomid === this . currSation . id ) {
this . chatList . push ( Object . assign ( { } , r , { type : 'other' } ) ) ;
}
} // 前端
send(params, type = 'mess') { // 发送消息
if (!this.message && !params) {
return;
}
let val = {
name: this.user.name,
mes: this.message,
time: utils.formatTime(new Date()),
avatar: this.user.photo,
nickname: this.user.nickname,
read: [this.user.name],
roomid: this.currSation.id,
style: 'mess',
userM: this.user.id
};
this.chatList.push(Object.assign({},val,{type: 'mine'})); // 更新视图
this.$socket.emit('mes', val);
this.message = '';
}
// 后端 接收消息后存储到数据库,并转发给房间内其他成员,不包括发送者。
socket.on('mes', (val) => { // 聊天消息
apiList.saveMessage(val);
socket.to(val.roomid).emit('mes', val);
});
All messages will be stored in mongodb. When switching rooms, historical messages will be obtained. When in the current room, the latest news will only be appended to the dom and will not be retrieved from the database. The chat window only displays the latest 100 messages by default, and more messages can be viewed in the chat history.
// 前端 获取指定房间的历史消息
this . $socket . emit ( 'getHistoryMessages' , { roomid : v . id , offset : 1 , limit : 100 } ) ;
// 后端 关联表、分页、排序
messages . find ( { roomid : params . roomid } )
. populate ( { path : 'userM' , select : 'signature photo nickname' } ) // 关联用户基本信息
. sort ( { 'time' : - 1 } )
. skip ( ( params . offset - 1 ) * params . limit )
. limit ( params . limit )
. then ( r => {
r . forEach ( v => { // 防止用户修改资料后,信息未更新
if ( v . userM ) {
v . nickname = v . userM . nickname ;
v . photo = v . userM . photo ;
v . signature = v . userM . signature ;
}
} ) ;
r . reverse ( ) ;
callback ( { code : 0 , data : r , count : count } ) ;
} ) . catch ( err => {
console . log ( err ) ;
callback ( { code : - 1 } ) ;
} ) ;Home page
Chat window, can be dragged or zoomed, chat wallpaper and text color settings.
Personal settings
Application space
This article mainly talks about the overall design of Vchat and the implementation of some main functions. In fact, there are still many pitfalls in the process of writing projects, such as mongoose inter-table query, file upload, etc. I will not go into details here, and I will update it if I have time in the future. If Vchat is helpful to you, remember to stare ^_^.