From b12a1de130bdcc56581634c48d2913cd0157c12b Mon Sep 17 00:00:00 2001 From: GXDo <211484214@qq.com> Date: Sun, 22 Mar 2026 19:27:36 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat:=E7=AC=94=E8=AE=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...260316-Node.js\345\244\215\344\271\240.md" | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 "\351\203\255\345\260\217\344\270\234/20260316-Node.js\345\244\215\344\271\240.md" diff --git "a/\351\203\255\345\260\217\344\270\234/20260316-Node.js\345\244\215\344\271\240.md" "b/\351\203\255\345\260\217\344\270\234/20260316-Node.js\345\244\215\344\271\240.md" new file mode 100644 index 0000000..8b6b100 --- /dev/null +++ "b/\351\203\255\345\260\217\344\270\234/20260316-Node.js\345\244\215\344\271\240.md" @@ -0,0 +1,56 @@ +# 笔记 + +```javascript + +import fs from 'fs'; + +let FILE_PATH = './peoplefile.json' + +let commander = process.argv[2]; +let name = process.argv[3]; +let judgment = process.argv[4]; +if(commander == 'add'){ + let arr = readFromJson(); + arr.push({ + name: name, + judgment: judgment, + }) + writeToJson(arr); + // printAmount(); + +}else if(commander == 'del'){ + +}else if(commander == 'amount'){ + +}else if(commander == 'clear'){ + writeToJson([]); +}else if(commander == 'list'){ + let arr = readFromJson(); + console.log(arr); + +}else{ + console.log('欢迎使用鉴定gay佬系统'); + +} + +function readFromJson(){ + if(fs.existsSync(FILE_PATH)){ + let jsonString = fs.readFileSync(FILE_PATH,'utf-8'); + if(jsonString.length > 0){ + return JSON.parse(jsonString); + } + return []; + } + return []; +} +function writeToJson(arr){ + let jsonString = JSON.stringify(arr); + fs.writeFileSync(FILE_PATH,jsonString); +} + +function printFile(){ + let arr = readFromJson(); + console.log(arr); + +} +``` \ No newline at end of file -- Gitee From c263ab8aa937e431cb2ba3ae36fcd666f9f73c80 Mon Sep 17 00:00:00 2001 From: GXDo <211484214@qq.com> Date: Sun, 22 Mar 2026 19:27:56 +0800 Subject: [PATCH 2/4] =?UTF-8?q?feat:=E7=AC=94=E8=AE=B0+=E7=BB=83=E4=B9=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...73\347\273\237\346\250\241\345\235\227.md" | 353 ++++++++++++++++++ 1 file changed, 353 insertions(+) create mode 100644 "\351\203\255\345\260\217\344\270\234/20260318-Node.js\345\237\272\346\234\254\346\250\241\345\235\227\344\270\216\347\263\273\347\273\237\346\250\241\345\235\227.md" diff --git "a/\351\203\255\345\260\217\344\270\234/20260318-Node.js\345\237\272\346\234\254\346\250\241\345\235\227\344\270\216\347\263\273\347\273\237\346\250\241\345\235\227.md" "b/\351\203\255\345\260\217\344\270\234/20260318-Node.js\345\237\272\346\234\254\346\250\241\345\235\227\344\270\216\347\263\273\347\273\237\346\250\241\345\235\227.md" new file mode 100644 index 0000000..6916826 --- /dev/null +++ "b/\351\203\255\345\260\217\344\270\234/20260318-Node.js\345\237\272\346\234\254\346\250\241\345\235\227\344\270\216\347\263\273\347\273\237\346\250\241\345\235\227.md" @@ -0,0 +1,353 @@ +# 笔记 + +## 常用的代码 +```javascript +// 1. console模块 - 多种输出方式 +console.log('普通信息'); + + +// 2. process模块 - 获取进程信息 + +// 3. Buffer模块 - 处理二进制 + +// 4. path模块 - 路径处理 + +``` + +## 一、内置模块概述 + +1. **定义**:Node.js 自带,无需安装,直接 `require` 使用的模块 +2. **特点**:C/C++ 编写、性能高、功能全、稳定可靠 +3. **核心模块**:console(控制台)、process(进程)、Buffer(二进制)、path(路径) + +## 二、核心模块详解 + +### 1. console 模块(控制台输出) + + +| 方法 | 作用 | +| :-----------------: | :-----------------------------------: | +| log/info/warn/error | 不同级别输出(error 会定位到 stderr) | +| table | 表格化输出数组 / 对象 | +| count/countReset | 计数 / 重置计数 | +| time/timeEnd | 代码执行计时 | +| assert | 断言(条件不满足时输出错误) | +| trace | 打印调用栈 | +| 格式化 | % s (字符串)、% d (数字)、% j (JSON) | + +### 2. process 模块(进程管理) + +#### 核心属性 / 方法 + +- **进程信息**:`version`(Node 版本)、`platform`(系统平台)、`arch`(架构)、`cwd()`(当前工作目录) +- **命令行参数**:`argv`(数组:[node 路径,脚本路径,用户参数...]) +- **进程控制**:`exit(code)`(退出程序)、`on('exit/SIGINT')`(监听信号) +- **环境变量**:`process.env`(获取系统环境变量) + +### 3. Buffer 模块(二进制处理) + +#### 核心操作 + +- **创建**:`Buffer.from('字符串')`、`Buffer.alloc(长度)`(初始化)、`Buffer.allocUnsafe(长度)`(不初始化) +- **转换**:`buf.toString(编码)`(默认 utf8) +- **操作**:`buf.write(内容)`、`Buffer.concat([buf1, buf2])` +- **长度**:中文字符占 3 字节(utf8) + +### 4. path 模块(路径处理) + +#### 核心方法 + +| 方法 | 作用 | +| :--------: | :------------------------: | +| join | 拼接路径(自动处理分隔符) | +| resolve | 解析为绝对路径 | +| basename | 获取文件名(可去掉扩展名) | +| extname | 获取文件扩展名 | +| dirname | 获取文件所在目录 | +| isAbsolute | 判断是否为绝对路径 | + +#### 全局路径变量 + +- `__dirname`:当前文件所在目录(绝对路径) +- `__filename`:当前文件完整路径 + +------ + +# 练习解答 + + +## 二、简答题答案 + +1. 请解释console.log和console.error的区别 + + + + - log 输出到标准输出(stdout),error 输出到标准错误(stderr) + - error 输出内容通常显示为红色,便于区分错误信息 + - 重定向时可分开处理(如日志文件和错误文件) + + + +2. process.argv返回的数组包含哪些内容?请举例说明。 + - 数组形式,包含: + - 索引 0:Node.js 可执行文件路径 + - 索引 1:当前执行的脚本文件路径 + - 索引 2+:用户传入的命令行参数 + - 示例:运行 `node app.js name age`,argv = [node 路径,app.js 路径,'name', 'age'] + + + +3. Buffer和普通数组有什么区别? + - Buffer 存储二进制数据(0-255 字节),数组存储任意类型数据 + - Buffer 长度固定,数组长度可动态变化 + - Buffer 由 C++ 实现,处理二进制性能更高 + - Buffer 专为 I/O 操作设计(文件、网络传输) + + + +4. __dirname和process.cwd()有什么区别? + - __dirname:当前文件所在的目录(固定不变) + - process.cwd ():当前 Node 进程的工作目录(可通过 cd 命令改变) + - 示例:在 /home 目录运行 `node /project/app.js`,__dirname=/project,cwd()=/home + + + +5. path.join()和path.resolve()有什么区别? + - join:拼接路径片段,仅处理分隔符,不解析绝对路径 + - resolve:从右到左解析,遇到绝对路径停止,最终返回绝对路径 + - 示例:`join('/a', 'b')` = `/a/b`;`resolve('/a', '/b')` = `/b` + + + +## 三、操作题解答 + +### 1. 输出所有环境变量 + +```javascript +// env.js +console.log('=== 系统环境变量 ==='); +// 遍历所有环境变量 +Object.entries(process.env).forEach(([key, value]) => { + console.log(`${key}: ${value}`); +}); +``` +### 2. 计算器程序 + +```javascript +// calc.js +const args = process.argv.slice(2); +const [command, num1, num2] = args; + +// 类型转换 +const a = Number(num1); +const b = Number(num2); + +// 参数校验 +if (isNaN(a) || isNaN(b)) { + console.error('请输入有效的数字'); + process.exit(1); +} + +// 计算逻辑 +switch (command) { + case 'add': + console.log(a + b); + break; + case 'sub': + console.log(a - b); + break; + case 'mul': + console.log(a * b); + break; + case 'div': + if (b === 0) { + console.error('除数不能为0'); + process.exit(1); + } + console.log(a / b); + break; + default: + console.log('支持的命令:add, sub, mul, div'); +} +``` + +测试: + + +```bash +node calc.js add 5 3 # 输出8 +node calc.js sub 5 3 # 输出2 +node calc.js mul 5 3 # 输出15 +``` + +### 3. Buffer 字符串转换验证 + +```javascript +// buffer-test.js +const str = 'Node.js 内置模块 你好'; + +// 转Buffer +const buf = Buffer.from(str, 'utf8'); +console.log('Buffer:', buf); + +// 转回字符串 +const newStr = buf.toString('utf8'); +console.log('转回字符串:', newStr); + +// 验证一致性 +console.log('是否一致:', str === newStr ? '是' : '否'); +``` + +运行:`node buffer-test.js` + +### 4. 路径解析程序 + +```javascript +// path-parser.js +const path = require('path'); +const args = process.argv.slice(2); + +if (!args[0]) { + console.error('请传入文件路径'); + process.exit(1); +} + +const filePath = args[0]; +const parsed = path.parse(filePath); + +console.log('=== 路径解析结果 ==='); +console.log('完整路径:', filePath); +console.log('文件名:', parsed.base); +console.log('文件名称(无扩展名):', parsed.name); +console.log('扩展名:', parsed.ext); +console.log('所在目录:', parsed.dir); +``` + +测试: + + +```bash +node path-parser.js /Users/test/project/index.js +``` + +### 5. 待办列表 CLI 工具 + + + +```javascript +// todo.js +const fs = require('fs'); +const path = require('path'); + +// 数据文件路径 +const todoFile = path.join(__dirname, 'todo.json'); + +// 初始化数据文件 +function initFile() { + if (!fs.existsSync(todoFile)) { + fs.writeFileSync(todoFile, JSON.stringify([], null, 2), 'utf8'); + } +} + +// 读取待办列表 +function getTodos() { + initFile(); + 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, ...params] = args; + +switch (command) { + // 添加待办 + case 'add': + const todos = getTodos(); + todos.push({ + id: Date.now(), + text: params.join(' '), + completed: false, + createTime: new Date().toLocaleString() + }); + saveTodos(todos); + console.log(`✅ 已添加待办: ${params.join(' ')}`); + break; + + // 列出所有待办 + case 'list': + const allTodos = getTodos(); + if (allTodos.length === 0) { + console.log('📋 暂无待办事项'); + break; + } + console.log('📋 待办列表:'); + allTodos.forEach((todo, index) => { + const status = todo.completed ? '✅' : '🔲'; + console.log(`${index + 1}. ${status} ${todo.text}`); + }); + break; + + // 删除待办 + case 'delete': + const id = parseInt(params[0]); + let currentTodos = getTodos(); + const beforeLength = currentTodos.length; + currentTodos = currentTodos.filter(todo => todo.id !== id && todo.id !== beforeLength); + // 兼容按索引删除 + if (beforeLength === currentTodos.length) { + const index = parseInt(params[0]) - 1; + if (index >= 0 && index < currentTodos.length) { + currentTodos.splice(index, 1); + } + } + saveTodos(currentTodos); + console.log('🗑️ 已删除待办'); + break; + + // 标记完成 + case 'done': + const doneId = parseInt(params[0]); + let todosDone = getTodos(); + todosDone = todosDone.map(todo => { + if (todo.id === doneId || todosDone.indexOf(todo) === doneId - 1) { + todo.completed = true; + } + return todo; + }); + saveTodos(todosDone); + console.log('✅ 已标记完成'); + break; + + default: + console.log(` +📖 使用帮助: + node todo.js add <待办内容> - 添加待办事项 + node todo.js list - 列出所有待办 + node todo.js delete - 删除待办事项 + node todo.js done - 标记待办完成 + `); +} +``` + +测试: + +```bash +# 添加待办 +node todo.js add 学习Node.js内置模块 +node todo.js add 完成练习作业 + +# 列出待办 +node todo.js list + +# 标记完成 +node todo.js done 1 + +# 删除待办 +node todo.js delete 2 +``` \ No newline at end of file -- Gitee From 288bd167668b8dff0f2e9c07c5e75817a83c5c5f Mon Sep 17 00:00:00 2001 From: GXDo <211484214@qq.com> Date: Sun, 22 Mar 2026 19:28:06 +0800 Subject: [PATCH 3/4] =?UTF-8?q?feat:=E7=AC=94=E8=AE=B0+=E7=BB=83=E4=B9=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...73\347\273\237\346\250\241\345\235\227.md" | 356 ++++++++++++++++++ 1 file changed, 356 insertions(+) create mode 100644 "\351\203\255\345\260\217\344\270\234/20260319-Node.js\347\263\273\347\273\237\346\250\241\345\235\227.md" diff --git "a/\351\203\255\345\260\217\344\270\234/20260319-Node.js\347\263\273\347\273\237\346\250\241\345\235\227.md" "b/\351\203\255\345\260\217\344\270\234/20260319-Node.js\347\263\273\347\273\237\346\250\241\345\235\227.md" new file mode 100644 index 0000000..5bff73d --- /dev/null +++ "b/\351\203\255\345\260\217\344\270\234/20260319-Node.js\347\263\273\347\273\237\346\250\241\345\235\227.md" @@ -0,0 +1,356 @@ +# 笔记 + +## 实现简单的文件游览器 + +### 核心代码 +```javascript +fs.existsSync() // 判断目录是否存在 + +fs.readdirSync() // 读取目录里所有内容 + +path.join() // 拼接完整路径,防止路径错误 + +forEach(){} // 遍历 + +let prefix = ' '.repeat(indent) // 生成层级缩进前缀(控制显示格式) + +isDirectory() // 判断是不是文件夹 +``` + +### 分析各个代码的定义以及作用 + +#### fs.existsSync() + 1. 定义 + - fs: Node.js的文件系统模块 + - exists: 英文意思 = 存在 + - Sync: 英文的意思 = 同步 + - 传入的路径是否真的存在于电脑中,返回true或者false + - 存在 → 返回true + - 不存在 → 返回false + 2. 作用 + - 提前做安全检查,防止程序崩溃 + +#### fs.isreaddirSync() + 1. 定义 + - Node.js 文件系统(fs)模块提供的同步方法,用于读取指定目录下所有的 文件 和 子文件夹名称,并返回一个数组。 + 2. 作用 + - 读取一个目录(文件夹)里面所有内容的名称列表 + - 输入:一个文件夹路径 + - 输出:数组,里面是该文件夹下所有文件/文件夹的名字 + - 同步:必须等读取完成,才会继续执行后面代码 + +#### path.join() +```javascript +1. 定义: +path.join('路径1','路径2','路径3'); +//使用前必须先导入: +import path from 'path'; + +``` + 2. 作用:把多个路径片段,智能拼接成一个完整、正确、跨平台的路径 + + +#### forEach() +```javascript +1.定义: +数组.forEach(function(当前元素,索引,原数组){ + //对当前元素的操作逻辑 +}); +``` + +#### let prefix = ' '.repeat(indent) + 1.定义: + - ':是一个包含4 个空格的字符串(你可以理解为一个 “缩进单位”) + - .repeat(indent):repeat() 是 JavaScript 字符串的内置方法,作用是将原字符串重复指定的次数,参数 indent 是重复次数 + 2.作用——实现目录结构层级缩进 + + + +#### isDirectory(dir) + 1. 定义: + - 函数名:isDirectory 是不是目录/文件夹 + - 参数dir: 接收的一个路径 + 2. 作用——判断一个路径是不是文件夹(目录) + - 如果是文件夹 → 返回true + - 如果不是文件夹 → 返回false + - 路径不存在 → 直接报错 + +# 练习 + +## 简答题 + +1. 请解释同步方法和异步方法的区别。 + 同步(Sync后缀): + - 执行时阻塞主线程,要完成操作才会执行后续代码; + + 异步(无Sync后缀): + - 执行时不阻塞主线程,操作在后台执行,完成后通过返回函数/Promise返回结果; + +2. fs.readFile和fs.readFileSync有什么区别? + - fs.readFile:不阻塞主线程 + - fs.readFileSync:阻塞主线程 + +3. 如何实现文件的追加写入? + + 方式 1(异步): + + - 使用 fs.appendFile (path, content, callback),自动以追加模式写入; + - 或 fs.writeFile (path, content, { flag: 'a'}, callback),flag: 'a' 表示追加; + + 方式 2(同步): + + - 使用 fs.appendFileSync (path, content); + - 或 fs.writeFileSync (path, content, { flag: 'a'}); + + 核心:打开文件时指定 **追加模式(flag: 'a')**,内容会写入文件末尾而非覆盖。 + +4. 什么是流式处理?它适合什么场景? + + 流式处理(Stream): + + - 不是一次性读取 / 写入整个文件,而是将数据拆分为小块(Buffer),逐块处理; + - 占用内存少,支持大文件操作,可边读边写(如管道流 pipe); + + 适用场景: + + - 处理大文件(如 GB 级日志、视频),避免内存溢出; + - 实时数据传输(如文件上传 / 下载、网络请求响应); + - 数据转换(如读取压缩文件并解压、格式转换); + +5. 如何监听文件的变化? + + Node.js 中核心方案: + + 1. 使用 fs.watch (path, [options], callback): + + - 监听文件 / 目录的变化(创建、修改、删除); + - 回调参数:eventType(变化类型,如 'change'/'rename')、filename(变化的文件名); + + + + 2. 使用 fs.watchFile (path, [options], callback): + + - 轮询方式监听文件变化(兼容性更好,CPU 占用略高); + - 回调参数:curr(当前文件状态)、prev(修改前文件状态); + + + + 第三方库(更易用): + + - chokidar:封装 fs.watch,解决跨平台兼容性问题,支持递归监听目录; + +## 操作题 + +1. **文件读取器**:创建一个程序,读取指定文件的内容并输出到控制台。 +```javascript +def read_file(file_path): + """ + 读取指定文件的内容并输出到控制台 + :param file_path: 目标文件的路径(相对/绝对) + """ + try: + # 使用 with 语句自动管理文件句柄(推荐做法,无需手动关闭) + with open(file_path, 'r', encoding='utf-8') as f: + content = f.read() # 读取全部内容 + print(f"=== 文件 {file_path} 的内容 ===") + print(content) + except FileNotFoundError: + print(f"错误:文件 {file_path} 不存在!") + except PermissionError: + print(f"错误:没有权限读取文件 {file_path}!") + except UnicodeDecodeError: + print(f"错误:文件 {file_path} 编码不是UTF-8,尝试使用GBK编码读取...") + with open(file_path, 'r', encoding='gbk') as f: + content = f.read() + print(f"=== 文件 {file_path} 的内容(GBK编码)===") + print(content) + except Exception as e: + print(f"读取文件失败:{str(e)}") + +# 测试调用(替换为你的文件路径) +if __name__ == "__main__": + read_file("test.txt") +``` + + +2. **文件写入器**:创建一个程序,接收命令行输入的内容,写入到文件中。 +```javascript +import sys + +def write_to_file(file_path, content, mode='w'): + """ + 将内容写入指定文件 + :param file_path: 目标文件路径 + :param content: 要写入的内容 + :param mode: 写入模式('w'覆盖,'a'追加) + """ + try: + with open(file_path, mode, encoding='utf-8') as f: + f.write(content) + print(f"内容已成功{'覆盖' if mode == 'w' else '追加'}到文件 {file_path}!") + except PermissionError: + print(f"错误:没有权限写入文件 {file_path}!") + except Exception as e: + print(f"写入文件失败:{str(e)}") + +# 从命令行接收输入 +if __name__ == "__main__": + # 示例1:直接运行脚本后手动输入内容 + # file_path = input("请输入要写入的文件路径:") + # content = input("请输入要写入的内容:") + + # 示例2:通过命令行参数传入(更符合"命令行输入"需求) + if len(sys.argv) < 3: + print("用法:python write_file.py <文件路径> <要写入的内容>") + print("示例:python write_file.py output.txt 'Hello World!'") + sys.exit(1) + + file_path = sys.argv[1] + content = sys.argv[2] + write_to_file(file_path, content) # 默认覆盖模式,如需追加改为 write_to_file(file_path, content, 'a') +``` +3. **目录复制**:实现一个函数,递归复制整个目录。 +```javascript +import os +import shutil + +def copy_directory(src_dir, dest_dir): + """ + 递归复制整个目录(包括子目录和文件) + :param src_dir: 源目录路径 + :param dest_dir: 目标目录路径 + """ + # 检查源目录是否存在 + if not os.path.isdir(src_dir): + print(f"错误:源目录 {src_dir} 不存在或不是目录!") + return + + try: + # 遍历源目录的所有文件/子目录 + for root, dirs, files in os.walk(src_dir): + # 构建目标目录的对应路径 + relative_path = os.path.relpath(root, src_dir) + target_root = os.path.join(dest_dir, relative_path) + + # 创建目标子目录(如果不存在) + if not os.path.exists(target_root): + os.makedirs(target_root) + print(f"创建目录:{target_root}") + + # 复制文件 + for file in files: + src_file = os.path.join(root, file) + dest_file = os.path.join(target_root, file) + # copy2 保留文件元数据(创建时间、修改时间等),比 copy 更友好 + shutil.copy2(src_file, dest_file) + print(f"复制文件:{src_file} -> {dest_file}") + + print(f"\n目录复制完成!源:{src_dir} -> 目标:{dest_dir}") + except PermissionError: + print(f"错误:没有权限访问 {src_dir} 或 {dest_dir}!") + except Exception as e: + print(f"目录复制失败:{str(e)}") + +# 测试调用 +if __name__ == "__main__": + copy_directory("source_dir", "target_dir") # 替换为实际的源/目标目录 + +``` +4. **文件搜索**:创建一个文件搜索工具,查找指定目录下所有特定类型的文件(如.js文件)。 +```javascript +import os + +def search_files(dir_path, file_ext): + """ + 查找指定目录下所有特定后缀的文件 + :param dir_path: 要搜索的目录路径 + :param file_ext: 文件后缀(如 ".js"、".txt",必须带点) + :return: 匹配的文件路径列表 + """ + matched_files = [] + + # 检查目录是否存在 + if not os.path.isdir(dir_path): + print(f"错误:目录 {dir_path} 不存在或不是目录!") + return matched_files + + try: + # 递归遍历目录 + for root, _, files in os.walk(dir_path): + for file in files: + # 检查文件后缀是否匹配(忽略大小写) + if file.lower().endswith(file_ext.lower()): + file_path = os.path.abspath(os.path.join(root, file)) + matched_files.append(file_path) + + # 输出结果 + print(f"\n在 {dir_path} 下找到 {len(matched_files)} 个 {file_ext} 文件:") + for idx, path in enumerate(matched_files, 1): + print(f"{idx}. {path}") + + return matched_files + except PermissionError: + print(f"错误:没有权限访问目录 {dir_path}!") + return matched_files + except Exception as e: + print(f"文件搜索失败:{str(e)}") + return matched_files + +# 测试调用 +if __name__ == "__main__": + # 搜索当前目录下所有 .js 文件 + search_files(".", ".js") + # 搜索 D 盘下所有 .txt 文件:search_files("D:/", ".txt") +``` +5. **日志系统**:创建一个简单的日志系统,支持写入日志到文件,包含时间戳和日志级别。 +```javascript +import datetime + +# 定义日志级别(常量) +LOG_LEVELS = { + "DEBUG": 10, + "INFO": 20, + "WARNING": 30, + "ERROR": 40, + "CRITICAL": 50 +} + +def write_log(message, level="INFO", log_file="app.log"): + """ + 写入日志到文件,包含时间戳和日志级别 + :param message: 日志内容 + :param level: 日志级别(DEBUG/INFO/WARNING/ERROR/CRITICAL) + :param log_file: 日志文件路径 + """ + # 验证日志级别 + level = level.upper() + if level not in LOG_LEVELS: + print(f"警告:无效的日志级别 {level},默认使用 INFO") + level = "INFO" + + # 生成时间戳(格式:YYYY-MM-DD HH:MM:SS.ms) + timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3] + + # 构造日志行 + log_line = f"[{timestamp}] [{level}] {message}\n" + + try: + # 追加模式写入(避免覆盖历史日志) + with open(log_file, "a", encoding="utf-8") as f: + f.write(log_line) + print(f"日志已写入:{log_line.strip()}") + except PermissionError: + print(f"错误:没有权限写入日志文件 {log_file}!") + except Exception as e: + print(f"写入日志失败:{str(e)}") + +# 测试调用 +if __name__ == "__main__": + write_log("程序启动成功", "INFO") + write_log("数据库连接超时", "WARNING") + write_log("用户登录失败:密码错误", "ERROR") + write_log("服务器磁盘空间不足!", "CRITICAL") + write_log("调试信息:变量 x = 10", "DEBUG") + +``` +--- -- Gitee From f07fe7e4c1df4a310036ad3f0ffc56d6b2daee1a Mon Sep 17 00:00:00 2001 From: GXDo <211484214@qq.com> Date: Sun, 22 Mar 2026 19:28:14 +0800 Subject: [PATCH 4/4] =?UTF-8?q?feat:=E7=AC=94=E8=AE=B0+=E7=BB=83=E4=B9=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...20-Node.jsHTTP\346\250\241\345\235\227.md" | 566 ++++++++++++++++++ 1 file changed, 566 insertions(+) create mode 100644 "\351\203\255\345\260\217\344\270\234/20260320-Node.jsHTTP\346\250\241\345\235\227.md" diff --git "a/\351\203\255\345\260\217\344\270\234/20260320-Node.jsHTTP\346\250\241\345\235\227.md" "b/\351\203\255\345\260\217\344\270\234/20260320-Node.jsHTTP\346\250\241\345\235\227.md" new file mode 100644 index 0000000..efd734b --- /dev/null +++ "b/\351\203\255\345\260\217\344\270\234/20260320-Node.jsHTTP\346\250\241\345\235\227.md" @@ -0,0 +1,566 @@ +# 笔记 + +## 基本框架 +```javascript +框架 +import http from 'http'; + +let app = http.createServer(); + +app.listen(3000,()); + +``` + +## 创建服务器:http.createServer() + +#### 1.基础定义与核心逻辑 + +- **作用**:创建 HTTP 服务器对象,该对象可通过 `listen()` 方法监听指定端口,接收客户端请求并处理。 + +- 参数 + + :一个回调函数(可选),回调函数会在每次有客户端请求时触发,接收两个核心参数: + + - `req`(request):请求对象,包含客户端请求的所有信息(如 URL、请求方法、请求头、请求体等)。 + - `res`(response):响应对象,用于向客户端返回数据(如状态码、响应头、响应内容)。 + +#### 2.示例 +```javascript +import http from 'http'; + +let server = http.createServer((req,res)=>{ + res.writeHead(200,{'Content-Type': 'text/plain;charse=utf-8'}); + + res.end('你好.'); +}); + +server.listen(3000,()=>{ + console.log('服务器已启动') +}) + +``` + +## 请求对象:req.method/req.url/req.headers +### 一、详解 +#### 1.req.method:获取HTTP请求方法 +- **作用**:返回客户端发起请求的HTTP方法(大写字符串),如GET、POST、PUT、DELETE、等。 +- **场景**:区分**获取数据**(GET)和**提交数据**(POST),实现不同的业务逻辑。 + +#### 2.req.url:获取请求的URL路径 +- **作用**: 返回客户端请求的URL路径(包含查询参数,但不包含域名/端口),如请求 `http://localhost:3000/user?id=1` 时,`req.url` 为 `/user?id=1`。 +- **注意**: 仅返回路径部分,如需解析查询参数(id=1),需手动处理或用url模块(Node.js内置)。 + +#### 3.req.headers:获取请求头信息 +- **作用**:返回一个对象,包含所有HTTP请求头信息(User-Agent、Content-Type、Cookie等),属性名称统一为小写(如user-agent而非User-Agent)。 +- **常用字段**: + - user-agent:客户端游览器/设备信息; + - content-type:请求体的格式(如application/json、application/x-www-form-urlencoded); + - cookie:客户端携带的Cookie信息。 + +### 二、示例 +```javascript +const http = require('http'); +const url = require('url'); + +const server = http.createServer((req,res)=>{ + // 1.设置响应头,解决中文乱码 + res.setHeader('Content-Type','text/plain;charset=utf-8'); + // 2.解析req的核心属性 + const method = req.method; // 获取请求方法 + const requestUrl = req.url; // 获取请求路径 + const headers = req.headers; // 获取请求头对象 + // 3. 解析 URL 中的查询参数(可选,进阶用法) + const urlObj = url.parse(requestUrl, true); // true 表示自动解析 query 为对象 + const pathname = urlObj.pathname; // 纯路径(不含查询参数) + const query = urlObj.query; // 查询参数对象}) + + // 4. 组装响应内容,展示所有解析结果 + const responseContent = ` + === 客户端请求信息 === + 请求方法(req.method):${method} + 原始请求路径(req.url):${requestUrl} + 纯路径(pathname):${pathname} + 查询参数(query):${JSON.stringify(query)} + + === 请求头信息(req.headers)=== + 客户端浏览器:${headers['user-agent']} + 请求内容类型:${headers['content-type'] || '无'} + 客户端 Cookie:${headers['cookie'] || '无'} + `; + + // 5. 返回响应 + res.end(responseContent); + }); + + // 启动服务器,监听 3000 端口 + server.listen(3000, () => { + console.log('服务器已启动:http://localhost:3000'); + console.log('测试建议:访问 http://localhost:3000/user?id=1&name=张三 查看效果'); + }); +``` + +# 练习 +1. 请解释req和res分别代表什么? + + **req**:全称 `request`,是 `http.IncomingMessage` 类型的对象,代表**客户端向服务器发送的请求**。 + + 包含请求头(headers)、URL、请求方法、查询参数、请求体等信息。 + + **res**:全称 `response`,是 `http.ServerResponse` 类型的对象,代表**服务器向客户端返回的响应**。 + + 用于设置响应状态码、响应头、发送响应内容(HTML/JSON/ 二进制等)。 + +2. http.createServer的回调函数接收哪些参数? + + 第一个参数:`request`(通常简写为 `req`)→ 客户端请求对象 + + 第二个参数:`response`(通常简写为 `res`)→ 服务器响应对象 +```javascript + +const http = require('http'); +const server = http.createServer((req, res) => { + // 处理请求并返回响应 + res.end('Hello World'); +}); +``` + +3. 如何获取URL中的查询参数? + + Node.js 需结合 `url` 模块(内置)解析 URL,步骤如下: + + 1. 引入 `url` 模块; + 2. 用 `new URL()` 解析 `req.url`(需拼接完整协议 + 主机); + 3. 通过 `searchParams` 获取查询参数。 +```javascript +const http = require('http'); +const url = require('url'); + +const server = http.createServer((req, res) => { + // 解析URL(需补充完整基地址) + const parsedUrl = new URL(req.url, `http://${req.headers.host}`); + // 获取单个参数 + const name = parsedUrl.searchParams.get('name'); + const age = parsedUrl.searchParams.get('age'); + // 获取所有参数(返回Map) + const allParams = Object.fromEntries(parsedUrl.searchParams); + + res.end(`姓名:${name},年龄:${age}`); +}); +server.listen(3000); +``` + +4. res.writeHead和res.setHeader有什么区别? + + | 特性 | res.setHeader | res.writeHead | + | -------- | ------------------------------------------ | --------------------------------------------- | + | 调用时机 | 可多次调用(设置多个头) | 只能调用一次 | + | 功能 | 仅设置响应头,不发送 | 同时设置状态码 + 响应头,并**立即发送响应头** | + | 优先级 | 若先调用 setHeader,writeHead 会覆盖同名头 | 最终生效的响应头由 writeHead 决定 | + + ```javascript + // 方式1:先setHeader,再writeHead(writeHead会覆盖) + res.setHeader('Content-Type', 'text/plain'); + res.writeHead(200, { 'Content-Type': 'text/html' }); // 最终生效 + + // 方式2:仅用setHeader(需通过res.end发送) + res.setHeader('Content-Type', 'application/json'); + res.statusCode = 200; // 单独设置状态码 + res.end(JSON.stringify({ code: 200 })); + ``` + + + +5. http.get和http.request有什么区别? + + | 特性 | http.get | http.request | + | ------------ | ---------------------------------- | ---------------------------------------- | + | 适用场景 | 仅 GET 请求(最常用) | 支持所有 HTTP 方法(POST/PUT/DELETE 等) | + | 手动结束请求 | 自动调用 `req.end()`,无需手动处理 | 必须手动调用 `req.end()` 完成请求发送 | + | 简化程度 | 高(封装了 GET 请求的通用逻辑) | 通用(需手动配置 method、headers 等) | + + ```javascript + const http = require('http'); + + // 1. http.get(GET请求,自动end) + http.get('http://localhost:3000', (res) => { + let data = ''; + res.on('data', chunk => data += chunk); + res.on('end', () => console.log(data)); + }); + + // 2. http.request(POST请求,需手动end) + const req = http.request({ + hostname: 'localhost', + port: 3000, + method: 'POST', + headers: { 'Content-Type': 'application/json' } + }, (res) => { + // 处理响应 + }); + req.write(JSON.stringify({ name: '张三' })); // 发送请求体 + req.end(); // 必须调用,结束请求 + ``` + + + +### 6.3 操作题 + +1. **静态服务器**:创建一个Web服务器,返回HTML页面,包含CSS样式。 + + ```javascript + // static-server.js + const http = require('http'); + const fs = require('fs'); + const path = require('path'); + + // 定义静态文件目录 + const staticDir = path.join(__dirname, 'public'); + + // 映射文件扩展名到Content-Type + const mimeTypes = { + '.html': 'text/html', + '.css': 'text/css', + '.js': 'application/javascript', + '.png': 'image/png', + '.jpg': 'image/jpeg' + }; + + const server = http.createServer((req, res) => { + // 处理根路径,默认返回index.html + const filePath = req.url === '/' + ? path.join(staticDir, 'index.html') + : path.join(staticDir, req.url); + + // 获取文件扩展名 + const extname = path.extname(filePath); + // 设置默认Content-Type + const contentType = mimeTypes[extname] || 'text/plain'; + + // 读取文件 + fs.readFile(filePath, (err, content) => { + if (err) { + // 404错误 + if (err.code === 'ENOENT') { + res.writeHead(404, { 'Content-Type': 'text/html' }); + res.end('

404 Not Found

'); + } else { + // 500错误 + res.writeHead(500); + res.end(`服务器错误:${err.code}`); + } + } else { + // 成功返回文件 + res.writeHead(200, { 'Content-Type': contentType }); + res.end(content); + } + }); + }); + + // 创建public目录和测试文件 + // 1. 创建public文件夹 + if (!fs.existsSync(staticDir)) { + fs.mkdirSync(staticDir); + } + + // 2. 创建index.html + fs.writeFileSync(path.join(staticDir, 'index.html'), ` + + + + + 静态服务器 + + + +

Node.js 静态服务器

+

这是静态HTML页面,加载了CSS样式

+ + + `, 'utf8'); + + // 3. 创建style.css + fs.writeFileSync(path.join(staticDir, 'style.css'), ` + body { background: #f5f5f5; } + h1 { color: #2c3e50; text-align: center; } + p { color: #666; font-size: 18px; } + `, 'utf8'); + + server.listen(3000, () => { + console.log('静态服务器运行在 http://localhost:3000'); + }); + ``` + + + +2. **计算器API**:创建RESTful API,支持加、减、乘、除运算。 + + ```javascript + // calc-api.js + const http = require('http'); + const url = require('url'); + + const server = http.createServer((req, res) => { + // 设置跨域和响应格式 + res.setHeader('Content-Type', 'application/json'); + res.setHeader('Access-Control-Allow-Origin', '*'); + + // 解析URL和参数 + const parsedUrl = new URL(req.url, `http://${req.headers.host}`); + const { pathname, searchParams } = parsedUrl; + + // 仅处理/calc路径 + if (pathname !== '/calc') { + res.writeHead(404); + res.end(JSON.stringify({ code: 404, msg: '接口不存在' })); + return; + } + + // 获取运算参数 + const method = searchParams.get('method'); // add/sub/mul/div + const a = Number(searchParams.get('a')); + const b = Number(searchParams.get('b')); + + // 参数校验 + if (isNaN(a) || isNaN(b) || !['add', 'sub', 'mul', 'div'].includes(method)) { + res.writeHead(400); + res.end(JSON.stringify({ + code: 400, + msg: '参数错误,格式:/calc?method=add&a=5&b=3' + })); + return; + } + + // 运算逻辑 + let result; + switch (method) { + case 'add': result = a + b; break; + case 'sub': result = a - b; break; + case 'mul': result = a * b; break; + case 'div': + if (b === 0) { + res.writeHead(400); + res.end(JSON.stringify({ code: 400, msg: '除数不能为0' })); + return; + } + result = a / b; + break; + } + + // 返回结果 + res.writeHead(200); + res.end(JSON.stringify({ + code: 200, + data: { a, b, method, result }, + msg: '运算成功' + })); + }); + + server.listen(3000, () => { + console.log('计算器API运行在 http://localhost:3000'); + console.log('测试示例:http://localhost:3000/calc?method=add&a=5&b=3'); + }); + ``` + + + +3. **图片服务器**:创建一个简单的图片服务器,根据URL返回指定目录下的图片。 + + ```javascript + // image-server.js + const http = require('http'); + const fs = require('fs'); + const path = require('path'); + + // 图片存储目录(可自行修改) + const imgDir = path.join(__dirname, 'images'); + // 创建目录(若不存在) + if (!fs.existsSync(imgDir)) { + fs.mkdirSync(imgDir); + console.log('已创建images目录,请放入测试图片(如test.jpg/test.png)'); + } + + // 图片类型映射 + const imgTypes = { + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.png': 'image/png', + '.gif': 'image/gif' + }; + + const server = http.createServer((req, res) => { + // 解析URL,获取图片名(如 /test.jpg) + const imgName = req.url.slice(1); // 去掉开头的/ + if (!imgName) { + res.writeHead(400); + res.end('请指定图片名,示例:http://localhost:3000/test.jpg'); + return; + } + + // 拼接图片路径 + const imgPath = path.join(imgDir, imgName); + const extname = path.extname(imgPath); + + // 校验图片类型 + if (!imgTypes[extname]) { + res.writeHead(400); + res.end('仅支持jpg/jpeg/png/gif格式'); + return; + } + + // 读取并返回图片 + fs.readFile(imgPath, (err, data) => { + if (err) { + res.writeHead(404); + res.end(`图片不存在:${imgName}`); + return; + } + + // 设置响应头,返回二进制图片 + res.writeHead(200, { 'Content-Type': imgTypes[extname] }); + res.end(data); + }); + }); + + server.listen(3000, () => { + console.log('图片服务器运行在 http://localhost:3000'); + console.log('使用方法:'); + console.log(`1. 将图片放入 ${imgDir} 目录`); + console.log('2. 访问 http://localhost:3000/[图片名] 查看(如 http://localhost:3000/test.jpg)'); + }); + ``` + + + +4. **天气查询**:创建一个天气查询工具,调用第三方API获取天气信息。 + + ```javascript + // weather.js + const https = require('https'); // 和风天气API是HTTPS + const querystring = require('querystring'); + + // 替换为自己的和风天气API Key(注册地址:https://dev.qweather.com/) + const API_KEY = '你的API Key'; + const BASE_URL = 'https://devapi.qweather.com/v7/weather/now'; + + // 天气查询函数 + function getWeather(city) { + // 1. 先通过城市名获取Location ID + const locationUrl = `https://geoapi.qweather.com/v2/city/lookup?${querystring.stringify({ + location: city, + key: API_KEY + })}`; + + // 发送城市查询请求 + https.get(locationUrl, (res) => { + let data = ''; + res.on('data', chunk => data += chunk); + res.on('end', () => { + const locationRes = JSON.parse(data); + if (locationRes.code !== '200' || !locationRes.location || locationRes.location.length === 0) { + console.log('❌ 城市不存在或查询失败'); + return; + } + + // 获取城市ID + const locationId = locationRes.location[0].id; + // 2. 查询天气 + const weatherUrl = `${BASE_URL}?${querystring.stringify({ + location: locationId, + key: API_KEY + })}`; + + https.get(weatherUrl, (res) => { + let weatherData = ''; + res.on('data', chunk => weatherData += chunk); + res.on('end', () => { + const weatherRes = JSON.parse(weatherData); + if (weatherRes.code !== '200') { + console.log('❌ 天气查询失败'); + return; + } + + // 格式化输出 + console.log(`\n📅 ${city} 实时天气:`); + console.log(`🌡️ 温度:${weatherRes.now.temp}°C`); + console.log(`💧 湿度:${weatherRes.now.humidity}%`); + console.log(`💨 风向:${weatherRes.now.windDir} ${weatherRes.now.windScale}级`); + console.log(`☁️ 天气:${weatherRes.now.text}`); + console.log(`⏰ 更新时间:${weatherRes.now.obsTime}`); + }); + }).on('error', (err) => { + console.log('❌ 请求失败:', err.message); + }); + }); + }).on('error', (err) => { + console.log('❌ 城市查询失败:', err.message); + }); + } + + // 获取命令行参数(城市名) + const args = process.argv.slice(2); + if (!args[0]) { + console.log('使用方法:node weather.js <城市名>'); + console.log('示例:node weather.js 北京'); + process.exit(1); + } + + // 执行查询 + getWeather(args[0]); + ``` + + + +5. **代理服务器**:创建一个简单的代理服务器,将请求转发到另一个服务器。 + + ```javascript + // proxy-server.js + const http = require('http'); + const url = require('url'); + + // 目标服务器配置 + const TARGET_HOST = 'www.baidu.com'; + const TARGET_PORT = 80; + + const proxyServer = http.createServer((req, res) => { + console.log(`转发请求:${req.method} ${req.url}`); + + // 解析请求URL + const parsedUrl = url.parse(req.url); + + // 配置转发请求参数 + const proxyOptions = { + hostname: TARGET_HOST, + port: TARGET_PORT, + path: parsedUrl.path, + method: req.method, + headers: req.headers // 转发客户端请求头 + }; + + // 发送转发请求 + const proxyReq = http.request(proxyOptions, (proxyRes) => { + // 设置响应头(转发目标服务器的响应头) + res.writeHead(proxyRes.statusCode, proxyRes.headers); + // 将目标服务器的响应体转发给客户端 + proxyRes.pipe(res); + }); + + // 处理请求体转发(如POST请求) + req.pipe(proxyReq); + + // 错误处理 + proxyReq.on('error', (err) => { + console.error('转发失败:', err); + res.writeHead(500); + res.end(`代理服务器错误:${err.message}`); + }); + }); + + proxyServer.listen(3000, () => { + console.log('代理服务器运行在 http://localhost:3000'); + console.log('访问 http://localhost:3000 即可代理访问百度'); + }); + ``` + + + + \ No newline at end of file -- Gitee