diff --git "a/\346\235\216\346\231\250\346\273\224/20260316-\345\237\272\346\234\254\345\206\205\347\275\256\346\250\241\345\235\227.md" "b/\346\235\216\346\231\250\346\273\224/20260316-\345\237\272\346\234\254\345\206\205\347\275\256\346\250\241\345\235\227.md" new file mode 100644 index 0000000000000000000000000000000000000000..33b9b0e9776f7bf3a37102fb502d8c97352c571f --- /dev/null +++ "b/\346\235\216\346\231\250\346\273\224/20260316-\345\237\272\346\234\254\345\206\205\347\275\256\346\250\241\345\235\227.md" @@ -0,0 +1,305 @@ +# 笔记 + +## 一、内置模块核心认知 + +### 1. 学习意义 + +Node.js 内置模块是满足日常开发核心需求的基础能力(如彩色日志、命令行参数、文件路径处理等),无需安装即可直接使用,是编写 Node.js 程序的必备基础。 + +### 2. 核心特点 + +- 无需安装:直接通过 `require` 引入,Node.js 自带; +- 高性能:底层由 C/C++ 编写,执行效率高; +- 功能全面:覆盖文件、网络、进程、二进制数据等场景; +- 稳定可靠:经过长期测试,生产环境可用。 + +## 二、四大核心内置模块详解 + +### 1. console 模块:增强版日志输出 + +核心能力是多样化、可视化的信息输出,常用方法: +| 分类 | 关键方法 | 用途 | +|------------|-----------------------------------|--------------------------| +| 基础输出 | log/info/warn/error | 不同级别日志(可区分场景) | +| 格式化输出 | %s(字符串)/%d(数字)/%j(JSON) | 精准控制输出格式 | +| 高级功能 | table、count、time/timeEnd、trace | 表格展示、计数、计时、调用栈追踪 | +| 校验 | assert | 断言判断(条件不满足时输出提示) | + +### 2. process 模块:进程与系统交互 + +核心是获取进程/系统信息、控制进程生命周期、处理命令行参数: + +- **进程信息**:`version`(Node 版本)、`platform`(系统平台)、`cwd()`(当前工作目录)、`env`(环境变量); +- **进程控制**:`exit()`(退出程序)、监听 `exit/SIGINT/uncaughtException` 事件(处理退出、中断、异常); +- **命令行参数**:`process.argv` 获取参数数组(前两项为 Node 路径/脚本路径,`slice(2)` 可提取自定义参数)。 + +### 3. Buffer 模块:二进制数据处理 + +Node.js 特有类型,专门处理文件读写、网络传输等场景的二进制数据: + +- **创建方式**:`Buffer.from()`(字符串/数组)、`Buffer.alloc()`(指定长度,初始化 0)、`Buffer.allocUnsafe()`(指定长度,不初始化,速度更快); +- **核心操作**:`toString()`(转字符串,可指定编码/截取范围)、`write()`(写入数据)、`concat()`(拼接 Buffer)、`length`(获取字节长度,中文占 3 字节)。 + +### 4. path 模块:文件路径处理 + +解决不同系统路径格式差异问题,核心方法: +| 功能 | 关键方法 | 用途 | +|--------------|-----------------------------------|--------------------------| +| 路径拼接/解析 | join()(拼接路径)、resolve()(转绝对路径) | 兼容不同系统路径分隔符 | +| 路径拆解 | basename()(文件名)、extname()(扩展名)、dirname()(目录)、parse()(解析路径对象) | 提取路径各部分信息 | +| 辅助判断 | isAbsolute()(判断绝对路径)、equals()(比较路径) | 路径合法性校验 | +| 常用变量 | **dirname(当前文件目录)、**filename(当前文件完整路径) | 快速获取文件相关路径 | + +## 三、实战应用场景 + +通过 4 个实操案例掌握模块用法: + +1. **信息展示程序**:用 `process` 模块输出 Node 版本、系统平台、工作目录等信息; +2. **彩色日志工具**:结合 `console` 模块实现带颜色、时间戳的分级日志(info/warn/error); +3. **命令行参数处理**:解析 `process.argv` 实现 add/delete/list 等自定义命令; +4. **Buffer 数据处理**:完成字符串与 Buffer 互转、拼接、写入等操作。 + +### 总结 + +1. Node.js 内置模块是基础核心能力,无需安装、性能高,覆盖日志、进程、二进制、路径四大高频场景; +2. console 侧重多样化输出,process 侧重进程/命令行交互,Buffer 处理二进制,path 解决路径兼容; +3. 核心高频用法:console.table/time、process.argv/cwd、Buffer.from/concat、path.join/resolve 是日常开发必用。 + +# 练习 + +选择题 +下面哪个方法可以获取Node.js的版本号?(B) +A. process.platform +B. process.version +C. process.cwd +D. process.argv +Buffer.alloc(10)创建的Buffer,长度是多少字节?(A) + +A. 10 +B. 20 +C. 1 +D. 不确定 +"你好"的Buffer长度是?(C) + +A. 2 +B. 3 +C. 6 +D. 10 +\_\_dirname表示什么?(B) + +A. 当前工作目录 +B. 当前文件所在目录 +C. Node.js安装目录 +D. 用户主目录 +console.time()和console.timeEnd()的作用是?(A) + +A. 计时 +B. 计数 +C. 打印表格 +D. 格式化输出 +6.2 简答题 +请解释console.log和console.error的区别。 +输出目标不同:console.log 输出到标准输出流(stdout),console.error 输出到标准错误流(stderr); +视觉区分:多数终端中,console.error 输出内容默认标红,console.log 为默认颜色; +用途不同:console.log 用于普通日志 / 信息展示,console.error 专门用于错误信息,便于日志分离(如将错误日志单独重定向)。 + +process.argv返回的数组包含哪些内容?请举例说明。 +process.argv 是存储命令行参数的数组,包含以下内容: +第 0 项:Node.js 可执行文件的完整路径; +第 1 项:当前执行的脚本文件的完整路径; +第 2 项及以后:用户传入的自定义命令行参数。 + +Buffer和普通数组有什么区别? +存储类型不同:普通数组可存任意类型(字符串、对象、函数等),Buffer 仅存 0-255 的字节(二进制数据); +内存位置不同:普通数组存在 V8 堆内存,Buffer 存在 Node.js 堆外内存(不受 V8 垃圾回收限制,处理大文件更高效); +编码支持:Buffer 支持 UTF-8、ASCII 等编码转换,普通数组无此能力; +长度固定:Buffer 创建后长度不可变,普通数组可通过 push/pop 动态修改长度。 + +\_\_dirname和process.cwd()有什么区别? +定义不同: +\_\_dirname:当前脚本文件所在的目录路径(固定不变,和执行命令的目录无关); +process.cwd():Node.js 进程的当前工作目录(即终端执行命令时的目录,可通过 cd 改变)。 + +path.join()和path.resolve()有什么区别? +特性 path.join() path.resolve() +核心逻辑 拼接路径片段,自动处理分隔符(/、\) 将路径片段解析为绝对路径(从根目录开始) +绝对路径触发 仅当片段包含根路径(如 /)时 遇到 /、../、./ 会从对应目录解析,无则基于当前工作目录 +示例 path.join('/a', 'b', '..') → /a path.resolve('/a', 'b', '..') → /a;path.resolve('a', 'b') → /Users/xxx/a/b + +6.3 操作题 +信息展示:创建一个程序,输出当前系统的所有环境变量。 +// 遍历process.env对象,输出所有环境变量 +console.log('=== 系统环境变量 ==='); +for (const [key, value] of Object.entries(process.env)) { +console.log(`${key}: ${value}`); +} + +参数解析:创建一个计算器程序,支持: +node calc.js add 5 3 → 输出 8 +node calc.js sub 5 3 → 输出 2 +node calc.js mul 5 3 → 输出 15 +// 获取自定义参数(去除前两项) +const args = process.argv.slice(2); +// 校验参数数量 +if (args.length < 3) { +console.error('用法:node calc.js [add/sub/mul] 数字1 数字2'); +process.exit(1); +} +const [command, num1Str, num2Str] = args; +// 转换为数字并校验 +const num1 = Number(num1Str); +const num2 = Number(num2Str); +if (isNaN(num1) || isNaN(num2)) { +console.error('数字参数必须是有效数字!'); +process.exit(1); +} +// 执行计算 +let result; +switch (command) { +case 'add': +result = num1 + num2; +break; +case 'sub': +result = num1 - num2; +break; +case 'mul': +result = num1 \* num2; +break; +default: +console.error('支持的命令:add(加)、sub(减)、mul(乘)'); +process.exit(1); +} +console.log(`结果:${result}`); +//测试 +node calc.js add 5 3 # 输出 8 +node calc.js sub 5 3 # 输出 2 +node calc.js mul 5 3 # 输出 15 + +Buffer操作:创建一个程序,将中文字符串转为Buffer,再转回字符串,验证是否一致。 +// 定义中文字符串 +const chineseStr = 'Node.js内置模块学习'; +// 转Buffer +const buf = Buffer.from(chineseStr, 'utf8'); +console.log('Buffer数据:', buf); +console.log('Buffer长度(字节):', buf.length); +// Buffer转回字符串 +const str = buf.toString('utf8'); +console.log('转回字符串:', str); +// 验证是否一致 +if (str === chineseStr) { +console.log('✅ 转换一致!'); +} else { +console.log('❌ 转换不一致!'); +} + +路径处理:创建一个程序,接收文件路径,输出文件名、扩展名、所在目录。 +const path = require('path'); +// 获取命令行传入的文件路径 +const filePath = process.argv[2]; +if (!filePath) { +console.error('用法:node path-parse.js <文件路径>'); +console.error('示例:node path-parse.js /Users/xxx/project/src/index.js'); +process.exit(1); +} +// 解析路径信息 +const basename = path.basename(filePath); // 文件名(含扩展名) +const extname = path.extname(filePath); // 扩展名 +const dirname = path.dirname(filePath); // 所在目录 +const filename = path.basename(filePath, extname); // 文件名(不含扩展名) +// 输出结果 +console.log('=== 路径解析结果 ==='); +console.log('完整路径:', filePath); +console.log('文件名(含扩展名):', basename); +console.log('文件名(不含扩展名):', filename); +console.log('扩展名:', extname); +console.log('所在目录:', dirname); +//测试 +node path-parse.js /Users/xxx/project/src/index.js + +综合应用:创建一个CLI工具,接受命令行参数,实现简单的待办列表功能(添加、列出、删除)。 +const fs = require('fs'); +const path = require('path'); +// 待办数据文件路径 +const todoFile = path.join(\_\_dirname, 'todo.json'); +// 初始化待办文件(不存在则创建空数组) +function initTodoFile() { +if (!fs.existsSync(todoFile)) { +fs.writeFileSync(todoFile, JSON.stringify([], null, 2), 'utf8'); +} +} +// 读取待办列表 +function getTodos() { +initTodoFile(); +const data = fs.readFileSync(todoFile, 'utf8'); +return JSON.parse(data); +} +// 保存待办列表 +function saveTodos(todos) { +fs.writeFileSync(todoFile, JSON.stringify(todos, null, 2), 'utf8'); +} +// 处理命令 +const args = process.argv.slice(2); +const command = args[0]; +switch (command) { +// 添加待办 +case 'add': { +const content = args.slice(1).join(' '); +if (!content) { +console.error('用法:node todo.js add <待办内容>'); +process.exit(1); +} +const todos = getTodos(); +todos.push({ id: Date.now(), content, createTime: new Date().toLocaleString() }); +saveTodos(todos); +console.log(`✅ 已添加待办:${content}`); +break; +} +// 列出所有待办 +case 'list': { +const todos = getTodos(); +if (todos.length === 0) { +console.log('📄 暂无待办事项'); +break; +} +console.log('=== 待办列表 ==='); +todos.forEach((todo, index) => { +console.log(`${index + 1}. [${todo.id}] ${todo.content}(创建时间:${todo.createTime})`); +}); +break; +} +// 删除待办(按id) +case 'delete': { +const id = Number(args[1]); +if (isNaN(id)) { +console.error('用法:node todo.js delete <待办ID>'); +process.exit(1); +} +let todos = getTodos(); +const beforeLength = todos.length; +todos = todos.filter(todo => todo.id !== id); +if (todos.length === beforeLength) { +console.error('❌ 未找到该ID的待办'); +process.exit(1); +} +saveTodos(todos); +console.log(`✅ 已删除ID为${id}的待办`); +break; +} +// 帮助信息 +case 'help': +default: { +console.log('待办列表工具用法:'); +console.log(' node todo.js add <内容> - 添加待办'); +console.log(' node todo.js list - 列出所有待办'); +console.log(' node todo.js delete - 删除指定待办'); +console.log(' node todo.js help - 查看帮助'); +break; +} +} +//测试 +添加待办 +node todo.js add 学习Node.js内置模块 +列出待办 +node todo.js list +删除待办(替换ID为实际输出的ID) +node todo.js delete 1710888888888 diff --git "a/\346\235\216\346\231\250\346\273\224/20260318-\346\226\207\344\273\266\347\263\273\347\273\237\346\250\241\345\235\2271.md" "b/\346\235\216\346\231\250\346\273\224/20260318-\346\226\207\344\273\266\347\263\273\347\273\237\346\250\241\345\235\2271.md" new file mode 100644 index 0000000000000000000000000000000000000000..762eb5ee42cce851ea0d29bea25f76c9fabd2d3d --- /dev/null +++ "b/\346\235\216\346\231\250\346\273\224/20260318-\346\226\207\344\273\266\347\263\273\347\273\237\346\250\241\345\235\2271.md" @@ -0,0 +1,97 @@ +# 笔记 +## 一、模块核心认知 +### 1. 学习价值 +Web 应用中文件操作的核心场景: +用户文件上传(头像 / 附件)、配置文件读取 +日志生成、数据备份 +Node.js 通过fs模块提供完整的文件操作能力 +### 2. 核心特点 +功能全覆盖:读写、复制、删除、移动、监听等 +两种操作模式:同步(方法名带Sync)/ 异步(回调 / Promise) +支持大文件:流式处理、权限控制 +## 二、核心 API 分类及用法 +### 1. 文件读写(核心) +操作类型 同步方法 异步回调方法 Promise 方法(推荐) +读取文件 fs.readFileSync(path, 'utf8') fs.readFile(path, 'utf8', (err, data) => {}) fs.promises.readFile(path, 'utf8') +写入文件 fs.writeFileSync(path, data) fs.writeFile(path, data, (err) => {}) fs.promises.writeFile(path, data) +追加写入 fs.appendFileSync(path, data) fs.appendFile(path, data, (err) => {}) fs.promises.appendFile(path, data) +关键说明: +读取二进制文件(图片 / 视频)时,无需指定编码,返回Buffer对象 +Promise 方式需结合async/await使用,错误通过try/catch捕获 +### 2. 文件基础操作 +操作 核心方法 说明 +检查存在性 fs.existsSync(path) 返回布尔值 +获取文件信息 fs.statSync(path) 可判断文件 / 目录、获取大小 / 时间 +删除文件 fs.unlinkSync(path) / fs.promises.unlink(path) 同步 / 异步删除文件 +重命名 / 移动 fs.renameSync(oldPath, newPath) 跨目录重命名即文件移动 +复制文件 fs.copyFileSync(src, dest) Node.js 14 + 内置,也可通过流实现 +### 3. 目录操作 +操作 核心方法 关键参数 / 说明 +创建目录 fs.mkdirSync(path, { recursive: true }) recursive: true支持多级创建 +读取目录 fs.readdirSync(path, { withFileTypes: true }) 可区分文件 / 目录 +删除目录 fs.rmSync(path, { recursive: true, force: true }) Node.js 16+,递归删除非空目录 +### 4. 文件监听 +// 监听文件变化(轻量) +fs.watch('./data.txt', (eventType, filename) => { + console.log(`文件${filename}发生${eventType}事件`); +}); + +// 监听文件详细变化(精度更高) +fs.watchFile('./data.txt', (curr, prev) => { + console.log('修改时间:', curr.mtime); +}); +## 三、实战案例(核心代码) +### 1. 文件复制工具 +const fs = require('fs'); +const path = require('path'); + +function copyFile(src, dest) { + if (!fs.existsSync(src)) return console.error('源文件不存在'); + // 确保目标目录存在 + fs.mkdirSync(path.dirname(dest), { recursive: true }); + fs.copyFileSync(src, dest); +} +### 2. 目录备份工具(带时间戳) +class BackupTool { + constructor(sourceDir, backupDir) { + this.sourceDir = sourceDir; + this.backupDir = backupDir; + } + + backup() { + // 创建带时间戳的备份目录 + const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); + const targetDir = path.join(this.backupDir, `backup_${timestamp}`); + fs.mkdirSync(targetDir, { recursive: true }); + + // 遍历复制文件 + fs.readdirSync(this.sourceDir, { withFileTypes: true }).forEach(item => { + const src = path.join(this.sourceDir, item.name); + if (item.isFile()) fs.copyFileSync(src, path.join(targetDir, item.name)); + }); + } +} +### 3. 递归目录浏览器(带文件大小格式化) +function browseDir(dirPath, indent = 0) { + const items = fs.readdirSync(dirPath, { withFileTypes: true }); + const prefix = ' '.repeat(indent); + + items.forEach(item => { + const fullPath = path.join(dirPath, item.name); + if (item.isDirectory()) { + console.log(`${prefix}📁 ${item.name}/`); + browseDir(fullPath, indent + 1); // 递归子目录 + } else { + const size = fs.statSync(fullPath).size; + console.log(`${prefix}📄 ${item.name} (${formatSize(size)})`); + } + }); +} + +// 格式化文件大小(B/KB/MB/GB) +function formatSize(bytes) { + const k = 1024; + const sizes = ['B', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)) || 0; + return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i]; +} diff --git "a/\346\235\216\346\231\250\346\273\224/20260319-\346\226\207\344\273\266\347\263\273\347\273\237\346\250\241\345\235\2272.md" "b/\346\235\216\346\231\250\346\273\224/20260319-\346\226\207\344\273\266\347\263\273\347\273\237\346\250\241\345\235\2272.md" new file mode 100644 index 0000000000000000000000000000000000000000..8bfc85829ee9da84a39bead5187e040d8992f84e --- /dev/null +++ "b/\346\235\216\346\231\250\346\273\224/20260319-\346\226\207\344\273\266\347\263\273\347\273\237\346\250\241\345\235\2272.md" @@ -0,0 +1,330 @@ +## 四、核心速查 +### 1. 高频同步方法(脚本 / 简单场景) +const fs = require('fs'); +// 读写 +fs.readFileSync(path, 'utf8'); // 读文件 +fs.writeFileSync(path, data); // 写文件 +fs.appendFileSync(path, data); // 追加写 + +// 目录 +fs.mkdirSync(path, { recursive: true }); // 创建多级目录 +fs.readdirSync(path); // 读目录内容 + +// 操作 +fs.existsSync(path); // 检查存在性 +fs.unlinkSync(path); // 删除文件 +fs.renameSync(old, new);// 重命名/移动 +### 2. 高频异步方法(服务 / 高并发场景) +const fs = require('fs').promises; // Promise版 +async function fileOps() { + try { + await fs.readFile(path, 'utf8'); + await fs.writeFile(path, data); + await fs.copyFile(src, dest); + } catch (err) { + console.error('操作失败:', err); + } +} +## 总结 +fs模块核心分同步 / 异步两类方法,异步(Promise+async/await)是 Node.js 推荐写法,避免阻塞事件循环; +文件操作核心场景:读写(基础)、目录管理(创建 / 删除 / 遍历)、文件监听(实时变更); +关键技巧:创建目录时加{ recursive: true }支持多级目录,复制大文件优先用流(createReadStream/createWriteStream)。 +# 练习 +选择题 +下面哪个方法可以检查文件是否存在?(B) +A. fs.checkFileSync() +B. fs.existsSync() +C. fs.statSync() +D. fs.findSync() + +写入文件使用哪个方法?(B) +A. fs.readFileSync() +B. fs.writeFileSync() +C. fs.copyFileSync() +D. fs.mkdirSync() + +appendFileSync和writeFileSync的区别是?(B) +A. 没有区别 +B. append是追加,write是覆盖 +C. append是同步,write是异步 +D. append只能追加字符串 + +如何创建一个多级目录?(B) +A. fs.mkdirSync('./a/b/c') +B. fs.mkdirSync('./a/b/c', { recursive: true }) +C. fs.mkdirSync('./a/b/c', { deep: true }) +D. fs.createDirSync('./a/b/c') + +下面哪个方法可以获取文件的详细信息(大小、时间等)?(C) +A. fs.existsSync() +B. fs.readdirSync() +C. fs.statSync() +D. fs.watch() + +6.2 简答题 +请解释同步方法和异步方法的区别。 +维度 同步方法(带 Sync) 异步方法(无 Sync) +执行方式 阻塞主线程,直到操作完成 非阻塞,操作在后台执行,完成后触发回调 / Promise +错误处理 需用 try/catch 捕获 回调中通过第一个参数 err 接收,Promise 用 catch 捕获 +适用场景 简单脚本、初始化操作(少量文件) 服务端、高并发场景(避免阻塞事件循环) +调用形式 直接返回结果 const res = fs.readFileSync() 回调 /async-await fs.readFile(path, (err, res) => {}) + +fs.readFile和fs.readFileSync有什么区别? +fs.readFileSync:同步读取,调用后立即阻塞线程,直到文件读取完成,直接返回文件内容,错误需用try/catch捕获; +fs.readFile:异步读取,调用后不阻塞线程,文件读取完成后通过回调函数返回内容(第一个参数是错误,第二个是内容),也可通过fs.promises.readFile结合async/await使用; +核心差异:是否阻塞 Node.js 事件循环,同步适合简单场景,异步适合服务端等高性能场景。 + +如何实现文件的追加写入? +const fs = require('fs'); +const fsPromises = require('fs').promises; +// 方式1:同步追加(推荐简单场景) +fs.appendFileSync('./log.txt', '新日志内容\n', 'utf8'); +// 方式2:异步回调 +fs.appendFile('./log.txt', '新日志内容\n', (err) => { + if (err) console.error('追加失败:', err); +}); +// 方式3:Promise+async/await(推荐服务端) +async function appendData() { + await fsPromises.appendFile('./log.txt', '新日志内容\n'); +} +appendData(); + +什么是流式处理?它适合什么场景? +流式处理:将大文件 / 数据拆分成多个小块(chunk),分批次读取 / 写入,而非一次性加载到内存中; +核心优势:降低内存占用,避免大文件操作导致的内存溢出; +适用场景: +处理大文件(如 GB 级日志、视频、压缩包); +网络文件传输(如客户端上传大文件到服务器); +实时数据处理(如逐行读取日志并分析); +const fs = require('fs'); +// 流式读取大文件 +const readStream = fs.createReadStream('./big-file.txt'); +readStream.on('data', (chunk) => { + console.log('读取到小块:', chunk.length, '字节'); +}); +// 流式写入 +const writeStream = fs.createWriteStream('./output.txt'); +readStream.pipe(writeStream); // 管道传输,边读边写 + +如何监听文件的变化? +const fs = require('fs'); +// 方式1:fs.watch(轻量、跨平台,推荐) +const watcher = fs.watch('./config.json', (eventType, filename) => { + console.log(`文件${filename}发生${eventType}事件(change/rename)`); + // 事件类型:change(内容修改)、rename(重命名/删除) +}); +// 方式2:fs.watchFile(更详细,轮询机制,精度高但性能稍差) +fs.watchFile('./config.json', { interval: 1000 }, (curr, prev) => { + if (curr.mtime > prev.mtime) { + console.log('文件内容已修改,新修改时间:', curr.mtime); + } +}); +// 停止监听 +// watcher.close(); // 停止fs.watch +// fs.unwatchFile('./config.json'); // 停止fs.watchFile + +6.3 操作题 +文件读取器:创建一个程序,读取指定文件的内容并输出到控制台。 +// read-file.js +const fs = require('fs'); +const path = require('path'); +// 获取命令行参数中的文件路径(node read-file.js ./test.txt) +const filePath = process.argv[2]; +if (!filePath) { + console.error('请指定要读取的文件路径,用法:node read-file.js <文件路径>'); + process.exit(1); +} +// 处理绝对路径/相对路径 +const fullPath = path.resolve(filePath); +try { + // 读取文件内容 + const content = fs.readFileSync(fullPath, 'utf8'); + console.log('===== 文件内容 ====='); + console.log(content); + console.log('===================='); +} catch (err) { + console.error('读取文件失败:', err.message); +} +//运行 +node read-file.js ./test.txt + +文件写入器:创建一个程序,接收命令行输入的内容,写入到文件中。 +// write-file.js +const fs = require('fs'); +const path = require('path'); +// 获取命令行参数(node write-file.js ./output.txt "要写入的内容") +const [filePath, content] = process.argv.slice(2); +if (!filePath || !content) { + console.error('用法:node write-file.js <文件路径> <要写入的内容>'); + process.exit(1); +} +const fullPath = path.resolve(filePath); +try { + // 确保目标目录存在 + const dir = path.dirname(fullPath); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + // 写入文件 + fs.writeFileSync(fullPath, content, 'utf8'); + console.log(`内容已成功写入文件:${fullPath}`); +} catch (err) { + console.error('写入文件失败:', err.message); +} +//运行 +node write-file.js ./output.txt "Hello Node.js 文件写入器" + +目录复制:实现一个函数,递归复制整个目录。 +// copy-dir.js +const fs = require('fs'); +const path = require('path'); +/** + * 递归复制目录 + * @param {string} srcDir 源目录路径 + * @param {string} destDir 目标目录路径 + */ +function copyDirectory(srcDir, destDir) { + // 检查源目录是否存在 + if (!fs.existsSync(srcDir)) { + console.error(`源目录不存在:${srcDir}`); + return; + } + // 创建目标目录(多级) + if (!fs.existsSync(destDir)) { + fs.mkdirSync(destDir, { recursive: true }); + } + // 读取源目录所有文件/子目录 + const items = fs.readdirSync(srcDir, { withFileTypes: true }); + for (const item of items) { + const srcPath = path.join(srcDir, item.name); + const destPath = path.join(destDir, item.name); + if (item.isDirectory()) { + // 递归复制子目录 + copyDirectory(srcPath, destPath); + } else { + // 复制文件 + fs.copyFileSync(srcPath, destPath); + console.log(`已复制:${srcPath} -> ${destPath}`); + } + } +} +// 命令行参数:node copy-dir.js ./source ./dest +const [srcDir, destDir] = process.argv.slice(2); +if (!srcDir || !destDir) { + console.error('用法:node copy-dir.js <源目录> <目标目录>'); + process.exit(1); +} +copyDirectory(srcDir, destDir); +console.log('目录复制完成!'); +//运行 +node copy-dir.js ./source-dir ./dest-dir + +文件搜索:创建一个文件搜索工具,查找指定目录下所有特定类型的文件(如.js文件)。 +// search-files.js +const fs = require('fs'); +const path = require('path'); +/** + * 递归搜索指定类型的文件 + * @param {string} dir 搜索目录 + * @param {string} ext 文件扩展名(如.js、.txt) + * @returns {string[]} 匹配的文件路径列表 + */ +function searchFiles(dir, ext) { + let result = []; + // 统一扩展名格式(确保以.开头) + const extension = ext.startsWith('.') ? ext : `.${ext}`; + try { + const items = fs.readdirSync(dir, { withFileTypes: true }); + for (const item of items) { + const fullPath = path.join(dir, item.name); + if (item.isDirectory()) { + // 递归搜索子目录 + result = result.concat(searchFiles(fullPath, extension)); + } else if (path.extname(fullPath) === extension) { + // 匹配扩展名,加入结果 + result.push(fullPath); + } + } + } catch (err) { + console.error(`无法访问目录:${dir},错误:${err.message}`); + } + return result; +} +// 命令行参数:node search-files.js ./src js +const [searchDir, ext] = process.argv.slice(2); +if (!searchDir || !ext) { + console.error('用法:node search-files.js <搜索目录> <文件扩展名>'); + console.error('示例:node search-files.js ./src js'); + process.exit(1); +} +const matchedFiles = searchFiles(searchDir, ext); +console.log(`\n找到 ${matchedFiles.length} 个.${ext} 文件:`); +matchedFiles.forEach(file => console.log(`- ${file}`)); +//运行 +node search-files.js ./src js # 搜索src目录下所有.js文件 + +日志系统:创建一个简单的日志系统,支持写入日志到文件,包含时间戳和日志级别。 +// logger.js +const fs = require('fs'); +const path = require('path'); +/** + * 简单日志系统 + * 支持级别:info、warn、error、debug + */ +class Logger { + constructor(logDir = './logs') { + this.logDir = logDir; + // 确保日志目录存在 + if (!fs.existsSync(logDir)) { + fs.mkdirSync(logDir, { recursive: true }); + } + } + /** + * 生成格式化的时间戳 + * @returns {string} 格式:YYYY-MM-DD HH:mm:ss + */ + getTimestamp() { + const now = new Date(); + return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`; + } + /** + * 写入日志 + * @param {string} level 日志级别 + * @param {string} message 日志内容 + */ + log(level, message) { + // 日志级别大写 + const logLevel = level.toUpperCase(); + // 按日期分文件(如:2024-03-20.log) + const date = new Date().toISOString().split('T')[0]; + const logFile = path.join(this.logDir, `${date}.log`); + // 日志格式:[时间戳] [级别] 内容 + const logContent = `[${this.getTimestamp()}] [${logLevel}] ${message}\n`; + // 追加写入日志(异步,避免阻塞) + fs.appendFile(logFile, logContent, 'utf8', (err) => { + if (err) console.error('写入日志失败:', err.message); + }); + // 同时输出到控制台 + console.log(logContent.trim()); + } + // 快捷方法 + info(message) { this.log('info', message); } + warn(message) { this.log('warn', message); } + error(message) { this.log('error', message); } + debug(message) { this.log('debug', message); } +} +// 测试使用 +const logger = new Logger('./my-logs'); +// 示例调用 +logger.info('应用启动成功'); +logger.warn('数据库连接超时,将重试'); +logger.error('用户登录失败:用户名或密码错误'); +logger.debug('调试信息:请求参数 {id: 123}'); +// 也可作为模块导出供其他文件使用 +// module.exports = Logger; +//运行 +node logger.js # 直接运行测试 +#或在其他文件中引入 +// const Logger = require('./logger'); +// const logger = new Logger(); +// logger.info('业务日志内容'); \ No newline at end of file diff --git "a/\346\235\216\346\231\250\346\273\224/20260320-HTTP\346\250\241\345\235\227.md" "b/\346\235\216\346\231\250\346\273\224/20260320-HTTP\346\250\241\345\235\227.md" new file mode 100644 index 0000000000000000000000000000000000000000..35df6ef9c283e26beb4344163bdf8cebbe4ebff1 --- /dev/null +++ "b/\346\235\216\346\231\250\346\273\224/20260320-HTTP\346\250\241\345\235\227.md" @@ -0,0 +1,299 @@ +# 笔记 +## 一、核心价值与作用 +HTTP 模块是 Node.js 原生模块,无需额外安装,核心作用是让 Node.js 具备处理 HTTP 协议的能力: +服务端:创建 Web 服务器、解析客户端请求(Method/URL/ 参数)、返回响应(HTML/JSON 等) +客户端:向其他服务器发送 HTTP 请求(GET/POST 等) +## 二、核心 API 与用法 +1. 服务端核心能力 +功能 关键 API / 操作 +创建服务器 http.createServer((req, res) => {}) + server.listen(端口) +解析请求 req.method(请求方法)、req.url(请求地址)、new URL (req.url) 解析 URL / 查询参数 +构建响应 res.statusCode(状态码)、res.setHeader ()(响应头)、res.end ()(返回内容) +处理不同请求方法 判读 req.method(GET/POST/DELETE),POST 需监听 req 的 data/end 事件获取请求体 +处理路由 解析 URL 的 pathname,根据不同路径返回不同内容 +2. 客户端核心能力 +请求类型 关键实现 +GET 请求 简化版:http.get(url, 回调);完整版:http.request(options, 回调) +POST 请求 配置 options(method:POST),设置 Content-Length,通过 req.write () 发送请求体 +## 三、实战步骤(核心案例) +1. 基础 Web 服务器 +创建多路由服务器,根据不同 URL 返回不同内容,支持 404 处理: +const http = require('http'); +const server = http.createServer((req, res) => { + const urlObj = new URL(req.url, `http://${req.headers.host}`); + res.writeHead(200, { 'Content-Type': 'text/html;charset=utf-8' }); + + switch(urlObj.pathname) { + case '/': res.end('

首页

'); break; + case '/about': res.end('

关于

'); break; + default: res.statusCode=404; res.end('

404页面不存在

'); + } +}); +server.listen(3000); +2. RESTful API 服务器 +实现用户增删查改(CRUD),返回 JSON 格式响应: +const http = require('http'); +const users = [{id:1, name:'张三'}]; // 模拟数据 + +const server = http.createServer((req, res) => { + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Content-Type', 'application/json;charset=utf-8'); + const urlObj = new URL(req.url, `http://${req.headers.host}`); + + // GET /api/users - 获取所有用户 + if (req.method === 'GET' && urlObj.pathname === '/api/users') { + res.end(JSON.stringify(users)); + } + // POST /api/users - 创建用户 + else if (req.method === 'POST' && urlObj.pathname === '/api/users') { + let body = ''; + req.on('data', chunk => body += chunk); + req.on('end', () => { + const newUser = JSON.parse(body); + newUser.id = users.length + 1; + users.push(newUser); + res.end(JSON.stringify({success: true, user: newUser})); + }); + } + // 其他方法/路由省略... +}); +server.listen(3001); +3. HTTP 客户端 +封装 GET/POST 请求方法,通过 Promise 简化异步处理: +const http = require('http'); +// GET请求封装 +function httpGet(url) { + return new Promise((resolve, reject) => { + http.get(url, res => { + let data = ''; + res.on('data', chunk => data += chunk); + res.on('end', () => resolve(data)); + }).on('error', reject); + }); +} +// POST请求封装(略) +## 四、核心注意事项 +处理 POST 请求时,需通过req.on('data')和req.on('end')拼接请求体 +解析 URL 推荐使用new URL()(Node.js 10 + 支持),比 url 模块更易用 +返回中文需设置响应头Content-Type: text/html;charset=utf-8 +跨域需设置Access-Control-Allow-Origin: * +## 总结 +HTTP 模块是 Node.js 实现 Web 服务的基础,核心是createServer创建服务、request/get发送请求; +服务端核心是解析req(请求)、构建res(响应),客户端核心是封装请求方法处理异步响应; +实战中可基于该模块实现基础路由、RESTful API,是学习 Express/Koa 等框架的前置基础。 +# 练习 +选择题 +创建一个HTTP服务器需要使用哪个模块?(B) +A. url +B. http +C. fs +D. path + +获取请求的方法(GET、POST)应该访问哪个属性?(B) +A. req.url +B. req.method +C. req.headers +D. req.body + +设置响应状态码使用哪个属性?(B) +A. res.status +B. res.statusCode +C. res.code +D. res.statusNumber + +返回JSON数据需要设置什么响应头?(B) +A. 'Content-Type': 'text/html' +B. 'Content-Type': 'application/json' +C. 'Content-Type': 'text/plain' +D. 'Content-Type': 'application/xml' + +使用哪个方法发送响应?(C) +A. res.send() +B. res.write() +C. res.end() +D. res.close() + +6.2 简答题 +请解释req和res分别代表什么? +req(request):请求对象,包含客户端发送给服务器的所有请求信息,比如请求方法、URL、请求头、请求参数、请求体等。 +res(response):响应对象,用于服务器向客户端返回数据,包括设置状态码、响应头、发送响应体(HTML/JSON/ 文件等)。 + +http.createServer的回调函数接收哪些参数? +回调函数接收两个核心参数: +第一个参数:req(请求对象),存储客户端请求的所有信息; +第二个参数:res(响应对象),用于构建并返回给客户端的响应。 +示例: +http.createServer((req, res) => { + // req: 请求对象,res: 响应对象 + res.end('Hello World'); +}); + +如何获取URL中的查询参数? +Node.js 推荐使用URL类解析,步骤如下: +通过new URL(req.url, 完整域名)解析请求 URL; +通过searchParams.get(参数名)获取单个参数,或searchParams.entries()获取所有参数。 +示例: +const http = require('http'); +const server = http.createServer((req, res) => { + // 解析URL(需补全域名,避免相对路径问题) + const urlObj = new URL(req.url, `http://${req.headers.host}`); + // 获取单个参数 + const name = urlObj.searchParams.get('name'); + // 获取所有参数 + const allParams = Object.fromEntries(urlObj.searchParams.entries()); + res.end(`name: ${name}, 所有参数: ${JSON.stringify(allParams)}`); +}); +server.listen(3000); + +res.writeHead和res.setHeader有什么区别? +特性 res.setHeader res.writeHead +核心作用 单个设置响应头(可多次调用) 一次性设置状态码 + 响应头 +调用时机 可在 res.write ()/res.end () 前 必须在 res.write ()/res.end () 前 +覆盖规则 重复设置会覆盖之前的值 一旦调用,后续 setHeader 无效 +返回值 无 返回 res 对象,支持链式调用 +示例: +// 方式1:setHeader(分步设置) +res.setHeader('Content-Type', 'application/json'); +res.statusCode = 200; // 单独设置状态码 +// 方式2:writeHead(一次性设置) +res.writeHead(200, { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*' +}); + +http.get和http.request有什么区别? +特性 http.get http.request +请求方法 仅支持 GET 请求 支持所有 HTTP 方法(GET/POST/DELETE 等) +调用复杂度 简化版,无需手动调用 req.end () 完整版,需手动调用 req.end () 结束请求 +错误处理 需单独监听 error 事件 需监听 error 事件 +适用场景 简单 GET 请求 复杂请求(POST/PUT、自定义头) +示例: +// http.get(简化GET) +http.get('http://localhost:3000', (res) => { /* 处理响应 */ }); +// http.request(通用) +const req = http.request({ + method: 'POST', + hostname: 'localhost', + port: 3000 +}, (res) => { /* 处理响应 */ }); +req.write(JSON.stringify({ name: '张三' })); // 发送请求体 +req.end(); // 必须调用end() + +6.3 操作题 +静态服务器:创建一个Web服务器,返回HTML页面,包含CSS样式。 +功能:访问服务器返回包含 CSS 样式的 HTML 页面,核心是读取本地 HTML/CSS 文件并返回。 +const http = require('http'); +const fs = require('fs'); +const path = require('path'); +const server = http.createServer((req, res) => { + // 解析请求路径 + const urlObj = new URL(req.url, `http://${req.headers.host}`); + let filePath = urlObj.pathname === '/' ? '/index.html' : urlObj.pathname; + // 拼接本地文件路径 + const fullPath = path.join(__dirname, 'static', filePath); + // 根据文件后缀设置Content-Type + const ext = path.extname(fullPath); + const contentTypeMap = { + '.html': 'text/html; charset=utf-8', + '.css': 'text/css; charset=utf-8', + '.js': 'application/javascript' + }; + const contentType = contentTypeMap[ext] || 'text/plain'; + // 读取文件并返回 + fs.readFile(fullPath, (err, data) => { + if (err) { + res.writeHead(404, { 'Content-Type': 'text/html' }); + res.end('

404 文件不存在

'); + return; + } + res.writeHead(200, { 'Content-Type': contentType }); + res.end(data); + }); +}); +// 目录结构: +// ├── static +// │ ├── index.html +// │ └── style.css +// └── server.js +server.listen(3000, () => { + console.log('静态服务器运行在 http://localhost:3000'); +}); +配套文件(static/index.html): + + + + + 静态服务器 + + + +

Hello 静态服务器

+

这是带CSS样式的页面

+ + +static/style.css: +body { background: #f5f5f5; } +h1 { color: #2c3e50; } +p { color: #666; } + +计算器API:创建RESTful API,支持加、减、乘、除运算。 +功能:通过 GET 请求传递参数(num1、num2、op),返回运算结果。 +const http = require('http'); +const server = http.createServer((req, res) => { + // 设置跨域和响应类型 + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Content-Type', 'application/json; charset=utf-8'); + const urlObj = new URL(req.url, `http://${req.headers.host}`); + // 仅处理/calc路径 + if (urlObj.pathname !== '/calc') { + res.statusCode = 404; + res.end(JSON.stringify({ error: '仅支持 /calc 路径' })); + return; + } + // 获取参数 + const num1 = parseFloat(urlObj.searchParams.get('num1')); + const num2 = parseFloat(urlObj.searchParams.get('num2')); + const op = urlObj.searchParams.get('op'); + // 参数校验 + if (isNaN(num1) || isNaN(num2)) { + res.statusCode = 400; + res.end(JSON.stringify({ error: 'num1/num2 必须是数字' })); + return; + } + // 运算逻辑 + let result; + switch (op) { + case '+': result = num1 + num2; break; + case '-': result = num1 - num2; break; + case '*': result = num1 * num2; break; + case '/': + if (num2 === 0) { + res.statusCode = 400; + res.end(JSON.stringify({ error: '除数不能为0' })); + return; + } + result = num1 / num2; + break; + default: + res.statusCode = 400; + res.end(JSON.stringify({ error: 'op仅支持 + - * /' })); + return; + } + // 返回结果 + res.end(JSON.stringify({ + num1, num2, op, result + })); +}); +server.listen(3001, () => { + console.log('计算器API运行在 http://localhost:3001'); + console.log('测试示例:http://localhost:3001/calc?num1=10&num2=5&op=+'); +}); +//测试 +加法:http://localhost:3001/calc?num1=10&num2=5&op=+ → 返回 {"num1":10,"num2":5,"op":"+","result":15} +除法:http://localhost:3001/calc?num1=10&num2=0&op=/ → 返回错误提示 + +图片服务器:创建一个简单的图片服务器,根据URL返回指定目录下的图片。 + +天气查询:创建一个天气查询工具,调用第三方API获取天气信息。 + +代理服务器:创建一个简单的代理服务器,将请求转发到另一个服务器。 \ No newline at end of file