node-koa2入门
中间件:https://github.com/koajs/koa/wiki#middleware
学习教程:
项目地址:https://github.com/Datura35422/nodejs-demo-jiudao
概念
Koa2 被推出的原因
express 和 koa 框架都是相同的作者 TJ 写的。
express 框架内置了很多中间件,主要面对是企业级开发,底层采用的是 ES5 的方式。
koa2 出现主要是小而精,没有内置中间件,所以中间件自己安装,底层采用的是 ES7 的方式编写的。
两个框架的相关 api 大同小异。
koa2 与 express 区别
express
- 中间件调用呈直线型,即串行。使用 callback 的方式进行调用中间件,next() 函数只能在中间件底部使用,中间件一个一个往下执行。
- 基础Connect中间件,自身封装了路由、视图处理等功能。
- 弊端是callback回调方式,不可组合、异常不可捕获。
- express 的路由是自身集成的;
- express 采用传统的函数形式 function 进行启动;
koa2
- 中间件呈U型,即“洋葱模型”。使用 Promise 的方式进行顺序调用中间件。所以中间件的 next() 函数可以使用
async / await
的方式进行调用,中间件一层一层往下执行,到达中心后再一层一层往外执行,因此是洋葱模型。 - 利用co作为底层运行框架,利用Generator的特性,实现“无回调”的异步处理。
- 利用 async 函数、Koa2丢弃回调函数,增强错误处理。
- 低级中间件层中提供高级“语法糖”,提高了互操性、稳健性。
- Koa2新增了一个Context对象,用来代替Express的Request和Response,作为请求的上下文对象。 还有Node原生提供的req、res、socket等对象。
- Koa的需要引入中间件Koa-router。
- koa采用new Koa()方式启动;
使用区别
参考链接:https://juejin.cn/post/6844903968041091080
koa(Router = require(‘koa-router’)) | express(假设不使用app.get之类的方法) | |
---|---|---|
初始化 | const app = new koa() | const app = express() |
实例化路由 | const router = Router() | const router = express.Router() |
app级别的中间件 | app.use | app.use |
路由级别的中间件 | router.get | router.get |
路由中间件挂载 | app.use(router.routes()) | app.use(‘/‘, router) |
监听端口 | app.listen(3000) | app.listen(3000) |
koa2 中间件洋葱模型
koa2 中间件处理逻辑 koa-compose
'use strict'
/**
* Expose compositor.
*/
module.exports = compose
/**
* Compose `middleware` returning
* a fully valid middleware comprised
* of all those which are passed.
*
* @param {Array} middleware
* @return {Function}
* @api public
*/
function compose (middleware) {
// 判断中间件数组
if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
// 遍历 检测 中间件数组中的函数类型
for (const fn of middleware) {
if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
}
/**
* @param {Object} context
* @return {Promise}
* @api public
*/
return function (context, next) {
// last called middleware #
// 传入当前内容和下一个中间件函数 闭包记录传入的中间件数组
// 初始化 index
let index = -1
return dispatch(0)
// 定义 dispatch 函数
function dispatch (i) {
// 如果 当前位置小于等于 index 则表示 next() 重复调用
// next 会传入索引值 如果索引值没有改变则表示重复调用
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
// 记录当前中间件的索引值
index = i
// 赋值当前中间件函数
let fn = middleware[i]
// 如果当前位置超出了中间件的最后一个索引 则表示进入到了最里面的中间件 赋值为最里面中间件的next()
if (i === middleware.length) fn = next
// 如果下一个函数不存在 则返回完成状态
if (!fn) return Promise.resolve()
try {
// 执行当前函数 将下一个中间件函数赋值给当前中间件next参数
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
} catch (err) {
return Promise.reject(err)
}
}
}
}
jest 简单的测试用例
利用事件循环机制,将后续的内容加到微任务队列。
it('should work', async () => {
const arr = []
const stack = []
// 中间件 1
stack.push(async (context, next) => {
arr.push(1)
await wait(1)
await next() // 进入执行中间件2
await wait(1)
arr.push(6)
})
// 中间件 2
stack.push(async (context, next) => {
arr.push(2)
await wait(1)
await next() // 进入执行中间件3
await wait(1)
arr.push(5)
})
// 中间件 3
stack.push(async (context, next) => {
arr.push(3)
await wait(1)
await next() // 已经到洋葱中心 继续向下执行 回到上层中间件
await wait(1)
arr.push(4)
})
await compose(stack)({})
expect(arr).toEqual(expect.arrayContaining([1, 2, 3, 4, 5, 6]))
})
1-1 koa2导学
遇到的问题
Koa框架到底为我们做了什么
他的内部到底是什么样子
他向下一直到Nodejs底层,到底是怎么处理事件循环的
一个异步的HTTP过程,到底是怎么进行的
技术栈:MongoDB、Puppeteer、AntDesign、Bootstrap、Parcel
实战
上传图片 base64
// 教程:https://www.huaweicloud.com/articles/2687012349fabd74e9cc9da11c0ca640.html
const path = require('path');
const { writeFileSync } = require('fs');
const { Buffer } = require('buffer');
const Koa = require('koa');
// https://github.com/koajs/router/blob/master/API.md
const Router = require('@koa/router');
// https://github.com/koajs/koa-body
const koaBody = require('koa-body');
// https://github.com/koajs/static
const koaStatic = require('koa-static');
// https://github.com/koajs/cors
// const cors = require('@koa/cors');
const Crypto = require('crypto');
const SizeOf = require('image-size');
const images = require('images');
const app = new Koa();
const router = new Router();
// app.use(cors({
// 'Access-Control-Allow-Methods': 'POST',
// 'Access-Control-Allow-Origin': '*'
// }))
function _getHash(salt = Date.now().toString()) {
return Crypto.createHash('md5').update(salt).digest('hex');
}
app.use(koaStatic(path.join(__dirname, 'public')));
router.post('/img',
koaBody({
multipart: true,
// https://github.com/node-formidable/formidable
formidable: {
uploadDir: path.resolve(__dirname, './public/uploads'),
keepExtensions: true,
hash: 'sha1',
}
}),
(ctx, next) => {
const file = ctx.request.files.file;
const basename = path.basename(file.path);
ctx.body = {
url: `${ctx.origin}/uploads/${basename}`,
hash: file.hash
}
}
);
router.post('/pixel',
koaBody(),
(ctx, next) => {
const data = ctx.request.body
// => POST body
const pixel = data.pixel
if (pixel && Array.isArray(pixel)) {
const sizeOld = pixel.length / 4
console.log(sizeOld)
const base64 = pixel.reduce((base, item) => {
const reg = /rgba\(([\d]+), ([\d]+), ([\d]+), 255\)/g;
const match = reg.exec(item).slice(1);
match.forEach(item => {
if (item !== 0) {
base += String.fromCharCode(item)
}
})
return base
}, '');
// 转图片
const buf = Buffer.from(base64, 'base64');
const unit = new Uint8Array(buf); // 返回一个被 string,编码格式是base64(默认编码格式是utf-8)的值初始化的新的 Buffer 实例
const salt = Date.now().toString();
const hash = _getHash(salt);
const imgPath = `${hash}.jpg`;
// 计算图片大小
const { width, height } = SizeOf(buf);
console.log(width, height)
const sizeNew = width * height;
const multiple = Math.abs((sizeOld / sizeNew));
const newWidth = multiple * width;
const newHeight = multiple * height;
const newHash = _getHash(salt + 'new');
console.log(multiple, newHeight, newWidth)
try {
// 用fs写入文件
const uploadDir = path.resolve(__dirname, `./public/uploads`);
writeFileSync(`${uploadDir}/${imgPath}`, unit);
images(buf).size(newWidth, newHeight).save(`${uploadDir}/${newHash}.jpg`);
ctx.body = {
url: `${ctx.origin}/uploads/${imgPath}`,
hash
};
} catch(err) {
console.log(err);
}
}
}
);
app
.use(router.routes())
.use(router.allowedMethods());
app.listen(3000, () => {
console.log('启动成功: http://localhost:3000');
})