前言
上一篇我们已经讲了如何使用websocket结合ndde.js实现双向通信。
下面我们来讲下,通过websocket和nodejs-websocket,构建一个小型聊天室。
先来看下实现效果:
先讲下大概思路:
客户端需要传给服务器当前登陆者的昵称、uid和发送的内容(如果有的话),服务端收到数据后,把信息同步广播给当前聊天室。
整体大致思路如下:
1、刚进入页面,通过 localStorage.getItem(“WEB_IM_USER”) 获取缓存里是否存储了用户信息;
2、没有用户信息,弹窗填写用户昵称;有用户信息,直接连接;
3、通过传type类型为1还是2,区分是刚进入聊天室还是发送消息,通过websocket.send()方法发送客户端消息;
4、服务端通过on(“text”, function(){})获取客户端发送的内容,并通过server.connections方法广播到客户端;
5、客户端收到信息,渲染到视图上。
上代码:
服务端:
const ws = require('nodejs-websocket')
const moment = require('moment')
function broadcast(obj) {
server.connections.forEach(function (conn) { // 注意:这里是server,不是ws
conn.sendText(JSON.stringify(obj)) // 注意:这里得转成字符串发送过去,不然会报错。
})
}
const server = ws.createServer(function (conn) {
conn.on('text', function (data) {
const obj = JSON.parse(data)
switch (obj.type) {
case 1:
broadcast({
type: 1,
nickname: obj.nickname,
uid: obj.uid,
msg: `${obj.nickname}进入了聊天室`,
date: moment().format('YYYY-MM-DD HH:mm:ss')
})
break;
case 2:
broadcast({
type: 2,
nickname: obj.nickname,
uid: obj.uid,
msg: obj.msg,
date: moment().format('YYYY-MM-DD HH:mm:ss')
})
break;
}
})
conn.on('close', function (e) {
console.log(e, '服务端连接关闭')
})
conn.on('error', function (e) {
console.log(e, '服务端异常')
})
}).listen(8888)
console.log('服务端已开启')
客户端:
视图层:
<template>
<div class="home">
<el-dialog
:visible="showInfoDialog"
>
<el-input type="text" v-model="nickname" placeholder="请输入你的昵称" ></el-input>
<span slot="footer" class="dialog-footer">
<el-button @click="showInfoDialog = false">取 消</el-button>
<el-button type="primary" @click="sure">确 定</el-button>
</span>
</el-dialog>
<div class="right">
<div class="body im-record" id="im-record">
<div class="ul">
<!-- user为靠右展示样式,如果uid一致说明是本人 -->
<div class="li" :class="{user: item.uid == uid}" v-for="(item, index) in messageList" :key="index">
<template v-if="item.type===1">
<p class="join-tips">{{item.msg}}</p>
</template>
<template v-else>
<p class="message-date">
<span class="m-nickname">{{item.nickname}}</span> {{item.date}}</p>
<p class="message-box">{{item.msg}}</p>
</template>
</div>
</div>
</div>
<div class="im-footer">
<el-input placeholder="请输入你想说的内容..." v-model="msg" class="im-footer_inp"/>
<el-button class="im-footer_btn" type="primary" @click="send">发送</el-button>
</div>
</div>
</div>
</template>
逻辑层:
import moment from 'moment' // 需要下载moment。npm install moment --save
export default {
data () {
return {
ws: '',
showInfoDialog: false,
nickname: '',
uid: this.uid,
messageList: [],
msg: '' // 输入的消息内容
}
},
mounted () {
this.init()
},
methods: {
// 初始化
init () {
if (window.WebSocket) {
let user = {}
if (localStorage.getItem('WEB_IM_USER')) user = JSON.parse(localStorage.getItem('WEB_IM_USER'))
this.nickname = user.nickname || ''
this.uid = user.uid || ''
// 没有当前人信息就弹窗去填写
if (!this.uid) {
this.showInfoDialog = true
} else {
this.contactSocket()
}
// 监听回车事件
const vm = this
document.onkeydown = function (event) {
const e = event || window.event;
if (e && e.keyCode == 13) {
console.log('触发enter')
vm.send()
}
}
} else {
console.log('当前浏览器不支持WebSocket!')
}
},
// 模态框确认事件
sure () {
this.showInfoDialog = false
this.contactSocket()
},
// enter触发 或者 点击“发送”触发
send () {
if (!this.msg) return;
this.sendMessage(2, this.msg)
},
// 发送信息给客户端
sendMessage (type, msg) {
const data = {
uid: this.uid,
type,
nickname: this.nickname,
msg
}
this.ws.send(JSON.stringify(data))
this.msg = ''
},
// 连接websocket
contactSocket () {
const that = this
this.ws = new WebSocket('ws://192.168.1.124:8888')
const ws = this.ws
ws.onopen = function () {
console.log('连接服务器成功')
// 没有当前人信息的话,需要缓存下
if (!that.uid) {
that.uid = 'web_im_' + moment().valueOf();
localStorage.setItem('WEB_IM_USER', JSON.stringify({
uid: that.uid,
nickname: that.nickname
}))
}
that.sendMessage(1)
}
ws.onmessage = function (e) {
that.messageList.push(JSON.parse(e.data))
}
ws.onclose = function () {
console.log('连接已关闭')
}
}
}
}
样式层:
<style scoped lang="less">
.right {
position: relative;
flex: 1;
height: 600px;
margin: 0 auto;
.im-title {
height: 30px;
padding-left: 20px;
border-bottom: 1px solid #ccc;
line-height: 30px;
font-size: 16px;
}
.im-footer {
position: absolute;
bottom: 0;
left: 0;
display: flex;
width: 100%;
.im-footer_inp {
width: 80%;
}
.im-footer_btn {
width: 20%;
}
}
.im-record {
width: 100%;
height: 540px;
overflow-y: auto;
.join-tips {
position: relative!important;
display: block;
width: 100%;
left: 0!important;
transform: none!important;
color: #cccccc;
font-size: 15px;
text-align: center;
}
.li {
position: relative;
margin-bottom: 15px;
text-align: left;
color: #46b0ff;
&:after {
content: '';
display: block;
clear: both;
}
.message-date {
font-size: 16px;
color: #b9b8b8;
}
.m-nickname {
color: #46b0ff;
}
&.user {
text-align: right;
}
}
.message-box {
line-height: 30px;
font-size: 20px;
}
}
}
</style>