1丶安装
2丶前端
3丶后端
4丶总结
技术栈:vue3.2 , koa
体验网址: hyyyh.top/socket
1丶安装
前端需要 socket.io-client
npm i -s socket.io-client
后端需要 socket.io 和 koa
npm i -s socket.io
npm i -s koa
2丶前端实现
新建一个websocket.ts文件
import {io} from 'socket.io-client'
// import.meta.env.VITE_SOCKET_BASE_URL
// 默认连接的地址,与后端相对应,例如:http://localhost:3006
// 链接 服务端
const socket = io(import.meta.env.VITE_SOCKET_BASE_URL, {
query: {}, //携带的参数
transports: ['websocket']
})
export default socket
新建socket.vue然后引入文件
import socket from '../../../websocket/index' //引入刚才的ts文件
在onmounted里监听socket事件
onMounted(() => {
// 监听,获取用户列表
socket.on('userList', (userList) => {
data.userlist = userList
})
//获取信息列表
socket.on('chatList', (chatList) => {
data.chatList = chatList
})
})
发送消息的函数
// 发送消息
const sendChat = () => {
if (isEmpty(data.chatMessage)) return //判断非空
let { uuid, id, name, head } = data.userInfo
//需要传到后端的数据
let info={
uuid: uuid,
id: id,
username: name,
head: head,
message: scope(data.chatMessage),
type: 'text',
createtime: getFullDate()
}
socket.emit('message',info
); //发送
data.chatMessage = ''
}
登录函数,用于通知后端有用户加入聊天室
// 登录
const login = () => {
data.userInfo.uuid = socket.id
socket.emit('login', data.userInfo)
}
记得要在onunmounted里面关闭socket
onUnmounted(() => {
socket.close()
})
3丶后端实现
第一步就是要利用koa搭建一个基础的服务器
function WEBSOCKET() {
const Koa = require('koa');
const app = new Koa();
// 监听端口
server.listen(3006, () => {
console.log('listening on *:3006');
});
}
module.exports = {
WEBSOCKET
}
在这个基础上添加websocket相关代码
const server = require('http').createServer(app.callback());
const io = require('socket.io')(server);
let userList = []
let chatList = []
// socket连接
io.on('connect', (socket) => {
console.log(`用户 <${socket.id}> 连接了`); //当有人连接时就会触发
// 登录
socket.on('login', (userinfo) => {
console.log(userinfo.name + '登录成功!');
userList.push(userinfo)
io.emit('userList', userList) //返回给前端用户列表
io.emit('chatList', chatList); //返回给前端消息列表
})
// 接收消息
socket.on('message', (msg) => {
chatList.push(msg) //
用户发送的消息保存到数组里
io.emit('chatList', chatList); //
将更新后的数组返回给前端,重新渲染
});
// 断开连接
socket.on('disconnect', () => {
console.log(socket.id + '断开了');
if (userList == []) return
//有用户断开时,记得从用户列表里清除该用户
userList = userList.filter(userinfo =>userinfo.uuid != socket.id)
io.emit('userList', userList);
});
});
后端全部代码
function WEBSOCKET() {
var Koa = require('koa');
var app = new Koa();
const Router = require('koa-router');
const fs = require('fs');
const server = require('http').createServer(app.callback());
const io = require('socket.io')(server);
app.use(async (ctx, next) => {
// ctx.set('Access-Control-Allow-Origin', 'http://localhost'); //这个表示任意域名都可以访问,这样写不能携带cookie了。
ctx.set('Access-Control-Allow-Origin', 'http://localhost:8080'); //这个表示任意域名都可以访问,这样写不能携带cookie了。
ctx.set('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');
ctx.set('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
ctx.set('Access-control-Allow-Credentials', 'true');
if (ctx.request.method === 'OPTIONS') { // 直接响应数据 (***** 这里是最重要的if ***)
ctx.body = 200
}
await next();
});
let userList = []
let chatList = []
// socket连接
io.on('connect', (socket) => {
console.log(`用户 <${socket.id}> 连接了`);
// 登录
socket.on('login', (userinfo) => {
console.log(userinfo.name + '登录成功!');
userList.push(userinfo)
console.log(userinfo);
io.emit('userList', userList)
io.emit('chatList', chatList);
})
// 消息
socket.on('message', (msg) => {
chatList.push(msg)
// console.log(msg);
io.emit('chatList', chatList);
});
// 断开连接
socket.on('disconnect', () => {
console.log(socket.id + '断开了');
if (userList == []) return
userList = userList.filter(userinfo =>userinfo.uuid != socket.id)
console.log(userList);
io.emit('userList', userList);
});
});
// 监听端口
server.listen(3006, () => {
console.log('listening on *:3006');
});
}
module.exports = {
WEBSOCKET
}
前端页面全部代码
<template>
<div>
<blogheaderVue :bgColor='true' />
<div class='socket' :style='{
backgroundColor: store.state.themeColor.color
}'>
<div class='socket_container flex-jcc'>
<div class='socket_list_container'>
<div class='user_list'>
<div class='user_list_title'>聊天室人数({{ data.userlist.length }})</div>
<div class='user_list_container'>
<div class='user_list_item flex-aic' v-for='(item, index) in data.userlist' :key='item.id'>
<img :src='item.head' :alt='item.name'><span>{{ item.name }}{{ item.uuid ==
data.userInfo.uuid
? '(我)' : '' }}</span>
</div>
</div>
</div>
<div class='chat_list'>
<div class='chat_list_container' ref='chat_list_container'>
<div class='chat_list_item' v-for='(item, index) in data.chatList' :key='item.id'>
<div class='chat_list_item_info'
:style='{ justifyContent: item.uuid == data.userInfo.uuid ? 'flex-end' : '' }'>
<img :src='item.head' alt='' v-if='item.uuid != data.userInfo.uuid'>
<span>{{ item.username }}{{ item.id==1?'(博主)':'' }}</span>
<img :src='item.head' alt='' v-if='item.uuid == data.userInfo.uuid'>
</div>
<div class='chat_list_item_message'
:style='{ alignItems: item.uuid == data.userInfo.uuid ? 'flex-end' : 'flex-start' }'>
<p>{{ item.message }}</p>
<div class='chat_list_item_message_date' > {{ item.createtime }}</div>
</div>
</div>
</div>
<div class='chat_dialog'>
<div class='chat_tool'></div>
<div class='chat_container'>
<textarea v-model='data.chatMessage' placeholder='请不要尝试攻击哦!' ></textarea>
<button @click='sendChat()'>发送</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang='ts'>
import blogheaderVue from '../../../components/blog-header/blogheader.vue';
import { useStore } from 'vuex';
import socket from '../../../websocket/index'
import { reactive, onMounted, onUnmounted, ref } from 'vue'
import { DATA, USERLIST } from './socket'
import { isEmpty } from 'lodash';
import {getFullDate} from '../../../func/Date/Date'
import scope from 'lodash/escape';
let store = useStore()
let chat_list_container: any = ref('')
const data: DATA = reactive({
chatList: [],
userlist: [],
chatMessage: '',
userInfo: {
uuid: '-1',
id: localStorage.getItem('id') || '-1',
name: localStorage.getItem('name') || '游客',
head: localStorage.getItem('header') || 'https://hyyyh.top/icon/anonymousHeader.png'
},
textarea: ''
})
onMounted(() => {
getUuid()
window.addEventListener('keydown', (e)=>ListenerEnter(e))
// 监听
socket.on('userList', (userList) => {
// console.log(userList);
data.userlist = userList
})
socket.on('chatList', (chatList) => {
// console.log(chatList);
data.chatList = chatList
})
})
// 发送消息
const sendChat = () => {
if (isEmpty(data.chatMessage)) return
let { uuid, id, name, head } = data.userInfo
socket.emit('message', {
uuid: uuid,
id: id,
username: name,
head: head,
message: scope(data.chatMessage),
type: 'text',
createtime: getFullDate()
});
data.chatMessage = ''
// 滚动动画
setTimeout(() => {
chat_list_container.value.scrollTo({
top: 9999,
behavior: 'smooth'
})
}, 200)
}
// 登录
const login = () => {
data.userInfo.uuid = socket.id
// console.log(data.userInfo);
socket.emit('login', data.userInfo)
}
// 获取uuid
const getUuid = () => {
let getUuid = setInterval(() => {
if (socket.id !== undefined) {
clearInterval(getUuid);
data.userInfo.uuid = socket.id
console.log(data.userInfo.uuid);
login() //登录
}
}, 100);
}
const ListenerEnter =(e:KeyboardEvent)=>{
e.key == 'Enter' && sendChat()
}
onUnmounted(() => {
socket.close()
window.removeEventListener('keydown',ListenerEnter)
})
</script>
<style scoped lang='less'>
@import url('./socket.less');
</style>