Section-4 Koa 框架的控制器以及设计更合理的目录结构
Lesson-1 控制器简介
什么是控制器
- 拿到路由分配的任务,并执行
- 在Koa中,是一个中间件
为什么要控制器?
- 获取 HTTP 请求参数
- 处理业务逻辑
- 发送 HTTP 响应
获取 HTTP 请求参数
- Query String,如?q=keyword,是可选项
- Router Params,如/user/:id,是必选项
- Body,如{name: "李雷"},请求体,一般为json
- Header,如Accept、Cookie
发送 HTTP 响应
- 发送 Status,如 200/400 等
- 发动 Body,如{name: "李雷"}
- 发送 Header,如 Allow、 Content-type
编写控制器最佳实践
- 每个资源的控制器放在不同的文件里
- 尽量使用类+类方法的形式编写控制器
- 严谨的错误处理
Lesson-2 获取 HTTP 请求参数
操作步骤
- 学习断点调试
- 获取 query
- 获取 router params
- 获取 body
- 获取 header
调试使用的是vscode,点击F5,进入调试(这里注意一下,不要自己在其他地方执行npm start,也不需要自己来执行这个命令行启动服务器,当进入调试的时候,将会自动启动服务器,否则会出现冲突报错)
在git里先启动了服务,调试的时候会报错
进入正常调试的vscode界面
获取 query
也就是获取请求中 ? 后面的参数
请求地址为 localhost:3000/user/master?query=彩蛋
,获取代码为 ctx.query
获取 router params
这个前面一直出现,也就是获取路径后的参数
请求地址为 localhost:3000/user/master?query=彩蛋
,获取代码为 ctx.params
获取 body
获取请求体,一般我们常用格式为json。这里需要安装一个 koa-bodyparser 插件,否则获取不到。不过后续使用的时候其实会有问题,参考文章 koa2 使用 koa-body 代替 koa-bodyparser 和 koa-multer,不过为了不增加目前难度,就还是使用的koa-bodyparser。顺便说一下,全局引用中间件,就是app.use(中间件),否则就是路由级引用,也就是router.use('路由地址', 中间件)
执行npm i koa-bodyparser -S
// index.js
const bodyparser = require('koa-bodyparser');
app.use(bodyparser());
请求地址为 localhost:3000/user
,请求方法为 POST ,获取代码为 ctx.request.body
image.png
获取 header
获取请求头,依旧使用刚才获取请求体的内容,这样能看到请求的content-type
请求地址为 localhost:3000/user,请求方法为 POST ,获取代码为 ctx.request.header
Lesson-3 发送 HTTP 响应
操作步骤
- 发送 status
- 发送 body
- 发送 header
- 实现用户的增删改查
之前已经使用过的,我这里就不再重复上代码了,简单带过去
发送status
设置body.status = 204
,就是设置响应为204状态码
发送body
设置ctx.body = '这是设置body'
,就是设置响应body,内容为'这是设置body'
发动 header
使用ctx.set方法,以之前的代码为例,让人家知道,user除了有GET方法,还有POST方法
// 获取用户列表
userRouter.get('/', (ctx) => {
ctx.set('Allow', 'GET, POST'); // 设置请求头
ctx.body = [
{name: '韩梅梅'},
{name: '李蕾'}
];
});
设置响应头
实现用户的增删改查
这里我就不放 postman 验证截图了,只上代码,后面会加上 mongoDB,不过现在还没有,所以先使用的内存数据库(其实就是变量)
const Koa = require('koa');
const bodyparser = require('koa-bodyparser');
const Router = require('koa-router');
const app = new Koa();
const userRouter = new Router({prefix: '/user'});
// 内存数据库
const db = [{name: '李雷'}];
// 获取用户列表
userRouter.get('/', (ctx) => {
ctx.body = db;
});
// 增加用户
userRouter.post('/', (ctx) => {
db.push(ctx.request.body);
ctx.body = ctx.request.body;
});
// 获取特定用户
userRouter.get('/:id', (ctx) => {
ctx.body = db[+ctx.params.id];
});
// 修改特定用户
userRouter.put('/:id', (ctx) => {
db[+ctx.params.id] = ctx.request.body;
ctx.body = ctx.request.body;
});
// 删除用户
userRouter.delete('/:id', (ctx) => {
db.splice(+ctx.params.id, 1);
ctx.status = 204; // 没有内容,但是成功了
});
app.use(bodyparser());
app.use(userRouter.routes());
app.use(userRouter.allowedMethods());
app.listen(3000, () => {
console.log(`start server...`);
});
Lesson-4 更合理的目录结构
操作步骤
- 将路由单独放在一个目录
- 将控制器单独放在一个目录
- 使用类 + 类方法的方式组织控制器
重构文件目录
现在的想法是这样,创建app目录,原本根目录下的index.js文件移动到app里面去,作为总入口文件
创建routes文件夹,里面用于存放所有页面的路由,并导出
创建controllers文件夹,里面用于存放对应页面的控制器(控制器其实就是中间件,也就是方法),并使用类 + 类方法导出的形式来进行维护
文件目录现在变为这样
|- 根目录
|- app
|- routes
|- home.js -- 主页路由
|- users.js -- 用户列表页路由
|- index.js -- 批量读取文件,并批量注册到app上
|- controllers
|- home.js -- 主页控制器
|- users.js -- 用户列表页控制器
|- node_modules
|- LICENSE
|- package-lock.json
|- package.json
|- README.md
使用类 + 类方法的形式导出中间件
现在将用户列表页相关的中间件全部提取出来,创建一个 UserCtl 类,里面有所有增删改查的方法,主页也一样,不重复展示代码
// 内存数据库,测试使用
const db = [{name: '李雷'}];
class UsersCtl {
find (ctx) {
ctx.body = db;
}
findId (ctx) {
ctx.body = db[+ctx.params.id];
}
create (ctx) {
db.push(ctx.request.body);
ctx.body = ctx.request.body;
}
update (ctx) {
db[+ctx.params.id] = ctx.request.body;
ctx.body = ctx.request.body;
}
delete (ctx) {
db.splice(+ctx.params.id, 1);
ctx.status = 204; // 没有内容,但是成功了
}
}
module.exports = new UsersCtl();
重构主页路由
其实就是原本user所对应的所有方法,现在是使用ES Module来获取对应的方法
const { find, findId, create, update, delete: del } = require('../controllers/users');
随便以router.get('/', find);
为例,这里 find
不能写成 find(ctx)
,因为后者的写法是立即执行,而我们是需要路由匹配到了才能获取到ctx上下文环境,因此这里只是相当于注入一个方法,这个方法会接受两个参数 ctx 和 next,不过这里暂时没用到 next 就没写
const Router = require('koa-router');
const router = new Router({prefix: '/user'});
const { find, findId, create, update, delete: del } = require('../controllers/users');
// 获取用户列表
router.get('/', find);
// 增加用户
router.post('/', findId);
// 获取特定用户
router.get('/:id', create);
// 修改特定用户
router.put('/:id', update);
// 删除用户
router.delete('/:id', del);
module.exports = router;
实现批量注册路由,启动并完善请求头
实现的思路是遍历routes文件夹下的所有文件(因为该文件下只存放router),因此创建一个index.js文件,使用nodejs自带的rs模块遍历所有的文件名,将其注册启动等
// node自带模块,用于读取文件
const fs = require('fs');
module.exports = app => {
// 返回一个包含指定目录下所有文件名称的数组对象,会把当前文件也读取进去
fs.readdirSync(__dirname).forEach(file => {
if(file === 'index.js') return; // 如果是index.js页面就返回,也就是当前文件,不是路由不能进行注册
const route = require(`./${file}`);
app.use(route.routes()).use(route.allowedMethods());
});
}
重构后的总入口文件
const Koa = require('koa');
const bodyparser = require('koa-bodyparser');
const app = new Koa(); // 实例化koa
const routes = require('./routes');
// 启动路由
app.use(bodyparser());
routes(app);
app.listen(3000, () => {
console.log(`start server...`);
});