WebSocket主从服务器架构完整教程

目录

1. 前言:为什么要学习WebSocket主从架构

第一章:基础知识准备

2.1 什么是WebSocket

生活中的例子

技术特点

2.2 WebSocket vs HTTP

什么时候用WebSocket?

2.3 什么是主从架构

生活中的例子

技术架构图

2.4 环境准备

需要的软件

项目结构

第二章:WebSocket基础入门

3.1 第一个WebSocket服务器

3.2 第一个WebSocket客户端

3.3 消息通信基础

消息格式设计

常见消息类型

完整的消息处理示例

第三章:主从架构设计原理

4.1 架构设计思路

为什么需要主从架构?

架构演进过程

4.2 核心组件说明

1. 主服务器(Master Server)

2. 从服务器(Slave Server)

3. 客户端(Client)

4.3 通信协议设计

通信流程详解

消息协议规范

第四章:主服务器详细实现

5.1 主服务器架构

核心类设计

5.2 从服务器管理

从服务器信息结构

从服务器注册流程

5.3 负载均衡实现

多种负载均衡策略

5.4 完整代码实现

第五章:从服务器详细实现

6.1 从服务器架构

核心类设计

6.2 与主服务器通信

连接管理

6.3 客户端管理

客户端连接处理

6.4 完整代码实现

第六章:客户端详细实现

7.1 客户端连接流程

连接流程图

7.2 自动重连机制

重连策略


1. 前言:为什么要学习WebSocket主从架构

想象一下,你正在开发一个实时在线游戏,有成千上万的玩家同时在线。如果只用一台服务器,会发生什么?

  • 服务器压力巨大,可能随时崩溃
  • 玩家体验差,延迟高
  • 无法扩展,玩家数量受限

这就是为什么我们需要学习WebSocket主从架构!它能帮助我们:

  1. 分散压力:把玩家分配到不同的服务器
  2. 提高性能:每个服务器只处理部分玩家
  3. 易于扩展:需要时随时增加服务器
  4. 高可用性:一台服务器挂了,其他的继续工作

本教程将带你从零开始,一步步掌握这个强大的架构!


第一章:基础知识准备

2.1 什么是WebSocket

生活中的例子

想象两种通信方式:

  1. 写信(HTTP)

    • 你写一封信(请求)
    • 寄给朋友
    • 等待回信(响应)
    • 想再说话?再写一封信
  2. 打电话(WebSocket)

    • 拨通电话(建立连接)
    • 随时可以说话(发送消息)
    • 对方也能随时回话(接收消息)
    • 直到挂断电话(断开连接)

WebSocket就像打电话,一旦连接建立,双方可以随时通信!

技术特点
// HTTP方式(传统)
客户端: "服务器,现在几点?"
服务器: "下午3点"
// 连接断开客户端: "服务器,现在几点?" // 需要重新连接
服务器: "下午3点01分"
// 连接又断开// WebSocket方式
客户端 <--持续连接--> 服务器
客户端: "现在几点?"
服务器: "下午3点"
客户端: "天气如何?"
服务器: "晴天"
服务器: "对了,有新消息!" // 服务器主动推送
// 连接一直保持

2.2 WebSocket vs HTTP

特性HTTPWebSocket
通信方式请求-响应全双工
连接状态短连接长连接
服务器推送不支持支持
实时性低(需要轮询)
资源消耗高(频繁建立连接)低(保持连接)
适用场景普通网页实时应用
什么时候用WebSocket?

适合使用WebSocket的场景

  • 在线聊天
  • 实时游戏
  • 股票行情
  • 协同编辑
  • 直播弹幕
  • IoT实时数据

不适合使用WebSocket的场景

  • 静态网页
  • RESTful API
  • 文件下载
  • 表单提交

2.3 什么是主从架构

生活中的例子

想象一个大型餐厅的厨房:

     总厨师长(主服务器)|分配任务和协调|+-----+-----+-----+|     |     |     |厨师1 厨师2 厨师3 厨师4(从服务器)|     |     |     |菜品1 菜品2 菜品3 菜品4(处理客户请求)
  • 总厨师长:不做菜,只负责分配订单给各个厨师
  • 厨师们:实际做菜,每个厨师负责一部分订单
  • 优势:效率高,一个厨师累了还有其他的

这就是主从架构的核心思想!

技术架构图
                    ┌─────────────────┐│   主服务器       ││   (Master)      ││                 ││ · 管理从服务器   ││ · 分配客户端     ││ · 负载均衡       ││ · 健康检查       │└────────┬────────┘│┌────────────────────┼────────────────────┐│                    │                    │┌────▼─────┐        ┌────▼─────┐        ┌────▼─────┐│从服务器1  │        │从服务器2  │        │从服务器3  ││(Slave 1) │        │(Slave 2) │        │(Slave 3) ││          │        │          │        │          ││·处理业务  │        │·处理业务  │        │·处理业务  ││·管理客户端│        │·管理客户端│        │·管理客户端│└────┬─────┘        └────┬─────┘        └────┬─────┘│                    │                    │客户端1,2,3          客户端4,5,6          客户端7,8,9

2.4 环境准备

需要的软件
  1. Node.js(版本 14.0 以上)

    # 检查是否安装
    node --version# 如果没有安装,去官网下载:https://nodejs.org
    
  2. 代码编辑器(推荐 VS Code)

    • 下载地址:https://code.visualstudio.com
  3. 创建项目目录

    # 创建项目文件夹
    mkdir websocket-master-slave
    cd websocket-master-slave# 初始化项目
    npm init -y# 安装依赖
    npm install ws
    
项目结构
websocket-master-slave/
├── package.json          # 项目配置文件
├── master-server.js      # 主服务器
├── slave-server.js       # 从服务器
├── client.js            # 客户端
├── config/              # 配置文件夹
│   ├── master.json      # 主服务器配置
│   └── slave.json       # 从服务器配置
├── logs/                # 日志文件夹
├── test/                # 测试文件夹
└── README.md            # 项目说明

第二章:WebSocket基础入门

在深入主从架构之前,我们先来掌握WebSocket的基础知识。

3.1 第一个WebSocket服务器

让我们从最简单的WebSocket服务器开始:

// simple-server.js
const WebSocket = require('ws');// 创建WebSocket服务器,监听8080端口
const wss = new WebSocket.Server({ port: 8080 });console.log('WebSocket服务器启动在端口 8080');// 当有客户端连接时
wss.on('connection', function connection(ws) {console.log('新客户端连接了!');// 向客户端发送欢迎消息ws.send('欢迎连接到WebSocket服务器!');// 当收到客户端消息时ws.on('message', function incoming(message) {console.log('收到消息:', message.toString());// 回复客户端ws.send(`服务器收到了你的消息: ${message}`);});// 当客户端断开连接时ws.on('close', function close() {console.log('客户端断开连接');});// 错误处理ws.on('error', function error(err) {console.error('WebSocket错误:', err);});
});// 每5秒向所有连接的客户端发送时间
setInterval(() => {wss.clients.forEach((client) => {if (client.readyState === WebSocket.OPEN) {client.send(`当前时间: ${new Date().toLocaleTimeString()}`);}});
}, 5000);

代码解释

  1. new WebSocket.Server({ port: 8080 }) - 创建服务器
  2. wss.on('connection', ...) - 监听新连接
  3. ws.on('message', ...) - 监听消息
  4. ws.send(...) - 发送消息
  5. wss.clients - 所有连接的客户端

3.2 第一个WebSocket客户端

// simple-client.js
const WebSocket = require('ws');// 连接到服务器
const ws = new WebSocket('ws://localhost:8080');// 连接成功时
ws.on('open', function open() {console.log('成功连接到服务器!');// 发送消息给服务器ws.send('你好,服务器!');// 每3秒发送一次消息setInterval(() => {if (ws.readyState === WebSocket.OPEN) {ws.send(`客户端时间: ${new Date().toLocaleTimeString()}`);}}, 3000);
});// 收到服务器消息时
ws.on('message', function incoming(data) {console.log('收到服务器消息:', data.toString());
});// 连接关闭时
ws.on('close', function close() {console.log('与服务器断开连接');
});// 错误处理
ws.on('error', function error(err) {console.error('连接错误:', err);
});// 处理进程退出
process.on('SIGINT', () => {console.log('\n正在关闭连接...');ws.close();process.exit();
});

3.3 消息通信基础

消息格式设计

在实际应用中,我们通常使用JSON格式传递消息:

// 消息格式示例
const message = {type: 'chat',        // 消息类型data: {              // 消息数据user: '张三',text: '大家好!',time: Date.now()}
};// 发送时转换为字符串
ws.send(JSON.stringify(message));// 接收时解析JSON
ws.on('message', (data) => {const message = JSON.parse(data);console.log(`${message.data.user}: ${message.data.text}`);
});
常见消息类型
// 定义消息类型常量
const MessageTypes = {// 系统消息CONNECT: 'connect',           // 连接DISCONNECT: 'disconnect',     // 断开HEARTBEAT: 'heartbeat',       // 心跳ERROR: 'error',              // 错误// 业务消息CHAT: 'chat',                // 聊天JOIN_ROOM: 'join_room',      // 加入房间LEAVE_ROOM: 'leave_room',    // 离开房间USER_LIST: 'user_list',      // 用户列表// 主从通信REGISTER: 'register',         // 注册SYNC: 'sync',                // 同步BROADCAST: 'broadcast'       // 广播
};
完整的消息处理示例
// message-handler.js
class MessageHandler {constructor(ws) {this.ws = ws;this.handlers = new Map();this.registerHandlers();}// 注册消息处理器registerHandlers() {this.handlers.set('chat', this.handleChat.bind(this));this.handlers.set('join_room', this.handleJoinRoom.bind(this));this.handlers.set('heartbeat', this.handleHeartbeat.bind(this));}// 处理收到的消息handleMessage(data) {try {const message = JSON.parse(data);const handler = this.handlers.get(message.type);if (handler) {handler(message.data);} else {console.log('未知消息类型:', message.type);}} catch (error) {console.error('消息解析错误:', error);}}// 处理聊天消息handleChat(data) {console.log(`[聊天] ${data.user}: ${data.text}`);// 广播给其他用户this.broadcast({type: 'chat',data: data});}// 处理加入房间handleJoinRoom(data) {console.log(`${data.user} 加入了房间 ${data.room}`);// 处理加入房间的逻辑}// 处理心跳handleHeartbeat(data) {// 回复心跳this.send({type: 'heartbeat',data: { timestamp: Date.now() }});}// 发送消息send(message) {if (this.ws.readyState === WebSocket.OPEN) {this.ws.send(JSON.stringify(message));}}// 广播消息(需要访问所有客户端)broadcast(message) {// 这里需要服务器支持}
}

第三章:主从架构设计原理

4.1 架构设计思路

为什么需要主从架构?

让我们通过一个故事来理解:

小明开了一家网络游戏公司,最初只有100个玩家,一台服务器轻松应对。随着游戏火爆,玩家增长到10000人,服务器开始卡顿。小明买了更好的服务器,但玩家继续增长到100000人,再好的单台服务器也扛不住了。

这时,小明想到:为什么不用多台服务器分担压力呢?

这就是主从架构的起源!

架构演进过程
  1. 单服务器阶段

    所有客户端 --> 单一服务器优点:简单
    缺点:性能瓶颈、单点故障
    
  2. 简单集群阶段

    客户端 --> 负载均衡器 --> 多个独立服务器优点:负载分散
    缺点:服务器间无法通信
    
  3. 主从架构阶段

    客户端 --> 从服务器 <--> 主服务器 <--> 从服务器 <-- 客户端优点:负载分散、统一管理、服务器间可通信
    缺点:架构复杂度增加
    

4.2 核心组件说明

1. 主服务器(Master Server)

职责

  • 🎯 从服务器的注册和管理
  • ⚖️ 负载均衡(决定客户端连接哪个从服务器)
  • 💓 健康检查(监控从服务器状态)
  • 📢 消息路由(跨服务器消息转发)
  • 📊 全局状态管理

类比:就像交通指挥中心,不直接运送乘客,但协调所有公交车的调度。

2. 从服务器(Slave Server)

职责

  • 👥 处理客户端连接
  • 💼 执行具体业务逻辑
  • 📨 向主服务器报告状态
  • 🔄 与其他从服务器同步数据

类比:就像具体的公交车,实际运送乘客,并向调度中心报告位置。

3. 客户端(Client)

职责

  • 🔗 向主服务器请求分配
  • 🎮 连接到指定的从服务器
  • 📱 执行应用功能

类比:就像乘客,先问调度中心该上哪辆车,然后上车。

4.3 通信协议设计

通信流程详解
1. 从服务器启动并注册Slave --> Master: {type: "register", name: "slave-1", capacity: 100}Master --> Slave: {type: "register_success", slaveId: "s1"}2. 心跳保活Slave --> Master: {type: "heartbeat", slaveId: "s1"}Master --> Slave: {type: "heartbeat_ack"}3. 客户端请求分配Client --> Master: {type: "request_server"}Master --> Client: {type: "server_assigned", host: "192.168.1.2", port: 8081}4. 客户端连接从服务器Client --> Slave: {type: "connect", userId: "u123"}Slave --> Client: {type: "connect_success"}5. 跨服务器消息Slave1 --> Master: {type: "forward", target: "slave2", data: {...}}Master --> Slave2: {type: "forwarded", from: "slave1", data: {...}}
消息协议规范
// 基础消息结构
{id: "唯一消息ID",type: "消息类型",timestamp: "时间戳",data: {// 具体数据}
}// 主从通信消息类型
const MasterSlaveProtocol = {// 从服务器 -> 主服务器SLAVE_REGISTER: 'slave:register',        // 注册SLAVE_HEARTBEAT: 'slave:heartbeat',      // 心跳SLAVE_STATUS: 'slave:status',            // 状态报告SLAVE_SYNC: 'slave:sync',                // 数据同步// 主服务器 -> 从服务器MASTER_REGISTER_ACK: 'master:register_ack',   // 注册确认MASTER_HEARTBEAT_ACK: 'master:heartbeat_ack', // 心跳确认MASTER_COMMAND: 'master:command',             // 命令下发MASTER_BROADCAST: 'master:broadcast',         // 广播消息// 客户端 -> 主服务器CLIENT_REQUEST: 'client:request',        // 请求分配// 主服务器 -> 客户端MASTER_ASSIGN: 'master:assign',          // 分配服务器MASTER_REJECT: 'master:reject'           // 拒绝请求
};

第四章:主服务器详细实现

5.1 主服务器架构

主服务器是整个系统的大脑,让我们详细实现它的每个功能。

核心类设计
// master-server-core.js
class MasterServer {constructor(config) {// 配置this.config = {port: config.port || 8080,heartbeatInterval: config.heartbeatInterval || 10000,heartbeatTimeout: config.heartbeatTimeout || 30000,...config};// 核心数据结构this.slaves = new Map();        // 从服务器集合this.clients = new Map();       // 客户端分配记录this.stats = {                  // 统计信息totalConnections: 0,totalMessages: 0,startTime: Date.now()};// 状态this.slaveIdCounter = 0;        // 从服务器ID计数器this.isRunning = false;         // 运行状态}
}

5.2 从服务器管理

从服务器信息结构
class SlaveInfo {constructor(ws, registerData) {this.id = null;                      // 由主服务器分配this.ws = ws;                        // WebSocket连接this.name = registerData.name;       // 从服务器名称this.host = registerData.host;       // 主机地址this.port = registerData.port;       // 端口号this.capacity = registerData.capacity || 100;  // 容量this.currentLoad = 0;                // 当前负载this.status = 'active';              // 状态this.lastHeartbeat = Date.now();     // 最后心跳时间this.metadata = registerData.metadata || {};   // 额外信息this.performance = {                 // 性能指标cpu: 0,memory: 0,responseTime: 0};}// 获取负载率getLoadRate() {return this.currentLoad / this.capacity;}// 是否可用isAvailable() {return this.status === 'active' && this.getLoadRate() < 0.9;}// 更新心跳updateHeartbeat() {this.lastHeartbeat = Date.now();}// 转换为客户端可见信息toClientInfo() {return {id: this.id,host: this.host,port: this.port,name: this.name};}
}
从服务器注册流程
// 在 MasterServer 类中
registerSlave(ws, data) {// 验证注册信息if (!this.validateSlaveData(data)) {this.sendToWebSocket(ws, {type: 'register_error',error: '注册信息不完整'});return;}// 检查是否重复注册const existingSlave = this.findSlaveByHostPort(data.host, data.port);if (existingSlave) {console.log(`从服务器 ${data.name} 重新连接`);// 更新现有连接existingSlave.ws = ws;existingSlave.status = 'active';existingSlave.updateHeartbeat();ws.slaveId = existingSlave.id;this.sendToWebSocket(ws, {type: 'register_success',slaveId: existingSlave.id,message: '重新连接成功'});return;}// 创建新的从服务器记录const slaveInfo = new SlaveInfo(ws, data);slaveInfo.id = ++this.slaveIdCounter;// 保存到集合this.slaves.set(slaveInfo.id, slaveInfo);ws.slaveId = slaveInfo.id;// 发送注册成功消息this.sendToWebSocket(ws, {type: 'register_success',slaveId: slaveInfo.id,config: {heartbeatInterval: this.config.heartbeatInterval}});console.log(`✅ 从服务器 ${slaveInfo.name} (ID: ${slaveInfo.id}) 注册成功`);// 通知其他从服务器this.broadcastToSlaves({type: 'slave_joined',slave: slaveInfo.toClientInfo()}, slaveInfo.id);// 触发事件this.emit('slave:registered', slaveInfo);
}// 验证从服务器数据
validateSlaveData(data) {return data.name && data.host && data.port;
}// 根据主机和端口查找从服务器
findSlaveByHostPort(host, port) {for (const slave of this.slaves.values()) {if (slave.host === host && slave.port === port) {return slave;}}return null;
}

5.3 负载均衡实现

多种负载均衡策略
// load-balancer.js
class LoadBalancer {constructor() {this.strategies = new Map();this.currentStrategy = 'least_connections';this.roundRobinIndex = 0;// 注册所有策略this.registerStrategies();}registerStrategies() {

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

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

相关文章

Java的extends通配符

在Java泛型中&#xff0c;extends通配符用于限定泛型类型的上界&#xff0c;即指定泛型可以是某个类型或其子类型。它有两种常见用法&#xff1a;类型参数限定和通配符限定&#xff0c;下面详细介绍&#xff1a; 1. 类型参数限定&#xff08;在类/方法定义中&#xff09; 在定义…

vue自定义提示框组件

不想要elementui的消息提示&#xff0c;自定义一个组件系统统一使用 一、写页面 vue &#xff08;我放的目录是src/plugins/message.vue&#xff09;&#xff08;这里面使用elementui 里面icon 需要单独引入&#xff09; <template><Transition name"down"&…

自动驾驶数据集综述:统计特征、标注质量与未来展望

自动驾驶数据集综述&#xff1a;统计特征、标注质量与未来展望 A Survey on Autonomous Driving Datasets: Statistics, Annotation Quality, and a Future Outlook 得益于硬件和深度学习技术的快速进步&#xff0c;自动驾驶近年来迅速发展并展现出良好的性能。高质量的数据集…

redis数据结构和数据类型

1.动态字符串SIMPLE DYNAMIC STRING(SDS)观察上图中的SDS结构&#xff0c;头部包含字符串长度和分配的空间&#xff0c;可以以O&#xff08;1&#xff09;的时间复杂度计算出字符串长度&#xff0c;并且有了字符串长度后可以无视c语言的字符串缺陷&#xff08;\0作为结尾标识&a…

深度学习--神经网络

一、深度学习的简单概念深度学习是一种模仿人类大脑的运行方式&#xff0c;从大量数据中学习特征的学习模式。深度学习是机器学习的子集&#xff0c;它与机器学习的关系如下&#xff1a;二、感知神经网络2.1简单定义神经网络&#xff08;Neural Networks&#xff09;是一种模拟…

.NET 程序的强名称签名与安全防护技术干货

在 .NET 开发领域&#xff0c;保障程序的安全性和完整性至关重要。强名称签名和有效的安全防护措施是实现这一目标的关键手段。下面将详细介绍 .NET 程序的强名称签名以及相关的安全防护方法。一、什么是强名称签名强名称签名是 .NET 框架提供的一种安全机制&#xff0c;其主要…

DNS(Domain Name System,域名系统)

目录 **一、DNS的核心功能****二、DNS的工作原理****1. 解析流程(以车载导航请求为例)****2. 关键机制****三、车载以太网中DNS的特殊性**1. **高可靠性要求**2. **低延迟优化**3. **安全挑战与防护****四、DNS相关协议与技术****五、车载DNS配置示例****六、DNS故障排查工具…

优化 ECharts 多条折线:折线数据不完整导致的X轴日期错乱问题

目录 一、简单介绍 1.1 常见类型 二、时间轴错乱问题 2.1 示例 2.2 示例完整代码 2.3 问题分析 2.4 修复方法 第一步 第二步 2.5 优化后完整代码 一、简单介绍 ECharts 是一款基于 JavaScript 的数据可视化图表库&#xff0c;动态图表是 ECharts 的一个重要应用场景…

网络安全之注入攻击:原理、危害与防御之道

网络安全之注入攻击&#xff1a;原理、危害与防御之道 引言 在OWASP Top 10安全风险榜单中&#xff0c;注入攻击常年占据首位。2023年Verizon数据泄露调查报告显示&#xff0c;67%的Web应用漏洞与注入类攻击直接相关。本文从技术视角系统解析注入攻击的核心原理、典型场景及防御…

Python爬虫动态IP代理报错全解析:从问题定位到实战优化

目录 一、代理IP失效&#xff1a;爬虫的"隐形杀手" 1.1 失效场景复现 1.2 解决方案 二、403封禁&#xff1a;反爬机制的"精准打击" 2.1 封禁原理剖析 2.2 破解方案 三、速度瓶颈&#xff1a;代理性能的"致命短板" 3.1 性能对比测试 3.2…

机器学习基础知识【 激活函数、损失函数、优化器、 正则化、调度器、指标函数】

目录标题机器学习基础知识概览激活函数 (Activation Functions)损失函数 (Loss Functions / Cost Functions)优化器 (Optimizers)正则化 (Regularization)调度器 (Schedulers / Learning Rate Schedulers)指标函数 (Metric Functions)其他重要概念训练流程机器学习基础知识概览…

【达梦数据库|JPA】后端数据库国产化迁移记录

项目背景 经典的springbootjpa&#xff0c;java1.8数据库MySQL需要迁移到国产化数据库达梦上 开发环境安装 最简单的方式&#xff1a; 官方网站下载安装时选择“典型安装”即可 Linux安装 国产化一律上docer不要犹豫 下载三方提供的docker镜像按页面文档启动即可同上下载官…

ubuntu22默认安装firefox使用snap安装还老打不开解决办法

终极解决方案&#xff08;100% 避免 Snap 版 Firefox&#xff09; 步骤 1&#xff1a;彻底移除 Snap 版 Firefox bash sudo snap remove --purge firefox 步骤 2&#xff1a;添加 Mozilla 官方 PPA&#xff08;提供 .deb 版 Firefox&#xff09; bash sudo add-apt-repository …

MyBatis02-mybatis-config.xml配置文件讲解

mybatis-config.xml 是 MyBatis 的核心配置文件&#xff0c;用于配置整个 MyBatis 框架的全局行为&#xff0c;比如环境&#xff08;数据源&#xff09;、事务、类型别名、插件、Mapper 映射等。示例&#xff1a;<?xml version"1.0" encoding"UTF-8" ?…

合上电脑不关机

在Debian 系统上&#xff0c;如何实现合上电脑不关机的效果&#xff1f; 可以修改配置文件&#xff1a; sudo vim /etc/systemd/logind.conf1.找到 HandleLidSwitch &#xff0c;将其值改为 ignore &#xff08;处理盖子开关为忽略&#xff09; 2.将 LidSwitchIgnoreInhibited …

服务器深夜告警?可能是攻击前兆!

凌晨三点&#xff0c;刺耳的告警铃声把你从梦中惊醒&#xff1a;服务器CPU 100%&#xff0c;内存耗尽&#xff01;你手忙脚乱地登录服务器&#xff0c;发现某个进程疯狂占用资源。是程序Bug&#xff1f;还是业务突增&#xff1f;排查半天&#xff0c;最后在角落的日志里发现蛛丝…

重学前端003 --- CSS 颜色

文章目录文档声明head颜色模型div根据在这里 Freecodecamp 实践&#xff0c;记录笔记总结。 文档声明 在文档顶部添加 DOCTYPE html 声明 <!DOCTYPE html>head title 元素为搜索引擎提供了有关页面的额外信息。 它还通过以下两种方式显示 title 元素的内容&#xff1a…

学弟让我帮忙写一个学生管理系统的后端,我直接上科技

&#x1f4dd;个人主页&#xff1a;哈__ 期待您的关注 目录 一、飞算AI简介 二、系统开发 2.1 需求提出 2.2 系统模块的设计 2.3 数据库表格设计 2.4 接口规范设计 2.5 源码生成 三、总结 学弟这两天有一个小组合作的任务&#xff0c;应该是培训吧要写一个学生管理…

《P3038 [USACO11DEC] Grass Planting G》

题目描述 给出一棵有 n 个节点的树&#xff0c;有 m 个如下所示的操作&#xff1a; 将两个节点之间的 路径上的边 的权值均加一。 查询两个节点之间的 那一条边 的权值&#xff0c;保证两个节点直接相连。 初始边权均为 0。 输入格式 第一行两个整数 n,m&#xff0c;含义…

NestJS

文章的地址 NestJShttps://equinox-primrose-ceb.notion.site/NestJS-22d4b8031e0f80b39fc7fe1ff111f802 不产生测试的.spec.ts文件的配置 "generateOptions": {"spec": false }创建模型 nest g m xx 创建服务 nest g s xx 创建处理 nest g c xx CRU…