目录
Node.js 是一个异步的世界,官方 API 支持的都是 callback 形式的异步编程模型,这会带来许多问题,例如:
1、callback 嵌套问题
2、异步函数中可能同步调用 callback 返回数据,带来不一致性。
为了解决以上问题 Koa 出现了。
Koa -- 基于 Node.js 平台的下一代 web 开发框架。
Koa 是由 Express 原班人马打造的,致力于成为一个更小、更富有表现力、更健壮的Web 框架。 使用 koa 编写 web 应用,可以免除重复繁琐的回调函数嵌套, 并极大地提升错误处理的效率。koa 不在内核方法中绑定任何中间件, 它仅仅提供了一个轻量优雅的函数库,使得编写 Web 应用变得得心应手。开发思路和 express 差不多,最大的特点就是可以避免异步嵌套。
安装:
cnpm install koa --save
1、一个简单例子
var Koa= require('koa');
var app = new koa();
app.use(async (ctx) => {
ctx.body = '这是一个简单的koa例子';
});
app.listen(8001);
2、koa-router
(1)、一个简单的例子
var Koa = require('koa');
var app = new Koa();
/**
* 1、安装
* cnpm install koa-router --save
*
* 2、引用和实例化
* var Router = require('koa-router');
* var router = new Router();
*
* 3、使用router.get('/', async (ctx) => {});
*
* 4、开启路由
* app.use(router.routes());
* app.use(router.allowedMethods());
*/
var Router = require('koa-router');
var router = new Router();
router.get('/', async (ctx) => {
ctx.body = '首页'; // 类似原生的 res.writeHead(), res.end('首页');
}).get('/news', async (ctx) => {
ctx.body = '这是一个新闻页面';
});
// 作用:开启路由
app.use(router.routes());
// 作用:这是官方文档的推荐用法,我们可以
// 看到 router.allowedMethods()用在了路由匹配 router.routes()之后,所以在当所有
// 路由中间件最后调用.此时根据 ctx.status 设置 response 响应头
app.use(router.allowedMethods());
app.listen(8001);
// 路由引用的默认方式
var Router = require('koa-router');
var router = new Router();
// 简单的写法
var router = require('koa-router')();
(2)、获取get传值
var Koa = require('koa');
var app = new Koa();
var router = require('koa-router')();
// get传值以及获取get传值
// http://127.0.0.1:8001//news?cid=123&aid=456
router.get('/news', async (ctx) => {
console.log(ctx.query); // { cid: '123', aid: '456' } // ******推荐使用这种
console.log(ctx.request.query); // { cid: '123', aid: '456' }
console.log(ctx.querystring); // cid=123&aid=456
console.log(ctx.request.querystring); // cid=123&aid=456
console.log(ctx.url); // /news?cid=123&aid=456
console.log(ctx.request.url); // /news?cid=123&aid=456
ctx.body = '这是一个新闻页面';
});
// 开启路由
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(8001);
(3)、动态传值
var Koa = require('koa');
var app = new Koa();
var router = require('koa-router')();
// 动态路由:
// http://127.0.0.1:8001/newscontent/123
router.get('/newscontent/:aid', async (ctx) => {
console.log(ctx.params); // { aid: '123' }
ctx.body = '这是一个新闻页面';
});
// http://127.0.0.1:8001/paper/123/456
router.get('/paper/:aid/:cid', async (ctx) => {
console.log(ctx.params); // { aid: '123', cid: '456' }
ctx.body = '这是一个新闻页面';
});
// 开启路由
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(8001);
3、中间件(有点类似Express的中间件)
(1)、应用级中间件
var Koa = require('koa');
var app = new Koa();
var Router = require('koa-router');
var router = new Router();
/**
* 应用级中间件,不管是放在 哪个位置,都会在访问路由之前被访问到,
* 一匹配到这个中间件,就会终止后面的程序,所以需要通过next()使得路由往下匹配
*/
app.use(async (ctx, next) => {
console.log(new Date());
next();
});
router.get('/', async (ctx) => {
ctx.body = '首页'; // 类似原生的 res.writeHead(), res.end('首页');
});
router.get('/news', async (ctx) => {
ctx.body = '这是一个新闻页面';
});
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(8001);
(2)、路由中间件
var Koa = require('koa');
var app = new Koa();
var Router = require('koa-router');
var router = new Router();
router.get('/', async (ctx) => {
ctx.body = '首页'; // 类似原生的 res.writeHead(), res.end('首页');
})
/**
* 路由中间件,在匹配路由前做判断用
*/
router.get('/news', async (ctx, next) => {
console.log('这是一个路由中间件');
next();
});
router.get('/news', async (ctx) => {
ctx.body = '这是一个新闻页面';
});
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(8001);
(3)、错误中间件
var Koa = require('koa');
var app = new Koa();
var Router = require('koa-router');
var router = new Router();
/**
* 错误处理中间件:比如在匹配路由的时候,如果匹配不到,则返回404,可以通过应用级中间件的next() 后来实现
*/
app.use(async (ctx, next) => {
console.log(ctx.url);
next();
// 当所有的路由匹配完之后,会进到这里来
if (ctx.status == 404) {
ctx.status = 404;
ctx.body = '这是一个404页面';
}
});
router.get('/', async (ctx) => {
ctx.body = '首页'; // 类似原生的 res.writeHead(), res.end('首页');
});
router.get('/news', async (ctx) => {
ctx.body = '这是一个新闻页面';
});
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(8001);
(4)、中间件的执行流程——洋葱圈(从外到里,在从里到外)
var Koa = require('koa');
var app = new Koa();
var Router = require('koa-router');
var router = new Router();
/**
* 中年件执行顺序:洋葱圈图:从外到里,再从里到外(类似堆栈,先进后出)
* 匹配路由的过程中,先匹配next()之前的信息,然后next(),再匹配路由信息,最后再匹配next()后面的信息
* 例如此例子打印顺序:
* 01 这是最先打印的信息
02 这是第二个打印的信息
03 这是第三个打印的信息
04 这是第四个打印的信息
05 这是最后打印的信息
*/
app.use(async (ctx, next) => {
console.log('01 这是最先打印的信息');
next();
console.log('05 这是最后打印的信息');
});
app.use(async (ctx, next) => {
console.log('02 这是第二个打印的信息');
next();
console.log('04 这是第四个打印的信息');
});
router.get('/', async (ctx) => {
ctx.body = '首页'; // 类似原生的 res.writeHead(), res.end('首页');
});
router.get('/news', async (ctx) => {
console.log('03 这是第三个打印的信息');
ctx.body = '这是一个新闻页面';
});
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(8001);
4、ejs模板
var Koa = require('koa');
var app = new Koa();
var router = require('koa-router')();
/**
* 1、安装koa-views和ejs
* cnpm install koa-views --save
* cnpm install ejs --save
*
* 2、引用,这里不需要引用ejs,会自动使用的
* var views = require('koa-views');
*
* 3、配置中间件,这里“__dirname”是指模板引用存放的目录
* 方式一:app.use(views(__dirname, { map: {html: 'ejs' }}))
* 方式二:app.use(views(__dirname, { extension: 'ejs' }))
*
* 4、使用:
* await ctx.render('index'); 注意,这种方式配置的模板文件扩展名是“html”
* await ctx.render('index.ejs'); 注意,这种方式配置的模板文件扩展名是“ejs”
*/
var views = require('koa-views');
// 配置第三方中间件
// app.use(views('05 koa_ejs模板引擎/views', { map: {html: 'ejs' }}));
app.use(views('05 koa_ejs模板引擎/views', { extensions: 'ejs' }));
// 全局的共享信息设置
// 给ctx.state设置全局的属性,这样每个页面都可以共拿到
app.use(async (ctx, next) => {
ctx.state = {
username: '张三',
sex: '男'
};
await next();
});
router.get('/', async (ctx) => {
// 异步改同步操作,注意*****
await ctx.render('index.ejs', {
msg: '这是一个ejs信息'
});
});
router.get('/news', async (ctx) => {
let list = ['1111','2222','3333'];
let hcode = '<h2>我是一段html代码</h2>'
await ctx.render('news.ejs', {
list: list,
hcode: hcode
});
});
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(8001);
// index.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
这里引用的方式和原生ejs相比,可以省略-
<%- include public/header.ejs %>
<% include public/header.ejs %>
这是一个ejs模板引擎——ejs
<h2><%=msg%></h2>
</body>
</html>
// new.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
这里引用的方式和原生ejs相比,可以省略-
<%- include public/header.ejs %>
<% include public/header.ejs %>
<p>
<div>这是通过全局设置(ctx.state)获取的属性</div>
username:<%= username %><br>
sex:<%= sex %><br>
</p>
这是一个新闻页面
<ul>
<% for (var i = 0; i < list.length; i++) { %>
<li><%= list[i] %></li>
<% } %>
</ul>
通过<%= hcode %>这种方式是这样效果:<br>
<%= hcode %><br><br>
通过<%- hcode %>这种方式是这样效果:<br>
<%- hcode %>
</body>
</html>
5、获取post传值
(1)、原生
var Koa = require('koa'),
app = new Koa(),
router = require('koa-router')(),
views = require('koa-views'),
common = require('./modules/common');
app.use(views('06 koa_psot_koa-bodyparser/views', { extensions: true }));
router.get('/', async (ctx) => {
// 异步改同步操作,注意*****
await ctx.render('index.ejs');
});
router.post('/doPost', async (ctx) => {
// ********注意
// 由于原生的写法是一个异步操作,而这里需要将异步变同步,所以可以通过封装成一个方法来实现
// await ctx.render('index.ejs');
let data = await common.getPostData(ctx);
ctx.res.writeHead(200, {'Content-Type': 'text/html;charset=utf-8'});
ctx.body = data; // username=admin&password=123456
});
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(8001);
// common.js
exports.getPostData = function(ctx) {
return new Promise( (resolve, reject) => {
try {
var str = '';
ctx.req.on('data', (chunk) => {
str += chunk;
});
ctx.req.on('end', (chunk) => {
resolve(str);
});
} catch (err) {
reject(err);
}
} );
}
(2)、koa-bodyparser
/**
*
* 1、安装
* cnpm install koa-bodyparser --save
*
* 2、引用
* var bodyParser = require('koa-bodyparser');
*
* 3、配置第三方中间件
* app.use(bodyParser());
*
* 4、获取post数据
* ctx.request.body;
* // {"username":"admin","password":"123456"}
*
*/
var Koa = require('koa'),
app = new Koa(),
router = require('koa-router')(),
views = require('koa-views'),
bodyParser = require('koa-bodyparser');
app.use(views('06 koa_psot_koa-bodyparser/views', { extensions: true }));
// 引用koa-bodyparser中间件
app.use(bodyParser());
router.get('/', async (ctx) => {
// 异步改同步操作,注意*****
await ctx.render('index.ejs');
});
router.post('/doPost', async (ctx) => {
ctx.body = ctx.request.body; // {"username":"admin","password":"123456"}
});
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(8001);
6、静态文件托管
/**
* 1、安装
* cnpm install koa-static --save
*
* 2、引用
* var static = require('koa-static');
*
* 3、配置中间件
* app.use(static('07 koa_koa-static/statics'));
*/
var Koa = require('koa'),
app = new Koa(),
router = require('koa-router')(),
views = require('koa-views'),
static = require('koa-static');
app.use(views('07 koa_koa-static/views', { extensions: true }));
// 引用koa-static中间件,可以配置多个,通过next()向下寻找
// http://127.0.0.1:8001/css/basic.css
// <img src="images/iphone8.jpg"> http://127.0.0.1:8001/images/iphone8.jpg
app.use(static('07 koa_koa-static/statics'));
// <img src="images/李伊利雅.jpg"> http://127.0.0.1:8001/images/李伊利雅.jpg
app.use(static('07 koa_koa-static/public'));
router.get('/', async (ctx) => {
// 异步改同步操作,注意*****
await ctx.render('index.ejs');
});
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(8001);
// index.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="css/basic.css">
</head>
<body>
<h2 class="title">这是一个h2</h2>
<form action="/doPost" method="post">
用户名:<input type="text" name="username">
<br>
<br>
密 码:<input type="password" name="password">
<br>
<br>
<input type="submit" value="提交">
</form>
<img src="images/iphone8.jpg">
<img src="images/李伊利雅.jpg">
</body>
</html>
目录结构:
7、高效的模板引擎art-template
/**
*
* 高性能模板引擎art-template,由腾讯开发,速度快,且直接ejs和angular的写法
*
* 1、安装
* cnpm install art-template --save
* cnpm install koa-art-template --save
*
* 2、引用
* var render = require('koa-art-template');
* var path = require('path');
*
* 3、配置
* render(app, {
root: path.join(__dirname, 'views'), // 配置模板引擎的位置,这里通过path拼接成绝对路径
extname: '.html', // 后缀名
debug: process.env.NODE_ENV !== 'production' // 是否开始调试模式
});
*
* 4、使用
* ` ctx.render('index');
*/
var Koa = require('koa'),
app = new Koa(),
router = require('koa-router')(),
render = require('koa-art-template'),
path = require('path');
render(app, {
root: path.join(__dirname, 'views'),
extname: '.html',
debug: process.env.NODE_ENV !== 'production'
});
// ejs写法
router.get('/', async (ctx) => {
var data = {
name: '这是ejs的写法',
h: '<h2>这是一个好</h2>',
num : 12,
list: [1111,2222,33333]
}
await ctx.render('index', {
data
});
});
// angular写法
router.get('/news', async (ctx) => {
var data = {
name: '这是angular的写法',
h: '<h2>这是一个好</h2>',
num : 12,
list: [1111,2222,33333]
}
await ctx.render('news', {
data
});
});
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(8001);
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="css/basic.css">
</head>
<br>
<h2 class="title">这是一个koa-art-template</h2>
<hr>
<br>
<%= data.name %>
<hr>
<br>
<%= 1 + 2 %>
<hr>
<br>
<%= data.h %>
<%- data.h %>
<hr>
<br>
<% if (data.num > 15) { %>
大于15
<% } else { %>
小于15
<% } %>
<hr>
<br>
<ul>
<% for (var i = 0; i < data.list.length; i++) { %>
<li><%= i %>-----<%= data.list[i] %></li>
<% } %>
</ul>
<hr>
<br>
这里引入外部文件,注意和ejs的区别,这里是带括号的
<%- include('./public/footer') %>
<hr>
<br>
</body>
</html>
// news.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="css/basic.css">
</head>
<br>
<h2 class="title">这是一个koa-art-template</h2>
<hr>
<br>
{{data.name}}
<hr>
<br>
{{1 + 2}}
<hr>
<br>
{{data.h}}
{{@ data.h}}
<hr>
<br>
{{ if data.num > 15 }}
大于15
{{ else }}
小于15
{{/if}}
<hr>
<br>
<ul>
{{each data.list}}
<li>{{$index}}----{{$value}}</li>
{{/each}}
</ul>
<hr>
<br>
这里引入外部文件,注意和ejs的区别,这里是带括号的
<%- include('./public/footer') %>
<hr>
<br>
</body>
</html>
8、Cookie
/**
*
* 不需要安装
*
* 1、设置cookie:ctx.cookie.set(name, value, options); // 这里options和Express的一样,只不过多了一个overwrite,但用处不大
*
* 2、获取cookie:ctx.cookie.get(name);
*
*/
var Koa = require('koa'),
app = new Koa(),
router = require('koa-router')(),
render = require('koa-art-template'),
path = require('path');
render(app, {
root: path.join(__dirname, 'views'),
extname: '.html',
debug: process.env.NODE_ENV !== 'production'
});
router.get('/', async (ctx) => {
ctx.cookies.set('userinfo', 'zhangsan', {
maxAge: 60 * 1000 * 60
});
await ctx.render('index');
});
router.get('/news', async (ctx) => {
var userinfo = ctx.cookies.get('userinfo');
await ctx.render('news', {
userinfo
});
});
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(8001);
由于koa的cookies无法设置成中文,所以这里可以通过Buffer对象,可以将中文转成base64;在获取的时候,可以再通过Buffer对象转成中文
router.get('/', async (ctx) => {
// 默认下koa是不允许设置中文的,可以通过将中文设置成base64,获取的时候再转成中文即可
let userinfo = new Buffer('张三').toString('base64');
ctx.cookies.set('userinfo', userinfo, {
maxAge: 60 * 1000 * 60
});
await ctx.render('index');
});
router.get('/news', async (ctx) => {
let data = ctx.cookies.get('userinfo');
let userinfo = new Buffer(data, 'base64').toString();
await ctx.render('news', {
userinfo
});
});
9、Session
/**
*
* 1、安装
* cnpm install koa-session --save
*
* 2、引用
* const session = require('koa-session');
*
* 3、配置Session
*
* app.keys = ['some secret hurr'];
*
* const CONFIG = {
key: 'koa:sess', // 默认
maxAge: 86400000, // 配置过期时间,【需要修改】
overwrite: true, // 无效果,默认即可
httpOnly: true, // true表示只能在服务端访问,默认
signed: true, // 加密,默认
rolling: false, // 是否每次访问的时候都强制刷新,【需要修改】
renew: false, // 在快要过期的时候访问时,刷新session,【需要修改】
};
*
* 4、设置中间件
* app.use(session(CONFIG, app));
*
* 5、设置 session
* ctx.session.userinfo = '张三';
*
* 6、获取 session
* ctx.session.userinfo
*/
var Koa = require('koa'),
app = new Koa(),
router = require('koa-router')(),
render = require('koa-art-template'),
path = require('path'),
session = require('koa-session');
render(app, {
root: path.join(__dirname, 'views'),
extname: '.html',
debug: process.env.NODE_ENV !== 'production'
});
// 这里要注意这是keys
app.keys = ['some secret hurr'];
const CONFIG = {
key: 'koa:sess', // 默认
maxAge: 5000, // 配置过期时间,【需要修改】
overwrite: true, // 无效果,默认即可
httpOnly: true, // true表示只能在服务端访问,默认
signed: true, // 加密,默认
rolling: true, // 是否每次访问的时候都强制刷新,【需要修改】
renew: true // 在快要过期的时候访问时,刷新session,【需要修改】
};
app.use(session(CONFIG, app));
router.get('/login', async (ctx) => {
ctx.session.userinfo = '张三';
ctx.body = '登录成功';
});
router.get('/', async (ctx) => {
console.log(ctx.session.userinfo);
await ctx.render('index');
});
router.get('/news', async (ctx) => {
var userinfo = ctx.session.userinfo;
await ctx.render('news', {
userinfo
});
});
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(8001);
10、路由模块化
通过引入外部的路由子模块文件(层级路由):“router.use('/', index)”的方式,来模块化路由,例如下面的这个例子:
// app.js
const Koa = require('koa'),
router = require('koa-router')(),
render = require('koa-art-template'),
path = require('path');
// 引入模块化的后台管理模块
var admin = require('./routers/admin');
// 引入模块化的api模块
var api = require('./routers/api');
// 引入模块化的前台模块
var index = require('./routers/default');
var app = new Koa();
render(app, {
root: path.join(__dirname, 'views'),
extname: '.html',
debug: process.env.NODE_ENV !== 'production'
});
/**
* 前台配置方式一:
* 这种配置方式,要求在模块中,不能写“/”:get('about')
* /
* /about
* /product
*/
// router.use('/', index);
/**
* 前台配置方式二:
* 这种配置方式,要求在模块中,要写“/”:get('/about')
*/
router.use(index);
/**
* /admin
* /admin/user
* ...
*/
router.use('/admin', admin.routes());
/**
* /api
* /api/newslist
* ...
*/
router.use('/api', api); // 在api模块中暴露,同时开始路由
app.use(router.routes()).use(router.allowedMethods());
app.listen(8001);
// default.js
// 这里要引入koa-router模块
var router = require('koa-router')();
// /
router.get('/', async ctx => {
await ctx.render('default/index');
});
/**
* 前台配置方式一:router.use('/', index);
*/
// // 注意,前台的配置和后台的配置不一样,页面通过
// // /about访问,不能写成'/about'
// router.get('about', async ctx => {
// await ctx.render('default/about');
// });
// // /product
// router.get('product', async ctx => {
// await ctx.render('default/index');
// });
/**
* 前台配置方式二:router.use(index);
*/
// 注意,这里和方式一不一样在于,需要写“/”
// /about
router.get('/about', async ctx => {
await ctx.render('default/about');
});
// /product
router.get('/product', async ctx => {
await ctx.render('default/index');
});
// 这类要在暴露这个路由模块的同时,开启路由
module.exports = router.routes();
// admin.js
// 路由模块化,子路由模块、层级路由
var router = require('koa-router')();
var user = require('./admin/user'),
focus = require('./admin/focus'),
newscate = require('./admin/newscate');
// /admin
router.get('/', async (ctx) => {
await ctx.render('admin/index');
});
// /admin/user
router.use('/user', user);
// /admin/focus
router.use('/focus', focus);
// /admin/newscate
router.use('/newscate', newscate);
// 暴露当前路由模块
module.exports = router;