控制器是一類操作的集合,用來(lái)響應(yīng)用戶同一類的請(qǐng)求。
創(chuàng)建文件 src/home/controller/article.js
,表示 home
模塊下有名為 article
控制器,文件內(nèi)容類似如下:
"use strict";
import Base from "./base.js";
export default class extends Base {
/**
* index action
* @return {Promise} []
*/
indexAction(){
//auto render template file index_index.html
return this.display();
}
}
如果不想使用 ES6 語(yǔ)法,那么文件內(nèi)容類似如下:
"use strict";
var Base = require("./base.js");
module.exports = think.controller(Base, {
/**
* index action
* @return {Promise} []
*/
indexAction: function(self){
//auto render template file index_index.html
return self.display();
}
});
注:上面的 Base
表示定義一個(gè)基類,其他的類都繼承該基類,這樣就可以在基類里做一些通用的處理。
控制器里可以很方便的使用 Generator Function 來(lái)處理異步嵌套的問題。
"use strict";
import Base from "./base.js";
export default class extends Base {
/**
* index action
* @return {Promise} []
*/
* indexAction(){
let model = this.model("user");
let data = yield model.select();
return this.success(data);
}
}
"use strict";
var Base = require("./base.js");
module.exports = think.controller(Base, {
/**
* index action
* @return {Promise} []
*/
indexAction: function *(){
var model = this.model("user");
var data = yield model.select();
return this.success(data);
}
});
借助 Babel 編譯,還可以在控制器里使用 async 和 await。
"use strict";
import Base from "./base.js";
export default class extends Base {
/**
* index action
* @return {Promise} []
*/
async indexAction(){
let model = this.model("user");
let data = await model.select();
return this.success(data);
}
}
"use strict";
var Base = require("./base.js");
module.exports = think.controller(Base, {
/**
* index action
* @return {Promise} []
*/
indexAction: async function(){
var model = this.model("user");
var data = await model.select();
return this.success(data);
}
});
ES6 里的 class 有 contructor 方法,但動(dòng)態(tài)創(chuàng)建的類就沒有該方法了,為了統(tǒng)一初始化執(zhí)行的方法,將該方法統(tǒng)一定義為 init
。
該方法在類實(shí)例化的時(shí)候自動(dòng)調(diào)用,無(wú)需手工調(diào)用。
"use strict";
import Base from "./base.js";
export default class extends Base {
init(http){
super.init(http); //調(diào)用父類的init方法
...
}
}
"use strict";
var Base = require("./base.js");
module.exports = think.controller(Base, {
init: function(http){
this.super("init", http); //調(diào)用父類的init方法
...
}
});
init
方法里需要調(diào)用父類的 init 方法,并將參數(shù) http
傳遞進(jìn)去。
ThinkJS 支持前置操作,方法名為 __before
,該方法會(huì)在具體的 Action 調(diào)用之前自動(dòng)調(diào)用。如果前置操作里阻止了后續(xù)代碼繼續(xù)執(zhí)行,則不會(huì)調(diào)用具體的 Action,這樣可以提前結(jié)束請(qǐng)求。
"use strict";
import Base from "./base.js";
export default class extends Base {
/**
* 前置方法
* @return {Promise} []
*/
__before(){
...
}
}
一個(gè) Action 代表一個(gè)要執(zhí)行的操作。如: url 為 /home/article/detail
,解析后的模塊為 /home
,控制器為article
, Action 為 detail
,那么執(zhí)行的 Action 就是文件 src/home/controller/aritcle
里的detailAction
方法。
"use strict";
import Base from "./base.js";
export default class extends Base {
/**
* 獲取詳細(xì)信息
* @return {Promise} []
*/
detailAction(self){
...
}
}
如果解析后的 Action 值里含有 _
,會(huì)自動(dòng)做轉(zhuǎn)化,具體的轉(zhuǎn)化策略見 路由 -> 大小寫轉(zhuǎn)化。
ThinkJS 支持后置操作,方法名為 __after
,該方法會(huì)在具體的 Action 調(diào)用之后執(zhí)行。如果具體的 Action 里阻止了后續(xù)的代碼繼續(xù)執(zhí)行,則后置操作不會(huì)調(diào)用。
當(dāng)解析后的 url 對(duì)應(yīng)的控制器存在,但 Action 不存在時(shí),會(huì)試圖調(diào)用控制器下的魔術(shù)方法 __call
。這里可以對(duì)不存在的方法進(jìn)行統(tǒng)一處理。
"use strict";
import Base from "./base.js";
export default class extends Base {
/**
* @return {Promise} []
*/
__call(){
...
}
}
當(dāng) url 不存在或者當(dāng)前用戶沒權(quán)限等一些異常請(qǐng)求時(shí),這時(shí)候會(huì)調(diào)用錯(cuò)誤處理。 ThinkJS 內(nèi)置了一套詳細(xì)的錯(cuò)誤處理機(jī)制,具體請(qǐng)見 擴(kuò)展功能 -> 錯(cuò)誤處理。
控制器里在使用用戶提交的數(shù)據(jù)之前,需要對(duì)數(shù)據(jù)合法性進(jìn)行校驗(yàn)。為了降低控制器里的邏輯復(fù)雜度,ThinkJS 提供了一層 Logic 專門用來(lái)處理數(shù)據(jù)校驗(yàn)和權(quán)限校驗(yàn)等相關(guān)操作。
詳細(xì)信息請(qǐng)見 擴(kuò)展功能 -> 數(shù)據(jù)校驗(yàn)。
控制器里可以通過(guò) assign
和 display
方法進(jìn)行變量賦值和模版渲染,具體信息請(qǐng)見 這里。
在控制器中可以通過(guò) this.model
方法快速獲得一個(gè)模型的實(shí)例。
export default class extends think.controller.base {
indexAction(){
let model = this.model("user"); //實(shí)例化模型 user
...
}
}
model 方法更多使用方式請(qǐng)見 API -> think.http.base。
控制器在實(shí)例化時(shí),會(huì)將 http
傳遞進(jìn)去。該 http
對(duì)象是 ThinkJS 對(duì) req
和 res
重新包裝的一個(gè)對(duì)象,而非 Node.js 內(nèi)置的 http 對(duì)象。
Action 里如果想獲取該對(duì)象,可以通過(guò) this.http
來(lái)獲取。
"use strict";
import Base from "./base.js";
export default class extends Base {
indexAction(){
let http = this.http;
}
}
關(guān)于 http
對(duì)象包含的屬性和方法請(qǐng)見 API -> http。
有時(shí)候,項(xiàng)目里需要提供一些 Rest 接口給第三方使用,這些接口無(wú)外乎就是增刪改查等操作。
如果手工去書寫這些操作則比較麻煩,ThinkJS 提供了 Rest Controller,該控制器會(huì)自動(dòng)含有通用的增刪改查等操作。如果這些操作不滿足需求,也可以進(jìn)行定制。具體請(qǐng)見 這里。
Node.js 里經(jīng)常有很多異步操作,而異步操作常見的處理方式是使用回調(diào)函數(shù)或者 Promise。這些處理方式都會(huì)增加一層作用域,導(dǎo)致在回調(diào)函數(shù)內(nèi)無(wú)法直接使用 this
,簡(jiǎn)單的處理辦法是在頂部定義一個(gè)變量,將 this
賦值給這個(gè)變量,然后在回調(diào)函數(shù)內(nèi)使用這個(gè)變量。如:
module.exports = think.controller({
indexAction: function(){
var self = this; //這里將 this 賦值給變量 self,然后在后面的回調(diào)函數(shù)里都使用 self
this.model("user").find().then(function(data){
return self.model("article").where({user_id: data.id}).select();
}).then(function(data){
self.success(data);
})
}
})
如果每個(gè) Action 里都要使用者手工寫一個(gè) var self = this
,勢(shì)必比較麻煩。為了解決這個(gè)問題,ThinkJS 在 Action 里直接提供了一個(gè)參數(shù),這個(gè)參數(shù)等同于 var self = this
,具體如下:
module.exports = think.controller({
//參數(shù) self 等同于 var self = this
indexAction: function(self){
this.model("user").find().then(function(data){
return self.model("article").where({user_id: data.id}).select();
}).then(function(data){
self.success(data);
})
}
})
當(dāng)然更好的解決辦法是推薦使用 ES6 里的 Generator Function 和 Arrow Function,這樣就可以徹底解決 this 作用域的問題。
export default class extends think.controller.base {
* indexAction(){
let data = yield this.model("user").find();
let result = yield this.model("article").where({user_id: data.id}).select();
this.success(result);
}
}
module.exports = think.controller({
indexAction: function(){
this.model("user").find().then(data => {
return this.model("article").where({user_id: data.id}).select();
}).then(data => {
this.success(data);
})
}
})
項(xiàng)目中經(jīng)常要提供一些接口,這些接口一般都是直接輸出 JSON 格式的數(shù)據(jù),并且會(huì)有標(biāo)識(shí)表明當(dāng)前接口是否正常。如果發(fā)生異常,需要將對(duì)應(yīng)的錯(cuò)誤信息隨著接口一起輸出??刂破骼锾峁┝?nbsp;this.success
和this.fail
方法來(lái)輸出這樣的接口數(shù)據(jù)。
可以通過(guò) this.success
方法輸出正常的接口數(shù)據(jù),如:
export default class extends think.controller.base {
indexAction(){
let data = {name: "thinkjs"};
this.success(data);
}
}
輸出結(jié)果為 {errno: 0, errmsg: "", data: {"name": "thinkjs"}}
,客戶端可以通過(guò) errno
是否為 0 來(lái)判斷當(dāng)前接口是否有異常。
可以通過(guò) this.fail
方法輸出含有錯(cuò)誤信息的接口數(shù)據(jù),如:
export default class extends think.controller.base {
indexAction(){
this.fail(1000, "connect error"); //指定錯(cuò)誤號(hào)和錯(cuò)誤信息
}
}
輸出結(jié)果為 {errno: 1000, errmsg: "connect error"}
,客戶端判斷 errno
大于 0,就知道當(dāng)前接口有異常,并且通過(guò) errmsg
拿到具體的錯(cuò)誤信息。
如果每個(gè)地方輸出錯(cuò)誤的時(shí)候都要指定錯(cuò)誤號(hào)和錯(cuò)誤信息勢(shì)必比較麻煩,比較好的方式是把錯(cuò)誤號(hào)和錯(cuò)誤信息在一個(gè)地方配置,然后輸出的時(shí)候只要指定錯(cuò)誤號(hào),錯(cuò)誤信息根據(jù)錯(cuò)誤號(hào)自動(dòng)讀取。
錯(cuò)誤信息支持國(guó)際化,所以配置放在 src/common/config/locale/[lang].js
文件中。如:
export default {
10001: "get data error"
}
通過(guò)上面的配置后,執(zhí)行 this.fail(10001)
時(shí)會(huì)自動(dòng)讀取到對(duì)應(yīng)的錯(cuò)誤信息。
在程序里執(zhí)行 this.fail(10001)
雖然能輸出正確的錯(cuò)誤號(hào)和錯(cuò)誤信息,但人不能直觀的看出來(lái)錯(cuò)誤號(hào)對(duì)應(yīng)的錯(cuò)誤信息是什么。
這時(shí)可以將 key 配置為大寫字符串,值為錯(cuò)誤號(hào)和錯(cuò)誤信息。如:
export default {
GET_DATA_ERROR: [1234, "get data error"] //key 必須為大寫字符或者下劃線才有效
}
執(zhí)行 this.fail('GET_DATA_ERROR')
時(shí)也會(huì)自動(dòng)取到對(duì)應(yīng)的錯(cuò)誤號(hào)和錯(cuò)誤信息。
默認(rèn)輸出的錯(cuò)誤號(hào)的 key 為 errno
,錯(cuò)誤信息的 key 為 errmsg
。如果不滿足需求的話,可以修改配置文件src/common/config/error.js
。
export default {
key: "errno", //error number
msg: "errmsg", //error message
}
如果輸出的 JSON 數(shù)據(jù)里不想包含 errno
和 errmsg
的話,可以通過(guò) this.json
方法輸出 JSON。如:
export default class extends think.controller.base {
indexAction(){
this.json({name: "thinkjs"});
}
}
可以通過(guò) get
方法獲取 GET 參數(shù),如:
export default class extends think.controller.base {
indexAction(){
let name = this.get("name");
let allParams = this.get(); //獲取所有 GET 參數(shù)
}
}
如果參數(shù)不存在,那么值為空字符串。
可以通過(guò) post
方法獲取 POST 參數(shù),如:
export default class extends think.controller.base {
indexAction(){
let name = this.post("name");
let allParams = this.post(); //獲取所有 POST 參數(shù)
}
}
如果參數(shù)不存在,那么值為空字符串。
可以通過(guò) file
方法獲取上傳的文件,如:
export default class extends think.controller.base {
indexAction(){
let file = this.file("image");
let allFiles = this.file(); //獲取所有上傳的文件
}
}
返回值是個(gè)對(duì)象,包含下面的屬性:
{
fieldName: "file", //表單字段名稱
originalFilename: filename, //原始的文件名
path: filepath, //文件保存的臨時(shí)路徑,使用時(shí)需要將其移動(dòng)到項(xiàng)目里的目錄,否則請(qǐng)求結(jié)束時(shí)會(huì)被刪除
size: 1000 //文件大小
}
如果文件不存在,那么值為一個(gè)空對(duì)象 {}
。
可以通過(guò) this.jsonp
方法輸出 JSONP 格式的數(shù)據(jù),callback 的請(qǐng)求參數(shù)名默認(rèn)為 callback
。如果需要修改請(qǐng)求參數(shù)名,可以通過(guò)修改配置 callback_name
來(lái)完成。
isGet()
當(dāng)前是否是 GET 請(qǐng)求isPost()
當(dāng)前是否是 POST 請(qǐng)求isAjax()
是否是 AJAX 請(qǐng)求ip()
獲取請(qǐng)求用戶的 ipredirect(url)
跳轉(zhuǎn)到一個(gè) urlwrite(data)
輸出數(shù)據(jù),會(huì)自動(dòng)調(diào)用 JSON.stringifyend(data)
結(jié)束當(dāng)前的 http 請(qǐng)求json(data)
輸出 JSON 數(shù)據(jù),自動(dòng)發(fā)送 JSON 相關(guān)的 Content-Typejsonp(data)
輸出 JSONP 數(shù)據(jù),請(qǐng)求參數(shù)名默認(rèn)為 callback
success(data)
輸出一個(gè)正常的 JSON 數(shù)據(jù),數(shù)據(jù)格式為 {errno: 0, errmsg: "", data: data}
fail(errno, errmsg, data)
輸出一個(gè)錯(cuò)誤的 JSON 數(shù)據(jù),數(shù)據(jù)格式為 {errno: errno_value, errmsg: string, data: data}
download(file)
下載文件assign(name, value)
設(shè)置模版變量display()
輸出一個(gè)模版fetch()
渲染模版并獲取內(nèi)容cookie(name, value)
獲取或者設(shè)置 cookiesession(name, value)
獲取或者設(shè)置 sessionheader(name, value)
獲取或者設(shè)置 headeraction(name, data)
調(diào)用其他 Controller 的方法,可以跨模塊model(name, options)
獲取模型實(shí)例完整方法列表請(qǐng)見 API -> Controller。
更多建議: