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

原創(chuàng):荊秀網(wǎng) 網(wǎng)頁即時(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ā)過程中通常按照需求、業(yè)務(wù)流程、任務(wù)派發(fā)等,都是以一個(gè)或者多個(gè) Controller 為邊界進(jìn)行劃分。

Controller 作為接收用戶輸入、業(yè)務(wù)流程處理、響應(yīng)處理展示的“處理器”,其構(gòu)成和實(shí)現(xiàn)也有非常多的方式方法,以及技巧。

Action 定義

從外部用戶(使用者)感知角度來看,最先體現(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 要求凡是公開出來可以被訪問的方法名都要增加 Action 的后綴,例如 indexAction,來看代碼:

// src/home/controller/user.js
export default class extends Base {
  indexAction(){
    //
  }
}

暫且忽略 indexAction 方法內(nèi)部的實(shí)現(xiàn),想要訪問到這個(gè)方法的 url 組成規(guī)則是 /module/controller/action (注:這是默認(rèn)路由,可以通過更改路由規(guī)則改變 url 的組成方式,后文詳述),也就是 /home/user/index

OK,按照自己的需要去組織 Controller 內(nèi)的 Action 即可,來看代碼:

// src/home/controller/user.js
export default class extends Base {
  indexAction(){
    // 訪問 url:domain.com/home/user/index
  }
  listAction(){
    // 訪問 url:domain.com/home/user/list
  }
  detailAction(){
    // 訪問 url:domain.com/home/user/detail
  }
  orderListAction(){
    // 訪問 url:domain.com/home/user/order_list
  }
  orderDetailAction(){
    // 訪問 url:domain.com/home/user/order_detail
  }
  _getPoints(){
    // private function
  }
  _getBalance(){
    // private function
  }
  // etc...
}

注意第四、第五個(gè)方法使用了多個(gè)單詞的駝峰命名(有強(qiáng)迫癥的筒子要開心了~),這種情況下訪問 url 就會(huì)有些不同了。

另外可以看到,第六和第七個(gè)方法不帶 Action 后綴,這會(huì)被識(shí)別為私有方法(ES6 仍然不提供 private 關(guān)鍵字來標(biāo)記私有方法,因此方法名前使用一個(gè)下劃線前綴來標(biāo)識(shí))。

so,就是這么簡單,接下來就是考慮怎么去布局 Controller 的方法了。

注:thinkjs 路由默認(rèn)是強(qiáng)制小寫英文字母的,這一點(diǎn)在開發(fā)中要注意。

基類與繼承鏈

如果使用 thinkjs module [moduleName] 命令來創(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] 命令來創(chuàng)建每個(gè) Controller,那么每個(gè) Controller 都會(huì)繼承此基類。

import Base from './base.js';

這樣我們可以迅速建立起兩層 Class 的繼承鏈。這個(gè)特性你會(huì)想到怎么用?沒錯(cuò),用戶 Session 的檢測和處理,來看代碼:

// src/home/controller/base.js
'use strict';
export default class extends think.controller.base {
  init(...args) {
    super.init(...args);
  }
  /**
   * 檢測session數(shù)據(jù)
   * 如果有問題就返回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)放置在這里。之所以說強(qiáng)關(guān)聯(lián),表示符合下列情況的方法可以考慮放在基類中:

  • 業(yè)務(wù)相關(guān):與用戶業(yè)務(wù)流程無關(guān)的方法不要放在這里(例如日期格式化這種的方法應(yīng)當(dāng)放置在全局函數(shù)中)
  • 方法調(diào)用方:超過一個(gè)的(例如 Session 檢測方法在后臺(tái)模塊的幾乎所有 Controller 子類都會(huì)用到)
  • 方法調(diào)用次數(shù):超過一次的

關(guān)于業(yè)務(wù)相關(guān)性的理解人人不同,這里僅做示例而不是定論,開發(fā)人員大可按照自己的理解去劃分業(yè)務(wù)邊界,本文主要專注于框架的使用。

前面提到的放置公共業(yè)務(wù)方法是基類的一種玩法,可是基類還有一種玩法:使用邏輯方法來處理中斷或者跳轉(zhuǎn),來看代碼:

// 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ù)"
}

如果是頁面訪問,也可以重定向到其他 Controller 處理頁面。

表單提交與處理

除了通常的頁面切換,Controller 還有一個(gè)重要的工作就是處理用戶數(shù)據(jù),其中以表單提交(以及 AJAX 提交)為重。

GET 提交/訪問

thinkjs 提供了 this.get([paramName]) 方法來獲取 GET 參數(shù)。

let auth = this.get('auth');
console.log(auth); // 打?。簒yz

可能有的筒子不喜歡一個(gè)一個(gè)的寫參數(shù)名(或者需要對(duì)參數(shù)排序計(jì)算簽名),那么 get 方法如果沒有入?yún)?,則獲取到全部 get 參數(shù):

let params = this.get();
console.log(params); // 打印:{ auth: 'xyz' }

POST 提交

thinkjs 提供了 this.post() 方法來獲取 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 來指定上傳的內(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è)簡潔明了的 thinkjs 文件上傳demo,可以看到其中的工作方法。

不過官網(wǎng)沒有說明的是同時(shí)上傳多個(gè)文件的返回?cái)?shù)據(jù)的結(jié)構(gòu),試一下就知道!來看代碼:

<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)系,處理起來非常方便。

其中 path 屬性是到臨時(shí)文件的位置。如果整個(gè)上傳業(yè)務(wù)邏輯正確,應(yīng)當(dāng)主動(dòng)將文件從臨時(shí)位置中移走,例如移動(dòng)到 www/static/upload/ 中;如果服務(wù)端代碼沒有將文件移動(dòng)到其他位置,那么最終框架會(huì)自動(dòng)刪除臨時(shí)文件。

輸出到響應(yīng)

處理完了用戶數(shù)據(jù),最終需要向客戶端瀏覽器返回內(nèi)容。返回內(nèi)容的處理不屬于 Controller 的工作范疇(當(dāng)然你可以用 Controller 也是可以做到的)。這個(gè)過程就是 Controller 挑選模版,給定數(shù)據(jù)(變量),然后統(tǒng)統(tǒng)交給模版引擎來處理。

ThinkJS 默認(rèn)支持的模版引擎有:ejsjade,swignunjucks,默認(rèn)模版引擎為 ejs,可以根據(jù)需要修改為其他的模版引擎。(來自官網(wǎng)文檔)

這個(gè)過程簡化到兩個(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 來渲染并輸出到響應(yīng)
}
listAction(){
  return this.display(); // 系統(tǒng)會(huì)去找 view/home/index_list.html 來渲染并輸出到響應(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)正文編碼等)??磮D:

箭頭所指就是 ResponseHeader 內(nèi)容(還包括一個(gè) X-Powered-By 字段,嘿嘿~)。

注:更詳細(xì)的模版引擎工作方式會(huì)另文描述。

輸出 JSON 到響應(yīng)

Controller 默認(rèn)輸出的響應(yīng) content-typetext/html 主要用于頁面顯示。但是以下兩種情況下需要 Controller 輸出 JSON 格式的響應(yīng):

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

thinkjs 提供了 this.successthis.fail 來負(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 表示沒有錯(cuò)誤,可以讀取 data 屬性;大于 0 表示出現(xiàn)錯(cuò)誤
  • errmsg 錯(cuò)誤描述,errno 大于 0 的時(shí)候有值,可以用來提示用戶

假如覺得 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 也是沒跑了~

thinkjs 提供的方法是 this.jsonp 。callback 的請(qǐng)求參數(shù)名默認(rèn)為 callback。如果需要修改請(qǐng)求參數(shù)名,可以通過修改配置 callback_name 來完成。

下面我們簡單實(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 國產(chǎn) MVC 框架 ThinkJS 開發(fā) config 篇(荊秀網(wǎng))
下一篇:Node.js 國產(chǎn) MVC 框架 ThinkJS 開發(fā) controller 篇(續(xù))(荊秀網(wǎng))

原創(chuàng):荊秀網(wǎng) 網(wǎng)頁即時(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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容