本篇文章难度较大哦,小伙伴们👀️
本文要讲解的代码作者已上传至云仓库: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>
能够看到这里的小伙伴给自己鼓鼓掌吧,未来的前端是你们的🎉️