# Koa2

# 简单启动

npm i koa2 --save

const koa = require("koa2");
const app = new koa();
app.use(async ctx=>{
  ctx.body="hello world";
})
app.listen(3000)

# 不简单的启动文件

const Koa = require("koa2");
const helmet = require("koa-helmet");
const route = require("koa-route");
const cors = require("koa-cors");
const moment = require("moment");
const koajwt = require("koa-jwt");
const compress = require("koa-compress");
const koaBody = require("koa-body");
const serve = require("koa-static");
const path = require("path");
const config = require("./config/index.js");
const router = require("./controller/index.js");
const app = new Koa();

app.use(
  cors({
    allowMethods: ["GET", "POST", "DELETE", "PUT", "PATCH"],
  })
);
app.use(helmet());
app.use(async (ctx, next) => {
  if (ctx.method === "OPTIONS") {
    ctx.body = 200;
  } else {
    await next();
  }
});

app.use(serve(__dirname + "/public"));

app.use(
  koaBody({
    formLimit: "10mb",
    jsonLimit: "10mb",
    textLimit: "10mb",
  })
);

app.use(function(ctx, next) {
  return next().catch((err) => {
    if (err.status === 401) {
      ctx.status = 401;
      ctx.body = "err";
    } else {
      throw err;
    }
  });
});
app.use(
  koajwt({
    secret: config.tokenSecret,
    debug: true,
    passthrough: true,
  }).unless({
    path: ["/articles"], // [/^\/login/]
  })
);

app.use(router.auth); // auth权限控制
app.use(router.post("/main"), router.main);
app.use(router.post("/doLogin"), router.doLogin);
// app.use(compress()); // 对资源文件进行压缩
app.listen(3000);

# auth 权限控制

const auth = async (ctx, next) => {
  // 这可以放在config
  const lengthPath = ["/doLogin", "/insertUser", "/articles", "/main"];
  /**
   * ctx.request.path
   * ctx.url
   * ctx.state.user
   * ctx.response.status
   */

  for (let i = 0; i < lengthPath.length; i++) {
    if (ctx.request.path === lengthPath[i]) {
      await next();
      return;
    }
  }
  if (!ctx.state.user) {
    ctx.response.status = 401;
    ctx.response.body = "请先登录";
  } else {
    await next();
  }
};

# 常用的依赖包安装

koa-bodyparser, koa-router, koa-views + ejs, koa-static, koa-session, koa-jwt, koa-helmet, koa-compress, koa-logger, koa-convert, koa-compose, koa-http-request, koa-conditional-get, koa-csrf, koa-ejs, koa-etag, koa-favicon, koa-generic-session, koa-onerror, koa-redis, koa-resource-router, koa-rewrite, koa-rt, koa-safe-jsonp, koa-static-cache

# 解析请求体

  • koa-bodyparser form-data 类型不太方便
  • 其他插件body-parserkoa-body
const Koa = require("koa2");
const app = new Koa();
const bodyParser = require("koa-bodyparser");
app.use(bodyParser());
const htmlText = (body) => {
  return `
      <!DOCTYPE html>
      <html lang="en">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>demo</title>
      </head>
      <body>
        ${body}
      </body>
      </html>
      `;
};
app.use(async (ctx) => {
  if (ctx.url === "/" && ctx.method === "GET") {
    let html = htmlText("<h1>body parser demo</h1>");
    ctx.body = html;
  } else if (ctx.url === "/" && ctx.method === "POST") {
    let postData = ctx.request.body;
    console.log(postData);
    ctx.body = postData;
  } else {
    ctx.body = htmlText("<h1>404</h1>");
  }
});
app.listen(3000);

tips: koa-body 会与 koa-bodyParser 冲突。

# detectJSON
app.use(
  bodyParser({
    detectJSON: function(ctx) {
      return /\.json$/i.test(ctx.path);
    },
  })
);
# extendTypes
app.use(
  bodyParser({
    extendTypes: {
      json: ["application/x-javascript"], // application/json  multipart/form-data
    },
  })
);
# onerror
app.use(
  bodyParser({
    onerror: function(err, ctx) {
      ctx.throw("body parse error", 422);
    },
  })
);
# disableeBodyParser
app.use(async (ctx, next) => {
  if (ctx.path === "/disable") ctx.disableBodyParser = true;
  await next();
});
app.use(bodyParser());

# 可以给项目加中间件

// 添加params属性,方便获取get或post请求参数
app.use(async (ctx, next)=>{
  ctx.params = {
    ...ctx.request.body,
    ...ctx.query,
  }
  await next();
})

# 路由

  • koa-router
const Koa = require("koa2");
const Router = require("koa-router");
const bodyParser = require("koa-bodyparser");
const app = new Koa();
app.use(bodyParser())
const router = new Router();
// post delete put get
router.get("/", async (ctx)=>{
  ctx.body="首页"})
// 启动路由
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(3000)

# get 请求获取参数

Get 传值通过request接收,但接收方法有两个: query 和 querystring

  • query:返回的是格式化好的参数对象
  • querystring: 返回的是请求字符串
// /newlink?aid=123&&name=zhangsan
console.log(ctx.request.url);
console.log(ctx.request.query);   //{ aid: '123', name: 'zhangsan' }  对象
console.log(ctx.request.querystring);   //aid=123&name=zhangsan

# 设置路由可以跨域

koa-cors

# 模板

koa-views ejs

# 静态资源处理

koa-static

# session 验证

koa-session session 是一种记录客户状态的机制,和 Cookie 不同的是,Cookie 是在客户端浏览器,而 session 是保存在服务器的。

# Session 工作流程

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

# session 使用

npm i koa-session --save

文档

app.keys = ["some secret hurr"];
const CONFIG = {
  key: "koa:sess", // cookie key (default is koa:sess)
  maxAge: 86400000, // cookie的过期时间 (default is 1 days)
  overwrite: true, // 是否可以overwrite
  httpOnly: true, // cookie是否只有服务器端可以访问(default true)
  signed: true, // 签名默认true
  rolling: false, // 在每次请求时强行设置cookie,这将重置cookie过期时间
  renew: false,
};
app.use(session(CONFIG, app));

使用

设置值 ctx.session.username="周三"
获取值 ctx.session.username
  1. cookie 数据存放在客户的浏览器上,session 数据放在服务器上
  2. cookie 会被窃取,所以应该使用 session
  3. session 会在一定时间内保存在服务器,访问增多会占用服务器的性能,考虑服务器性能方面的压力,应适当使用 cookie
  4. 单个 cookie 保存的数据不能超过 4k, 最多不超过 20 个 cookie

# token 验证

  • koa-jwt

# koa-jwt主要知识点

  1. 主要提供路由权限控制功能,对需要限制的资源请求进行检查。
  2. token 默认被携带在 Headers 中的名为Authorization键值对中。
  3. 可以提供 Cookie 来提供令牌。
  4. 通过添加passthrough选项来保证始终传递到下一个中间件 app.use(jwt({secret: "shared-secret",passthrough:true}))
  5. 使用ctx.key来表示解码数据,然后就可以通过ctx.state.jwtdata代替ctx.state.user获得解码数据.
  6. secret的值可以使用函数代替,以此来产生动态的加密密钥
  7. koa-jwt依赖于jsonwebtokenkoa-unless两个库

# 可选择参数

declare function jwt(options: jwt.Options): jwt.Middleware;
declare namespace jwt {
  export interface Options {
    secret: string | Buffer;
    key?:string;
    tokenKey?: string;
    getToken?(opts: jwt.Options): string;
    isRevoked?(ctx: Koa.Context, decodedToken: object, token: string):Promise<boolean>;
    passthrough?: boolean;
    cookie?: string;
    debug?: boolean;
    audience?: string;
    issuer?: string;
    algorithms?: string[];
  }
  export interface Middlware extends Koa.Middleware {
    unless(params?:any): any;
  }
}

# 网络安全

  • koa-helmet helmet通过增加如:Strict-Transport-Security,X-Frame-Options,X-Frame-Options等 HTTP 头,提高Express应用程序的安全性。

# 压缩响应体

koa-compress

# 输出请求日志

  1. 日志大体分为访问日志和应用日志。 访问日志记录客户端对项目的访问,主要是 http 请求,这些属于运营数据,也可以帮助改进和提升网站的性能和用户体验。 应用日志是项目中需要特殊标记和记录位置打印日志,包括出现异常的地方,方便开发查询项目的运行和定位 bug。
  2. 日志记录什么东西:接口前后,重要方法,容易出处的地方,业务需求的埋点。 注意:手机号,银行卡,身份证等敏感信息不能打到日志里,用唯一标识找出错误信息。

# 常用插件

  • log4js
  • koa-logger

# log4js

# 日志级别【从高到底】

在应用中按照级别记录日志,日后可以按照指定级别输出高于指定级别的日志。 off, mark, fatal, error, warn, info, debug, trace, all

# 应用
  1. 依赖安装
npm i log4js --save
npm i ip --save
  1. 文件结构
// ./middleware/index.js
// ./middleware/mi-log/logger.js
// ./middleware/mi-log/access.js
// ./middleware/mi-log/index.js

# koa-logger

npm i koa-logger --save
const logger = require("koa-logger");
const Koa = require("koa");
const app = new Koa();
app.use(logger())

# 数据库

mongoose

# 缓存

ioredis

# 文档

jsdoc

# 常用的文件结构

├── node_modules
├── package-lock.json
├── package.json
└── src
    ├── app.js  主入口
    ├── config   全局用的参数,如:数据库地址,用户名,地址,端口号等等
    │   └── index.js
    ├── controller   具体业务编写
    │   └── controllerUser.js
    ├── middleware   中间层,每个接口都经过这里
    │   ├── auth.js
    │   ├── index.js
    │   └── log.js
    ├── models    数据库模型或URI接口转接
    │   ├── logs.js
    │   └── user.js
    ├── plugin   插件
    │   ├── mongodb.js
    │   └── redis.js
    ├── public    公共资源
    ├── route     路由分发
    │   ├── routeIndex.js
    │   └── routeUser.js
    ├── test   测试
    |   └── index.spec.js
    ├── util   方法库
    └── view   模板
        ├── index.ejs
        └── layout.ejs