Koa2 框架入门与进阶
    
    
    
    前言
   
    
     什么是框架(frame):
    
   
- 封装原生代码的 API
 - 规范流程和格式
 - 让开发人员更加关注于业务代码,提高开发效率
 
    
     框架(frame)和库(lib)的区别:
    
   
- 框架是唯一的,库就可以共存
 - 框架关注全流程,库关注单个功能
 - 框架和库类比于 Vue 和 lodash
 
    
    
    一、Koa 简介
   
    
    
    1、koa2 是什么
   
- 
     Koa 是一个新的
web 框架
,致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 - 
     利用
async 函数
丢弃传统的回调函数,并增强错误处理。Koa 没有
任何预置
的中间件,可快速而愉快地编写服务端应用程序。 - koa 是基于 nodejs 平台的下一代 web 开发框架(koa2 是 nodejs web server 框架)
 - 
     koa2 的官网:
https://koa.bootcss.com
 - koa2 可以通过 async/await 语法高效编写 web server
 - 使用中间件机制,能合理拆分业务代码
 
    
    
    2、koa 核心概念
   
- Koa Application(应用程序)
 - Context(上下文)
 - Request(请求)、Response(响应)
 
    
    
    3、koa 的安装与基本使用
   
- 
     
初始化:
 
npm init
// or
npm init -y
- 
     
安装:
 
npm install koa --save
- 
     
基本使用:
 
const Koa = require('koa')
const app = new Koa()
// ctx 读 context 上下文的意思
app.use(async (ctx) => {
	ctx.body = 'hello world'
})
app.listen(3000)
    
    
    二、Koa2 安装&配置
   
    
    
    1、koa2 环境搭建
   
    
    
    (1)使用脚手架 koa-generator 创建 koa2 项目
   
- 
     
安装 koa2 脚手架:
实际项目,不会从 0 开始 搭建 koa2 环境 - 类比于 @vue-cli 或 @vue/cli
 
// 安装 koa2 脚手架
npm install -g koa-generator
// 验证是否安装成功
koa2 --version
- 
     
使用脚手架 koa-generator 创建 koa2 项目:koa2 项目名
 
// koa2 项目名
koa2 test
- 
     
运行 koa2 框架
 
// 进入该项目
cd test
// 下载安装项目依赖
npm install
// 运行 koa2 项目
npm run dev
    
    
    (2)介绍项目目录和文件
   
- 入口文件 app.js
 - 已安装的 npm 插件
 - 各个目录和文件
 
    
    
    (3)在项目中新建一个路由
   
- 新建路由文件:在 routers 目录文件下创建一个新的 js 文件(例如:comment.js)
 - 
     
新建路由:
 
// comment.js
const Router = require('koa-router')
const router = new Router()
// 或者写为
// const router = require('koa-router')()
// 定义路由前缀,没有可不添加
router.prefix('/api')
// 定义路由:模拟获取
router.get('/list', async (ctx, next) => {
	ctx.body = 'get /api/list'
})
// 定义路由:模拟创建
router.post('/create', async (ctx, next) => {
	ctx.body = 'post /api/create'
})
// 输出路由
module.exports = router
- 
     
在 app.js 中引入和注册路由:
 
// app.js
const Koa = require('koa')
const app = new Koa()
const views = require('koa-views')
const json = require('koa-json')
const onerror = require('koa-onerror')
const bodyparser = require('koa-bodyparser')
const logger = require('koa-logger')
// 引入路由
const index = require('./routes/index')
const users = require('./routes/users')
const comments = require('./router/comments')
// error handler:错误处理器
onerror(app)
// middlewares:中间件
app.use(bodyparser({
  enableTypes:['json', 'form', 'text']
}))
app.use(json())
app.use(logger())
app.use(require('koa-static')(__dirname + '/public'))
app.use(views(__dirname + '/views', {
  extension: 'pug'
}))
// logger 打印当前请求所花费的时间
app.use(async (ctx, next) => {
  const start = new Date()
  await next()
  const ms = new Date() - start
  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
})
// routes 注册路由
// comments.allowedMethods() 是对于 404 或者 返回是空的情况的一种补充
app.use(index.routes(), index.allowedMethods())
app.use(users.routes(), users.allowedMethods())
app.use(comments.routes(), comments.allowedMethods())
// error-handling
app.on('error', (err, ctx) => {
  console.error('server error', err, ctx)
});
module.exports = app
    
    
    3、koa2 处理 http 请求
   
    
     如何接受数据和返回数据:
    
   
- ctx 即 req 和 res 的集合
 - querystring 和 Request Body
 - Response Body 和 Content-type
 
// 定义路由:模拟获取
router.get('/list', async (ctx, next) => {
	const query = ctx.query   // req 的功能
	// ctx.body = 'get /api/list'  // res 的功能
	// 如果要返回 JSON 格式,可以直接返回 json 格式
	ctx.body = {
		errno: 0,
		data: [
			{ user: 'zs', content: 'content1' },
			{ user: 'ls', content: 'content2' }
		]
	}
})
// 定义路由:模拟创建
router.post('/create', async (ctx, next) => {
	const body = ctx.request.body   // 得到 request body
	// ctx.body = 'post /api/create'
	ctx.body = {
		errno: 0,
		message: 'success'
	}
})
    
     对比 koa2 代码和 nodejs 原生代码:
    
   
- nodejs 原生代码:
 
// 模拟获取信息的 url
	if(path === '/api/list' && method === 'GET') {
		const result = {
			errno: 0,
			data: [
				{ username: 'zll', content: 'content1'},
				{ username: 'lgk', content: 'content2'}
			]
		}
		res.writeHead(200, { 'Content-type': 'application/json' })
		// JSON.stringify(result) 将 JSON 转为 字符串形式
		res.end(JSON.stringify(result))
	}
	
	// 模拟发送信息的 url
	if(path === '/api/create' && method === 'POST') {
		const result = {
			errno: 0,
			message: '发送信息成功'
		}
		res.writeHead(200, { 'Content-type': 'application/json' })
		// JSON.stringify(result) 将 JSON 转为 字符串形式
		res.end(JSON.stringify(result))
	}
    
    
    4、koa2 洋葱圈模型
   
- 
     洋葱圈模型执行:一个请求首先会经过最外层的中间件,这个中间件还没有执行完成又继续向内一层执行,依次向内,直到最内层执行完成,然后才会逐层向外继续执行未完成的操作

@图片来源于网络 
    
    
    5、async(异步) 和 await(等待)
   
- async 作为一个关键字放在函数的前面,表示该函数是一个异步函数,意味着该函数的执行不会阻塞后面代码的执行
 - await 只能在异步函数 async function中使用,否则会报错;使用 await 后会暂停当前 async function 的执行,等待 await 对应的 Promise 处理完成。
 
    
    
    三、Koa 常用中间件介绍
   
    
    
    1、koa 中间件介绍
   
    
     什么是中间件:
    
   
- 一个流程上,独立的业务模块
 - 可扩展,可插拔
 - 类似于工厂的流水线
 
    
     为何使用中间件:
    
   
- 拆分业务模块,使代码清晰
 - 统一使用中间件,使得各业务代码都规范标准
 - 扩展性好,易添加、易删除
 
    
     koa2 中间件:
    
   
- 回顾 koa2 框架中的 app.js
 - 所有的 app.use(…) 都是中间件:所有的网络请求都经过 app.use()
 - 
     路由也是中间件(只不过限制了 url 规则):只有符合规则的请求才通过
     
    
     中间件的执行顺序:按照洋葱模型执行
    
   
- 中间件的执行顺序与 app.use() 的调用顺序有关,如果调用靠前的中间件没有执行 next() ,就会导致该中间件之后的中间件不再执行
 
    
     模拟登陆验证功能:
    
   
- 登陆校验,可使用中间件来实现
 - 一种情况是:所有的接口(路由)都需要登陆校验
 - 另一种情况是:只有一部分接口需要登陆校验
 
// 模拟所有的接口(路由)都需要登陆校验
app.use(async (ctx, next) => {
	const query = ctx.query
	if(query.user === 'zs') {
		// 模拟登陆成功
		await next()  // 执行下一步中间件,下一步一般到路由
	} else {
		// 模拟登陆失败
		ctx.body = '请登陆'
	}
})
    
    
    2、常用中间件介绍
   
    
    
    路由:koa-router
   
- 安装 koa-router
 
npm install koa-router --save
- 使用 koa-router
 
const Koa = require('koa')
const app = new Koa()
const Router = require('koa-router')
const router = new Router()
// 可以在路由之前设置前缀
router.prefix('/api')
// 获取请求参数
router.get('/list1', (ctx) => {
	// name: 'lgk', age: '12'
	const params = ctx.request.query
	ctx.body = { 
		name: params.name,
		age: params.age
	}
})
router.get('/list', (ctx) => {
	let { body } = ctx.request
	ctx.body = {
		...body
	}
})
app.use(router.routes()).use(router.allowedMethods())
    
    
    协议解析:koa-body
   
- 安装
 
npm install --save koa-body
- 使用
 
const Koa = require('koa')
const app = new Koa()
const koaBody = require('koa-body')
app.use(koaBody())
    
    
    跨域处理:@koa/cors
   
- 安装
 
npm install --save @koa/cors
- 使用
 
const Koa = require('koa')
const app = new Koa()
const CORS = require('@koa/cors')
app.use(CORS())
    
    
    格式化数据:koa-json
   
- 浏览器在每次发送 GET 请求时,会在浏览器显示格式化后的 JSON 数据
 - 安装
 
npm install --save koa-json
- 使用
 
const Koa = require('koa')
const app = new Koa()
const json = require('koa-json')
// 参数的意思是:只有在URL中添加参数的时候才会使用
// 并且在参数后面再添加 &pretty
app.use(json({ pretty: false, param: 'pretty' }))
    
    
    合并路由:koa-combine-routers
   
- 合并路由
 - 安装
 
npm install --save koa-combine-routers
- 使用
 
// routes.js
const Router = require('koa-router')
const combineRouters = require('koa-combine-routers')
 
const dogRouter = new Router()
const catRouter = new Router()
 
dogRouter.get('/dogs', async ctx => {
  ctx.body = 'ok'
})
 
catRouter.get('/cats', async ctx => {
  ctx.body = 'ok'
})
 
// 在路由之后使用
const router = combineRouters(
  dogRouter,
  catRouter
)
 
module.exports = router
const Koa = require('koa')
const app = new Koa()
const combineRouters = require('./routes')
 
app.use(combineRouters())
    
    
    安全header:koa-helmet
   
- 会在请求中加入安全头,使应用更加安全
 - 安装
 
npm install --save koa-helmet
- 使用
 
const Koa = require('koa')
const app = new Koa()
const helmet = require('koa-helmet')
// 在路由之前使用
app.use(helmet())
    
    
    静态资源:koa-static
   
- 用于引入静态资源
 - 安装
 
npm install --save koa-static
- 使用
 
const Koa = require('koa')
const app = new Koa()
const statics = require('koa-static')
const path = require('path')
// 在路由之前使用
// __dirname 表示当前目录
app.use(statics(path.join(__dirname, '../public')))
    
    
    整合中间件:koa-compose
   
- koa-compose 集成中间件
 - 安装
 
npm install -S koa-compose
- 使用
 
const koaBody = require('koa-body');
const cors = require('@koa/cors');
const jsonUtil = require('koa-json');
const helmet = require('koa-helmet');
const statics = require('koa-static');
const koaCompose = require('koa-compose');
const router = require('./routes/routes')
// app.use(helmet())
// app.use(statics(path.join(__dirname, '../public')))
// app.use(router())
const middleware = koaCompose([
  koaBody(),
  statics(path.join(__dirname, '../public')),
  cors(),
  jsonUtil({ pretty: false, param: 'pretty' }),
  helmet()
])
app.use(middleware)
app.use(router())
    
    
    合并 Webpack 配置文件:webpack-merge
   
- 安装
 
npm install -D webpack-merge
- 使用
 
const webpackMerge = require('webpack-merge');
const TerserWebpackPlugin = require('terser-webpack-plugin');
const webpackConfig = require('./webpack.config.base');
const prodWebpackConfig = webpackMerge.merge(webpackConfig, {
  mode: 'production',
  optimization: {
    minimizer: [
      new TerserWebpackPlugin({
        terserOptions: {
          warnings: false,
          compress: {
            warnings: false,
            // 是否注释掉 console
            drop_console: false,
            dead_code: true,
            drop_debugger: true
          },
          output: {
            comments: false,
            beautify: false
          }
        },
        // parallel: true,
        // sourceMap: false
      })
    ],
    splitChunks: {
      cacheGroups: {
        commons: {
          name: 'commons',
          chunks: 'initial',
          minChunks: 3,
          enforce: true
        }
      }
    }
  }
})
module.exports = prodWebpackConfig
    
    
    压缩中间件:koa-compress
   
- 安装
 
npm install koa-compress -S
- 使用
 
const koaCompress = require('koa-compress');
import router from './routes/routes';
const isDevMode = process.env.NODE_ENV === 'production' ? false : true
// app.use(helmet())
// app.use(statics(path.join(__dirname, '../public')))
// app.use(router())
const middleware = koaCompose([
  koaBody(),
  statics(path.join(__dirname, '../public')),
  cors(),
  jsonUtil({ pretty: false, param: 'pretty' }),
  helmet()
])
if(!isDevMode) {
  app.use(koaCompress())
}
app.use(middleware)
app.use(router())
app.listen(3000)
    
    
    四、Koa 开发配置
   
    
    
    1、Koa 配置开发热加载ES6语法支持&webpack配置
   
    
    
    监测 JS 变化(热更新):nodemon
   
- 安装
 
npm install --save-dev nodemon
- 配置
 
// package.json
// src/index.js 是监测的文件
"scripts": {
	"dev": "nodemon src/index.js"
}
- 使用
 
npm run dev
    
    
    支持ES6语法:
   
- 安装
 
npm install --save-dev webpack webpack-cli
- 安装
 
// 每次打包都会重新清理 dist 目录
npm install -save-dev clean-webpack-plugin
// 对 node_modules 目录下的目录做排除处理
npm install -D webpack-node-externals
// 
npm install -D @babel/core
npm install -D @babel/node
npm install -D @babel/preset-env
npm install -D babel-loader
// 设置环境变量
npm install -D cross-env
- 创建 webpack.config.js 文件
 
const path = require('path');
const nodeExternals = require('webpack-node-externals');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const webpackConfig = {
  target: 'node',
  mode: 'development',
  devtool: 'eval-source-map',
  entry: {
    server: path.join(__dirname, './src/app.js')
  },
  output: {
    filename: '[name].bundle.js',
    path: path.join(__dirname, './dist')
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        use: {
          loader: 'babel-loader'
        },
        exclude: [path.join(__dirname, '/node_modules')]
      }
    ]
  },
  externals: [
    nodeExternals()
  ],
  plugins: [
    new CleanWebpackPlugin()
  ],
  // node: {
  //   console: true,
  //   global: true,
  //   process:true,
  //   Buffer: true,
  //   __filename: true,
  //   __dirname: true,
  //   setImmediate: true,
  //   path: true
  // }
}
module.exports = webpackConfig
- 创建 .babelrc 文件
 
{
  "presets": [
    [
      "@babel/preset-env", 
      {
        "targets": {
          "node": "current"
        }
      }
    ]
  ]
}
- 配置 package.json 文件
 
{
  "name": "community-api",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev:commonjs": "nodemon src/app.js",
    "dev:es6": "nodemon --exec babel-node src/app.js",
    "webpack:debug": "node --inspect-brk ./node_modules/.bin/webpack --inline --progress"
  },
  "author": "lgk",
  "license": "ISC",
  "dependencies": {
    "@koa/cors": "^3.1.0",
    "koa": "^2.13.4",
    "koa-body": "^4.2.0",
    "koa-combine-routers": "^4.0.2",
    "koa-helmet": "^6.1.0",
    "koa-json": "^2.0.2",
    "koa-router": "^10.1.1",
    "koa-static": "^5.0.0"
  },
  "devDependencies": {
    "@babel/core": "^7.15.8",
    "@babel/node": "^7.15.8",
    "@babel/preset-env": "^7.15.8",
    "babel-loader": "^8.2.3",
    "clean-webpack-plugin": "^4.0.0",
    "cross-env": "^7.0.3",
    "nodemon": "^2.0.14",
    "webpack": "^5.59.1",
    "webpack-cli": "^4.9.1",
    "webpack-node-externals": "^3.0.0"
  }
}
    
    
    五、调试
   
    
    
    1、console
   
- 使用 console.log() 进行调试
 
    
    
    2、debug
   
- 设置 debugger 进行调试
 - 然后在 Chrome 浏览器点击 inspect 进行逐步调试
 
npx node --inspect-brk ./node_modules/.bin/webpack --inline --progress
    
    
    3、VsCode
   
- 点击左侧的 debug 配置后进行调试
 
{
  // 使用 IntelliSense 了解相关属性。 
  // 悬停以查看现有属性的描述。
  // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen",
      "name": "nodemon",
      "program": "${workspaceFolder}src/app.js",
      "request": "launch",
      "restart": true,
      "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/nodemon",
      "skipFiles": [
        "<node_internals>/**"
      ],
      "type": "pwa-node",
      "runtimeArgs": ["--exec", "babel-node"]
    }
  ]
}
    
    
    六、其他
   
    
    
    1、npm-check-updates
   
- 用于检查 npm 依赖包是否有新的版本
 - 全局安装 npm-check-updates
 
npm install -g npm-check-updates
- 使用 ncu 命令检查 package.json 是否有可更新版本
 
ncu
- 删除原来的 node_modules
 
rm -rf node_modules/
- 重新安装
 
npm install
    
    
    总结
   
    
     community-api 项目源码:
    
    
     https://gitee.com/lgk2021/community-api
    
   
 
版权声明:本文为weixin_49809163原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。