当前位置: 首页 > news >正文

聊天室全栈开发-保姆级教程(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:新增好友功能,实现单聊

有问题的同学欢迎在评论区讨论学习!!!

http://www.lryc.cn/news/617221.html

相关文章:

  • MathType关联Wps实现公式编辑【Tex语法适配】
  • 2438. 二的幂数组中查询范围内的乘积
  • 【liunx】web高可用---nginx
  • 编译Android版本可用的高版本iproute2
  • 机器学习 - Kaggle项目实践(1)Titanic
  • C++多态详解
  • SDI设计中,为何SD-SDI模式下,接收器用DRU实现,在3G-SDI模式下,使用transceiver实现
  • 多轮会话记忆的核心挑战
  • Spring Boot 中 @Transactional 解析
  • 自动化备份全网服务器数据平台项目
  • P2865 [USACO06NOV] Roadblocks G
  • ListNode* dummy = new ListNode();什么意思
  • 【功能测试】软件集成测试思路策略与经验总结
  • 使用纯NumPy实现回归任务:深入理解机器学习本质
  • 小结: getSpringFactoriesInstances从 `spring.factories` 文件中加载和实例化指定类型的类
  • 一维码+二维码+字符识别
  • 关于开发面对颠覆性需求变更的思考
  • SpringBoot 实现 Excel 导入导出功能的三种实现方式
  • MySQL语句,体系结构等基础知识解析
  • 量子计算:叩响金融定价革命的大门——期权定价的范式转移
  • 【PyTorch学习笔记 - 01】 Tensors(张量)
  • MLAG双活网络妙招:BGP + 静态VRRP实现智能负载均衡
  • MATLAB实现遗传算法求解路网路由问题
  • 【FAQ】Win11创建资源不足绕开微软账号登录
  • 【数据结构入门】树
  • 2025世界机器人大会|具身智能机器人十大发展趋势
  • 人脸识别系统技术文档
  • C9800 ISSU升级
  • Netty使用CA证书实现tls双认证
  • Linux ethernet驱动移植之常见问题