💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
## Koa与Express区别 https://juejin.cn/post/6844903968041091080 两个框架都对http进行了封装。相关的api都差不多 Koa 洋葱模型,中间件执行顺序是 U 形的,在每一次 next() 后,都会进入下一个中间件,等最后一个中间件执行完以后,再从最后一个中间件的 next() 之后的代码往回执行 Express 直线型,按照顺序一个一个 next 下去 ![](https://img.kancloud.cn/70/9b/709b1e4eeda21bdd7443172f20b03fe4_933x365.png) >express内置了许多中间件可供使用,而koa没有。 express包含路由,视图渲染等特性,而koa只有http模块。 express的中间件模型为线型,而koa的中间件模型为U型,也可称为洋葱模型构造中间件。 express通过回调实现异步函数,在多个回调、多个中间件中写起来容易逻辑混乱。 ```js // express写法 app.get('/test', function (req, res) { fs.readFile('/file1', function (err, data) { if (err) { res.status(500).send('read file1 error'); } fs.readFile('/file2', function (err, data) { if (err) { res.status(500).send('read file2 error'); } res.type('text/plain'); res.send(data); }); }); }); // koa通过generator和async/await使用同步来处理异步,好于callback和promise app.use(async (ctx, next) => { await next(); var data = await doReadFile(); ctx.response.type = 'text/plain'; ctx.response.body = data; }); ``` # Koa.js https://mp.weixin.qq.com/s/YiMplcQPTSbO2vz3fAzUeA - koa 源码的 lib 目录 ```js lib |- application.js --- 入口文件:Application 类初始化 |- context.js --- 上下文:所有的req及res的方法集,控制哪些方法可读或者可写 |- request.js |- response.js ``` ## **application.js** >Application: 基本服务器框架 Context: 服务器框架基本数据结构的封装,用以 http 请求解析及响应 *Middleware: 中间件,也是洋葱模型的核心机制 application.js里面封装了很多方法: - **use 方法**: 初始化完 koa 实例后(const app = new Koa()),调用 **app.use()** 去挂载中间件,判断中间件为 function还是为 generator function 类型,将中间件函数 push 到 middleware数组中,循环调用 middleware 中的方法去执行, - **listen 方法**:在 use 完中间件之后**app.listen(3000)** ,node 原生启动 http 服务的方法--http.createServer - **compose 方法**:生成洋葱模型,通过 koa-compose 包实现 。 fn(context, dispatch.bind(null, i + 1)),中间件的第二个参数 next就是dispatch.bind(null, i + 1) ,执行 next() 实际上是下一个中间件的执行,这样await next() --后面所有中间件串联执行,即下上文中间件部分的执行顺序。 - **createContext 方法**:`const ctx = this.createContext(req, res) ` 将 req, res 及 this.request, this.response 都挂载到了 context 上,并通过赋值理清了循环引用层级关系。 - **handleRequest 方法**:`this.handleRequest(ctx, fn)`拿到 ctx 和 compose 生成的洋葱模型,开始逐一消费中间件。 ## 手写koa框架源码 - req 对象代表了一个HTTP请求,其具有一些属性来保存请求中的一些数据,比如query string,parameters,body,HTTP headers等等; - res 对象代表了当一个HTTP请求到来时,Express 程序返回的HTTP响应。`res.send([body])`发送HTTP响应 ```js const http = require('http') function compose (middlewares) { return ctx => { const dispatch = (i) => { const middleware = middlewares[i] if (i === middlewares.length) { return } // app.use((ctx, next) => {}), 取出当前中间件,并执行 // 在中间件中调用 next() 时,控制权交给下一个中间件;如果未调用,则接下来的中间件不会执行 return middleware(ctx, () => dispatch(i+1)) } return dispatch(0) } } // 使用 Context 对 req/res 进行了封装,并把 req/res 中多个属性代理到 Context 中,方便访问 class Context { constructor(req, res){ this.req = req; this.res = res } } class Application { constructor () { this.middlewares = [] } listen (...args) { const server = http.createServer(this.callback()) // 在listen 中处理请求并监听端口号 server.listen(...args) } // app.callback() 将返回handleRequest 函数,方便测试 callback () { return async (req, res) => { const ctx = new Context(req, res) // 使用 compose 合成所有中间件,在中间件中会做一些:路由解析、Body解析、 异常处理、统一认证等 const fn = compose(this.middlewares) try { await fn(ctx) } catch (e) { ctx.res.statusCode = 500 // 异常处理函数---状态码 ctx.res.end('Internel Server Error') } ctx.res.end(ctx.body) } } // 注册中间件,并收集在中间件数组中 use (middleware) { this.middlewares.push(middleware) } } module.exports = Application ``` - `app.use`的回调函数依然是原生的`http.crateServer`回调函数,而在koa中回调参数是一个Context对象。 - 构建 Context 在 koa 中,app.use 的回调参数为一个 ctx 对象,而非原生的 req/res。因此要构建一个 Context 对象,并使用 ctx.body 构建响应: >app.use(ctx => ctx.body = 'hello, world'): 通过在 http.createServer 回调函数中进一步封装 Context 实现 Context(req, res): 以 request/response 数据结构为主体构造 Context 对象 # **使用koaJs框架** - 安装koa2项目生成器并创建项目: `npm install koa-generator -g` - **Sequelize连接MySQL** Sequelize具有强大的事务支持,关联关系,读取和复制等功能; 在项目的根目录下创建一个config目录,config目录中创建db.js,mysql连接。 - 创建schema、modules、controllers - **schema--数据表模型实例** 建立与数据表的对应关系,用代码建表。schema目录下的article.js用来创建数据表模型--创建数据表 - **module--实体模型** modules目录下创建article.js文件,为文章表,该文件为文章的实例,存放对数据表在操作的各种方法。 - **controllers--控制器** 控制器的主要作用为功能的处理,controller目录下创建article.js,主要是对api的实现 - **路由** 路径,主要是作为请求的url,请求的路径来处理一些请求,返回数据 ## **常用插件** - Sequelize:连接MySQL - koa-bodyparser: 处理 post 请求体中的参数 - **log4js**: log4j记录日志,必须放在第一个中间件,才能保证所以的请求及操作会先经过logger进行记录再到下一个中间件。 - response.js:新建response.js中间件对返回前端的响应进行处理,用 ctx.body返回前端,有些东西重复写--进行封装,使得返回的格式一致,暴露出responseHandler和errorHandler - koa-cors:解决跨域 ```js // koa-cors.js插件源码 /* 1、如果需要支持 cookies, Access-Control-Allow-Origin 不能设置为 *,且 Access-Control-Allow-Credentials 需要为 true (注意前端请求需要设置 withCredentials = true) * 2、当method=OPTIONS时, 属于预检(复杂请求), 当为预检时可直接返回空响应体, 对应的http状态码为204 * 3、通过 Access-Control-Max-Age 可以设置预检结果的缓存, 单位(秒) * 4、通过 Access-Control-Allow-Headers 设置需要支持的跨域请求头 * 5、通过 Access-Control-Allow-Methods 设置需要支持的跨域请求方法 */ // app.js 使用koa2-cors插件 const Koa = require('koa'); const bodyParser = require('koa-bodyparser'); //post数据处理 const router = require('koa-router')(); //路由模块 const cors = require('koa2-cors'); //跨域处理 const app = new Koa(); app.use( cors({ origin: function(ctx) { //设置允许来自指定域名请求 if (ctx.url === '/test') { return '*'; } // 允许来自所有域名请求 return 'http://localhost:8080'; //只允许http://localhost:8080这个域名的请求 }, maxAge: 5, //指定本次预检请求的有效期,单位为秒。 credentials: true, //是否允许发送Cookie allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], //所允许的HTTP请求方法 allowHeaders: ['Content-Type', 'Authorization', 'Accept'], //服务器支持的head字段 exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'] //获取其他自定义字段 }) ); router.post('/', async function (ctx) { ctx.body = '请求成功了' }); app.listen(3000); ``` - **JWT**: 用于 jwt 权限认证,依赖于json-web-token;用来在身份提供者和服务提供者间传递被认证的用户身份信息 >整体流程: 用户使用用户名密码来请求服务器 服务器进行验证用户的信息,通过验证发送给用户一个token 客户端存储token,并在每次请求时附送上这个token值 服务端验证token值,并返回数据 * * koa-jwt:对token比对校检鉴权 ```js const Koa = require('koa'); const jwt = require('koa-jwt'); const app = new Koa(); app.use(async (ctx, next) => { // 鉴权失败,错误处理 return next().catch((err) => { if (401 == err.status) {ctx.body = { code: 50001, message: '用户鉴权失败' }; } else { throw err; } }); }); app.use(jwt({ secret: JWT_SECRET }).unless({ path: [/^\/public/, /\/login/] })); ``` * `JWT_SECRET`是一个**加密因子**,安全性,可设置成`123456`,可用密钥生成器生成密钥。 * 后面的path路径是设置匹配不需要鉴权的路由或目录,比如所有的public开头的、登录xxxx/login的请求都不需要鉴权。 * * jsonwebtoken 库生成token。 ```js // controller/UserController.js const jwt = require('jsonwebtoken'); async login(ctx, next) { // 在登录成功后 const token = jwt.sign({ uid: user._id }, JWT_SECRET, { expiresIn: '15d' }); ctx.body = { code: 200, entry: { token: token}, }; } ``` >{ uid: user._id } payload 数据载体,放些参数在 token 中,比如用户id。 JWT_SECRET 加密因子,要跟 koa-jwt 设置的保持一致。 expiresIn 设置 token 的过期时间。