Node.js 國(guó)產(chǎn) MVC 框架 ThinkJS 開發(fā) controller 篇

原創(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秽澳,swignunjucks,默認(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-typetext/html 主要用于頁(yè)面顯示。但是以下兩種情況下需要 Controller 輸出 JSON 格式的響應(yīng):

  • 作為 REST API 接口給請(qǐng)求方返回?cái)?shù)據(jù)
  • 給 AJAX 請(qǐng)求返回?cái)?shù)據(jù)

thinkjs 提供了 this.successthis.fail 來(lái)負(fù)責(zé)輸出標(biāo)準(zhǔn)的 JSON 響應(yīng)煌抒,如前所述仍劈,這兩個(gè)方法同樣能夠返回正確的響應(yīng)頭(content-typeapplication/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é)得 errnoerrmsg 這兩個(gè)字段名不合適需要修改為其他名字的(比如我習(xí)慣使用 errmsg)仪壮,可以修改 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ù)看看:

  1. 配置 JSONP 的 callback 參數(shù)名蝶缀,此參數(shù)為全局有效,定義一次即可
// src/common/config/config.js
export default {
  // jsonp 請(qǐng)求的 callback 參數(shù)名薄货,此參數(shù)名要告知前端開發(fā)人員
  callback_name: 'callbacks'
}
  1. 客戶端 js 發(fā)起 JSONP 請(qǐng)求(注意 jsonpjsonpCallback 這兩個(gè)參數(shù)的值)
$.ajax({
  url          : '/home/user/ajax_get_user_info',
  dataType     : 'jsonp',
  jsonp        : 'callbacks',
  jsonpCallback: 'myfunc',
  success      : function(res, textStatus) {
    console.log(res);
  }
});
  1. 服務(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);
}
  1. 服務(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/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末翁都,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子谅猾,更是在濱河造成了極大的恐慌柄慰,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件税娜,死亡現(xiàn)場(chǎng)離奇詭異坐搔,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)敬矩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門概行,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人弧岳,你說(shuō)我怎么就攤上這事凳忙∫堤ぃ” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵涧卵,是天一觀的道長(zhǎng)勤家。 經(jīng)常有香客問(wèn)我,道長(zhǎng)柳恐,這世上最難降的妖魔是什么伐脖? 我笑而不...
    開封第一講書人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮乐设,結(jié)果婚禮上讼庇,老公的妹妹穿的比我還像新娘。我一直安慰自己近尚,他們只是感情好巫俺,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著肿男,像睡著了一般。 火紅的嫁衣襯著肌膚如雪却嗡。 梳的紋絲不亂的頭發(fā)上舶沛,一...
    開封第一講書人閱讀 49,749評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音窗价,去河邊找鬼如庭。 笑死,一個(gè)胖子當(dāng)著我的面吹牛撼港,可吹牛的內(nèi)容都是我干的坪它。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼帝牡,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼往毡!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起靶溜,我...
    開封第一講書人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤开瞭,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后罩息,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嗤详,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年瓷炮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了葱色。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡娘香,死狀恐怖苍狰,靈堂內(nèi)的尸體忽然破棺而出办龄,到底是詐尸還是另有隱情,我是刑警寧澤舞痰,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布土榴,位于F島的核電站,受9級(jí)特大地震影響响牛,放射性物質(zhì)發(fā)生泄漏玷禽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一呀打、第九天 我趴在偏房一處隱蔽的房頂上張望矢赁。 院中可真熱鬧,春花似錦贬丛、人聲如沸撩银。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)额获。三九已至,卻和暖如春恭应,著一層夾襖步出監(jiān)牢的瞬間抄邀,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工昼榛, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留境肾,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓胆屿,卻偏偏與公主長(zhǎng)得像奥喻,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子非迹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容