使用egg-socket.io创建websocket服务e

src=http___upload-images.jianshu.io_upload_images_1397370-cf4d573bffc7880b.png&refer=http___upload-images.jianshu.jpg

egg 创建 webSocket 服务

安装依赖项

  • egg-cors解决跨域问题。
  • egg-jwt来创建token,在客户端创建连接的时候需要携带token
  • egg-socket.io用来创建websocket服务器。
yarn add egg-cors egg-jwt egg-socket.io
复制代码

配置

  1. /config/plugin.js中开启对应的插件

    module.exports = {
        io: {
           enable: true,
           package: 'egg-socket.io'
        },
        jwt: {
           enable: true,
           package: 'egg-jwt'
        },
        cors: {
           enable: true,
           package: 'egg-cors'
        }
    }
    复制代码
  2. /config/config.default.js中配置对应的参数

    module.exports = appInfo => {
        // 1. egg-jwt 配置项
        config.jwt = {
           secret: '^_^', // 用于解密和加密密钥
           sign: {
              // jwt.sign(***,***,[options,***])方法中,options的默认设置可以在这里配置;
              // 多少s后过期,jwt.sing(plyload,secret,{expiresIn:number})会被合并,调用时设置优先级更高;
              expiresIn: 60 * 60 * 24
           }
        }
        // 2. egg-cors跨域配置
        config.cors = {
           // origin: () => 'http://localhost:8080', // 此处只能设置一个域,函数的返回值为一个字符串,或者直接设置未字符串
           allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH'
        }
        // 3. egg-socket.io配置项
        exports.io = {
           namespace: {
              '/echart': {
                 connectionMiddleware: ['auth', 'connection'],
                 packetMiddleware: []
              }
           }
        }
        // 安全配置 (https://eggjs.org/zh-cn/core/security.html)
        config.security = {
           csrf: {
              enable: false,
              ignoreJSON: false
           },
           // 允许访问接口的白名单,此处可以设置多个域,
           domainWhiteList: ['http://localhost:8080', 'http://127.0.0.1:8081']
        }
    
        // 处理http请求的中间件
        config.middleware = ['auth', 'notfoundHandler']
    
        const userConfig = {
           // 用来生成token的数据,此项目并没有使用数据库,不使用用户信息来生成token
           websocketToken: '小梁子吖'
        }
    }
    复制代码

    创建处理http请求的中间件

    // /app/middleware/auth.js   token权限验证
    'use strict'
    module.exports = () => {
        return async function (ctx, next) {
            const url = ctx.url
            const token = ctx.headers.authorization
            // url 白名单,直接next()
            const urlWhite = ['/', '/api/login']
            if (urlWhite.includes(url)) {
                await next()
                return
            }
            // token 拦截其余url
            if (!token) {
                ctx.status = 401
                return false
            }
            try {
                ctx.app.verifyToekn(token)
                await next()
            } catch (error) {
                ctx.status = 403
                ctx.body = ctx.failed(error.message)
            }
        }
    }
    复制代码
    // /app/middleware/notfound_handler.js 自定义404处理
    'use strict'
    module.exports = () => {
        return async function notFoundHandler(ctx, next) {
            await next()
            if (ctx.status === 404 && !ctx.body) {
                if (ctx.acceptJSON) {
                    ctx.body = { error: 'Not Found' }
                } else {
                    ctx.status = 404
                    // ctx.failed() 是在 /app/extend/context.js中自定义方法
                    ctx.body = ctx.failed('not found')
                }
            }
        }
    }
    复制代码

使用

  1. 首先应该有生成token的接口。

    // /app/controller/user.js
    
    'use strict'
    const { Controller } = require('egg')
    class UserController extends Controller {
        async login() {
           const { ctx, app } = this
           const token = app.createToken({ name: app.config.websocketToken }) // 创建token
           ctx.body = ctx.success({ token })
        }
    }
    
    module.exports = UserController
    复制代码
  2. 使用egg-socket.io来创建websocket

    /*
    * /app/io/controller/netWorkUser.js
    */
    'use strict'
    
    const { Controller } = require('egg')
    
    class NspController extends Controller {
        async exchange() {
           const { app } = this
           const users = app.io.of('/echart')
           users.emit('user_get', {
              message: 'user_get',
              data: [
                 { type: '5G', number: 10000 },
                 { type: '4G', number: 5000 },
                 { type: '3G', number: 3000 }
              ]
           })
        }
    }
    
    module.exports = NspController
    复制代码

    egg-socket.io中间件, [/app/io/middleware/*.js]

    /*
    * /app/io/middleware/auth.js  鉴权
    */
    'use strict'
    // 当有客户端连接的时候来验证token
    module.exports = () => {
        return async (ctx, next) => {
           const requestQuery = ctx.query
           const { token } = requestQuery
           // 如果没有携带token,则关闭连接
           if (!token) return ctx.socket.disconnect()
           try {
              const { name } = ctx.app.verifyToekn(token)
              //   解析成功,但并不是我们的token数据,关闭连接
              if (name !== ctx.app.config.websocketToken) return ctx.socket.disconnect()
           } catch (error) {
              // 解析token失败
              return ctx.socket.disconnect()
           }
           await next()
        }
    }
    复制代码
    /*
    * /app/io/controller/middleware/connection.js
    */
    'use strict'
    // 在每一个客户端连接或者退出的时候发生作用
    module.exports = () => {
        return async (ctx, next) => {
           ctx.socket.emit('client_success', {
              message: 'client_success',
              data: ctx.success({
                 message: `${ctx.headers.origin}客户端连接成功`
              })
           })
           await next()
           // 当有客户端退出的时候,执行以下操作
           const origin = ctx.request.headers.origin
           console.log(`${origin} 用户退出!`)
        }
    }
    复制代码

Vue 中创建连接

  • 使用egg-socket.io创建的websokcet服务必须使用socket.io-client来进行连接,不能直接通过new WebSocket(url)来建立连接。

  • egg-socket.io是使用socket.io来创建websocket服务。详见socket.io/docs/v4/

  1. 安装socket.io-slice@2.4

    yarn add socket.io-slice@2.4
    复制代码
  2. /src/network/websocket.js

    import io from 'socket.io-client'
    const socket = io('ws://127.0.0.1:7001/echart', {
        reconnection: true, // 是否重连
        reconnectionAttempts: 30, // 重新连接的次数
        reconnectionDelay: 1000, // 每过多长时间重连一次
        timeout: 5000, // 超时时间
        query: {
           token: window.sessionStorage.getItem('token')
        }
    })
    socket.on('connect', () => {
        console.log('连接成功') // x8WIv7-mJelg7on_ALbx
    })
    socket.on('disconnect', res => {
        console.log('连接关闭') // undefined
    })
    socket.on('client_success', response => {
        console.log(response)
    })
    export default socket
    复制代码
  3. /src/app.vue

    <template>
        <router-link style='margin-right: 20px' to='/'>
           Home
        </router-link>
        <router-link to='/about'>About</router-link>
        <router-view />
    </template>
    复制代码
  4. /src/views/Home.vue

    <template>
      <div class="home">
        <button @click="getToekn">获取token</button>
      </div>
    </template>
    
    <script>
    import axios from 'axios'
    
    export default {
      setup () {
        const getToekn = async () => {
          const { data } = await axios.post('http://127.0.0.1:7001/api/login')
          console.log(data)
          window.sessionStorage.setItem('token', `Bearer ${data.data.token}`)
        }
        return { getToekn }
      }
    }
    </script>
    复制代码
  5. /src/views/About.vue

    <template>
      <div class="home">
        <button @click="getToekn">获取token</button>
      </div>
    </template>
    
    <script>
    import axios from 'axios'
    
    export default {
      setup () {
        const getToekn = async () => {
          const { data } = await axios.post('http://127.0.0.1:7001/api/login')
          console.log(data)
          window.sessionStorage.setItem('token', `Bearer ${data.data.token}`)
        }
        return { getToekn }
      }
    }
    </script>
    复制代码

动画.gif