本篇文章难度较大哦,小伙伴们👀️

本文要讲解的代码作者已上传至云仓库:https://gitee.com/xiao-zhe-is-not-lazy/chat

学习本篇文章之前记得先去看:

1.登录鉴权——JWT(前后端分离) | Lazychild's Blog
2. 初始ws模块 | Lazychild's Blog

先写好一个基本的登录鉴权功能

之前文章写过了,小伙伴们自己去看看吧👀️

前端一共有两个界面:

  • 登录界面
  • 聊天室界面

到了这一步应该实现前端进行登录,后端进行token校验,成功后跳转至聊天室这个界面(只要跳转就行,后面会讲聊天室界面对于token的校验)

代码展示

  • 登录界面代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>登录</title>
    <script src="https://unpkg.com/axios@1.1.2/dist/axios.min.js"></script>
    <script>
        axios.interceptors.response.use(function (response) {
            // 2xx 范围内的状态码都会触发该函数。
            // 对响应数据做点什么
            const autorization = response.headers.autorization
            autorization && localStorage.setItem('token', autorization)
            return response
        }, function (error) {
            // 超出 2xx 范围的状态码都会触发该函数。
            // 对响应错误做点什么
            return Promise.reject(error);
        });
    </script>
</head>

<body>
    <h2>聊天室登录界面</h2>
    <div>账号:<input type="text" id="username"></div>
    <div>密码:<input type="text" id="password"></div>
    <button id="btn">登录</button>
    <script>
        btn.onclick = function () {
            axios.post('http://localhost:3000/login', {
                username: username.value,
                password: password.value
            }).then(res => {
                if (res.data.ok) {
                    location.href = '/index'
                } else {
                    alert('账号或密码错误')
                }
            }).catch(err => {
                console.log(err)
            })
        }
    </script>
</body>

</html>
  • 基本的路由代码
var express = require('express');
var jwtObj = require('../utils/jsonwebtoken')
const mysql2 = require('mysql2')
var router = express.Router();

/* GET home page. */
router.get('/login', function (req, res, next) {
    res.type('html');
    res.render('login')
})
router.get('/index', function (req, res, next) {
    res.type('html');
    res.render('index')
})

let mony, name
router.post('/login', async function (req, res) {
    a = 'users'
    // 创建连接池
    const config = handleConfig()
    const promisePool = mysql2.createPool(config).promise()
    let users = await promisePool.query(`SELECT * FROM ${a} WHERE name='${req.body.username}' AND password='${req.body.password}'`)  //sql语句
    if (users[0].length) {
        mony = users[0][0].mony
        name = users[0][0].name
          // 将token放在header中
          const token = jwtObj.sign({ name, mony }, '1h')
          res.header('Autorization', token)
        res.send({
            ok: 1
        })
    } else {
        res.send({ ok: 0 })
    }
})
module.exports = router;


// 连接数据库的基本配置
function handleConfig() {
    return {
        host: 'localhost',
        port: 3306,
        user: "root",
        password: "",
        database: "maizuo",
        connectLimit: 1
    }
}
  • token加密/解密代码
const { json } = require('express')
const jwt = require('jsonwebtoken')
const key = 'maizuoc312asdpkj'  //秘钥

const obj={
    // 加密
    sign: function(data,time){
        const token=jwt.sign(data, key, { expiresIn: time })
        return token
    },
    verify: function(token){
        try {
            return jwt.verify(token, key)
        } catch (error) {
            return false            
        }
    }
}

module.exports=obj

各种依赖包记得下载

  • 依赖包展示(package.json文件)
{
  "name": "nodeapp2-jwt",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www"
  },
  "dependencies": {
    "cookie-parser": "~1.4.4",
    "debug": "~2.6.9",
    "ejs": "~2.6.1",
    "express": "~4.16.1",
    "http-errors": "~1.6.3",
    "jsonwebtoken": "^9.0.0",
    "morgan": "~1.9.1",
    "mysql2": "^3.3.3",
    "ws": "^8.13.0"
  }
}

进入主题

ws模块大家之前应该就使用过了吧,这里主要是与之前的登录鉴权实现一些聊天的基本功能,比如群聊,单聊等,站长这里只讲功能,页面的美化得靠大家,
直接代码展示
node服务端ws代码展示(建议新建一个文件独立写ws的代码,容易维护):

const WebSocket = require("ws")
const { WebSocketServer } = require("ws")
const JWT = require('../utils/jsonwebtoken')

const wss = new WebSocketServer({ port: 8080 });

wss.on('connection', function connection(ws, req) {
  ws.on('error', console.error);
  //  验证token
  const payload = JWT.verify(req.url.split('=')[1])
  if (payload) {
    ws.user = payload

    // 通知在线用户的人数
    sendAll()
  } else {
    ws.send(createMessage(WebSocketType.Error, null, "登录已过期"))
  }

  ws.on('message', function message(data, isBinary) {
    // 解析前端发送过来的消息进行判断
    const msgObj = JSON.parse(data)
    // 判断,进行逻辑处理
    switch (msgObj.type) {
      case WebSocketType.groupList:
        break;
      case WebSocketType.groupChat:
        wss.clients.forEach(function each(client) {
          if (client !== ws && client.readyState === WebSocket.OPEN) {
            client.send(data, { binary: isBinary });
          }
        });
        break;
      case WebSocketType.singleChat:
        wss.clients.forEach(function each(client) {
          if (client.user.name == msgObj.to && client !== ws && client.readyState === WebSocket.OPEN) {
            client.send(data, { binary: isBinary });
          }
        });
        break;
    }
  })
  // 当服务器断开时触发
  ws.on('close', () => {
    wss.clients.delete(ws.user)
    sendAll()
  })
});

const WebSocketType = {
  Error: 0,
  groupList: 1, //在线人数
  groupChat: 2, //群聊
  singleChat: 3 //单聊
}

function createMessage(type, user, data) {
  return JSON.stringify({
    type,
    user,
    data
  })
}

// 给所有的在线用户实时发送当前的用户列表
function sendAll() {
  wss.clients.forEach(function each(client) {
    if (client.readyState === WebSocket.OPEN) {
      client.send(createMessage(WebSocketType.groupList, null, JSON.stringify(Array.from(wss.clients).map(item => item.user))))
    }
  });
}

前端聊天室代码展示:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>聊天室</title>
    <script src="https://unpkg.com/axios@1.1.2/dist/axios.min.js"></script>
</head>

<body>
    <h1>聊天室</h1>
    <input type="text" class="message">
    <select name="" id="onlineUsers"></select>
    <button id="btn">发送</button>
    <script>
        // to字段主要是在单聊时,知道要发给谁
        function createMessage(type, data, to) {
            return JSON.stringify({
                type,
                data,
                to
            })
        }
        let btn = document.querySelector('#btn')
        let message = document.querySelector('.message')
        let onlineUsers = document.querySelector('#onlineUsers')
        const WebSocketType = {
            Error: 0,
            groupList: 1, //在线人数
            groupChat: 2, //群聊
            singleChat: 3 //单聊
        }
        const ws = new WebSocket(`ws://localhost:8080?token=${localStorage.getItem('token')}`)

        ws.onopen = () => {
            console.log("服务器已连接")
        }
        ws.onmessage = (msgObj) => {
            // 解析后端传过来的值,进行判断
            let dataObj = JSON.parse(msgObj.data)
            switch (dataObj.type) {
                case WebSocketType.Error:
                    location.href = '/login'
                    break;
                case WebSocketType.groupList:
                    // 获取用户列表
                    onlineUsers.innerHTML ='<option value="all">群发</option>' + JSON.parse(dataObj.data).map(item =>`<option value="${item.name}">${item.name}</option>`)
                    break;
                //群聊 
                case WebSocketType.groupChat:
                    console.log('群聊', dataObj)
                    break;
                //单聊
                case WebSocketType.singleChat:
                    console.log('单聊', dataObj)
                    break;
            }
            // 发送消息
            btn.onclick = function () {
                if (onlineUsers.value == 'all') {
                    // 群发
                    ws.send(createMessage(WebSocketType.groupChat, message.value))
                }else{
                    // 单聊
                    ws.send(createMessage(WebSocketType.singleChat, message.value, onlineUsers.value))
                }
            }
        }
        ws.onerror = () => {
            console.log("error")
        }
    </script>
</body>

</html>

能够看到这里的小伙伴给自己鼓鼓掌吧,未来的前端是你们的🎉️