老web换新枝----Sails.js移动设备的全新生产力(五)
自定義模型操作
? ? ? 目前為止,我們的進展非常順利,我們使用了?Sails?的默認路由來訪問或修改模型實例。這些默認設(shè)置(包含在?Sails?Blueprint?API?中)負責(zé)我們期望從?Web?或移動應(yīng)用程序獲得的基本的創(chuàng)建(create)、讀取(read)、更新(update)和刪除(delete)功能。但是所有開發(fā)生產(chǎn)?HTTP?API?的開發(fā)人員都會告訴您,簡單的?CRUD?只有這點能耐。即使您從這里著手,也需要能夠定制基本路由與控制器的映射。
Blueprint?API?對您很有幫助,但最終您需要某個更強大、靈活和可自定義(或同時具備這三種特性)的工具來構(gòu)建您的客戶和用戶想要的應(yīng)用程序。對于大部分開發(fā)周期,都可以使用藍圖路由來建立原型,然后將此框架替換為自定義的路由和關(guān)聯(lián)的控制器。
除了?CRUD?路由,一個常規(guī)?Sails.js?安裝中已經(jīng)預(yù)先定義了其他一些控制器-路由組合。但是,從很大程度上講,您往往想創(chuàng)建自己的映射來獲得所需的行為。
映射復(fù)雜的查詢
? ? ? ?在上一篇教程中,您將我們最開始擁有的博客?API?擴展為了一個更龐大的內(nèi)容管理系統(tǒng)(CMS)后端。盡管您目前構(gòu)想的是讓這個應(yīng)用程序為一個網(wǎng)絡(luò)博客提供支持,但它還可以用于其他用途。該?RESTful?API?可供幾乎任何想要獲取并顯式博客文章或?RSS?提要的前端應(yīng)用程序訪問,而且它允許搜索查詢。擴展該?API?后得到了多種新模型類型,分別是?Author、Entry?和?Comment。每個?Entry?擁有一個?Author?和Comment,這些類型也鏈接回它們引用的?Entry。
? ? ? ?切換成?CMS?后,越來越明確地表明您希望能夠設(shè)置?Sails?的默認路由所不支持的操作。假設(shè)您想能夠發(fā)出復(fù)雜的查詢(比如針對一個由特定作者在特定日期后編寫的,按給定條件進行組織的文章),或者獲取與一個特定標簽對應(yīng)的所有文章。您可能需要提供的不是采用原生?JSON?的格式,而是采用兼容?RSS?或?Atom?的?XML?格式的文章。在輸入端,您可能希望添加一個導(dǎo)入功能,使?CMS?能夠通過?RSS?或?Atom?提要獲取并存儲完整的現(xiàn)有博客。
實際上,您可以做很多事情,而且許多事情都是?Sails?默認不支持的。
控制器的作用
? ? ? ?在傳統(tǒng)?MVC?模式中,控制器(controller)定義模型(model)與其視圖(view)之間的交互。當(dāng)某個模型的數(shù)據(jù)發(fā)生更改時,控制器會確保附加到該模型的每個視圖都會相應(yīng)地更新。用戶在視圖內(nèi)執(zhí)行某項操作時,控制器也會獲得通知。如果該操作必須更改某個模型,控制器會向所有受影響的視圖發(fā)出通知。從架構(gòu)角度講,模型、控制器和視圖都是在服務(wù)器上定義的。視圖通常是某種形式的?HTML?模板(理想情況下包含極少的代碼);模型是域類或?qū)ο?#xff1b;控制器是路由背后的代碼塊。
? ? ? ?在?HTTP?API?中,三個應(yīng)用程序組件之間的關(guān)系類似但不等同于?MVC。不同于?MVC?架構(gòu),HTTP?API?的模型、視圖和控制器通常并不都包含在同一個服務(wù)器上。具體地講,視圖通常位于服務(wù)器外,會是一個單頁?Web?應(yīng)用程序或移動應(yīng)用程序的形式。
在架構(gòu)上,HTTP?API?或多或少與?MVC?應(yīng)用程序有些類似:
·?模型?是通過線路交換的工件。(HTTP?API?使用了一種專為簡化傳輸而設(shè)計的模型,該模型有時稱為?ViewModel。)
·?控制器?是?API?的動詞形式;它們的存在是為“執(zhí)行”某個操作而不是“成為”某個東西。
在?HTTP?API?中,當(dāng)一個視圖將?HTTP?請求傳到控制器端點時,該請求直接依靠控制器來執(zhí)行。控制器獲取通過?URL?或請求正文傳入的數(shù)據(jù),對一個或多個現(xiàn)有模型執(zhí)行某個操作(或創(chuàng)建新模型),并生成響應(yīng)返回給視圖來進行更新。控制器的響應(yīng)通常是?API?開發(fā)人員定義的?HTTP?狀態(tài)代碼和?JSON?主體的組合。
理論介紹得已經(jīng)足夠多了,現(xiàn)在讓我們開始實現(xiàn)控制吧。
文檔:目前,對于錯誤場景的含義,HTTP?API?設(shè)計中還沒有標準。錯誤編碼?500?究竟是表明請求未被處理,還是表明它已被處理,但由于執(zhí)行請求的邏輯中的內(nèi)部錯誤而失敗了?所以應(yīng)該始終顯式地文檔化您的?HTTP?API?中的錯誤編碼的含義。
·?視圖?顯示了通過?HTTP?API?收到的數(shù)據(jù)。
創(chuàng)建一個控制器
? ? ? 開始使用?Sails?控制器的最簡單方法是,創(chuàng)建一個返回靜態(tài)數(shù)據(jù)的控制器。在本例中,您將創(chuàng)建一個簡單的控制器,它返回?CMS?API?的一個用戶友好的版本。這將是一個運行迅速且容易使用的?API,業(yè)務(wù)客戶可使用它測試服務(wù)器是否在正常運行。客戶還能夠大體了解任何最近的更改,比如對?API?的升級,并相應(yīng)地調(diào)整其?Web?或移動前端。
可通過兩種方式在?Sails?中創(chuàng)建新控制器:可以使用?sails?命令生成框架,或者可以讓這些文件為您生成控制器。在后一種情況下,生成器使用?Sails?約定規(guī)則所定義的命名系統(tǒng)來識別和放置文件,這些文件通常是空的。對于第一個控制器,我們將會使用生成器:
~$?sails?generate?controller?System
info: Created a new controller ("System") at api/controllers/SystemController.js!
? ? ?控制器位于您的 Sails 項目的?api/controllers?目錄中。根據(jù)約定,它們擁有?controller?后綴。如果您在控制器名稱后附加額外的描述符,Sails 會假設(shè)這些是要在控制器上執(zhí)行的操作(方法)。通過提前指定操作,您可以節(jié)省一些步驟;否則默認生成器會生成一個空的控制器,如這里所示:
? ?/**
???*?SystemController
???*
???*?@description?::?Server-side?logic?for?managing?the?System
???*?@help????????::?See?http://sailsjs.org/#!/documentation/concepts/Controllers
???*/
??module.exports?=?{
??};
? ? ?運行一個稍微不同的命令——sails?generate?controller?System?version——會留下更多工作讓您處理:控制器位于您的?Sails?項目的?api/controllers?目錄中。根據(jù)約定,它們擁有?controller?后綴。如果您在控制器名稱后附加額外的描述符,Sails?會假設(shè)這些是要在控制器上執(zhí)行的操作(方法)。通過提前指定操作,您可以節(jié)省一些步驟;否則默認生成器會生成一個空的控制器,如這里所示:
? /**
???*?SystemController
???*
???*?@description?::?Server-side?logic?for?managing?Systems
???*?@help????????::?See?http://sailsjs.org/#!/documentation/concepts/Controllers
???*/
??module.exports?=?{
????/**
?????*?`SystemController.version()`
?????*/
????version:?function?(req,?res)?{
??????return?res.json({
????????todo:?'version()?is?not?implemented?yet!'
??????});
????}
??};
? ? ? 添加命令會對搭建的方法端點進行布局,但您可以看到,這些命令目前未執(zhí)行太多工作,僅返回有幫助的錯誤消息。使用該生成器創(chuàng)建已建立框架的控制器,最初可能很有幫助。隨著時間的推移,許多開發(fā)人員最終僅在自己最喜歡用的文本編輯器中執(zhí)行?File|New。如果您決定這么做,則需要正確地命名該文件和端點(例如?foocontroller.js?中的?FooController),并手動編寫導(dǎo)出的函數(shù)。這些方式都沒有對錯,所以請使用最適合您的方式。
實現(xiàn)第一個簡單的控制器非常容易:
??module.exports?=?{
? ?/**
???*?`SystemController.version()`
???*/
??version:?function?(req,?res)?{
????return?res.json({
??????version:?'0.1'
????});
??}
}",
綁定和調(diào)用控制器
接下來,您希望綁定您的控制器,然后調(diào)用它。綁定(Binding)將控制器映射到一個路由;調(diào)用(invoking)向該路由發(fā)出合適的?HTTP?請求。基于此控制器的文件名和所調(diào)用方法的名稱,此控制器的默認路由將是?/System/version。
就個人而言,我不喜歡將“system”放在?URL?中,而是更喜歡使用“/version”。Sails?允許將控制器的調(diào)用綁定到選擇的任何路由,所以我們將它設(shè)置為綁定到?/version。
您可以在?Sails?路由表中創(chuàng)建一個條目來設(shè)置控制器路由,該路由表存儲在?config/routes.js?中。只要您創(chuàng)建一個?Sails?應(yīng)用程序,就會生成這個默認文件。除去所有注釋后,此文件幾乎是空的:
odule.exports.routes?=?{
??/***************************************************************************
??*??????????????????????????????????????????????????????????????????????????*
??*?Make?the?view?located?at?`views/homepage.ejs`?(or?`views/homepage.jade`,?*
??*?etc.?depending?on?your?default?view?engine)?your?home?page.??????????????*
??*??????????????????????????????????????????????????????????????????????????*
??*?(Alternatively,?remove?this?and?add?an?`index.html`?file?in?your?????????*
??*?`assets`?directory)??????????????????????????????????????????????????????*
??*??????????????????????????????????????????????????????????????????????????*
??***************************************************************************/
??'/':?{
????view:?'homepage'
??}
};
大體上講,config/routes.js?包含一組路由(routes)和目標(targets)。路由是相對?URL,目標是您希望?Sails?調(diào)用的對象。默認路由是?/?URL?模式,它調(diào)出?Sails?的默認主頁。如果您愿意的話,還可以將此默認路由替換為一個常規(guī)?HTML?頁面。這樣,該?HTML?將位于您的?assets?目錄中,就在我們將一直使用的?api?目錄旁邊。
升級?CMS?的?HTML?對于像?React.js?這樣的單頁應(yīng)用程序框架可能是一種有趣的練習(xí),但這里的目的是添加一個?/version?路由,并讓它指向?SystemController.version?方法。為此,您需要向?config.js?所導(dǎo)入的?JSON?對象添加額外的一行代碼:
module.exports.routes?=?{
??'get?/version':?'SystemController.version'
};
Blueprint?API?中的控制器
將此路由保存到?config.js?文件中后,向?/version?發(fā)出?HTTP?GET?請求會發(fā)回您之前設(shè)置的相同?JSON?結(jié)果。
創(chuàng)建模型時,Sails?的工具會自動為該模型創(chuàng)建一個控制器。所以,即使您沒有打算創(chuàng)建它們,您的每個方法也已經(jīng)有一個控制器:AuthorController、EntryController?等。
為了進一步熟悉?Sails?控制器和路由,我們假設(shè)您希望?AuthorController?持有一個方法,該方法返回您?CMS?中所有作者的簡歷的聚合列表。該實現(xiàn)非常簡單——只需要獲取所有?Authors,提取它們的簡歷,并傳回該列表:
??module.exports?=?{
??bios:?function(req,?res)?{
????Author.find({})
??????.then(function?(authors)?{
????????console.log("authors?=?",authors);
????????var?bs?=?[];
????????authors.forEach(function?(author)?{
??????????bs.push({
????????????name:?author.fullName,
????????????bio:?author.bio
??????????});
????????});
????????res.json(bs);
??????})
??????.catch(function?(err)?{
????????console.log(err);
????????res.status(500)
??????????.json({?error:?err?});
??????});
??}
};
測試狀態(tài)如果您使用了默認的?/author/bios?路由,您的?routes.js?中甚至不需要特殊條目。在這種情況下,默認條目就夠用了(如果您不這么認為,您知道如何更改它),所以我們暫時保留默認路由。
測試狀態(tài)
您可能發(fā)現(xiàn)種子控制器(seed?controller)對一些測試很有用。這種控制器將數(shù)據(jù)庫初始化為一種已知狀態(tài),就象這樣:
module.exports?=?{
????run:?function(req,?res)?{
????????Author.create({
????????????fullName:?"Fred?Flintstone",
????????????bio:?"Lives?in?Bedrock,?blogs?in?cyberspace",
????????????username:?"fredf",
????????????email:?"fred@flintstone.com"
????????}).exec(function?(err,?author)?{
????????????Entry.create({
????????????????title:?"Hello",
????????????????body:?"Yabba?dabba?doo!",
????????????????author:?author
????????????}).exec(function?(err,?created)?{
????????????????Entry.create({
????????????????????title:?"Quit",
????????????????????body:?"Mr?Slate?is?a?jerk",
????????????????????author:?author.id
????????????????}).exec(function?(err,?created)?{
????????????????????return?res.send("Database?seeded");
????????????????});
????????????});
????????});
????}
};
? ?在這種情況下,該已知狀態(tài)被綁定到控制器的默認路由?/seed/run。您還可以為不同的測試和/或開發(fā)場景設(shè)置不同的種子方法(seed?method)。對于所關(guān)注的路由,可使用?curl?命令將數(shù)據(jù)庫設(shè)置為特定狀態(tài)。但需要確保您在生產(chǎn)代碼中禁用或刪除了這些路由。
管理控制器輸入:
現(xiàn)在,您已擁有一個沒有輸入的非常簡單的控制器。但是,控制器通常需要從調(diào)用方獲取輸入。有三種類型的控制器輸入:
1.?請求正文中發(fā)送的表單參數(shù)。這是通過?Web?接受輸入的傳統(tǒng)機制。
2.?通過請求正文中的?JSON?對象發(fā)送的輸入數(shù)據(jù)。此概念與表單參數(shù)相同,但發(fā)送的內(nèi)容類型為application/json,而不是?form/multipart-form-data。客戶端通常更容易生成輸入數(shù)據(jù),而且服務(wù)器也更容易使用。
3.?通過參數(shù)指定并通過?URL?路由中的占位符發(fā)送的輸入。請求某個特定作者的文章時,您通常希望在?URL?自身中傳遞作者標識符。一個示例是?/author/1/entries,其中的“1”是作者的唯一標識符。這樣,您就保留了博客文章包含在作者資源中的外觀,即使這些文章在物理上未與該作者存儲在一起。(示例應(yīng)用程序就是如此,Entry?對象存儲在一個與?Author?對象不同的集合或表中。)
表單參數(shù)屬于傳統(tǒng)的?Express?樣式?request.getParam()?調(diào)用的領(lǐng)域,已在其他地方具有明確規(guī)定。而且?HTTP?API?也不經(jīng)常使用表單參數(shù),所以我們暫時放棄該方法。第二種方法非常適合?CMS?應(yīng)用程序。
獲取輸入
捕獲通過?JSON?對象發(fā)送的值通常很簡單,只需使用一個?request.body.field。如果輸入是一個位于?JSON?最高層級的數(shù)組,您則可以使用?request.body[idx].field。
首先,您將創(chuàng)建一個端點,它將返回?CMS?數(shù)據(jù)庫中所有?Entry?對象的?RSS?XML?提要。(在實際的系統(tǒng)中,需要限制此數(shù)字來支持分頁模式,比如返回最近的?20?篇文章,但我們暫時保持簡單即可。)命名您的控制器(我在下面選擇了?FeedController),并在它之上放置一個?RSS?方法,使默認路由(/feed/rss)變得有意義:
var?generateRSS?=?function(entries)?{
??var?rss?=?
????'<rss?version="2.0">'?+
????'<channel>'?+?
????'<title>SailsBlog</title>';
??//?Items
??entries.forEach(function?(entry)?{
????rss?+=?'<item>'?+
'<title>'?+?entry.title?+?'</title>'?+
??????'<description>'?+?entry.body?+?'</description>'?+
??????'</item>';
??});
??//?Closing? rss?+=?'</channel>'?+
????'</rss>';
??return?rss;
}
module.exports?=?{
??rss:?function?(req,?res)?{
????Entry.find({})
??????.then(function?(entries)?{
????????var?rss?=?generateRSS(entries);
????????res.type("rss");
????????res.status(200);
????????res.send(rss);
??????})
??????.catch(function?(err)?{
????????console.log(err);
????????res.status(500)
??????????.json({?error:?err?});
??????});
????return?res;
??}
};
現(xiàn)在,這是一個很小的提要,但是,如果您想將提要限制到一個或多個特定作者,該怎么辦?在這種情況下,您需要設(shè)置一個新路由(/author/{id}/rss)。新路由將獲取?URL?中傳遞的標識符,然后使用它限制查詢,僅查找給定作者編寫的文章。該?RSS?方法的剩余部分基本相同。
看看該方法在代碼中的效果。首先,FeedController?獲取一個針對每個作者的?RSS?方法,就像之前一樣:
module.exports?=?{
??rss:?//?as?before
??authorRss:?function(req,?res)?{
????Entry.find({?'author'?:?req.param("authorID")?})
??????.then(function?(entries)?{
????????var?rss?=?generateRSS(entries);
????????res.type("rss");
????????res.status(200);
????????res.send(rss);
??????})
??????.catch(function?(err)?{
????????console.log(err);
????????res.status(500)
??????????.json({?error:?err?});
??????});
? ?
????return?res;
??}
};
不同的是上面是一個查詢,它現(xiàn)在被限制為僅查找所有具有某個作者?ID?的?Entry?對象。請注意?HTTP?請求中包含的參數(shù)?authorID?的指令。authorID(在本例中為傳入的?URL?模式的第三部分)的映射在routes.js?文件中指定,如下所示:
var?generateRss?=?//?as?before
module.exports?=?{
??rss:?//?as?before
??authorRss:?function(req,?res)?{
????Entry.find({?'author'?:?req.param("authorID")?})
??????.then(function?(entries)?{
????????var?rss?=?generateRSS(entries);
????????res.type("rss");
????????res.status(200);
????????res.send(rss);
??????})
??????.catch(function?(err)?{
????????console.log(err);
????????res.status(500)
??????????.json({?error:?err?});
??????});
????return?res;
??}
};
routes?文件中指定的參數(shù)是區(qū)分大小寫的,所以請確保您保持了一致的命名約定。大小寫差異是應(yīng)用程序代碼中一種常見但不易察覺的錯誤來源。
轉(zhuǎn)載于:https://blog.51cto.com/risingair/1870253
總結(jié)
以上是生活随笔為你收集整理的老web换新枝----Sails.js移动设备的全新生产力(五)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如果你梦到一个人说明他在想你对吗
- 下一篇: 安装ISO系统(原版系统)系统终极方法