Koa学习笔记

103 阅读2分钟

Koa介绍

Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。 Koa 并没有捆绑任何中间件, 而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序。

基本使用

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

app.use(ctx => {
  ctx.body = 'Hello World'
})

app.listen(4000, () => {
  console.log('server is listening at 4000');
})

功能列表

路由(Router)

const Router = require('koa-router');
const router = new Router();

router.get('/', async (ctx) => {
  ctx.body = 'root';
});

app.use(router.routes());
app.use(router.allowedMethods());

缓存(Cookie)

router.get('/setCookie', async (ctx) => {
  // 设置Cookie
  ctx.cookies.set('id', '123456', {
    maxAge: 60 * 60 * 1000
  })
  ctx.body = 'cookie set success'
})

router.get('/getCookie', async (ctx) => {
  // 获取Cookie
  const id = ctx.cookies.get('id')
  ctx.body = `cookie id:${id}`
})

会话(Session)

使用Cookie存储

const session = require('koa-session');

router.post('/login', async (ctx) => {
  const data = ctx.request.body;
  // koa-session在ctx中添加了session对象
  if (ctx.session.username) {
    ctx.body = `Welcome, ${ctx.session.username}`;
  } else {
    ctx.session = data;
    ctx.body = `Login Success!`;
  }
});

app.keys = ['secret keys'];
sessionConfig = {
  key: 'sessionId', // Cookie字段
  maxAge: 60 * 60 * 1000, // 过期时间
  signed: true // 默认true,使用app.keys对session内容进行签名,会在Cookie中生成sessionId.sig字段
};
app.use(session(sessionConfig, app));

使用外部存储

Session放在Cookie中既不安全,服务端也无法干预,实际使用时Session都存放在服务端。服务端的Session存储可以使用数据库也可以使用Redis缓存,一般使用Redis。

  1. 引用外部存储
redisConfig = { host: '127.0.0.1', port: 6379, password: '', db: 1 };
sessionConfig = {
  key: 'sessionId',
  maxAge: 60 * 60 * 1000,
  signed: true,
  store: new RedisStore(redisConfig) // 这里引入外部存储,需要按照规范提供一个类对象
};
  1. 存储类定义
const Redis = require('ioredis');

class RedisStore {
  constructor(redisConfig) {
    this.redis = new Redis(redisConfig);
  }
  // 要求实现get,set,destory三个异步方法
  async get(key) {
    const data = await this.redis.get(`SESSION:${key}`);
    return JSON.parse(data);
  }

  async set(key, sess, maxAge) {
    return await this.redis.set(`SESSION:${key}`,JSON.stringify(sess),'EX',maxAge / 1000);
  }

  async destroy(key) {
    return await this.redis.del(`SESSION:${key}`);
  }
}

跨域处理(CORS)

const cors = require('@koa/cors');

app.use(
  cors({
    origin: '*',
  })
);

Token认证(JWT)

const { sign } = require('jsonwebtoken');
const jwt = require('koa-jwt');

// 用于token签名
const secret = 'my secret';

router.post('/login', async (ctx) => {
  const { username } = ctx.request.body;
  // 创建token
  const token = sign({ username }, secret, { expiresIn: '1h' });
  ctx.body = {
    code: 1000,
    msg: 'success',
    token,
  };
});

// 校验token,unless中路径不需要校验
app.use(jwt({ secret }).unless({ path: ['/', '/login'] }));

日志记录(Logger)

const path = require('path');
const log4js = require('log4js');

log4js.configure({
  // 适配器
  appenders: {
    out: {
      type: 'stdout',
    },
    access: {
      type: 'dateFile',
      filename: path.join(__dirname, 'access'),
      alwaysIncludePattern: true, // 将pattern拼接到文件路径,此处为access.2022-04-18-17.log
      pattern: 'yyyy-MM-dd-hh.log',
    },
  },
  // 日志类型
  categories: {
    default: { appenders: ['out'], level: 'info' },
    access: { appenders: ['access'], level: 'info' },
  },
});

const defaultLogger = log4js.getLogger();
const accessLogger = log4js.getLogger('access');

defaultLogger.info('default log');
accessLogger.info('access log');

静态文件(Static)

const static = require('koa-static');
app.use(static(path.resolve(__dirname, '../static')));

模板渲染(Template)

const views = require('koa-views');
app.use(views(path.resolve(__dirname, '../static'), {extension: 'pug'}));

app.use(async (ctx) => {
  await ctx.render('index', {
    title: 'koa',
  });
});

文件上传(Upload)

前端

<body>
  <input type="file" />
  <button id="upload">点击上传</button>
  <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.26.1/axios.js"></script>
  <script>
    document.querySelector('#upload').onclick = function () {
      // 获取文件对象
      const file = document.querySelector('input').files[0];
      // 构建表单数据
      const form = new FormData();
      form.append('file', file);
      // 上传表单数据
      axios.post('/upload', form).then((response) => {
        console.log(response.data);
      });
    };
  </script>
</body>

后端

const koaBody = require('koa-body');
app.use(
  koaBody({
    multipart: true, // 允许文件上传
    formidable: {
      maxFileSize: 200 * 1024 * 1024, // 上传文件大小限制
    },
  })
);
router.post('/upload', async (ctx) => {
  // 获取文件对象
  const file = ctx.request.files.file;
  // 读取文件数据(文件上传后会自动放在临时目录/var/folders/00xxxx)
  const data = fs.readFileSync(file.path);
  // 存储文件数据(把文件数据重新写入指定路径)
  fs.writeFileSync(path.resolve(__dirname, file.name), data);
    ctx.body = { message: '上传成功' };
  });

文件下载(Download)

前端

<body>
  <button id="download">立即下载</button>
  <script>
    document.querySelector('#download').onclick = function () {
      window.open('/download');
    };
  </script>
</body>

后端

const koaSend = require('koa-send');
router.get('/download', async (ctx) => {
  const path = `static/1.jpg`;
  // 设置content-disposition: attachment; filename="1.jpg" 文件是附件的形式,提示前端下载而不是展示
  ctx.attachment(path);
  // 发送文件
  await koaSend(ctx, path);
});

外部缓存(Redis)

const Redis = require('ioredis');

redisConfig = { host: '127.0.0.1', port: 6379, password: '', db: 1 };
app.use(async (ctx, next) => {
  ctx.redis = new Redis(redisConfig);
  await next();
});

router.get('/setName', async (ctx) => {
  await ctx.redis.set('name', 'zhangsan');
  ctx.body = 'set name success!';
});

router.get('/getName', async (ctx) => {
  const name = await ctx.redis.get('name');
  ctx.body = `get name ${name}`;
});

辅助工具

合并中间件(koa-compose)

const compose = require('koa-compose');

async function midwareOne(ctx, next) {
  console.log('midware one before');
  await next();
  console.log('midware one after');
}

async function midwareTwo(ctx, next) {
  console.log('midware two before');
  await next();
  console.log('midware two after');
}

app.use(compose([midwareOne, midwareTwo]));

合并路由(koa-combine-routers)

const Router = require('koa-router');
const combineRouters = require('koa-combine-routers');

const routerOne = new Router();
const routerTwo = new Router();

routerOne.get('/one', async (ctx) => {
  ctx.body = 'one';
});

routerTwo.get('/two', async (ctx) => {
  ctx.body = 'two';
});

const router = combineRouters(routerOne, routerTwo);

app.use(router());

美化响应JSON(koa-json)

JSON格式美化后会添加额外的换行、空格等字符,增大响应体积,生产中默认关闭。

const json = require('koa-json');
// 访问 /info时不美化,访问 /info?pretty时美化
app.use(json({ pretty: false, param: 'pretty' }));

router.get('/info', async (ctx) => {
  ctx.body = {
    name: 'zhangsan',
    age: 20,
  };
});

旧版本中间件转换(koa-convert)

koa2.x之前使用生成器generator构造中间件,2.x以后使用Promise构造中间件,所以有时需要把生成器中间件转换为Promise中间件。

const convert = require('koa-convert');

function* generatorMiddleware(next) {
  console.log('generator middleware before');
  yield next;
  console.log('generator middleware after');
}

async function promiseMiddleware(ctx, next) {
  console.log('promise middleware before');
  await next();
  console.log('promise middleware after');
}

app.use(convert(generatorMiddleware));
app.use(promiseMiddleware);

// convert.compose:将新旧版本中间件一起合并
// app.use(convert.compose(generatorMiddleware, promiseMiddleware));