koa-bootstrap--numjacks

搭建一个node-koa2全栈开发环境。

koa1.0 koa2.0 express 区别在哪?
koa1.0 generator可以把异步代码写成同步的样子。es6
koa2.0 promise和async解决异步。es7

入门:基本的koa使用

1
2
3
4
5
6
7
8
9
10
11
12
13
// 导入koa,和koa 1.x不同,在koa2中,我们导入的是一个class,因此用大写的Koa表示:
const Koa = require('koa');
// 创建一个Koa对象表示web app本身:
const app = new Koa();
// 对于任何请求,app将调用该异步函数处理请求:
app.use(async (ctx, next) => {
await next();
ctx.response.type = 'text/html';
ctx.response.body = '<h1>Hello, koa2!</h1>';
});
// 在端口3000监听:
app.listen(3000);
console.log('app started at port 3000...');

很简单。就是ctx中间件封装了request和response模块。

处理URL

假如我们要调用不同的接口,返回不同的内容。koa如何才能做这样的拦截。
很简单。就是request模块中有path。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
app.use(async (ctx, next) => {
if (ctx.request.path === '/') {
ctx.response.body = 'index page';
} else {
await next();
}
});
app.use(async (ctx, next) => {
if (ctx.request.path === '/test') {
ctx.response.body = 'TEST page';
} else {
await next();
}
});
app.use(async (ctx, next) => {
if (ctx.request.path === '/error') {
ctx.response.body = 'ERROR page';
} else {
await next();
}
});

这样写感觉代码太过于复杂。

引入koa-router

利用中间间处理url映射。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const Koa = require('koa');
// 注意require('koa-router')返回的是函数:
const router = require('koa-router')();
const app = new Koa();
// log request URL:
app.use(async (ctx, next) => {
console.log(`Process ${ctx.request.method} ${ctx.request.url}...`);
await next();
});
// add url-route:
router.get('/hello/:name', async (ctx, next) => {
var name = ctx.params.name;
ctx.response.body = `<h1>Hello, ${name}!</h1>`;
});
router.get('/', async (ctx, next) => {
ctx.response.body = '<h1>Index</h1>';
});
// add router middleware:
app.use(router.routes());
app.listen(3000);
console.log('app started at port 3000...');

这样写好多了。

但是这样写几乎框架没有可用性。越写越复杂化。
因为没有人会想在写完一个模块的时候,第二天找不到这个文件在哪了,更不说假如二次开发会带来什么后果。
所有的URL处理函数都放到app.js里显得很乱,而且,每加一个URL,就需要修改app.js。随着URL越来越多,app.js就会越来越长。
为了解决耦合度,必须通过解耦合的方式来处理。
于是,我们分层。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
url2-koa/
|
+- .vscode/
| |
| +- launch.json <-- VSCode 配置文件
|
+- controllers/
| |
| +- login.js <-- 处理login相关URL
| |
| +- users.js <-- 处理用户管理相关URL
|
+- app.js <-- 使用koa的js
|
+- package.json <-- 项目描述文件
|
+- node_modules/ <-- npm安装的所有依赖包

我们把app作为一个加载所有资源的文件。把他加载到koa框架中去。只要这个进程存在,用户访问时的路径就会被拦截下来。

注册url

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function addMapping(router, mapping) {
for (var url in mapping) {
if (url.startsWith('GET ')) {
var path = url.substring(4);
router.get(path, mapping[url]);
console.log(`register URL mapping: GET ${path}`);
} else if (url.startsWith('POST ')) {
var path = url.substring(5);
router.post(path, mapping[url]);
console.log(`register URL mapping: POST ${path}`);
} else {
console.log(`invalid URL: ${url}`);
}
}
}
function addControllers(router) {
var files = fs.readdirSync(__dirname + '/controllers');
var js_files = files.filter((f) => {
return f.endsWith('.js');
});

for (var f of js_files) {
console.log(`process controller: ${f}...`);
let mapping = require(__dirname + '/controllers/' + f);
addMapping(router, mapping);
}
}
addControllers(router);

上面已经做了代码简化。

全栈目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
view-koa/
|
+- .vscode/
| |
| +- launch.json <-- VSCode 配置文件
|
+- controllers/ <-- Controller
|
+- views/ <-- html模板文件
|
+- static/ <-- 静态资源文件
|
+- controller.js <-- 扫描注册Controller
|
+- app.js <-- 使用koa的js
|
+- package.json <-- 项目描述文件
|
+- node_modules/ <-- npm安装的所有依赖包

再弄个页面文件就好啦。

MVC

调用ctx.render(view, model)完成页面输出。
如何才能做真实的登录访问呢。首先,登录后返回cookie给前端页面。
往后每次调用一个后端参数,都需要带上cookie做登录访问。

bootstrap

1
2
3
4
5
6
7
8
9
view-koa/
|
+- static/
|
+- css/ <- 存放bootstrap.css等
|
+- fonts/ <- 存放字体文件
|
+- js/ <- 存放bootstrap.js等

查找范围文件的中间件.并返回body中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const path = require('path');
const mime = require('mime');
const fs = require('mz/fs');
// url: 类似 '/static/'
// dir: 类似 __dirname + '/static'
function staticFiles(url, dir) {
return async (ctx, next) => {
let rpath = ctx.request.path;
// 判断是否以指定的url开头:
if (rpath.startsWith(url)) {
// 获取文件完整路径:
let fp = path.join(dir, rpath.substring(url.length));
// 判断文件是否存在:
if (await fs.exists(fp)) {
// 查找文件的mime:
ctx.response.type = mime.lookup(rpath);
// 读取文件内容并赋值给response.body:
ctx.response.body = await fs.readFile(fp);
} else {
// 文件不存在:
ctx.response.status = 404;
}
} else {
// 不是指定前缀的URL,继续处理下一个middleware:
await next();
}
};
}

module.exports = staticFiles;

集成Nunjucks

集成Nunjucks实际上也是编写一个middleware,这个middleware的作用是给ctx对象绑定一个render(view, model)的方法,这样,后面的Controller就可以调用这个方法来渲染模板了。
我们创建一个templating.js来实现这个middleware:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
const nunjucks = require('nunjucks');
function createEnv(path, opts) {
var
autoescape = opts.autoescape === undefined ? true : opts.autoescape,
noCache = opts.noCache || false,
watch = opts.watch || false,
throwOnUndefined = opts.throwOnUndefined || false,
env = new nunjucks.Environment(
new nunjucks.FileSystemLoader(path || 'views', {
noCache: noCache,
watch: watch,
}), {
autoescape: autoescape,
throwOnUndefined: throwOnUndefined
});
if (opts.filters) {
for (var f in opts.filters) {
env.addFilter(f, opts.filters[f]);
}
}
return env;
}
function templating(path, opts) {
// 创建Nunjucks的env对象:
var env = createEnv(path, opts);
return async (ctx, next) => {
// 给ctx绑定render函数:
ctx.render = function (view, model) {
// 把render后的内容赋值给response.body:
ctx.response.body = env.render(view, Object.assign({}, ctx.state || {}, model || {}));
// 设置Content-Type:
ctx.response.type = 'text/html';
};
// 继续处理请求:
await next();
};
}
module.exports = templating;

app.js 处理过的中间件。加载过程如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
第一个middleware是记录URL以及页面执行时间:
app.use(async (ctx, next) => {
console.log(`Process ${ctx.request.method} ${ctx.request.url}...`);
var
start = new Date().getTime(),
execTime;
await next();
execTime = new Date().getTime() - start;
ctx.response.set('X-Response-Time', `${execTime}ms`);
});
第二个middleware处理静态文件:
if (! isProduction) {
let staticFiles = require('./static-files');
app.use(staticFiles('/static/', __dirname + '/static'));
}
第三个middleware解析POST请求:
app.use(bodyParser());
第四个middleware负责给ctx加上render()来使用Nunjucks:
app.use(templating('view', {
noCache: !isProduction,
watch: !isProduction
}));
最后一个middleware处理URL路由:
app.use(controller());

文章出处:
https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/001434501579966ab03decb0dd246e1a6799dd653a15e1b000

坚持原创技术分享,您的支持将鼓励我继续创作!