Lesson-1 控制器簡(jiǎn)介
什么是控制器
- 拿到路由分配的任務(wù),并執(zhí)行
- 在Koa中,是一個(gè)中間件
為什么要控制器?
- 獲取 HTTP 請(qǐng)求參數(shù)
- 處理業(yè)務(wù)邏輯
- 發(fā)送 HTTP 響應(yīng)
獲取 HTTP 請(qǐng)求參數(shù)
- Query String,如?q=keyword,是可選項(xiàng)
- Router Params,如/user/:id,是必選項(xiàng)
- Body,如{name: "李雷"},請(qǐng)求體,一般為json
- Header,如Accept、Cookie
發(fā)送 HTTP 響應(yīng)
- 發(fā)送 Status,如 200/400 等
- 發(fā)動(dòng) Body,如{name: "李雷"}
- 發(fā)送 Header,如 Allow、 Content-type
編寫控制器最佳實(shí)踐
- 每個(gè)資源的控制器放在不同的文件里
- 盡量使用類+類方法的形式編寫控制器
- 嚴(yán)謹(jǐn)?shù)腻e(cuò)誤處理
Lesson-2 獲取 HTTP 請(qǐng)求參數(shù)
操作步驟
- 學(xué)習(xí)斷點(diǎn)調(diào)試
- 獲取 query
- 獲取 router params
- 獲取 body
- 獲取 header
調(diào)試使用的是vscode,點(diǎn)擊F5,進(jìn)入調(diào)試(這里注意一下,不要自己在其他地方執(zhí)行npm start,也不需要自己來執(zhí)行這個(gè)命令行啟動(dòng)服務(wù)器,當(dāng)進(jìn)入調(diào)試的時(shí)候,將會(huì)自動(dòng)啟動(dòng)服務(wù)器,否則會(huì)出現(xiàn)沖突報(bào)錯(cuò))
獲取 query
也就是獲取請(qǐng)求中 ? 后面的參數(shù)
請(qǐng)求地址為 localhost:3000/user/master?query=彩蛋
,獲取代碼為 ctx.query
獲取 router params
這個(gè)前面一直出現(xiàn),也就是獲取路徑后的參數(shù)
請(qǐng)求地址為 localhost:3000/user/master?query=彩蛋
,獲取代碼為 ctx.params
獲取 body
獲取請(qǐng)求體,一般我們常用格式為json。這里需要安裝一個(gè) koa-bodyparser 插件,否則獲取不到。不過后續(xù)使用的時(shí)候其實(shí)會(huì)有問題,參考文章 koa2 使用 koa-body 代替 koa-bodyparser 和 koa-multer,不過為了不增加目前難度,就還是使用的koa-bodyparser。順便說一下,全局引用中間件,就是app.use(中間件),否則就是路由級(jí)引用,也就是router.use('路由地址', 中間件)
執(zhí)行npm i koa-bodyparser -S
// index.js
const bodyparser = require('koa-bodyparser');
app.use(bodyparser());
請(qǐng)求地址為 localhost:3000/user
,請(qǐng)求方法為 POST ,獲取代碼為 ctx.request.body
獲取 header
獲取請(qǐng)求頭,依舊使用剛才獲取請(qǐng)求體的內(nèi)容,這樣能看到請(qǐng)求的content-type
請(qǐng)求地址為 localhost:3000/user,請(qǐng)求方法為 POST ,獲取代碼為 ctx.request.header
Lesson-3 發(fā)送 HTTP 響應(yīng)
操作步驟
- 發(fā)送 status
- 發(fā)送 body
- 發(fā)送 header
- 實(shí)現(xiàn)用戶的增刪改查
之前已經(jīng)使用過的,我這里就不再重復(fù)上代碼了,簡(jiǎn)單帶過去
發(fā)送status
設(shè)置body.status = 204
,就是設(shè)置響應(yīng)為204狀態(tài)碼
發(fā)送body
設(shè)置ctx.body = '這是設(shè)置body'
,就是設(shè)置響應(yīng)body,內(nèi)容為'這是設(shè)置body'
發(fā)動(dòng) header
使用ctx.set方法,以之前的代碼為例,讓人家知道,user除了有GET方法,還有POST方法
// 獲取用戶列表
userRouter.get('/', (ctx) => {
ctx.set('Allow', 'GET, POST'); // 設(shè)置請(qǐng)求頭
ctx.body = [
{name: '韓梅梅'},
{name: '李蕾'}
];
});
實(shí)現(xiàn)用戶的增刪改查
這里我就不放 postman 驗(yàn)證截圖了,只上代碼,后面會(huì)加上 mongoDB,不過現(xiàn)在還沒有,所以先使用的內(nèi)存數(shù)據(jù)庫(其實(shí)就是變量)
const Koa = require('koa');
const bodyparser = require('koa-bodyparser');
const Router = require('koa-router');
const app = new Koa();
const userRouter = new Router({prefix: '/user'});
// 內(nèi)存數(shù)據(jù)庫
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; // 沒有內(nèi)容,但是成功了
});
app.use(bodyparser());
app.use(userRouter.routes());
app.use(userRouter.allowedMethods());
app.listen(3000, () => {
console.log(`start server...`);
});
Lesson-4 更合理的目錄結(jié)構(gòu)
操作步驟
- 將路由單獨(dú)放在一個(gè)目錄
- 將控制器單獨(dú)放在一個(gè)目錄
- 使用類 + 類方法的方式組織控制器
重構(gòu)文件目錄
現(xiàn)在的想法是這樣,創(chuàng)建app目錄,原本根目錄下的index.js文件移動(dòng)到app里面去,作為總?cè)肟谖募?br>
創(chuàng)建routes文件夾,里面用于存放所有頁面的路由,并導(dǎo)出
創(chuàng)建controllers文件夾,里面用于存放對(duì)應(yīng)頁面的控制器(控制器其實(shí)就是中間件,也就是方法),并使用類 + 類方法導(dǎo)出的形式來進(jìn)行維護(hù)
文件目錄現(xiàn)在變?yōu)檫@樣
|- 根目錄
|- app
|- routes
|- home.js -- 主頁路由
|- users.js -- 用戶列表頁路由
|- index.js -- 批量讀取文件,并批量注冊(cè)到app上
|- controllers
|- home.js -- 主頁控制器
|- users.js -- 用戶列表頁控制器
|- node_modules
|- LICENSE
|- package-lock.json
|- package.json
|- README.md
使用類 + 類方法的形式導(dǎo)出中間件
現(xiàn)在將用戶列表頁相關(guān)的中間件全部提取出來,創(chuàng)建一個(gè) UserCtl 類,里面有所有增刪改查的方法,主頁也一樣,不重復(fù)展示代碼
// 內(nèi)存數(shù)據(jù)庫,測(cè)試使用
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; // 沒有內(nèi)容,但是成功了
}
}
module.exports = new UsersCtl();
重構(gòu)主頁路由
其實(shí)就是原本user所對(duì)應(yīng)的所有方法,現(xiàn)在是使用ES Module來獲取對(duì)應(yīng)的方法
const { find, findId, create, update, delete: del } = require('../controllers/users');
隨便以router.get('/', find);
為例,這里 find
不能寫成 find(ctx)
,因?yàn)楹笳叩膶懛ㄊ橇⒓磮?zhí)行,而我們是需要路由匹配到了才能獲取到ctx上下文環(huán)境,因此這里只是相當(dāng)于注入一個(gè)方法,這個(gè)方法會(huì)接受兩個(gè)參數(shù) ctx 和 next,不過這里暫時(shí)沒用到 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;
實(shí)現(xiàn)批量注冊(cè)路由,啟動(dòng)并完善請(qǐng)求頭
實(shí)現(xiàn)的思路是遍歷routes文件夾下的所有文件(因?yàn)樵撐募轮淮娣舝outer),因此創(chuàng)建一個(gè)index.js文件,使用nodejs自帶的rs模塊遍歷所有的文件名,將其注冊(cè)啟動(dòng)等
// node自帶模塊,用于讀取文件
const fs = require('fs');
module.exports = app => {
// 返回一個(gè)包含指定目錄下所有文件名稱的數(shù)組對(duì)象,會(huì)把當(dāng)前文件也讀取進(jìn)去
fs.readdirSync(__dirname).forEach(file => {
if(file === 'index.js') return; // 如果是index.js頁面就返回,也就是當(dāng)前文件,不是路由不能進(jìn)行注冊(cè)
const route = require(`./${file}`);
app.use(route.routes()).use(route.allowedMethods());
});
}
重構(gòu)后的總?cè)肟谖募?/h3>
const Koa = require('koa');
const bodyparser = require('koa-bodyparser');
const app = new Koa(); // 實(shí)例化koa
const routes = require('./routes');
// 啟動(dòng)路由
app.use(bodyparser());
routes(app);
app.listen(3000, () => {
console.log(`start server...`);
});
const Koa = require('koa');
const bodyparser = require('koa-bodyparser');
const app = new Koa(); // 實(shí)例化koa
const routes = require('./routes');
// 啟動(dòng)路由
app.use(bodyparser());
routes(app);
app.listen(3000, () => {
console.log(`start server...`);
});