聊天室全棧開發-保姆級教程(Node.js+Websocket+Redis+HTML+CSS)

前言

最近在學習websocket全雙工通信,想要做一個聯機小游戲,做游戲之前先做一個聊天室練練手。
跟著本篇博客,可以從0搭建一個屬于你自己的聊天室。

準備階段

什么人適合學習本篇文章?
答:前端開發者,有一定的HTML+CSS+JavaScript基礎,會使用fetch進行網絡請求

技術棧

  • 前端三大件:HTML+CSS+JavaScript
  • Redis數據庫(本篇博客涉及到的是Redis基礎中的基礎,小白也可以輕松拿捏!)
  • Node.js+Express框架+pug模板引擎(本篇博客后端部分使用Node.js的Express框架完成,不了解的同學也不需要害怕,代碼簡單易懂,開箱即用,直接復制即可使用)

工具準備

1.編譯器

首先你需要有一個寫代碼的編譯器,這里推薦兩個(是我自己用的最多的,簡單方便的):

  1. vscode:老牌編譯器,功能強大,有豐富的插件資源,免費
    官網直接下載: https://code.visualstudio.com/
  2. trea:AI編譯器,新時代熱門編譯器,有國內和海外兩個版本
    國內:https://www.trae.cn/ide/download
    優點:完全免費內置許多國內ai,也可自添模型
    缺點:沒有國外ai,模型數量比國外版少很多
    國內版trea模型圖|100x146
    海外:https://www.trae.ai/download
    優點:部分免費,可充值為pro版,內置許多國外國內ai,可自添模型
    缺點:國外熱門ai如GPT,Claude模型等普通版要排隊很久,pro版不用排隊,但是每個月也有次數限制,一般夠用
    國外版trea模型圖|100

2.Node.js環境

兩個方法:
1.使用nvm下載node版本
2.直接下載node

這里不管是新手還是老手都強烈建議使用nvm來下載node,nvm的優勢在于可以同時管理多個node版本,并且在需要的時候隨時切換不同版本

vnm(下載安裝中文網敘述的很清楚,這里不在贅述,注意一點,安裝nvm前需要把電腦已有的node卸載):
下載:https://nvm.uihtm.com/doc/download-nvm.html
安裝:https://nvm.uihtm.com/doc/install.html

node(如果想要快速開始的話,可直接下載node版本,建議下載v20.x.x版本較穩定):
下載:https://nodejs.org/en/download
node下載

3.Redis數據庫

redis是linux環境下開發的高并發性能好的鍵值類型數據庫,要想在windows環境下使用,常用有三種方法:

  • 使用windows虛擬機模擬linux環境,運行redis
  • 使用Docker Desktop拉取鏡像并在容器中運行
  • 使用windows版redis,雖說比不上linux版的redis但是日常練習和教學完全夠用了

這里我們主要講第三種方法,想要使用windows版的redis需要訪問GitHub上的開源庫:https://github.com/redis-windows/redis-windows/releases,可能需要翻墻這個看運氣
在這里插入圖片描述
確保自己的電腦是windows,64位的,前四個都可下載,帶service 的要比不帶service的多一個自動添加服務的功能,你可以理解為有一個開機自啟動Redis的功能,因為我們不需要這個功能,所以我選擇下載的是第四個
下載好之后解壓到一個文件夾里面,解壓后的文件大概長這樣
在這里插入圖片描述

想要使用redis的服務,首先就是要運行起服務端
打開黑窗口運行:redis-server.exe redis.conf出現下圖的圖案就算運行服務端成功了
兩個注意點

  1. 運行命令的文件路徑需要是你解壓后的文件夾路徑,例如我這里就是:C:\software\redis,運行時需要換成你自己的路徑否則會報命令不存在的錯誤
  2. 運行完之后這個黑窗口不能關,關了就取消服務了,就會訪問不到redis了

解決方法
要解決上面提到的找不到命令的問題,除了在正確的路徑下運行命令外,還有一個很常用的方法,就是配置一下環境變量,這個很簡單,這里就不講了,如果有不懂的同學可以在評論區問我,到時候再解答

在這里插入圖片描述

開始實踐

如果你跟著步驟看到這里,你應該已經擁有了一個編譯器(寫代碼的地方),Node.js環境(運行后端服務的地方),redis數據庫(存儲數據的地方)

有了這些之后可以正式開始寫代碼了!

1.搭建項目

找一個存放項目的文件夾,新建chat-room文件夾,用來存放項目

在這里插入圖片描述

在trea里面打開chat-room文件夾(使用vscode打開也是一樣的,不影響項目運行)

在這里插入圖片描述

剛開始什么都沒有,讓我們先初始化一個package.json配置文件用來管理項目

使用npm工具來初始化package.json,這里我們使用npm init -y來快捷創建默認的配置文件

在這里插入圖片描述

執行完命令后你的工作區應該長這個樣子,并且內容如下

在這里插入圖片描述

我們的聊天室前端部分pug語法渲染要和express框架結合,所以我們先下載express框架,引入pug語法,使用npm install express pug nodemon下載依賴(nodemon是我后來加的,下面圖片沒有,是可選的,推薦下載可以用來實現node服務運行的時候熱重載

在這里插入圖片描述

按照下圖,創建項目的基礎結構

在這里插入圖片描述

現在我們逐個文件填充代碼,接下來的文件可直接復制使用,如果有疑問可直接復制給ai講解代碼,學會運用ai是當下我們程序員要培養的基本素養了

app.js:node服務啟動文件

// 引入 express 模塊const express = require("express");
const path = require("path");// 創建應用實例
const app = express();// 1. 引入靜態資源
app.get(express.static(path.join(__dirname, "public")));
// 2. 模板引擎
app.set("view engine", "pug");
// 3. 視圖目錄
app.set("views", path.join(__dirname, "views"));// 攔截/地址路由,渲染登錄頁面
app.use("/", (req, res) => {res.render("login", {title: "請登錄",});
});// 監聽listen
app.listen(3000, () => {console.log("服務器啟動成功");
});

layout.pug:布局文件

doctype html
htmlheadmeta(charset='utf-8')meta(name='viewport', content='width=device-width, initial-scale=1.0')title= title link(rel='stylesheet', href='/css/style.css')bodyblock content

login.pug:登錄頁面

extend layout 
block content .container.login-container h1= titleform(method='post', action='/login' id='loginForm')input(type='text',name='username',placeholder='請輸入用戶名',required,autocomplete='off')button(type='submit') 進入聊天室#error-message script(src='/js/login.js')

style.css:樣式文件(完整版的)

:root {--primary-color: #4caf50;--shadow-color: rgba(0, 0, 0, 0.1);
}* {margin: 0;padding: 0;box-sizing: border-box;
}body {font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;background: #f5f5f5;min-height: 100vh;display: flex;align-items: center;justify-content: center;
}.container {width: 100%;max-width: 400px;padding: 20px;
}.login-container {background: white;padding: 2rem;border-radius: 8px;box-shadow: 0 4px 6px var(--shadow-color);
}h1 {color: var(--primary-color);text-align: center;margin-bottom: 1.5rem;font-size: 1.8rem;
}form {display: flex;flex-direction: column;gap: 1rem;
}input {padding: 12px;border: 2px solid #e0e0e0;border-radius: 4px;font-size: 16px;transition: border-color 0.3s;
}input:focus {outline: none;border-color: var(--primary-color);
}button {background: var(--primary-color);color: white;border: none;padding: 12px;border-radius: 4px;font-size: 16px;cursor: pointer;transition: opacity 0.3s;
}button:hover {opacity: 0.9;
}#error-message {display: none;padding: 10px;margin-top: 15px;border-radius: 4px;background-color: #fef2f2;border: 1px solid #fecaca;color: #ef4444;transition: opacity 0.3s ease;
}#error-message.show {display: block;animation: fadeIn 0.3s ease;
}@keyframes fadeIn {from {opacity: 0;transform: translateY(-10px);}to {opacity: 1;transform: translateY(0);}
}

寫完這四個文件并且package.json中配置好啟動命令,就可以啟動項目了,我這里用的nodemon啟動的項目,沒有的同學可以把start后面的命令改為node app.js,但是還是推薦先下載一下npm install nodemon使用nodemon啟動項目

在這里插入圖片描述
在這里插入圖片描述

2.準備登錄接口和靜態聊天室

現在開始準備表單提交邏輯

public/js/login.js

// 獲取表單元素,監聽提交事件
document.getElementById("loginForm").addEventListener("submit", async (e) => {// 阻止表單默認提交e.preventDefault();const formData = new FormData(e.target);const username = formData.get("username");try {// 發送登錄請求const response = await fetch("/login", {method: "POST",headers: {"Content-Type": "application/json",},body: JSON.stringify({ username }),});const data = await response.json();if (!response.ok) {throw new Error(data.error || "登錄失敗");}// 登錄成功,重定向到聊天頁面if (data.success) {window.location.href = "/chat";}} catch (error) {showError(error.message);}
});function showError(message) {// 顯示錯誤提示const errorDiv = document.getElementById("error-message");errorDiv.textContent = message;errorDiv.classList.add("show");// 3秒后自動隱藏錯誤提示setTimeout(() => {errorDiv.classList.remove("show");}, 3000);
}

下載兩個依賴npm install jsonwebtoken dotenv用來提高登錄功能的健壯性,根目錄下新建.env文件,更新根目錄下的app.js添加登錄接口

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

// 引入 express 模塊
const express = require("express");
const path = require("path");
const jwt = require("jsonwebtoken");
require("dotenv").config();// 創建應用實例
const app = express();// 1. 引入靜態資源
app.use(express.static(path.join(__dirname, "public")));
// 2. 模板引擎
app.set("view engine", "pug");
// 3. 視圖目錄
app.set("views", path.join(__dirname, "views"));
// 4. 解析 JSON 請求體
app.use(express.json());// 攔截/地址路由,渲染登錄頁面
app.get("/", (req, res) => {res.render("login", {title: "請登錄",});
});// 處理登錄接口
app.post("/login", async (req, res) => {try {const { username } = req.body;if (!username) {return res.status(400).send("用戶名不能為空");}// 生成 tokenconst token = jwt.sign({ username }, process.env.JWT_SECRET, {expiresIn: "2h",});res.cookie("token", token, { httpOnly: true });res.json({success: true,});} catch (e) {console.error("登錄失敗:", e);res.status(500).send("服務器錯誤");}
});// 監聽listen
app.listen(3000, () => {console.log("服務器啟動成功");
});

現在可以登錄了,但是還需要準備chat頁面和對應的鑒權邏輯

新建views/chat.pug

extend layout 
block content  .container .chat-container#messages form#form.chat-forminput#input(type='text',placeholder='請輸入消息...',autocomplete='off')button(type='submit') 發送script(src='/js/chat.js')

下載 npm install cookie-parser依賴
在這里插入圖片描述
更新app.js

// 引入 express 模塊
const express = require("express");
const path = require("path");
const jwt = require("jsonwebtoken");
require("dotenv").config();
const cookieParser = require("cookie-parser");// 創建應用實例
const app = express();// 1. 引入靜態資源
app.use(express.static(path.join(__dirname, "public")));
// 2. 模板引擎
app.set("view engine", "pug");
// 3. 視圖目錄
app.set("views", path.join(__dirname, "views"));
// 4. 解析 JSON 請求體
app.use(express.json());
// 5. 解析 Cookie
app.use(cookieParser());// 攔截/地址路由,渲染登錄頁面
app.get("/", (req, res) => {res.render("login", {title: "請登錄",});
});// 處理登錄接口
app.post("/login", async (req, res) => {try {const { username } = req.body;if (!username) {return res.status(400).send("用戶名不能為空");}// 生成 tokenconst token = jwt.sign({ username }, process.env.JWT_SECRET, {expiresIn: "2h",});res.cookie("token", token, { httpOnly: true });res.json({success: true,});} catch (e) {console.error("登錄失敗:", e);res.status(500).send("服務器錯誤");}
});
// 處理聊天室路由
app.use("/chat", mustAuth, (_, res) =>res.render("chat", { title: "實時聊天室" })
);/* ====== 中間件:JWT 校驗 ====== */
function mustAuth(req, res, next) {const token = req.cookies.token;if (!token) return res.redirect("/");try {jwt.verify(token, process.env.JWT_SECRET);next();} catch {res.redirect("/");}
}// 監聽listen
app.listen(3000, () => {console.log("服務器啟動成功");
});

3.websocket實現實時通訊聊天室

現在登錄功能和靜態的聊天室頁面已經準備好了,接下來準備設置websocket,實現實時通訊

設置websocket服務端:根目錄下新建ws-server.js,并引入npm install ws依賴
新增ws-server.js

引入ws依賴

ws.server.js

const WebSocket = require("ws");
const jwt = require("jsonwebtoken");// 定義用戶狀態常量
const USER_STATUS = {ONLINE: 1,OFFLINE: 2,
};// 啟動 WebSocket 服務
module.exports = (server) => {const wss = new WebSocket.Server({server,// 握手階段攔截verifyClient: (info, cb) => {const cookies = info.req.headers.cookie || "";console.log("cookies", cookies);// 從 cookies 中提取 tokenconst token = cookies.match(/token=([^;]+)/)?.[1];if (!token) return cb(false, 401, "Missing token");try {jwt.verify(token, process.env.JWT_SECRET);cb(true); // 放行} catch {cb(false, 401, "Invalid token");}},});// 監聽客戶端連接wss.on("connection", (ws, req) => {console.log("客戶端連接成功");// 解析用戶名const cookies = req.headers.cookie || "";const token = cookies.match(/token=([^;]+)/)?.[1];ws.username = "匿名用戶";if (token) {try {const payload = jwt.verify(token, process.env.JWT_SECRET);ws.username = payload.username || ws.username;} catch (e) {// token無效,保持匿名console.error("Token 驗證失敗:", e);}}// 廣播歡迎消息給所有客戶端wss.clients.forEach((client) => {if (client.readyState === WebSocket.OPEN) {client.send(JSON.stringify({type: "sys",text: `歡迎 ${ws.username} 進入聊天室`,number: wss.clients.size,}));}});// 廣播ws.on("message", (data) => {console.log("收到消息:", data);const message = JSON.parse(data);// 廣播給所有客戶端wss.clients.forEach((client) => {if (client.readyState === WebSocket.OPEN) {client.send(JSON.stringify({type: "chat",from: ws.username,text: message.text,time: new Date().toLocaleString(),number: wss.clients.size,}));}});});// 監聽斷開連接ws.on("close", async () => {console.log(`用戶 ${ws.username} 斷開連接`);try {if (ws.username && ws.username !== "匿名用戶") {// 廣播用戶離開消息wss.clients.forEach((client) => {if (client !== ws && client.readyState === WebSocket.OPEN) {client.send(JSON.stringify({type: "sys",text: `${ws.username} 離開了聊天室`,number: wss.clients.size, // 減去即將斷開的連接}));}});}} catch (error) {console.error(`更新用戶 ${ws.username} 狀態失敗:`, error);}});});
};

準備websocket客戶端,public/js目錄下新建chat.js

public/js/chat.js

const socket = new WebSocket(`ws://${location.host}`);
const messages = document.getElementById("messages");
const form = document.getElementById("form");
const input = document.getElementById("input");// 監聽消息,并展示
socket.onmessage = (event) => {const { type, from, text, time, number } = JSON.parse(event.data);const div = document.createElement("div");div.innerHTML =type === "sys"? `<em>${text},當前在線人數: ${number}</em>`: `<strong>${from}</strong>: <small>${time}</small>:${text}`;messages.appendChild(div);messages.scrollTop = messages.scrollHeight; // 滾動到底部
};// 發送消息
form.addEventListener("submit", (e) => {e.preventDefault();const message = input.value.trim();if (!message) return;try {socket.send(JSON.stringify({ text: message }));input.value = ""; // 清空輸入框} catch (err) {console.error("發送消息失敗:", err);alert("發送失敗,請檢查網絡連接");}
});

更新app.js,建立websocket連接

// 引入 express 模塊
const express = require("express");
const path = require("path");
const jwt = require("jsonwebtoken");
require("dotenv").config();
const cookieParser = require("cookie-parser");// 創建應用實例
const app = express();// 1. 引入靜態資源
app.use(express.static(path.join(__dirname, "public")));
// 2. 模板引擎
app.set("view engine", "pug");
// 3. 視圖目錄
app.set("views", path.join(__dirname, "views"));
// 4. 解析 JSON 請求體
app.use(express.json());
// 5. 解析 Cookie
app.use(cookieParser());// 攔截/地址路由,渲染登錄頁面
app.get("/", (req, res) => {res.render("login", {title: "請登錄",});
});// 處理登錄接口
app.post("/login", async (req, res) => {try {const { username } = req.body;if (!username) {return res.status(400).send("用戶名不能為空");}// 生成 tokenconst token = jwt.sign({ username }, process.env.JWT_SECRET, {expiresIn: "2h",});res.cookie("token", token, { httpOnly: true });res.json({success: true,});} catch (e) {console.error("登錄失敗:", e);res.status(500).send("服務器錯誤");}
});// 處理聊天頁面路由
app.use("/chat", mustAuth, (_, res) =>res.render("chat", { title: "實時聊天室" })
);/* ====== 中間件:JWT 校驗 ====== */
function mustAuth(req, res, next) {const token = req.cookies.token;if (!token) return res.redirect("/");try {jwt.verify(token, process.env.JWT_SECRET);next();} catch {res.redirect("/");}
}app.use((err, req, res, next) => {// 錯誤處理中間件console.error(err);
});// 監聽listen
app.listen(3000, () => {console.log("服務器啟動成功");
});// 啟動 WebSocket 服務
require("./ws-server")(server);

到這里我們的實時聊天室已經實現了,項目根目錄下運行npm run start就可以把我們的聊天室在本地localhost:3000跑起來了

聊天室預覽

4.項目引入Redis,禁止重復登錄

雖然我們的聊天室已經完成了,但是基礎好的同學就會發現,我們的聊天室對于登錄的賬號是沒有限制的,所以就可能出現同一個賬戶重復登錄的情況

重復登錄情況
重復登錄

現在我們要借用redis記錄登錄狀態,進而防止重復登錄

本地啟動redis服務端

win+R打開運行窗口輸入cmd打開黑窗口

打開黑窗口
在安裝redis的目錄下輸入redis-server.exe redis.conf啟動redis服務,啟動完服務后,這個黑窗口不能關,一關,redis就斷開連接了
啟動redis

我們根目錄下新建configs/redis.js,同時下載npm install ioredis用來在項目中配置連接redis

新建redis.js

configs/redis.js

// 引入 ioredis 模塊
const Redis = require("ioredis");// 連接 Redis 數據庫const redis = new Redis({host: process.env.REDIS_HOST || "127.0.0.1",port: process.env.REDIS_PORT || 6379,password: process.env.REDIS_PASSWORD || "123456",
});// 監聽 Redis 連接事件redis.on("connect", () => {console.log("Redis 連接成功");
});
// 監聽 Redis 錯誤事件redis.on("error", (err) => {console.error("Redis 連接失敗:", err);
});
// 導出 Redis 實例module.exports = redis;

更新app.js

// 引入 express 模塊
const express = require("express");
const path = require("path");
const jwt = require("jsonwebtoken");
require("dotenv").config();
const cookieParser = require("cookie-parser");
const redis = require("./configs/redis"); // 新引入redis實例// 創建應用實例
const app = express();// 1. 引入靜態資源
app.use(express.static(path.join(__dirname, "public")));
// 2. 模板引擎
app.set("view engine", "pug");
// 3. 視圖目錄
app.set("views", path.join(__dirname, "views"));
// 4. 解析 JSON 請求體
app.use(express.json());
// 5. 解析 Cookie
app.use(cookieParser());// 攔截/地址路由,渲染登錄頁面
app.get("/", (req, res) => {res.render("login", {title: "請登錄",});
});// 處理登錄接口
app.post("/login", async (req, res) => {try {const { username } = req.body;if (!username) {return res.status(400).send("用戶名不能為空");}// 將用戶信息存儲到 Redisconst userKey = `user:${username}`;if ((await redis.hget(userKey, "status")) === "1") {return res.status(400).json({ error: "用戶已登錄,請勿重復登錄" });}// 生成 tokenconst token = jwt.sign({ username }, process.env.JWT_SECRET, {expiresIn: "2h",});// 如果用戶已存在,更新狀態await redis.hset(userKey, "status", 1);// 設置過期時間(2小時)await redis.expire(userKey, 7200);res.cookie("token", token, { httpOnly: true });res.json({success: true,});} catch (e) {console.error("登錄失敗:", e);res.status(500).send("服務器錯誤");}
});// 處理聊天頁面路由
app.use("/chat", mustAuth, (_, res) =>res.render("chat", { title: "實時聊天室" })
);/* ====== 中間件:JWT 校驗 ====== */
function mustAuth(req, res, next) {const token = req.cookies.token;if (!token) return res.redirect("/");try {jwt.verify(token, process.env.JWT_SECRET);next();} catch {res.redirect("/");}
}app.use((err, req, res, next) => {// 錯誤處理中間件console.error(err);
});// 監聽listen
const server = app.listen(3000, () => {console.log("服務器啟動成功");
});// 啟動 WebSocket 服務
require("./ws-server")(server);

更新完app.js后,你再運行項目,就會發現,不能同時登錄同一個賬戶了,但是還有一個問題,那就是即使我們退出一個賬號,想再次登錄的時候仍然登錄不了!這是因為沒有做用戶離開聊天室時,對登錄狀態更改的邏輯!

阻止重復登錄

更新根目錄下的ws-server.js處理用戶聊天室后,取消登錄狀態

ws-server.js

const WebSocket = require("ws");
const jwt = require("jsonwebtoken");
const redis = require("./configs/redis");// 定義用戶狀態常量
const USER_STATUS = {ONLINE: 1,OFFLINE: 2,
};// 啟動 WebSocket 服務
module.exports = (server) => {const wss = new WebSocket.Server({server,// 握手階段攔截verifyClient: (info, cb) => {const cookies = info.req.headers.cookie || "";console.log("cookies", cookies);// 從 cookies 中提取 tokenconst token = cookies.match(/token=([^;]+)/)?.[1];if (!token) return cb(false, 401, "Missing token");try {jwt.verify(token, process.env.JWT_SECRET);cb(true); // 放行} catch {cb(false, 401, "Invalid token");}},});// 監聽客戶端連接wss.on("connection", (ws, req) => {console.log("客戶端連接成功");// 解析用戶名const cookies = req.headers.cookie || "";const token = cookies.match(/token=([^;]+)/)?.[1];ws.username = "匿名用戶";if (token) {try {const payload = jwt.verify(token, process.env.JWT_SECRET);ws.username = payload.username || ws.username;// 設置用戶在線狀態const userKey = `user:${ws.username}`;redis.hset(userKey, "status", USER_STATUS.ONLINE).catch((err) => console.error("設置用戶在線狀態失敗:", err));} catch (e) {// token無效,保持匿名console.error("Token 驗證失敗:", e);}}// 廣播歡迎消息給所有客戶端wss.clients.forEach((client) => {if (client.readyState === WebSocket.OPEN) {client.send(JSON.stringify({type: "sys",text: `歡迎 ${ws.username} 進入聊天室`,number: wss.clients.size,}));}});// 廣播ws.on("message", (data) => {console.log("收到消息:", data);const message = JSON.parse(data);// 廣播給所有客戶端wss.clients.forEach((client) => {if (client.readyState === WebSocket.OPEN) {client.send(JSON.stringify({type: "chat",from: ws.username,text: message.text,time: new Date().toLocaleString(),number: wss.clients.size,}));}});});// 監聽斷開連接ws.on("close", async () => {console.log(`用戶 ${ws.username} 斷開連接`);try {if (ws.username && ws.username !== "匿名用戶") {const userKey = `user:${ws.username}`;// 更新用戶狀態為離線await redis.hset(userKey, "status", USER_STATUS.OFFLINE);console.log(`用戶 ${ws.username} 狀態已更新為離線`);// 廣播用戶離開消息wss.clients.forEach((client) => {if (client !== ws && client.readyState === WebSocket.OPEN) {client.send(JSON.stringify({type: "sys",text: `${ws.username} 離開了聊天室`,number: wss.clients.size, // 減去即將斷開的連接}));}});}} catch (error) {console.error(`更新用戶 ${ws.username} 狀態失敗:`, error);}});});
};

未完待續

到此為止,我們已經擁有了一個有一定登錄權限控制的實時聊天室了
但是我想我們的追求不應該止步于此,后續我會持續更新幾個新功能
感興趣的小伙伴可以適時再來查看這篇博客
  • 2025.8.17:完成UI樣式更新
  • 2025.8.20:新增好友功能,實現單聊

有問題的同學歡迎在評論區討論學習!!!

本篇博客涉及到的學習資源如下,想深入學習的同學可自行閱讀:

WebSocket:https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket
Express:https://express.js.cn/en/guide/routing.html
Redis:https://www.runoob.com/redis/redis-tutorial.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/93188.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/93188.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/93188.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

后臺管理系統-2-vue3之路由配置和Main組件的初步搭建布局

文章目錄1 路由搭建1.1 路由創建(router/index.js)1.2 路由組件(views/Main.vue)1.3 路由引入并注冊(main.js)1.4 路由渲染(App.vue)2 element-plus的應用2.1 完整引入并注冊(main.js)2.2 示例應用(App.vue)3 ElementPlusIconsVue的應用3.1 圖標引入并注冊(main.js)3.2 示例應用…

使用 Let’s Encrypt 免費申請泛域名 SSL 證書,并實現自動續期

使用 Let’s Encrypt 免費申請泛域名 SSL 證書&#xff0c;并實現自動續期 目錄 使用 Let’s Encrypt 免費申請泛域名 SSL 證書&#xff0c;并實現自動續期 &#x1f6e0;? 環境準備&#x1f4a1; 什么是 Let’s Encrypt&#xff1f;&#x1f9e0; Let’s Encrypt 證書頒發原…

一鍵自動化:Kickstart無人值守安裝指南

Kickstart文件實現自動安裝1. Kickstart文件概述1.1 定義與作用Kickstart文件是Red Hat系Linux發行版&#xff08;如RHEL、CentOS、Fedora&#xff09;用于實現自動化安裝的配置文件&#xff0c;采用純文本格式保存。它通過預設安裝參數的方式&#xff0c;使系統安裝過程無需人…

深度解讀 Browser-Use:讓 AI 驅動瀏覽器自動化成為可能

目錄 一、什么是 Browser-Use&#xff1f; 二、Browser-Use 的核心功能 1. AI 與瀏覽器的鏈接橋梁 2. 無代碼 / 低代碼操作界面 3. 支持多家 LLM 4. 開發體驗簡潔 可快速上手 三、核心價值與適用場景 四、與 Playwright 的結合使用 五、總結與展望 https://github.com…

React.memo、useMemo 和 React.PureComponent的區別

useMemo 和 React.memo 都是 React 提供的性能優化工具&#xff0c;但它們的作用和使用場景有顯著不同。以下是兩者的全面對比&#xff1a; 一、核心區別總結特性useMemoReact.memo類型React Hook高階組件(HOC)作用對象緩存計算結果緩存組件渲染結果優化目標避免重復計算避免不…

Lumerical INTERCONNECT ------ CW Laser 和 OPWM 組成的系統

Lumerical INTERCONNECT ------ CW Laser 和 OPWM 組成的系統 引言 正文 引言 這里我們來簡單介紹一下 CW Laser 與 OSA 組成的簡單系統結構的仿真。 正文 我們構建一個如下圖所示的仿真結構。 我們將 CWL 中的 power 設置為 1 W。 然后直接運行仿真查看結果如下: 雖然 …

想漲薪30%?別只盯著大廠了!轉型AI產品經理的3個通用方法,人人都能學!

在AI產品經理剛成為互聯網公司香餑餑的時候&#xff0c;剛做產品1年的月月就規劃了自己的轉型計劃&#xff0c;然后用3個月時間成功更換賽道&#xff0c;轉戰AI產品經理&#xff0c;漲薪30%。 問及她有什么上岸秘訣&#xff1f;她也復盤總結了3個踩坑經驗和正確路徑&#xff0c…

基于Hadoop的全國農產品批發價格數據分析與可視化與價格預測研究

文章目錄有需要本項目的代碼或文檔以及全部資源&#xff0c;或者部署調試可以私信博主項目介紹每文一語有需要本項目的代碼或文檔以及全部資源&#xff0c;或者部署調試可以私信博主 項目介紹 隨著我國農業數字化進程的加快&#xff0c;農產品批發市場每天都會產生海量的價格…

STM32在使用DMA發送和接收時的模式區別

在STM32的DMA傳輸中&#xff0c;發送使用DMA_Mode_Normal而接收使用DMA_Mode_Circular的設計基于以下關鍵差異&#xff1a;1. ?觸發機制的本質區別??發送方向&#xff08;TX&#xff09;?&#xff1a;由USART的?TXE標志&#xff08;發送寄存器空&#xff09;觸發?&#x…

【秋招筆試】2025.08.15餓了么秋招機考-第三題

?? 點擊直達筆試專欄 ??《大廠筆試突圍》 ?? 春秋招筆試突圍在線OJ ?? 筆試突圍在線刷題 bishipass.com 03. A先生的商貿網絡投資 問題描述 A先生是一位精明的商人,他計劃在 n n n 個城市之間建立商貿網絡。目前有 m m

Socket 套接字的學習--UDP

上次我們大概介紹了一些關于網絡的基礎知識&#xff0c;這次我們利用編程來深入學習一下一&#xff1a;套接字Socket1.1什么是Socketsocket API 是一層抽象的網絡編程接口,適用于各種底層網絡協議,如 IPv4、IPv6,. 然而, 各種網絡協議的地址格式并不相同。1.2套接字的分類套接字…

AI - MCP 協議(一)

AI應用開發的高級特性——MCP模型上下文協議&#xff0c;打通AI與外部服務的邊界。 ************************************************************************************************************** 一、需求分析 當你的AI具備了RAG的能力&#xff0c;具備了調用工具的…

在es中安裝kibana

一 安裝 1.1 驗證訪問https的連通性 # 測試 80 端口&#xff08;HTTP&#xff09; curl -I -m 5 http://目標IP:端口號 說明&#xff1a; -I&#xff1a;僅獲取 HTTP 頭部&#xff08;Head 請求&#xff09;&#xff0c;不下載正文&#xff0c;減少數據傳輸。 -m 5&#x…

嵌入式開發學習———Linux環境下網絡編程學習(二)

UDP服務器客戶端搭建UDP服務器代碼#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h>#define PORT 8080 #define BUFFER_SIZE 1024int main() {int sockfd;char buffer[BUFFER_SIZE…

UVa1465/LA4841 Searchlights

UVa12345 UVa1465/LA4841 Searchlights題目鏈接題意輸入格式輸出格式分析AC 代碼題目鏈接 本題是2010年icpc亞洲區域賽杭州賽區的I題 題意 在一個 n 行 m 列&#xff08;n≤100&#xff0c;m≤10 000&#xff09;的網格中有一些探照燈&#xff0c;每個探照燈有一個最大亮度 k&…

詳解區塊鏈技術及主流區塊鏈框架對比

文章目錄一、區塊鏈技術棧詳解二、主流區塊鏈框架對比1. 公有鏈&#xff08;Public Blockchain&#xff09;2. 聯盟鏈&#xff08;Consortium Blockchain&#xff09;3. 私有鏈&#xff08;Private Blockchain&#xff09;三、技術選型建議1. 按需求選擇框架2. 開發工具與生態四…

大模型 + 垂直場景:搜索 / 推薦 / 營銷 / 客服領域開發有哪些新玩法?

技術文章大綱&#xff1a;大模型 垂直場景的新玩法大模型與搜索領域的結合大模型在搜索領域的應用可以顯著提升搜索結果的準確性和用戶體驗。利用大模型進行語義理解和上下文關聯&#xff0c;能夠實現更精準的意圖識別。結合知識圖譜和動態索引優化&#xff0c;可以增強長尾查…

p5.js 3D盒子的基礎用法

點贊 關注 收藏 學會了 如果你剛接觸 p5.js&#xff0c;想嘗試 3D 繪圖&#xff0c;那么box()函數絕對是你的入門首選。它能快速繪制出 3D 長方體&#xff08;或正方體&#xff09;&#xff0c;配合簡單的交互就能做出酷炫的 3D 效果。本文會從基礎到進階&#xff0c;帶你吃…

【動態規劃 完全背包 卡常】P9743 「KDOI-06-J」旅行|普及+

本文涉及知識點 C動態規劃 完全背包 C記憶化搜索 「KDOI-06-J」旅行 題目描述 小 C 在 C 國旅行。 C 國有 nmn\times mnm 個城市&#xff0c;可以看做 nmn\times mnm 的網格。定義 (i,j)(i,j)(i,j) 表示在網格中第 iii 行第 jjj 列的城市。 該國有 222 種交通系統&#x…

pytest框架-詳解

目錄 一、前言 二、pytest安裝 2.1、安裝 2.2、驗證安裝 2.3、pytest文檔 三、pytest框架的約束 3.1、 python的命名規則 3.2、 pytest的命名規則 四、pytest的運行方式 4.1、主函數運行 4.2、命令行運行 五、pytest配置文件pytest.ini文件 六、前置和后置 七、as…