diff --git "a/\346\235\250\351\225\207\344\274\215/20260323.md" "b/\346\235\250\351\225\207\344\274\215/20260323.md" new file mode 100644 index 0000000000000000000000000000000000000000..6e5be83d8b0ce7b0dc3a0ecf56df7e5df38c6e8a --- /dev/null +++ "b/\346\235\250\351\225\207\344\274\215/20260323.md" @@ -0,0 +1,23 @@ +Stream模块笔记 +Stream 是node.js处理大数据的分段处理工具 把大文件/大数据切成一小块一小块的(chunk),边读边处理边写,不一次性把整个文件塞进内存。 + +分为两种 + +-可读流(Readable):数据的“来源”(比如要读取的大文件) +-可写流(Writeable):数据的“目的地”(比如要写入的新文件) +pipe用法,翻译管道(最省心的用法) + +-把Readable和Writeable“连起来”,数据会自动从来源留到目的地(node.js自动处理分段、速度匹配 +进度显示只需监听data事件,累加字节数算百分比 + +Crypto模块笔记 +Crypto是node.js内置的加密/解密工具模块,核心用处理数据的上锁和解锁 + +-- 哈希(Hash):不可逆加密(比如存密码) +将密码长度转换成固定长度的乱码,只能加密不能解密,适合存密码 + +-- 对称加密(Cipher/Decipher):可加可解(比如加密文件) +加密和解密用同一个密钥,像用同一把钥匙锁门和开门,适合加密需要还原的数据(如文件、敏感信息) + +-- HMAC:带密钥的哈希(比如接口签名) +比普通哈希多一层密钥,只有知道密钥才能生成相同的签名,适合接口参数验签(防止参数被篡改) \ No newline at end of file diff --git "a/\346\235\250\351\225\207\344\274\215/20260325.md" "b/\346\235\250\351\225\207\344\274\215/20260325.md" new file mode 100644 index 0000000000000000000000000000000000000000..e6cfa724513ddfd025779bcfd1f0465759f2c2f2 --- /dev/null +++ "b/\346\235\250\351\225\207\344\274\215/20260325.md" @@ -0,0 +1,104 @@ +简答题 +第一题:什么是事件循环?它在Node.js中起什么作用? +Node.js 是单线程,一次只能干一件事。事件循环就是一个死循环,不停检查:有没有要执行的异步任务?有就按顺序执行,没有就等着。 +让 JS 不用干等网络请求、读文件这些慢操作 一边等 I/O,一边能处理别的请求 保证代码不卡死、能并发 +第二题:宏任务和微任务有什么区别?请举例说明。 +微任务:插队,全部跑完再走 + +宏任务:排队,一次只走一个 + +顺序:同步 > 微任务 > 宏任务 + +第三题:请解释以下代码的执行顺序: +console.log('1'); +setTimeout(() => console.log('2'), 0); +Promise.resolve().then(() => console.log('3')); +process.nextTick(() => console.log('4')); +console.log('5'); +15432 + +第四题:什么是阻塞事件循环?它会对应用造成什么影响? +阻塞事件循环 = 主线程被同步耗时任务霸占,事件循环停摆 + +后果 = 服务不响应、异步任务全卡住、应用卡死 + +第五题:如何避免阻塞事件循环? +不让主线程长时间被同步任务霸占, CPU 密集用子线程,I/O 密集用异步 API。 + +操作题 +第一题:编写代码验证以下代码的执行顺序,并说明原因 +console.log('A') + +setTimeout(() => { + console.log('B') +}, 0) + +Promise.resolve().then(() => { + console.log('C') +}) + +process.nextTick(() => { + console.log('D') +}) + +console.log('E') +AEDCB 先跑同步代码:A、E + +再跑 nextTick(微任务优先级最高):D + +再跑 Promise.then(普通微任务):C + +最后跑 setTimeout(宏任务):B + +第二题:创建一个程序,模拟阻塞事件循环的情况。 +setInterval(() => { + console.log('定时器:', new Date().toLocaleTimeString()) +}, 1000) + +console.log('开始大计算,阻塞 3~5 秒...') + +// 同步大循环,阻塞事件循环 +for (let i = 0; i < 1000000000; i++) {} + +console.log('耗时同步任务结束,事件循环恢复') +第三题:非阻塞优化:将阻塞代码改写为非阻塞形式 +/*阻塞代码*/ +setInterval(() => { + console.log('定时器运行') +}, 1000) + +// 同步大循环,阻塞事件循环 +for (let i = 0; i < 1000000000; i++) {} +/*非阻塞代码*/ +setInterval(() => { + console.log('定时器运行') +}, 1000) + +let i = 0 +function run() { + i++ + if (i < 1000000000) { + setImmediate(run) + } +} + +run() +第四题:综合测试:创建一个程序,综合运用setTimeout、Promise、nextTick,验证执行顺序 +console.log('1: 同步开始'); + +setTimeout(() => { + console.log('2: setTimeout'); +}, 0); + +Promise.resolve().then(() => { + console.log('3: Promise.then'); +}); + +process.nextTick(() => { + console.log('4: nextTick'); +}); + +console.log('5: 同步结束'); +顺序 + +1: 同步开始 5: 同步结束 4: nextTick 3: Promise.then 2: setTimeout \ No newline at end of file diff --git "a/\346\235\250\351\225\207\344\274\215/20260326.md" "b/\346\235\250\351\225\207\344\274\215/20260326.md" new file mode 100644 index 0000000000000000000000000000000000000000..6495c9217c520ff2119d3662bdd866bcc6305bf5 --- /dev/null +++ "b/\346\235\250\351\225\207\344\274\215/20260326.md" @@ -0,0 +1,182 @@ +简答题 +第一题:请解释GET、POST、PUT、DELETE四种方法的区别。 +GET 获取资源 查询(查) + +POST 提交数据创建资源 新增(增) + +PUT 更新已有资源 修改(改) + +DELETE 删除资源 删除(删) + +第二题:query参数和params参数有什么区别?分别在什么时候使用? +路径 params = 找唯一东西(必须带 ID) + +问号 query = 筛选东西(可选条件) + +第三题:什么是RESTful API?它的设计原则是什么? +RESTful API = 用 URL 表示资源 + HTTP 方法表示行为 + HTTP 状态码表示结果 + +第四题:为什么要在路由中使用RESTful规范?有什么好处? +规范设计路由是为了让接口语义清晰、风格统一、易于维护、充分利用 HTTP 协议能力,降低前后端协作成本,同时方便扩展和自动化管理。 + +第五题:express.json()中间件的作用是什么? +express.json () 是 Express 内置中间件 + +作用:解析 JSON 格式请求体,把数据放到 req.body + +不加它,req.body = undefined,拿不到前端传的 JSON + +目前,打代码默认第一行就是它!!! + +操作题 +第一题:商品API:创建一个商品管理的RESTful API,支持增删改查。 +const express=require('express'); +const app=express(); + +app.use(express.json()); + +let products=[ + {id:1,name:'蓝莓',price:5}, + {id:2,name:'草莓',price:3} +] +//Get全部商品 +app.get('/products',(req,res)=>{ + res.send(products) +}) +//获取单个商品 +app.get('/products/:id',(req,res)=>{ + const id=parseInt(req.params.id) + const product=products.find(p=>p.id===id) + res.send(product) +}) +//新增商品 +app.post('/products',(req,res)=>{ + const newProduct={ + id:products.length+1, + name:req.body.name, + price:req.body.price + } + products.push(newProduct); + res.sendStatus(newProduct); +}); +//修改商品 +app.put('/products',(req,res)=>{ + const id=parseInt(req.params.id) + const product=products.find(p=>p.id===id) + product.name=req.body.name; + product.price=req.body.price; + res.send(product) +}); +//删除商品 +app.delete('/products/:id',(req,res)=>{ + const id=parseInt(req.params.id) + const product=products.find(p=>p.id!==id) + res.send({message:'删除成功'}) +}) +//监听 +app.listen(3000,()=>{ + console.log('http://localhost:3000'); +}) +第五题:统一响应格式:改造API,统一返回格式:{ code, data, message }。 +const express = require('express'); +const app = express(); + +app.use(express.json()); + +let products = [ + { id: 1, name: '蓝莓', price: 5 }, + { id: 2, name: '草莓', price: 3 } +]; + +// 1. 获取所有商品 +app.get('/products', (req, res) => { + res.json({ + code: 200, + data: products, + message: '获取商品列表成功' + }); +}); + +// 2. 获取单个商品 +app.get('/products/:id', (req, res) => { + const id = parseInt(req.params.id); + const product = products.find(p => p.id === id); + + if (!product) { + return res.json({ + code: 404, + data: null, + message: '商品不存在' + }); + } + + res.json({ + code: 200, + data: product, + message: '获取单个商品成功' + }); +}); + +// 3. 新增商品 +app.post('/products', (req, res) => { + const newProduct = { + id: products.length + 1, + name: req.body.name, + price: req.body.price + }; + products.push(newProduct); + + res.json({ + code: 200, + data: newProduct, + message: '新增商品成功' + }); +}); + +// 4. 修改商品 +app.put('/products/:id', (req, res) => { + const id = parseInt(req.params.id); + const product = products.find(p => p.id === id); + + if (!product) { + return res.json({ + code: 404, + data: null, + message: '商品不存在' + }); + } + + product.name = req.body.name; + product.price = req.body.price; + + res.json({ + code: 200, + data: product, + message: '修改商品成功' + }); +}); + +// 5. 删除商品 +app.delete('/products/:id', (req, res) => { + const id = parseInt(req.params.id); + const len = products.length; + products = products.filter(p => p.id !== id); + + if (products.length === len) { + return res.json({ + code: 404, + data: null, + message: '删除失败,商品不存在' + }); + } + + res.json({ + code: 200, + data: null, + message: '删除商品成功' + }); +}); + +app.listen(3000, () => { + console.log('服务已启动:http://localhost:3000'); +}); \ No newline at end of file diff --git "a/\346\235\250\351\225\207\344\274\215/20260327.md" "b/\346\235\250\351\225\207\344\274\215/20260327.md" new file mode 100644 index 0000000000000000000000000000000000000000..a891ba135374b1cdd44b281a496250fccd13ede8 --- /dev/null +++ "b/\346\235\250\351\225\207\344\274\215/20260327.md" @@ -0,0 +1,56 @@ +笔记 +一、项目结构(极简分层) + +• routes:路由文件,只定义接口路径,不写业务 + +• controllers:处理请求、调用服务、返回数据 + +• services:纯业务+数据库操作 + +• models:数据模型/表结构 + +• app.js:入口文件,仅挂载中间件、路由、启动服务 + +二、路由规范 + +用express.Router()拆分路由,按功能模块分文件(如user.js、goods.js) + +遵循RESTful风格:GET查、POST增、PUT改、DELETE删 + +统一接口前缀,如app.use('/api/user', userRouter) + +三、核心中间件(必用) + +• express.json():解析JSON请求体 + +• cors:解决跨域 + +• 统一错误处理中间件,捕获全局异常 + +四、分层开发原则 + +路由:只负责匹配请求,转发到控制器 + +控制器:接收参数,调用服务,统一返回格式(code+msg+data) + +服务:专注业务逻辑和CRUD数据库操作,不处理req/res + +五、CRUD关键要点 + +新增/修改:必做参数校验,避免无效数据 + +查询:单条查id,列表做分页,防数据过大 + +删除:优先软删除(标记字段),少用物理删除 + +数据库操作:用async/await,配合try/catch捕获错误 + +六、其他关键注意 + +敏感配置(数据库、密钥)用.env环境变量,不暴露 + +统一响应格式,方便前端对接 + +密码等敏感数据加密存储,不存明文 + +避免代码冗余,公共逻辑抽成工具函数 \ No newline at end of file diff --git "a/\346\235\250\351\225\207\344\274\215/20260330.md" "b/\346\235\250\351\225\207\344\274\215/20260330.md" new file mode 100644 index 0000000000000000000000000000000000000000..15e090fb4a53a6d1236ea99d2e6abfec4a263b30 --- /dev/null +++ "b/\346\235\250\351\225\207\344\274\215/20260330.md" @@ -0,0 +1,105 @@ +简答题 +第一题:什么是中间件?它在Express中起什么作用? +中间件 = 请求处理管道 + +所有功能(解析、日志、鉴权、跨域、错误处理)都是靠中间件实现的 + +路由本身,本质上也是一种 “匹配路径后才执行” 的中间件 + +第二题:中间件函数中的next()函数有什么作用?如果不调用会怎样? +next() = 放行,继续执行下一个中间件 / 路由 + +不调用 next() 且不返回响应 = 请求挂起、前端超时 + +第三题:应用级中间件和路由级中间件有什么区别? +应用级中间件:全局通用,绑在 app + +路由级中间件:局部专用,绑在 router,只对某一组接口生效 + +第四题:错误处理中间件和普通中间件有什么区别? +普通中间件:3 个参数,处理正常请求 + +错误处理中间件:4 个参数,专门捕获 next (err) 抛出的错误 + +第五题:如何在中间件中传递数据给后面的处理函数? +在中间件中将数据挂载到 req 对象上,后面的中间件和路由处理函数就可以通过 req 访问到这些数据。 + +操作题 +第一题:IP限制中间件:创建一个中间件,记录请求IP并限制某些IP访问。 +// 黑名单IP +const blackIP = ['127.0.0.1'] + +// IP限制中间件 +const ipFilter = (req, res, next) => { + const ip = req.ip + console.log('请求IP:', ip) + + if (blackIP.includes(ip)) { + return res.send('禁止访问') + } + next() +} + +// 使用 +app.use(ipFilter) +第二题:请求超时中间件:创建一个中间件,如果请求超过指定时间未响应,返回超时错误。 +// 请求超时中间件(5秒超时) +const timeout = (req, res, next) => { + const timer = setTimeout(() => { + res.status(504).send('请求超时') + }, 5000) + + // 正常响应时清除定时器 + const end = res.end + res.end = (...args) => { + clearTimeout(timer) + end.apply(res, args) + } + + next() +} + +// 使用 +app.use(timeout) +第三题:请求体验证:创建一个中间件,验证请求体中的必填字段。 +// 校验 body 必须有 name 和 age +const validateBody = (req, res, next) => { + const { name, age } = req.body + + if (!name || !age) { + return res.status(400).send('缺少必填字段:name 或 age') + } + + next() +} + +// 使用 +app.use(express.json()) // 必须先解析body +app.post('/user', validateBody, (req, res) => { + res.send('验证通过') +}) +第四题:统一错误响应:改造错误处理中间件,统一返回格式。 +import lombok.Data; + +@Data +public class R { + private int code; + private String msg; + private Object data; + + public static R ok(Object data) { + R r = new R(); + r.code = 200; + r.msg = "success"; + r.data = data; + return r; + } + + public static R fail(int code, String msg) { + R r = new R(); + r.code = code; + r.msg = msg; + r.data = null; + return r; + } +} \ No newline at end of file