原創(chuàng):荊秀網(wǎng) 網(wǎng)頁(yè)即時(shí)推送 https://xxuyou.com | 轉(zhuǎn)載請(qǐng)注明出處
鏈接:https://blog.xxuyou.com/nodejs-thinkjs-study-controller/
本系列教程以 ThinkJS v2.x 版本(官網(wǎng))為例進(jìn)行介紹惕艳,教程以實(shí)際操作為主步淹。
Controller 基本應(yīng)用
Controller 作為 MVC 框架的主力擔(dān)當(dāng),是開發(fā)人員接觸最多的部分。在開發(fā)過(guò)程中通常按照需求驶俊、業(yè)務(wù)流程、任務(wù)派發(fā)等躯概,都是以一個(gè)或者多個(gè) Controller 為邊界進(jìn)行劃分年栓。
Controller 作為接收用戶輸入、業(yè)務(wù)流程處理糜烹、響應(yīng)處理展示的“處理器”违诗,其構(gòu)成和實(shí)現(xiàn)也有非常多的方式方法,以及技巧疮蹦。
Action 定義
從外部用戶(使用者)感知角度來(lái)看诸迟,最先體現(xiàn) Controller 的地方就是輸入一個(gè) url 所能到達(dá)的地方,url 代表著用戶輸入愕乎、流程跳轉(zhuǎn)等動(dòng)作阵苇。例如:
domain.com/home/user/login
domain.com/?m=home&c=user&a=login
domain.com/home/order/detail/id/856
domain.com/?m=home&c=order&a=detail&id=856
thinkjs 要求凡是公開出來(lái)可以被訪問(wèn)的方法名都要增加 Action
的后綴,例如 indexAction
感论,來(lái)看代碼:
// src/home/controller/user.js
export default class extends Base {
indexAction(){
//
}
}
暫且忽略 indexAction
方法內(nèi)部的實(shí)現(xiàn)绅项,想要訪問(wèn)到這個(gè)方法的 url 組成規(guī)則是 /module/controller/action (注:這是默認(rèn)路由,可以通過(guò)更改路由規(guī)則改變 url 的組成方式比肄,后文詳述)快耿,也就是 /home/user/index
。
OK芳绩,按照自己的需要去組織 Controller 內(nèi)的 Action 即可掀亥,來(lái)看代碼:
// src/home/controller/user.js
export default class extends Base {
indexAction(){
// 訪問(wèn) url:domain.com/home/user/index
}
listAction(){
// 訪問(wèn) url:domain.com/home/user/list
}
detailAction(){
// 訪問(wèn) url:domain.com/home/user/detail
}
orderListAction(){
// 訪問(wèn) url:domain.com/home/user/order_list
}
orderDetailAction(){
// 訪問(wèn) url:domain.com/home/user/order_detail
}
_getPoints(){
// private function
}
_getBalance(){
// private function
}
// etc...
}
注意第四、第五個(gè)方法使用了多個(gè)單詞的駝峰命名(有強(qiáng)迫癥的筒子要開心了~)示括,這種情況下訪問(wèn) url 就會(huì)有些不同了铺浇。
另外可以看到,第六和第七個(gè)方法不帶 Action
后綴垛膝,這會(huì)被識(shí)別為私有方法(ES6 仍然不提供 private 關(guān)鍵字來(lái)標(biāo)記私有方法鳍侣,因此方法名前使用一個(gè)下劃線前綴來(lái)標(biāo)識(shí))。
so吼拥,就是這么簡(jiǎn)單倚聚,接下來(lái)就是考慮怎么去布局 Controller 的方法了。
注:thinkjs 路由默認(rèn)是強(qiáng)制小寫英文字母的凿可,這一點(diǎn)在開發(fā)中要注意惑折。
基類與繼承鏈
如果使用 thinkjs module [moduleName]
命令來(lái)創(chuàng)建一個(gè)模塊授账,那么該模塊的 Controller
都會(huì)存在一個(gè)基類 Base(base.js
)。
# 默認(rèn)生成的代碼清單
src/home/
├── config
│ └── config.js
├── controller
│ ├── base.js
│ └── index.js
├── logic
│ └── index.js
└── model
└── index.js
如果繼續(xù)使用 thinkjs controller [moduleName/][controllerName]
命令來(lái)創(chuàng)建每個(gè) Controller
惨驶,那么每個(gè) Controller
都會(huì)繼承此基類白热。
import Base from './base.js';
這樣我們可以迅速建立起兩層 Class 的繼承鏈。這個(gè)特性你會(huì)想到怎么用粗卜?沒(méi)錯(cuò)屋确,用戶 Session 的檢測(cè)和處理,來(lái)看代碼:
// src/home/controller/base.js
'use strict';
export default class extends think.controller.base {
init(...args) {
super.init(...args);
}
/**
* 檢測(cè)session數(shù)據(jù)
* 如果有問(wèn)題就返回false
* 如果OK就續(xù)命
* @returns {boolean}
* @private
*/
async checkSession() {
let userSess = await this.session('be_user');
if (think.isEmpty(userSess)) return false;
let userToken = userSess['token'];
if (think.isEmpty(userToken)) return false;
if (/^[a-z0-9]{128}$/.test(userToken) == false) return false;
let userExpire = userSess['expire'];
let now = +(new Date);
if (now >= userExpire) return false;
userSess['expire'] = now + this.config('backend.user')['session_life'];
await this.session('be_user', userSess);
return true;
}
}
這樣可以把強(qiáng)關(guān)聯(lián)的全部公共業(yè)務(wù)方法統(tǒng)統(tǒng)放置在這里续扔。之所以說(shuō)強(qiáng)關(guān)聯(lián)攻臀,表示符合下列情況的方法可以考慮放在基類中:
- 業(yè)務(wù)相關(guān):與用戶業(yè)務(wù)流程無(wú)關(guān)的方法不要放在這里(例如日期格式化這種的方法應(yīng)當(dāng)放置在全局函數(shù)中)
- 方法調(diào)用方:超過(guò)一個(gè)的(例如 Session 檢測(cè)方法在后臺(tái)模塊的幾乎所有 Controller 子類都會(huì)用到)
- 方法調(diào)用次數(shù):超過(guò)一次的
關(guān)于業(yè)務(wù)相關(guān)性的理解人人不同,這里僅做示例而不是定論纱昧,開發(fā)人員大可按照自己的理解去劃分業(yè)務(wù)邊界刨啸,本文主要專注于框架的使用。
前面提到的放置公共業(yè)務(wù)方法是基類的一種玩法识脆,可是基類還有一種玩法:使用邏輯方法來(lái)處理中斷或者跳轉(zhuǎn)设联,來(lái)看代碼:
// src/home/controller/base.js
'use strict';
export default class extends think.controller.base {
init(...args) {
super.init(...args);
// 要求全部 url 必須攜帶 auth 參數(shù)
let auth = this.get('auth');
if (think.isEmpty(auth)) {
return this.error(500, '全部 url 必須攜帶 auth 參數(shù)');
}
}
}
假如 url 中缺少 auth 參數(shù),那么 Class 會(huì)初始化失敗存璃,提示錯(cuò)誤:
{
"errno": 500,
"errmsg": "全部 url 必須攜帶 auth 參數(shù)"
}
如果是頁(yè)面訪問(wèn)仑荐,也可以重定向到其他 Controller 處理頁(yè)面。
表單提交與處理
除了通常的頁(yè)面切換纵东,Controller 還有一個(gè)重要的工作就是處理用戶數(shù)據(jù)粘招,其中以表單提交(以及 AJAX 提交)為重。
GET 提交/訪問(wèn)
thinkjs 提供了 this.get([paramName])
方法來(lái)獲取 GET 參數(shù)偎球。
let auth = this.get('auth');
console.log(auth); // 打尤髟:xyz
可能有的筒子不喜歡一個(gè)一個(gè)的寫參數(shù)名(或者需要對(duì)參數(shù)排序計(jì)算簽名),那么 get 方法如果沒(méi)有入?yún)⑺バ酰瑒t獲取到全部 get 參數(shù):
let params = this.get();
console.log(params); // 打优劾洹:{ auth: 'xyz' }
POST 提交
thinkjs 提供了 this.post()
方法來(lái)獲取 POST 參數(shù)。
let auth = this.post('auth');
console.log(auth); // 打用怠:xyz
獲取全部 post 參數(shù):
let params = this.post();
console.log(params); // 打雍:{ auth: 'xyz' }
上傳文件
如果需要接收用戶提交的二進(jìn)制流,需要給 form
元素增加屬性 enctype
來(lái)指定上傳的內(nèi)容類型 :
<form name="formName" method="POST" enctype="multipart/form-data">
<input type="file" name="myFile" />
</form>
thinkjs 提供了一個(gè) this.file([fileName])
方法淌友,這樣可以很方便的處理上傳文件了(開發(fā)人員并不需要自己拼接二進(jìn)制塊煌恢,上傳文件已經(jīng)被框架接收,并保存在系統(tǒng)臨時(shí)目錄中震庭,方法返回的只是一個(gè)包含相關(guān)信息的 Object)瑰抵。
這是一個(gè)簡(jiǎn)潔明了的 thinkjs 文件上傳demo,可以看到其中的工作方法器联。
不過(guò)官網(wǎng)沒(méi)有說(shuō)明的是同時(shí)上傳多個(gè)文件的返回?cái)?shù)據(jù)的結(jié)構(gòu)二汛,試一下就知道婿崭!來(lái)看代碼:
<form name="formName" method="POST" enctype="multipart/form-data">
<input type="file" name="myFile1" />
<input type="file" name="myFile2" />
<input type="submit" name="Submit" />
</form>
let files = think.extend({}, this.file());
console.log(files);
{
"myFile1": {
"fieldName": 'myFile1',
"originalFilename": '查詢身份證綁定的公眾號(hào).jpg',
"path": '/data/www/thinkjs_module/runtime/upload/twLYslNHfLzWxFaGR2Rqg_qb.jpg',
"headers": {
"content-disposition": 'form-data;name="myFile1";filename="查詢身份證綁定的公眾號(hào).jpg"',
"content-type": 'image/jpeg'
},
"size": 86411
},
"myFile2": {
"fieldName": 'myFile2',
"originalFilename": '查詢微信號(hào)綁定的公眾號(hào).jpg',
"path": '/data/www/thinkjs_module/runtime/upload/EP6KoSAMxlL9vU4uTFviNs7d.jpg',
"headers": {
"content-disposition": 'form-data;name="myFile2";filename="查詢微信號(hào)綁定的公眾號(hào).jpg"',
"content-type": 'image/jpeg'
},
"size": 95865
}
}
實(shí)踐出真知~ 如果是多個(gè)文件上傳,服務(wù)端 this.file()
返回的數(shù)據(jù)是以 input.name
為屬性的映射關(guān)系肴颊,處理起來(lái)非常方便氓栈。
其中 path 屬性是到臨時(shí)文件的位置。如果整個(gè)上傳業(yè)務(wù)邏輯正確婿着,應(yīng)當(dāng)主動(dòng)將文件從臨時(shí)位置中移走颤绕,例如移動(dòng)到 www/static/upload/
中;如果服務(wù)端代碼沒(méi)有將文件移動(dòng)到其他位置祟身,那么最終框架會(huì)自動(dòng)刪除臨時(shí)文件。
輸出到響應(yīng)
處理完了用戶數(shù)據(jù)物独,最終需要向客戶端瀏覽器返回內(nèi)容袜硫。返回內(nèi)容的處理不屬于 Controller 的工作范疇(當(dāng)然你可以用 Controller 也是可以做到的)。這個(gè)過(guò)程就是 Controller 挑選模版挡篓,給定數(shù)據(jù)(變量)婉陷,然后統(tǒng)統(tǒng)交給模版引擎來(lái)處理。
ThinkJS 默認(rèn)支持的模版引擎有:
ejs
官研,jade
秽澳,swig
和nunjucks
,默認(rèn)模版引擎為ejs
戏羽,可以根據(jù)需要修改為其他的模版引擎担神。(來(lái)自官網(wǎng)文檔)
這個(gè)過(guò)程簡(jiǎn)化到兩個(gè)方法即可完成這一連串工作任務(wù)委托:
-
this.assign(dataName, data)
將變量指派給模版引擎,并命名方便模版引擎調(diào)用 -
this.display([viewFileName])
顯示模版引擎渲染后的結(jié)果
看一下 this.display()
的工作細(xì)節(jié):
// src/home/controller/index.js
indexAction() {
return this.display(); // 系統(tǒng)會(huì)去找 view/home/index_index.html 來(lái)渲染并輸出到響應(yīng)
}
listAction(){
return this.display(); // 系統(tǒng)會(huì)去找 view/home/index_list.html 來(lái)渲染并輸出到響應(yīng)
}
實(shí)質(zhì)上 this.display()
所做的遠(yuǎn)不止我們看到的這么少始花,除了輸出響應(yīng)正文(ResponseBody妄讯,一堆的 HTML 代碼讓瀏覽器去解析),還負(fù)責(zé)輸出合法正確的響應(yīng)頭(ResponseHeader酷宵,返回網(wǎng)絡(luò)響應(yīng)狀態(tài)亥贸、響應(yīng)內(nèi)容類型、響應(yīng)正文編碼等)浇垦】恢茫看圖:
箭頭所指就是 ResponseHeader 內(nèi)容(還包括一個(gè) X-Powered-By 字段,嘿嘿~)男韧。
注:更詳細(xì)的模版引擎工作方式會(huì)另文描述朴摊。
輸出 JSON 到響應(yīng)
Controller 默認(rèn)輸出的響應(yīng) content-type
是 text/html
主要用于頁(yè)面顯示。但是以下兩種情況下需要 Controller 輸出 JSON 格式的響應(yīng):
- 作為 REST API 接口給請(qǐng)求方返回?cái)?shù)據(jù)
- 給 AJAX 請(qǐng)求返回?cái)?shù)據(jù)
thinkjs 提供了 this.success
和 this.fail
來(lái)負(fù)責(zé)輸出標(biāo)準(zhǔn)的 JSON 響應(yīng)煌抒,如前所述仍劈,這兩個(gè)方法同樣能夠返回正確的響應(yīng)頭(content-type
是 application/json
)。
this.success
方法接受一個(gè)入?yún)⒐炎常梢允?Array
也可以是 Object
贩疙,可根據(jù)業(yè)務(wù)需要自行組織結(jié)構(gòu)和內(nèi)容讹弯,調(diào)用之后返回的響應(yīng)正文是一個(gè)統(tǒng)一格式的 JSON,如:
{
"errno": 0,
"errmsg": "",
"data": {
"id": 234,
"user": "test"
}
}
可以看到入?yún)?shù)據(jù)被裝載在屬性 data 中(這個(gè)屬性名恒定為 data 不可更改)这溅。
this.fail
方法接受兩個(gè)入?yún)ⅲ哄e(cuò)誤編號(hào)和錯(cuò)誤文本组民,兩個(gè)參數(shù)均可根據(jù)業(yè)務(wù)需要自行組織,調(diào)用之后返回的響應(yīng)正文是一個(gè)統(tǒng)一格式的 JSON悲靴,如:
{
"errno": 90001,
"errmsg": "缺少必要參數(shù)"
}
這兩個(gè)方法返回標(biāo)準(zhǔn)的 JSON 響應(yīng)正文格式臭胜,有兩個(gè) JSON 屬性用于自我描述結(jié)果:
- errno 錯(cuò)誤編號(hào),等于 0 表示沒(méi)有錯(cuò)誤癞尚,可以讀取 data 屬性耸三;大于 0 表示出現(xiàn)錯(cuò)誤
- errmsg 錯(cuò)誤描述,errno 大于 0 的時(shí)候有值浇揩,可以用來(lái)提示用戶
假如覺(jué)得
errno
和errmsg
這兩個(gè)字段名不合適需要修改為其他名字的(比如我習(xí)慣使用err
和msg
)仪壮,可以修改src/common/config/error.js
文件達(dá)到目的。
假如不想使用 thinkjs 標(biāo)準(zhǔn)的 JSON 響應(yīng)正文格式胳徽,需要自行定義正文格式积锅,thinkjs 也提供了
this.json
方法,傳入一個(gè)Array
或者Object
參數(shù)养盗,方法會(huì)自動(dòng)對(duì)參數(shù)執(zhí)行JSON.stringify
方法轉(zhuǎn)化為格式良好的 JSON 響應(yīng)正文缚陷。
輸出 JSONP
Controller 既然可以輸出 JSON,那么輸出 JSONP 也是沒(méi)跑了~
thinkjs 提供的方法是 this.jsonp
往核。callback 的請(qǐng)求參數(shù)名默認(rèn)為 callback
箫爷。如果需要修改請(qǐng)求參數(shù)名,可以通過(guò)修改配置 callback_name
來(lái)完成铆铆。
下面我們簡(jiǎn)單實(shí)現(xiàn)一個(gè) JSONP 業(yè)務(wù)看看:
- 配置 JSONP 的 callback 參數(shù)名蝶缀,此參數(shù)為全局有效,定義一次即可
// src/common/config/config.js
export default {
// jsonp 請(qǐng)求的 callback 參數(shù)名薄货,此參數(shù)名要告知前端開發(fā)人員
callback_name: 'callbacks'
}
- 客戶端 js 發(fā)起 JSONP 請(qǐng)求(注意
jsonp
和jsonpCallback
這兩個(gè)參數(shù)的值)
$.ajax({
url : '/home/user/ajax_get_user_info',
dataType : 'jsonp',
jsonp : 'callbacks',
jsonpCallback: 'myfunc',
success : function(res, textStatus) {
console.log(res);
}
});
- 服務(wù)端 獲取用戶詳情數(shù)據(jù)
async ajaxGetUserInfoAction(){
if (!this.isAjax()) return this.fail(90001, 'Request must be AJAX');
let sess = await this.session('front');
let userModel = this.model('user');
let userInfo = await userModel.field('name,nickname,email').find(sess['id']);
if (think.isEmpty(userInfo)) return this.fail(10001, 'user is not exists!');
return this.jsonp(userInfo);
}
- 服務(wù)端輸出的 JSONP 正文
myfunc({"name": "xxuyou", "nickname": "荊秀網(wǎng)", "email": "cap@xxuyou.com"})
未完待續(xù)~
上一篇:Node.js 國(guó)產(chǎn) MVC 框架 ThinkJS 開發(fā) config 篇(荊秀網(wǎng))
下一篇:Node.js 國(guó)產(chǎn) MVC 框架 ThinkJS 開發(fā) controller 篇(續(xù))(荊秀網(wǎng))
原創(chuàng):荊秀網(wǎng) 網(wǎng)頁(yè)即時(shí)推送 https://xxuyou.com | 轉(zhuǎn)載請(qǐng)注明出處
鏈接:https://blog.xxuyou.com/nodejs-thinkjs-study-controller/