# 控制器
[TOC=2,3]
控制器是一類操作的集合,用來響應用戶同一類的請求。
## 定義控制器
創建文件?`src/home/controller/article.js`,表示?`home`?模塊下有名為?`article`?控制器,文件內容類似如下:
~~~
"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 語法,那么文件內容類似如下:
~~~
"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`?表示定義一個基類,其他的類都繼承該基類,這樣就可以在基類里做一些通用的處理。
## 使用 Generator Function
控制器里可以很方便的使用 Generator Function 來處理異步嵌套的問題。
#### ES6 方式
~~~
"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);
}
});
~~~
## 使用 async 和 await
借助 Babel 編譯,還可以在控制器里使用 async 和 await。
#### ES6 方式
~~~
"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);
}
});
~~~
## init 方法
ES6 里的 class 有 contructor 方法,但動態創建的類就沒有該方法了,為了統一初始化執行的方法,將該方法統一定義為?`init`。
該方法在類實例化的時候自動調用,無需手工調用。
#### ES6 方式
~~~
"use strict";
import Base from "./base.js";
export default class extends Base {
init(http){
super.init(http); //調用父類的init方法
...
}
}
~~~
#### 動態創建類方式
~~~
"use strict";
var Base = require("./base.js");
module.exports = think.controller(Base, {
init: function(http){
this.super("init", http); //調用父類的init方法
...
}
});
~~~
`init`?方法里需要調用父類的 init 方法,并將參數?`http`?傳遞進去。
## 前置操作 __before
ThinkJS 支持前置操作,方法名為?`__before`,該方法會在具體的 Action 調用之前自動調用。如果前置操作里阻止了后續代碼繼續執行,則不會調用具體的 Action,這樣可以提前結束請求。
#### ES6 方式
~~~
"use strict";
import Base from "./base.js";
export default class extends Base {
/**
* 前置方法
* @return {Promise} []
*/
__before(){
...
}
}
~~~
## Action
一個 Action 代表一個要執行的操作。如: url 為?`/home/article/detail`,解析后的模塊為?`/home`,控制器為`article`, Action 為?`detail`,那么執行的 Action 就是文件?`src/home/controller/aritcle`?里的`detailAction`?方法。
~~~
"use strict";
import Base from "./base.js";
export default class extends Base {
/**
* 獲取詳細信息
* @return {Promise} []
*/
detailAction(self){
...
}
}
~~~
如果解析后的 Action 值里含有?`_`,會自動做轉化,具體的轉化策略見?[路由 -> 大小寫轉化](76374#u5927u5C0Fu5199u8F6Cu5316)。
## 后置操作 __after
ThinkJS 支持后置操作,方法名為?`__after`,該方法會在具體的 Action 調用之后執行。如果具體的 Action 里阻止了后續的代碼繼續執行,則后置操作不會調用。
## 空操作 __call
當解析后的 url 對應的控制器存在,但 Action 不存在時,會試圖調用控制器下的魔術方法?`__call`。這里可以對不存在的方法進行統一處理。
~~~
"use strict";
import Base from "./base.js";
export default class extends Base {
/**
* @return {Promise} []
*/
__call(){
...
}
}
~~~
## 錯誤處理
當 url 不存在或者當前用戶沒權限等一些異常請求時,這時候會調用錯誤處理。 ThinkJS 內置了一套詳細的錯誤處理機制,具體請見?[擴展功能 -> 錯誤處理](76394)。
## 數據校驗
控制器里在使用用戶提交的數據之前,需要對數據合法性進行校驗。為了降低控制器里的邏輯復雜度,ThinkJS 提供了一層 Logic 專門用來處理數據校驗和權限校驗等相關操作。
詳細信息請見?[擴展功能 -> 數據校驗](76396)。
## 變量賦值和模版渲染
控制器里可以通過?`assign`?和?`display`?方法進行變量賦值和模版渲染,具體信息請見?[這里](76372)。
## 模型實例化
在控制器中可以通過?`this.model`?方法快速獲得一個模型的實例。
~~~
export default class extends think.controller.base {
indexAction(){
let model = this.model("user"); //實例化模型 user
...
}
}
~~~
model 方法更多使用方式請見?[API -> think.http.base](76406)。
## http 對象
控制器在實例化時,會將?`http`?傳遞進去。該?`http`?對象是 ThinkJS 對?`req`?和?`res`?重新包裝的一個對象,而非 Node.js 內置的 http 對象。
Action 里如果想獲取該對象,可以通過?`this.http`?來獲取。
~~~
"use strict";
import Base from "./base.js";
export default class extends Base {
indexAction(){
let http = this.http;
}
}
~~~
關于?`http`?對象包含的屬性和方法請見?[API -> http](76407)。
## Rest Api
有時候,項目里需要提供一些 Rest 接口給第三方使用,這些接口無外乎就是增刪改查等操作。
如果手工去書寫這些操作則比較麻煩,ThinkJS 提供了 Rest Controller,該控制器會自動含有通用的增刪改查等操作。如果這些操作不滿足需求,也可以進行定制。具體請見?[這里](76399)。
## this 作用域的問題
Node.js 里經常有很多異步操作,而異步操作常見的處理方式是使用回調函數或者 Promise。這些處理方式都會增加一層作用域,導致在回調函數內無法直接使用?`this`,簡單的處理辦法是在頂部定義一個變量,將?`this`賦值給這個變量,然后在回調函數內使用這個變量。如:
~~~
module.exports = think.controller({
indexAction: function(){
var self = this; //這里將 this 賦值給變量 self,然后在后面的回調函數里都使用 self
this.model("user").find().then(function(data){
return self.model("article").where({user_id: data.id}).select();
}).then(function(data){
self.success(data);
})
}
})
~~~
如果每個 Action 里都要使用者手工寫一個?`var self = this`,勢必比較麻煩。為了解決這個問題,ThinkJS 在 Action 里直接提供了一個參數,這個參數等同于?`var self = this`,具體如下:
~~~
module.exports = think.controller({
//參數 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);
})
}
})
~~~
當然更好的解決辦法是推薦使用 ES6 里的 Generator Function 和 Arrow Function,這樣就可以徹底解決 this 作用域的問題。
#### 使用 Generator Function
~~~
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);
}
}
~~~
#### 使用 Arrow Function
~~~
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);
})
}
})
~~~
## JSON 輸出
項目中經常要提供一些接口,這些接口一般都是直接輸出 JSON 格式的數據,并且會有標識表明當前接口是否正常。如果發生異常,需要將對應的錯誤信息隨著接口一起輸出。控制器里提供了?`this.success`?和`this.fail`?方法來輸出這樣的接口數據。
### 輸出正常的 JSON
可以通過?`this.success`?方法輸出正常的接口數據,如:
~~~
export default class extends think.controller.base {
indexAction(){
let data = {name: "thinkjs"};
this.success(data);
}
}
~~~
輸出結果為?`{errno: 0, errmsg: "", data: {"name": "thinkjs"}}`,客戶端可以通過?`errno`?是否為 0 來判斷當前接口是否有異常。
### 輸出含有錯誤信息的 JSON
可以通過?`this.fail`?方法輸出含有錯誤信息的接口數據,如:
~~~
export default class extends think.controller.base {
indexAction(){
this.fail(1000, "connect error"); //指定錯誤號和錯誤信息
}
}
~~~
輸出結果為?`{errno: 1000, errmsg: "connect error"}`,客戶端判斷?`errno`?大于 0,就知道當前接口有異常,并且通過?`errmsg`?拿到具體的錯誤信息。
### 配置錯誤號和錯誤信息
如果每個地方輸出錯誤的時候都要指定錯誤號和錯誤信息勢必比較麻煩,比較好的方式是把錯誤號和錯誤信息在一個地方配置,然后輸出的時候只要指定錯誤號,錯誤信息根據錯誤號自動讀取。
錯誤信息支持國際化,所以配置放在?`src/common/config/locale/[lang].js`?文件中。如:
~~~
export default {
10001: "get data error"
}
~~~
通過上面的配置后,執行?`this.fail(10001)`?時會自動讀取到對應的錯誤信息。
### 友好的錯誤號
在程序里執行?`this.fail(10001)`?雖然能輸出正確的錯誤號和錯誤信息,但人不能直觀的看出來錯誤號對應的錯誤信息是什么。
這時可以將 key 配置為大寫字符串,值為錯誤號和錯誤信息。如:
~~~
export default {
GET_DATA_ERROR: [1234, "get data error"] //key 必須為大寫字符或者下劃線才有效
}
~~~
執行?`this.fail('GET_DATA_ERROR')`?時也會自動取到對應的錯誤號和錯誤信息。
### 格式配置
默認輸出的錯誤號的 key 為?`errno`,錯誤信息的 key 為?`errmsg`。如果不滿足需求的話,可以修改配置文件`src/common/config/error.js`。
~~~
export default {
key: "errno", //error number
msg: "errmsg", //error message
}
~~~
### 輸出不包含錯誤信息的 JSON
如果輸出的 JSON 數據里不想包含?`errno`?和?`errmsg`?的話,可以通過?`this.json`?方法輸出 JSON。如:
~~~
export default class extends think.controller.base {
indexAction(){
this.json({name: "thinkjs"});
}
}
~~~
## 常用功能
### 獲取 GET 參數
可以通過?`get`?方法獲取 GET 參數,如:
~~~
export default class extends think.controller.base {
indexAction(){
let name = this.get("name");
let allParams = this.get(); //獲取所有 GET 參數
}
}
~~~
如果參數不存在,那么值為空字符串。
### 獲取 POST 參數
可以通過?`post`?方法獲取 POST 參數,如:
~~~
export default class extends think.controller.base {
indexAction(){
let name = this.post("name");
let allParams = this.post(); //獲取所有 POST 參數
}
}
~~~
如果參數不存在,那么值為空字符串。
### 獲取上傳的文件
可以通過?`file`?方法獲取上傳的文件,如:
~~~
export default class extends think.controller.base {
indexAction(){
let file = this.file("image");
let allFiles = this.file(); //獲取所有上傳的文件
}
}
~~~
返回值是個對象,包含下面的屬性:
~~~
{
fieldName: "file", //表單字段名稱
originalFilename: filename, //原始的文件名
path: filepath, //文件保存的臨時路徑,使用時需要將其移動到項目里的目錄,否則請求結束時會被刪除
size: 1000 //文件大小
}
~~~
如果文件不存在,那么值為一個空對象?`{}`。
### JSONP 格式數據輸出
可以通過?`this.jsonp`?方法輸出 JSONP 格式的數據,callback 的請求參數名默認為?`callback`。如果需要修改請求參數名,可以通過修改配置?`callback_name`?來完成。
### 更多方法
* `isGet()`?當前是否是 GET 請求
* `isPost()`?當前是否是 POST 請求
* `isAjax()`?是否是 AJAX 請求
* `ip()`?獲取請求用戶的 ip
* `redirect(url)`?跳轉到一個 url
* `write(data)`?輸出數據,會自動調用 JSON.stringify
* `end(data)`?結束當前的 http 請求
* `json(data)`?輸出 JSON 數據,自動發送 JSON 相關的 Content-Type
* `jsonp(data)`?輸出 JSONP 數據,請求參數名默認為?`callback`
* `success(data)`?輸出一個正常的 JSON 數據,數據格式為?`{errno: 0, errmsg: "", data: data}`
* `fail(errno, errmsg, data)`?輸出一個錯誤的 JSON 數據,數據格式為?`{errno: errno_value, errmsg: string, data: data}`
* `download(file)`?下載文件
* `assign(name, value)`?設置模版變量
* `display()`?輸出一個模版
* `fetch()`?渲染模版并獲取內容
* `cookie(name, value)`?獲取或者設置 cookie
* `session(name, value)`?獲取或者設置 session
* `header(name, value)`?獲取或者設置 header
* `action(name, data)`?調用其他 Controller 的方法,可以跨模塊
* `model(name, options)`?獲取模型實例
完整方法列表請見?[API -> Controller](76408)。
- 快速入門
- 介紹
- 創建項目
- 項目結構
- 代碼規范
- 升級指南
- 進階應用
- 模塊
- 控制器
- 視圖
- 配置
- 路由
- 模型
- 介紹
- 事務
- 關聯模型
- Mysql
- MongoDB
- SQLite
- Adapter
- 介紹
- Cache
- Session
- WebSocket
- Template
- 擴展功能
- thinkjs 命令
- 靜態資源訪問
- Middleware
- Service
- Cookie
- 錯誤處理
- 錯誤信息
- 數據校驗
- 國際化
- 路徑常量
- REST API
- 定時任務
- 線上部署
- 推薦模塊
- API
- think
- think.base
- think.http.base
- http
- controller
- rest controller
- model
- model.mongo
- middleware