socket.io-client实现实前后端时通信功能

这里我使用的后端 基于node.js的koa框架 前端使用的是vite

{"name": "hou","version": "1.0.0","description": "","main": "app.js","scripts": {"test": "echo \"Error: no test specified\" && exit 1","start": "node app.js","dev": "nodemon app.js"},"author": "","license": "ISC","dependencies": {"@koa/cors": "^5.0.0","bcryptjs": "^2.4.3","jsonwebtoken": "^9.0.2","koa": "^2.15.3","koa-bodyparser": "^4.4.1","koa-jwt": "^4.0.4","koa-router": "^13.0.1","nodemon": "^3.1.7","ws": "^8.18.0"}
}
const Koa = require("koa");
const Router = require("koa-router");
const bodyParser = require("koa-bodyparser");
const jwt = require("jsonwebtoken");
const bcrypt = require("bcryptjs");
const koaJwt = require("koa-jwt");
const cors = require("@koa/cors");const app = new Koa();
const router = new Router();
const secret = "supersecretkey"; // 用于签发 JWT 的密钥app.use(cors());
app.use(bodyParser());// 模拟用户数据库
const users = [{ id: 1, username: "user1", password: bcrypt.hashSync("password", 10) },{ id: 2, username: "user2", password: bcrypt.hashSync("password", 10) },{ id: 3, username: "user3", password: bcrypt.hashSync("password", 10) },{ id: 4, username: "user4", password: bcrypt.hashSync("password", 10) },{ id: 5, username: "user5", password: bcrypt.hashSync("password", 10) },
];// 登录路由
router.post("/login", async (ctx) => {const { username, password } = ctx.request.body;const user = users.find((u) => u.username === username);if (!user) {ctx.status = 400;ctx.body = { message: "没有此用户" };return;}const validPassword = bcrypt.compareSync(password, user.password);if (!validPassword) {ctx.status = 400;ctx.body = { message: "密码错误" };return;}const token = jwt.sign({ id: user.id, username: user.username }, secret, {expiresIn: "1h",});ctx.body = { token };
});// 获取用户信息路由(登录后可用)
router.get("/me", koaJwt({ secret }), async (ctx) => {const user = users.find((u) => u.id === ctx.state.user.id);ctx.body = user;
});// 中间件:使用 JWT 验证
app.use(koaJwt({ secret }).unless({ path: [/^\/login/] }));// 搜索用户接口(登录后可用)
router.get("/search", async (ctx) => {const query = ctx.query.q;const result = users.filter((user) => user.username.includes(query));ctx.body = result;
});// 添加好友接口(登录后可用)
let friends = {}; // { userId: [friendId1, friendId2, ...] }
router.post("/add-friend", async (ctx) => {const { friendId } = ctx.request.body;const userId = ctx.state.user.id; // 从 JWT 中提取用户信息if (!friends[userId]) {friends[userId] = [];}if (!friends[userId].includes(friendId)) {friends[userId].push(friendId);}ctx.body = { message: "Friend added" };
});// WebSocket 服务同样需要身份验证
const WebSocket = require("ws");
const http = require("http");
const server = http.createServer(app.callback());
const wss = new WebSocket.Server({ server });wss.on("connection", (ws, req) => {const token = req.url.split("?token=")[1];if (!token) {ws.close();return;}try {const decoded = jwt.verify(token, secret);ws.userId = decoded.id;ws.send("Connected to chat");ws.on("message", (message) => {// 广播消息仅限好友之间const friendIds = friends[ws.userId] || [];wss.clients.forEach((client) => {if (client.readyState === WebSocket.OPEN &&friendIds.includes(client.userId)) {client.send(message);}});});} catch (error) {ws.close();}
});app.use(router.routes()).use(router.allowedMethods());server.listen(3000, () => {console.log("Server running on http://localhost:3000");
});

前端

{"name": "vite-project","private": true,"version": "0.0.0","type": "module","scripts": {"dev": "vite","build": "vite build","preview": "vite preview"},"dependencies": {"socket.io-client": "^4.8.0","vue": "^3.4.37","vue-router": "^4.4.5"},"devDependencies": {"@vitejs/plugin-vue": "^5.1.2","vite": "^5.4.1"}
}

登录

<template><div><input v-model="username" placeholder="Username" /><input type="password" v-model="password" placeholder="Password" /><button @click="login">Login</button></div>
</template><script>
export default {data() {return {username: "",password: "",};},methods: {async login() {const response = await fetch("http://localhost:3000/login", {method: "POST",headers: { "Content-Type": "application/json" },body: JSON.stringify({username: this.username,password: this.password,}),});const data = await response.json();if (response.ok) {localStorage.setItem("token", data.token);this.$router.push("/"); // 登录成功后跳转到聊天页面} else {alert(data.message);}},},
};
</script>
<template><div><div><span>欢迎;{{ user.username }}</span></div><!-- 搜索和添加好友功能 --><div><input v-model="searchQuery" placeholder="Search for users" /><button @click="searchUsers">Search</button><div v-if="searchResults.length > 0"><h3>Search Results</h3><div v-for="user in searchResults" :key="user.id">{{ user.username }}<button @click="addFriend(user.id)">Add Friend</button></div></div></div><!-- 好友列表 --><div v-if="friends.length > 0"><h3>Friends List</h3><ul><liv-for="friend in friends":key="friend.id"@click="startChat(friend)">{{ friend.username }}</li></ul></div><!-- 聊天窗口 --><div v-if="currentChatUser"><h3>Chatting with {{ currentChatUser.username }}</h3><div class="chat-window"><divv-for="(msg, index) in messages":key="index":class="{ sent: msg.sentByUser, received: !msg.sentByUser }">{{ msg.text }} <span class="timestamp">{{ msg.timestamp }}</span></div></div><inputv-model="newMessage"@keyup.enter="sendMessage"placeholder="Type a message..."/><button @click="sendMessage">Send</button></div></div>
</template><script>
export default {data() {return {searchQuery: "", // 搜索框的输入searchResults: [], // 搜索结果friends: [], // 好友列表socket: null, // WebSocket 连接messages: [], // 聊天记录newMessage: "", // 输入的新消息currentChatUser: null, // 当前聊天的好友user: {}, // 当前用户信息};},created() {// 获取当前用户信息const token = localStorage.getItem("token");fetch("http://localhost:3000/me", {headers: { Authorization: `Bearer ${token}` },}).then((response) => response.json()).then((user) => {this.user = user;});},methods: {// 搜索用户async searchUsers() {const token = localStorage.getItem("token");const response = await fetch(`http://localhost:3000/search?q=${this.searchQuery}`,{headers: { Authorization: `Bearer ${token}` },});this.searchResults = await response.json();},// 添加好友async addFriend(friendId) {const token = localStorage.getItem("token");const response = await fetch("http://localhost:3000/add-friend", {method: "POST",headers: {Authorization: `Bearer ${token}`,"Content-Type": "application/json",},body: JSON.stringify({ friendId }),});if (response.ok) {alert("Friend added!");// 可选:可以在添加好友后,更新好友列表this.friends.push(this.searchResults.find((user) => user.id === friendId));}},// 开始与某个好友聊天startChat(friend) {this.currentChatUser = friend; // 设置当前聊天用户this.messages = []; // 清空当前消息// 连接 WebSocket(与服务器端的 WebSocket 实现保持一致)if (!this.socket) {const token = localStorage.getItem("token");this.socket = new WebSocket(`ws://localhost:3000?token=${token}`);// 监听 WebSocket 消息this.socket.onmessage = (event) => {if (event.data instanceof Blob) {// 如果是 Blob 类型的数据,将其转换为文本const reader = new FileReader();reader.onload = (event) => {const text = event.target.result;const message = {text: text,sentByUser: false,timestamp: new Date().toLocaleTimeString(), // 添加时间戳};this.messages.push(message); // 将消息添加到消息列表};reader.readAsText(event.data);} else {// 如果不是 Blob 数据,直接将消息显示const message = {text: event.data,sentByUser: false,timestamp: new Date().toLocaleTimeString(),};this.messages.push(message);}};}},// 发送消息sendMessage() {if (this.newMessage.trim() !== "") {const message = {text: this.newMessage,sentByUser: true,timestamp: new Date().toLocaleTimeString(), // 获取当前时间};this.messages.push(message); // 添加到消息列表this.socket.send(this.newMessage); // 发送消息this.newMessage = ""; // 清空输入框}},},
};
</script><style>
/* 样式调整 */
.chat-window {border: 1px solid #ccc;padding: 10px;height: 300px;overflow-y: scroll;margin-bottom: 10px;
}.sent {text-align: right;background-color: #d1f0d1; /* 发送的消息背景色 */
}.received {text-align: left;background-color: #f0f0f0; /* 接收的消息背景色 */
}.timestamp {font-size: 0.8em;color: #888; /* 时间戳颜色 */
}
</style>
import { createRouter, createWebHistory } from "vue-router";const routes = [{path: "/",name: "Home",component: import("../views/Home.vue"),},{path: "/login",name: "Login",component: import("../views/Login.vue"),},
];const router = createRouter({history: createWebHistory(import.meta.env.BASE_URL),routes,
});export default router;

在这里插入图片描述
主体功能初步实现,后期可以优化方向,设计数据库 将添加过的好友存储在数据表中,添加好友要先通过才能添加

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/54671.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ARM单片机的内存分布(重要)

ARM单片机的内存分布&#xff08;重要&#xff09; 一、S32K344的内存布局 MEMORY {int_pflash : ORIGIN 0x00400000, LENGTH 0x003D4000 /* 4096KB - 176KB (sBAF HSE)*/int_dflash : ORIGIN 0x10000000, LENGTH 0x00020000 /* 128KB …

BUUCTF [SCTF2019]电单车详解两种方法(python实现绝对原创)

使用audacity打开&#xff0c;发现是一段PT2242 信号 PT2242信号 有长有短&#xff0c;短的为0&#xff0c;长的为1化出来 这应该是截获电动车钥匙发射出的锁车信号 0 01110100101010100110 0010 0前四位为同步码0 。。。中间这20位为01110100101010100110为地址码0010为功…

不靠学历,不拼年资,怎么才能月入2W?

之前统计局发布了《2023年城镇单位就业人员年平均工资情况》&#xff0c;2023年全国城镇非私营单位和私营单位就业人员年平均工资分别为120698元和68340元。也就是说在去年非私营单位就业人员平均月薪1W&#xff0c;而私营单位就业人员平均月薪只有5.7K左右。 图源&#xff1a;…

两数之和、三数之和、四数之和

目录 两数之和 题目链接 题目描述 思路分析 代码实现 三数之和 题目链接 题目描述 思路分析 代码实现 四数之和 题目链接 题目描述 思路分析 代码实现 两数之和 题目链接 LCR 179. 查找总价格为目标值的两个商品 - 力扣&#xff08;LeetCode&#xff09; 题目…

EfficientFormer实战:使用EfficientFormerV2实现图像分类任务(一)

摘要 EfficientFormerV2是一种通过重新思考ViT设计选择和引入细粒度联合搜索策略而开发出的新型移动视觉骨干网络。它结合了卷积和变换器的优势&#xff0c;通过一系列高效的设计改进和搜索方法&#xff0c;实现了在移动设备上既轻又快且保持高性能的目标。这一成果为在资源受…

Redis-01 入门和十大数据类型

Redis支持两种持久化方式&#xff1a;RDB持久化和AOF持久化。 1.RDB持久化是将Redis的数据以快照的形式保存在磁盘上&#xff0c;可以手动触发或通过配置文件设置定时触发。RDB保存的是Redis在某个时间点上的数据快照&#xff0c;可以通过恢复RDB文件来恢复数据。 2.AOF持久化…

力扣P1706全排列问题 很好的引入暴力 递归 回溯 dfs

代码思路是受一个洛谷题解里面大佬的启发。应该算是一个dfs和回溯的入门题目&#xff0c;很好的入门题目了下面我会先给我原题解思路我想可以很快了解这个思路。下面是我自己根据力扣大佬写的。 我会进行详细讲解并配上图辅助理解大家请往下看 #include<iostream> #inc…

初始MYSQL数据库(7)—— 视图

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a; MYSQL 引言 前面我们学习MySQL数据库时&#xff0c;创建表之后&#xff0c;会在表中插入数据&#xff0c;在需要的时候&#xff0c;也会进行…

python文字转wav音频

借鉴博客 一.前期准备 1. pip install baidu-aip 2. pip install pydub 3. sudo apt-get install ffmpeg 二.代码 from aip import AipSpeech from pydub import AudioSegment import time#input your own APP_ID/API_KEY/SECRET_KEY APP_ID 14891501 API_KEY EIm2iXtvD…

示例:WPF中Grid显示网格线的几种方式

一、目的&#xff1a;介绍一下WPF中Grid显示网格线的几种方式 二、几种方式 1、重写OnRender绘制网格线&#xff08;推荐&#xff09; 效果如下&#xff1a; 实现方式如下&#xff1a; public class LineGrid : Grid{private readonly Pen _pen;public LineGrid(){_pen new P…

【Linux】深度解析与实战应用:GCC/G++编译器入门指南

&#x1f525; 个人主页&#xff1a;大耳朵土土垚 &#x1f525; 所属专栏&#xff1a;Linux系统编程 这里将会不定期更新有关Linux的内容&#xff0c;欢迎大家点赞&#xff0c;收藏&#xff0c;评论&#x1f973;&#x1f973;&#x1f389;&#x1f389;&#x1f389; 文章目…

RabbitMQ08_保证消息可靠性

保证消息可靠性 一、生产者可靠性1、生产者重连机制&#xff08;防止网络波动&#xff09;2、生产者确认机制Publisher Return 确认机制Publisher Confirm 确认机制 二、MQ 可靠性1、数据持久化交换机、队列持久化消息持久化 2、Lazy Queue 惰性队列 三、消费者可靠性1、消费者…

速通LLaMA3:《The Llama 3 Herd of Models》全文解读

文章目录 概览论文开篇IntroductionGeneral OverviewPre-TrainingPre-Training DataModel ArchitectureInfrastructure, Scaling, and EfficiencyTraining Recipe Post-TrainingResultsVision ExperimentsSpeech Experiments⭐Related WorkConclusionLlama 3 模型中的数学原理1…

细说硫酸钙防静电地板的材质结构和优势特点

防静电地板有全钢基材的、硫酸钙基材的、铝合金基材的&#xff0c;在一些防静电要求、承载要求、铺设要求、铺装效果要求很高的场合&#xff0c;如银行、电信机房、移动机房、智能化办公室、部队指挥中心&#xff0c;通常都会使用硫酸钙防静电地板。那么什么是硫酸钙防静电地板…

计算机毕业设计 二手图书交易系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

计算机毕业设计 基于Python的医疗预约与诊断系统 Django+Vue 前后端分离 附源码 讲解 文档

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

arthas-阿里远程诊断工具神器一定要掌握

文章目录 1. 背景介绍2. 安装下载3. 常用命令4. 常见案例4.1 案例一&#xff1a;使用logger 实时修改某个类的日志级别、4.2 案例二&#xff1a;使用watch 查看方法输入输出参数4.3 案例三&#xff1a;使用 Arthas 实现在线代码热更新 1. 背景介绍 通常&#xff0c;本地开发环…

文件上传、amrkdown编辑器

一、文件上传 这里我以图片为例&#xff0c;进行上传&#xff0c;上传到阿里云oss&#xff08;对象存在中&#xff09; 首先&#xff0c;我们先梳理一下&#xff0c;图片上传的流程 1、前端选择文件&#xff0c;提交文件 前端提交文件&#xff0c;我们可以使用ElementUI中的…

蓝队技能-应急响应篇Web内存马查杀JVM分析Class提取诊断反编译日志定性

知识点&#xff1a; 1、应急响应-Web内存马-定性&排查 2、应急响应-Web内存马-分析&日志 注&#xff1a;传统WEB类型的内存马只要网站重启后就清除了。 演示案例-蓝队技能-JAVA Web内存马-JVM分析&日志URL&内存查杀 0、环境搭建 参考地址&#xff1a;http…

有关 签到/签退 业务逻辑 的梳理与学习

导言 最近搞到了个签到管理&#xff0c;其中的业务逻辑感觉有点复杂(可能是我的方向不对),虽然是实现了&#xff0c;不过代码和逻辑很多&#xff0c;也有些乱&#xff0c;想趁着还记得逻辑来记录梳理一下&#xff0c;看看自己以后有没有更好的思路&#xff0c;或者有大佬有思路…