所有的URL处理函数都放到app.js
里显得很乱,而且,每加一个URL,就需要修改app.js
。随着URL越来越多,app.js
就会越来越长。
如果能把URL处理函数集中到某个js文件,或者某几个js文件中就好了,然后让app.js
自动导入所有处理URL的函数。这样,代码一分离,逻辑就显得清楚了。最好是这样:
url2-koa/ | +- .vscode/ | | | +- launch.json <-- VSCode 配置文件 | +- controllers/ | | | +- login.js <-- 处理login相关URL | | | +- users.js <-- 处理用户管理相关URL | +- app.js <-- 使用koa的js | +- package.json <-- 项目描述文件 | +- node_modules/ <-- npm安装的所有依赖包
于是我们把url-koa
复制一份,重命名为url2-koa
,准备重构这个项目。
我们先在controllers
目录下编写index.js
:
var fn_index = async (ctx, next) => { ctx.response.body = `<h1>Index</h1> <form action="/signin" method="post"> <p>Name: <input name="name" value="koa"></p> <p>Password: <input name="password" type="password"></p> <p><input type="submit" value="Submit"></p> </form>`; }; var fn_signin = async (ctx, next) => { var name = ctx.request.body.name || '', password = ctx.request.body.password || ''; console.log(`signin with name: ${name}, password: ${password}`); if (name === 'koa' && password === '12345') { ctx.response.body = `<h1>Welcome, ${name}!</h1>`; } else { ctx.response.body = `<h1>Login failed!</h1> <p><a href="/">Try again</a></p>`; } }; module.exports = { 'GET /': fn_index, 'POST /signin': fn_signin };
这个index.js
通过module.exports
把两个URL处理函数暴露出来。
类似的,hello.js
把一个URL处理函数暴露出来:
var fn_hello = async (ctx, next) => { var name = ctx.params.name; ctx.response.body = `<h1>Hello, ${name}!</h1>`; }; module.exports = { 'GET /hello/:name': fn_hello };
现在,我们修改app.js
,让它自动扫描controllers
目录,找到所有js
文件,导入,然后注册每个URL:
// 先导入fs模块,然后用readdirSync列出文件 // 这里可以用sync是因为启动时只运行一次,不存在性能问题: var files = fs.readdirSync(__dirname + '/controllers'); // 过滤出.js文件: var js_files = files.filter((f)=>{ return f.endsWith('.js'); }); // 处理每个js文件: for (var f of js_files) { console.log(`process controller: ${f}...`); // 导入js文件: let mapping = require(__dirname + '/controllers/' + f); for (var url in mapping) { if (url.startsWith('GET ')) { // 如果url类似"GET xxx": var path = url.substring(4); router.get(path, mapping[url]); console.log(`register URL mapping: GET ${path}`); } else if (url.startsWith('POST ')) { // 如果url类似"POST xxx": var path = url.substring(5); router.post(path, mapping[url]); console.log(`register URL mapping: POST ${path}`); } else { // 无效的URL: console.log(`invalid URL: ${url}`); } } }
如果上面的大段代码看起来还是有点费劲,那就把它拆成更小单元的函数:
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);
确保每个函数功能非常简单,一眼能看明白,是代码可维护的关键。
Controller Middleware
最后,我们把扫描controllers
目录和创建router
的代码从app.js
中提取出来,作为一个简单的middleware使用,命名为controller.js
:
const fs = require('fs'); function addMapping(router, mapping) { ... } function addControllers(router, dir) { ... } module.exports = function (dir) { let controllers_dir = dir || 'controllers', // 如果不传参数,扫描目录默认为'controllers' router = require('koa-router')(); addControllers(router, controllers_dir); return router.routes(); };
这样一来,我们在app.js
的代码又简化了:
... // 导入controller middleware: const controller = require('./controller'); ... // 使用middleware: app.use(controller()); ...
经过重新整理后的工程url2-koa
目前具备非常好的模块化,所有处理URL的函数按功能组存放在controllers
目录,今后我们也只需要不断往这个目录下加东西就可以了,app.js
保持不变。