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。
- 引用外部存储
redisConfig = { host: '127.0.0.1', port: 6379, password: '', db: 1 };
sessionConfig = {
key: 'sessionId',
maxAge: 60 * 60 * 1000,
signed: true,
store: new RedisStore(redisConfig) // 这里引入外部存储,需要按照规范提供一个类对象
};
- 存储类定义
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));