前言

最近在学习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…