koa2学习笔记

由 Express 原班人马打造的 koa,致力于成为一个更小、更健壮、更富有表现力的 Web 框架。使用 koa 编写 web 应用,通过async function,可以免除重复繁琐的回调函数嵌套,并极大地提升常用错误处理效率。

安装

npm install koa

官网:https://koajs.com/

Hello world

const Koa = require('koa');
const app = new Koa();

// logger
app.use(async (ctx, next) => {
    
    
  const start = new Date;
  await next();
  const ms = new Date - start;
  console.log(`${
      
      ctx.method} ${
      
      ctx.url} - ${
      
      ms}`);
});

// response
app.use(ctx => {
    
    
  ctx.body = 'Hello Koa';
});

app.listen(3000);

浏览器输入:http://localhost:3000/

可以看到屏幕显示 Hello Koa

把数据库挂载到context上

app.context.db = db(假设这里是mysql);

app.use(async ctx => {
    
    
  console.log(ctx.db);
});

原生路由

在koa中可以通过ctx.request.path获取用户请求的路径,我们可以将我们的body指向一个网页模板,通过fs.createReadStream()获取文件流

const Koa = require('koa');
const fs = require('fs');
const app = new Koa();
const main = ctx => {
    
    
 ctx.response.type = 'html';
 if(ctx.request.path === '/') {
    
    
   ctx.response.body = fs.createReadStream('./index.html');
 }
 else {
    
    
   ctx.response.body = '页面未找到';
 }
}
app.use(main);
app.listen(3000);

静态资源

如果是一个静态网站,比如它在static目录下

const Koa = require('koa');
const app = new Koa();
const path = require('path');
const static = require('koa-static');
const main = static(path.join(__dirname, 'static'));
app.use(main);
app.listen(3000);

错误ctx.throw()

const Koa = require('koa');
const app = new Koa();
const main = async function (ctx, next) {
    
    
  ctx.throw(500)
  // ctx.throw(400, 'name required');
};
            
app.use(main);
app.listen(3000);
const main = ctx => {
    
    
  ctx.response.status = 404;// 等同于ctx.throw(404)
  ctx.response.body = 'Page Not Found';
};

全局错误监听

app.on('error', err => {
    
    
  console.error('server error', err)
});

设置

app.env默认值为"development"

app.proxy设为true开启代理

cookie

ctx.cookies.set('name', 'tobi')
console.log('ccc:',ctx.cookies.get('name'))

也可以设置过期时间,签名等(ctx.cookies.set(‘name’, ‘tobi’, { signed: true });),见官网:https://koajs.com/

const app = new Koa();
// key用于cookie签名,尽量设置复杂些
app.keys = ['token1','token2'];

...

ctx.cookies.set("jt", "abcd", {
    
    
 signed: true,
});
router.get('/', async (ctx) => {
    
    
  ctx.cookies.set('name', 'txj', {
    
    
    // 设置过期时间
    maxAge: 60*1000*60,
    // 配置可以访问的页面
    path: '/news',
    // 当有多个子域名的时候使用,也可以不写,默认是请求的域名
    domain: '.xxx.com',
    // true 表示这个 cookie 只有服务器端可以方位,false 表示客户端也可以访问
    httpOnly: true
  })
})

在设置 jt 这个cookie的时候,koa会以 jt 的值 abcd 加上设置的密钥,生成校验值,并写入至 jt.sig 这个cookie中。后续的请求中,获取 jt 这个cookie时,则会根据 jt.sig 的值判断是否合法,安全性上又明显提升。

当生成cookie时,使用keys中的第一个元素来生成,而校验的时候,是从第一个至最后一个,一个个的校验,直到通过为止,所以在更新密钥的时候,只需要把新的密钥加到数组第一位则可以。

中间件链式调用

中间件决定响应请求,并希望绕过下游中间件可以简单地省略 next()。通常这将在路由中间件中,但这也可以任意执行。例如,以下内容将以 “two” 进行响应,但是所有三个都将被执行,从而使下游的 “three” 中间件有机会操纵响应。

app.use(async function (ctx, next) {
    
    
  console.log('>> one');
  await next();
  console.log('<< one');
});

app.use(async function (ctx, next) {
    
    
  console.log('>> two');
  ctx.body = 'two';
  await next();
  console.log('<< two');
});

app.use(async function (ctx, next) {
    
    
  console.log('>> three');
  await next();
  console.log('<< three');
});

以下配置在第二个中间件中省略了next(),并且仍然会以 “two” 进行响应,然而,第三个(以及任何其他下游中间件)将被忽略:

app.use(async function (ctx, next) {
    
    
  console.log('>> one');
  await next();
  console.log('<< one');
});

app.use(async function (ctx, next) {
    
    
  console.log('>> two');
  ctx.body = 'two';
  console.log('<< two');
});

app.use(async function (ctx, next) {
    
    
  console.log('>> three');
  await next();
  console.log('<< three');
});

当最远的下游中间件执行 next(); 时,它实际上是一个 noop 函数,允许中间件在堆栈中的任意位置正确组合。

从整体上看,就像一个洋葱,一层层调用,使用此特性,可以在核心业务的前面或者后面做一些公共处理逻辑。

将多个中间件与 koa-compose 相结合

有时您想要将多个中间件 “组合” 成一个单一的中间件,便于重用或导出。你可以使用 koa-compose

npm install koa-compose --save
const compose = require('koa-compose');

async function random(ctx, next) {
    
    
  if ('/random' == ctx.path) {
    
    
    ctx.body = Math.floor(Math.random() * 10);
  } else {
    
    
    await next();
  }
};

async function backwards(ctx, next) {
    
    
  if ('/backwards' == ctx.path) {
    
    
    ctx.body = 'sdrawkcab';
  } else {
    
    
    await next();
  }
}

async function pi(ctx, next) {
    
    
  if ('/pi' == ctx.path) {
    
    
    ctx.body = String(Math.PI);
  } else {
    
    
    await next();
  }
}

const all = compose([random, backwards, pi]);

app.use(all);          

安装路由

参考:https://github.com/koajs/router/blob/master/API.md

# npm .. 
npm i @koa/router
# yarn .. 
yarn add @koa/router

基本用法

const Koa = require('koa');
const Router = require('@koa/router');

const app = new Koa();
const router = new Router();

router.get('/', (ctx, next) => {
    
    
  ctx.body = 'Hello router';
});

// 调用router.routes()来组装匹配好的路由,返回一个合并好的中间件
// 调用router.allowedMethods()获得一个中间件,当发送了不符合的请求时,会返回 `405 Method Not Allowed` 或 `501 Not Implemented`
app
  .use(router.routes())
  .use(router.allowedMethods());

统一加前缀

const router = new Router({
    
    
  prefix: '/users'
});

// router.prefix('/users')

其他method

router.get("/users", async (ctx) => {
    
    
    console.log('查询参数', ctx.query);
    ctx.body = '获取用户列表';
})
    .get("/:id", async (ctx) => {
    
    
        const {
    
     id } = ctx.params
        ctx.body = `获取id为${
      
      id}的用户`;
    })
    .post("/", async (ctx) => {
    
    
        ctx.body = `创建用户`;
    })
    .put("/:id", async (ctx) => {
    
    
        const {
    
     id } = ctx.params
        ctx.body = `修改id为${
      
      id}的用户`;
    })
    .del("/:id", async (ctx) => {
    
    
        const {
    
     id } = ctx.params
        ctx.body = `删除id为${
      
      id}的用户`;
    })
	// 所有类型的请求,如get/post
    .all("/users/:id", async (ctx) => {
    
    
        ctx.body = ctx.params;
    });

前端使用axios的get请求的参数较多时,使用$axios.get(‘indexData’, { params: { sex: 1, name:‘txj’ …} }),后端使用ctx.query.sex / ctx.query.name获取传参

传参

router.get('/:category/:title', (ctx, next) => {
    
    
  console.log(ctx.params);
  // => { category: 'programming', title: 'how-to-node' }
});

router.get("/users", async (ctx) => {
    
    
    console.log('查询参数', ctx.query);
    ctx.body = '获取用户列表';
})

路由嵌套

const forums = new Router();
const posts = new Router();

posts.get('/', (ctx, next) => {
    
    ...});
posts.get('/:pid', (ctx, next) => {
    
    ...});
forums.use('/forums/:fid/posts', posts.routes(), posts.allowedMethods());

// responds to "/forums/123/posts" and "/forums/123/posts/123"
app.use(forums.routes());
可以使用这个功能把大路由拆分为多个子路由,比如:
const router = new Router();
const router1 = new Router();
...
const router2 = new Router();
...
const router3 = new Router();
...

router.get('/', (ctx, next) => {
    
    ...});
router.use('/router1', router1.routes(), router1.allowedMethods());
router.use('/router2', router2.routes(), router2.allowedMethods());
router.use('/router3', router3.routes(), router3.allowedMethods());

app.use(router.routes());

上面的router1,router2,router3位于不同 的文件中

重定向

由center重定向至login

router.redirect('/center', 'login');

生成URL

// 给路由取个名字
router.get('user', '/users/:id', (ctx, next) => {
    
    
  // ...
});
// 调用url方法使用路由名字生成链接
router.url('user', 3);
// => "/users/3"

router.url('user', {
    
     id: 3 });
// => "/users/3"

router.use((ctx, next) => {
    
    
  // redirect to named route
  ctx.redirect(ctx.router.url('sign-in'));
})

router.url('user', {
    
     id: 3 }, {
    
     query: {
    
     limit: 1 } });
// => "/users/3?limit=1"

router.url('user', {
    
     id: 3 }, {
    
     query: "limit=1" });
// => "/users/3?limit=1"

解析body json参数

koa无法解析http请求体中的数据,这时我们需要引入另外一个模块叫做koa-bodyparser。获取前端传的json参数或者form提交的参数

npm install koa-bodyparser

eg:

const Koa = require('koa');
const bodyParser = require('koa-bodyparser');

const app = new Koa();
app.use(bodyParser());

app.use(async ctx => {
    
    
  // the parsed body will store in ctx.request.body
  // if nothing was parsed, body will be an empty object {}
  ctx.body = ctx.request.body;
});

解析post请求

router.post('/', async (ctx, next) 
	// 通过 ctx.request.body 获取表单提交的数据
	// 获取的 post 的 body 已经被转为对象
	console.log(ctx.request.body)     
})

session

浏览器访问服务器并发送第一次请求时,服务器端会创建一个 session 对象,生成一个类似于 key,value 的键值对, 然后将 key(cookie)返回到浏览器(客户)端,浏览器下次再访问时,携带 key(cookie),找到对应的 session(value)。 客户的信息都保存在 session 中。

npm install koa-session --save

参考:https://github.com/koajs/session

// 引入
const session = require('koa-session')

// 设置中间件
app.keys = ['some secret hurr']

const CONFIG = {
    
    
	key: 'koa:sess', //cookie key (default is koa:sess)
	maxAge: 86400000, // cookie 的过期时间 maxAge in ms (default is 1 days) 
	httpOnly: true, //cookie 是否只有服务器端可以访问 httpOnly or not (default true) signed: true, //签名默认 true
	rolling: false, //在每次请求时强行设置cookie,这将重置cookie过期时间(默认:false)
	renew: true, // 快过期的时候重新设置
}

app.use(session(CONFIG, app))

// 设置
router.get('/login', async (ctx) => {
    
    
  // 设置 session
  ctx.session.userInfo = 'zachary'
  ctx.body = '登录成功'
})

// 获取
router.get('/', async (ctx) => {
    
    
	console.log(ctx.session.userInfo)
})

原理就是在内存中生成一个 session 的散列表。每次生成一个随机的字符串,并将其作为 cookie 下发给客户端

更多koa中间件:https://github.com/koajs/koa/wiki#middleware

其他

node热更新

开发的时候每次修改都要手动重新 run 一次 node 会非常的麻烦。因此需要一个能自动重启的库的帮助。这个库就是 nodemon:

npm install -g nodemon

全局安装nodemon,然后启动的时候不要通过 node 了,而是 nodemon:

nodemon app.js

发送请求

使用koa 接收客户端请求。但是很多时候,我们需要对请求做转发。因此,需要使用 nodejs 发送请求。

发送请求可以使用库 request-promise。它是 request 的 promise 版本:

npm install --save request
npm install --save request-promise
# request 是 request-promise 的依赖项,需要自己手动安装

app.use(async (ctx, next) => {
    
    
	// 先让路由响应
  await next()
	// 如果没有一个路由相应
  if (ctx.status === 404) {
    
    
    const url = `https://xxx.cn${
      
      ctx.url}`
    try {
    
    
      // 尝试请求转发
      let obj = await request.get(url)
      ctx.body = obj
    } catch (err) {
    
    
      // 转发请求发送失败
      console.log('发生了error')
      console.log(err.statusCode)
    }
  }
})

await 中发生的 error 需要通过 try…catch 捕获。

通过package配置环境变量

在scripts中添加start项,后面的值为启动app.js的命令

"scripts": {
    
    
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "set NODE_ENV='production'&& node app.js" 
},

在Mac和Linux上使用export, 在windows上export要换成set;如果想要同时兼容多种操作系统可以安装cross-env

这样我们在运行npm run start的时候,就会自动设置NODE_ENV的值为production

console.log(process.env.NODE_ENV); // production

如果是多个变量:set NODE_ENV=‘development’ && set NODE_PORT=‘3000’ && node app.js

**敲黑板:**字符串比较的时候

// 这里一定要去空格,否则是有时相等有时又不相等,因为执行命令时后面的空格也会带上
const isProduction = process.env.NODE_ENV.trim() === 'production'

如果不想代码里去空格,则要如下配置(或者&&的前方不要有空格)

"scripts": {
    
    
    "start": "set \"NODE_ENV=production\" && node main.js"
 }

设置环境变量

node中的环境变量在 process.env 中保存,使用库 cross-env 可以方便设置兼容 windows 的环境变量:

npm install --save-dev cross-env

然后在package.json配置

"scripts": {
    
    
    "start": "cross-env NODE_ENV='production' ttt='txj' node main.js",
	"dev": "nodemon main.js"
  }
......
const app = new Koa();
......
console.log('-----env:',app.env) // production
console.log(process.env.NODE_ENV) // production
console.log(process.env.ttt) // txj

环境配置文件dotenv

Dotenv是一个零依赖模块,它将环境变量从.env文件加载到process.env中

npm install dotenv --save

这个.env文件不要提交至git/svn,放在根目录

# .env file
#
# Add environment-specific variables on new lines in the form of NAME=VALUE
# 
DB_HOST=localhost
DB_USER=root
DB_PASS=s1mpl3

读取环境变量

// index.js
require('dotenv').config()
console.log(process.env) // remove this after you've confirmed it working

如果需要指定目录:require(‘dotenv’).config({ path: ‘.env’ })

允许跨域

需要借助第三方的库 koa-cors 进行允许跨域设置:

npm install koa-cors
app.use(cors({
    
    
  origin: function (ctx) {
    
    
    if (ctx.url === '/cors') {
    
    
      return '*' // 允许来自所有域名请求
    }
    return 'http://localhost:3000'
  },
  exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'],
  credentials: true,
  allowMethods: ['GET', 'POST', 'DELETE'], // 设置允许的HTTP请求类型
  allowHeaders: ['Content-Type', 'Authorization', 'Accept']
}))

其中origin字段对应于 access-control-allow-origin,通过它设置哪些站点发起哪些请求可以进行跨域。请求路径为 /cors 的这个请求可以进行跨域,但是不能获取 cookie 信息,而 http://localhost:3000 这个域名下的所有请求都可以进行跨域,且可以获取 cookie 信息,是受信的站点。

如果设置了 access-control-allow-origin*,那么就是允许跨域了。但是跨域的客户端请求,无法携带该域名下的 cookie 信息给服务端。

必须设置 access-control-allow-origin 为一个特定的域名,而不是 *。这样 Access-Control-Allow-Credentials 才会被默认置位 true,才可以跨域使用 cookie

redis

npm install redis --save

pm2

让node应用在后台运行

进程守护,系统奔溃自动重启;启动多进程,充分利用 CPU 和内存;自带日志记录

npm install pm2 -g

启动

pm2 start app.js

开发环境下使用 nodemon,线上环境可以使用 pm2

常用命令:

# 查看进程列表
pm2 list
# 重启进程
pm2 restart <AppName>/<id>
# 停止进程
pm2 stop <AppName>/<id>
# 删除进程
pm2 delete <AppName>/<id>
# 查看进程信息
pm2 info <AppName>/<id>
# 查看日志(console.log/error/warn)
pm2 log <AppName>/<id>
# 监控
pm2 monit <AppName>/<id>

npm命令:

npm run dev  等同于  pm2 start  npm -- run dev
npm start  等同于 pm2 start npm -- start 

# 命名进程名
pm2 start  npm --name test -- run dev
pm2 start npm --name test -- start

监听:

语法:pm2 start npm --watch --name – run ;
#其中 – watch监听代码变化,-- name重命名任务名称,-- run后面跟脚本名字

执行命令安装完pm2之后可能会报错【-bash: pm2: command not found】

这个时候需要手动创建软链接

先看下环境变量path在哪

# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/usr/local/java/jdk1.8.0_40/bin:/usr/local/java/jdk1.8.0_40/jre/bin:/usr/local/ffmpeg/:/usr/local/apache-maven-3.3.9//bin:/root/bin

然后执行ln -s 【pm2的安装目录]】 【path目录,比如:/usr/local/sbin】

# 要注意path环境变量是在后面
ln -s /usr/local/node-v10.15.3-linux-x64/bin/pm2 /usr/local/sbin

这个时候在执行 pm2 list

pm2 list

                        -------------

__/\\\\\\\\\\\\\____/\\\\____________/\\\\____/\\\\\\\\\_____
 _\/\\\/\\\_\/\\\\\\________/\\\\\\__/\\\///\\\___
  _\/\\\_______\/\\\_\/\\\//\\\____/\\\//\\\_\///______\//\\\__
   _\/\\\\\\\\\\\\\/__\/\\\\///\\\/\\\/_\/\\\___________/\\\/___
    _\/\\\/____\/\\\__\///\\\/___\/\\\________/\\\//_____
     _\/\\\_____________\/\\\____\///_____\/\\\_____/\\\//________
      _\/\\\_____________\/\\\_____________\/\\\___/\\\/___________
       _\/\\\_____________\/\\\_____________\/\\\__/\\\\\\\\\\\\\\\_
        _\///______________\///______________\///__\///__

猜你喜欢

转载自blog.csdn.net/dan_seek/article/details/122541064