利用 socket.io 实现简易聊天室

289 阅读1分钟

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>