賈薇
摘要:設(shè)計(jì)一個(gè)Ruby on Rails 5框架下的視圖管理方案,在不影響系統(tǒng)安全性的前提下,滿足生產(chǎn)環(huán)境中用戶在后臺(tái)自定義視圖,高自由度定制頁(yè)面外觀并即時(shí)動(dòng)態(tài)載入呈現(xiàn)的需求。
關(guān)鍵詞:Web;MVC;視圖;Ruby on Rails 5
中圖分類(lèi)號(hào):TP311? ? ? 文獻(xiàn)標(biāo)識(shí)碼:A
文章編號(hào):1009-3044(2022)30-0037-04
開(kāi)放科學(xué)(資源服務(wù))標(biāo)識(shí)碼(OSID):
一個(gè)多站點(diǎn)系統(tǒng)通常由不同用戶建立并管理,涵蓋不同內(nèi)容面向不同受眾;用戶對(duì)站點(diǎn)外觀有個(gè)性化要求。除了采用預(yù)制模板之外,高級(jí)用戶會(huì)希望系統(tǒng)提供自定義視圖功能,能夠高自由度地改變頁(yè)面布局、外觀及內(nèi)容排版方式,并能無(wú)須重啟即時(shí)生效;同時(shí)不能影響系統(tǒng)的安全性。
設(shè)計(jì)一個(gè)Ruby on Rails 5框架下的自定義視圖管理方案,并在Linux系統(tǒng)中實(shí)現(xiàn),使其具有上述功能。
1 系統(tǒng)環(huán)境
開(kāi)發(fā)環(huán)境為Ubuntu16.04操作系統(tǒng),Ruby on Rails 5.2.3(以下簡(jiǎn)稱(chēng)RoR),MySQL 5.7數(shù)據(jù)庫(kù)。
2 視圖
RoR遵循模型-視圖-控制器設(shè)計(jì)模式,即MVC模式。視圖是表達(dá)數(shù)據(jù)顯示邏輯的地方, 通常完成或輔助完成GUI 功能[1]。
2.1 RoR 5的視圖
RoR 5返回客戶端的完整 HTML 由 ERB 視圖文件和包裝它的布局文件,以及視圖可能引用的所有局部視圖文件組成[2]。
2.1.1 視圖模板
常用視圖模板為ERB模板,由Ruby代碼和HTML組成;<%%>標(biāo)簽內(nèi)為Ruby代碼,渲染后返回完整的HTML。ERB模板在RoR框架中以.erb文件的方式存儲(chǔ)。erb模板包含Ruby代碼,允許用戶上傳erb文件會(huì)帶來(lái)安全性問(wèn)題;攻擊者上傳了一個(gè)可執(zhí)行的腳本文件,并通過(guò)此文件進(jìn)行進(jìn)一步的惡意操作,會(huì)造成系統(tǒng)崩潰、數(shù)據(jù)泄露等嚴(yán)重安全事件[3]。
2.1.2 局部視圖
局部視圖的作用是把渲染過(guò)程分成多個(gè)更容易管理的部分。局部視圖從模板中提取代碼片斷并保存在獨(dú)立的文件中,然后在模板中重用[2]。在視圖中使用 render 方法來(lái)渲染局部視圖:
<%= render "menu" %>
上述代碼會(huì)在當(dāng)前路徑下載入_menu.html.erb 局部視圖文件并渲染。局部視圖的文件名總是以下劃線開(kāi)頭,以便和普通視圖文件區(qū)分開(kāi)來(lái),但在引用局部視圖時(shí)不寫(xiě)下劃線[2]。注意,此處支持相對(duì)路徑,以加載其他目錄中的局部視圖文件。同一個(gè)局部視圖文件可以多次加載,也可以在一個(gè)頁(yè)面上加載多個(gè)局部視圖文件[4]。
render方法通過(guò)locals關(guān)鍵字傳遞父視圖的參數(shù):
<%= render "menu",locals:{title: @now_title} %>
上述代碼表示將父視圖的@now_title傳遞到局部視圖,并通過(guò)title變量讀取。
2.1.3 局部布局
應(yīng)用于局部視圖的布局稱(chēng)為局部布局。局部布局和應(yīng)用于控制器動(dòng)作的全局布局不一樣,但兩者的工作方式類(lèi)似[2]。即局部布局只作用于頁(yè)面的局部區(qū)域。
2.2 視圖的嵌套
局部視圖中可以加載其他的局部視圖,方法同前述。
3 定義自定義布局
通過(guò)提供布局、視圖模板與局部視圖選項(xiàng)讓用戶自由選擇組合,配合默認(rèn)/自定義樣式文件,即可實(shí)現(xiàn)高自由度自定義頁(yè)面的效果。構(gòu)造一個(gè)模型與對(duì)應(yīng)的編輯器、解析方法,使用戶可以在后臺(tái)在線編輯頁(yè)面布局,并實(shí)現(xiàn)頁(yè)面外觀的動(dòng)態(tài)加載。
3.1 模型
需要自定義視圖的頁(yè)面通常用于提供給普通用戶瀏覽,在CMS中即首頁(yè)、欄目頁(yè)、內(nèi)容頁(yè)等[5]。創(chuàng)建模型FrontPage,用于關(guān)聯(lián)控制器與自定義視圖。FrontPage的act屬性用于關(guān)聯(lián)處理該頁(yè)面的控制器/動(dòng)作,如首頁(yè)記為“site/index”,即site控制器的index動(dòng)作;layout屬性用于定義該頁(yè)面使用的視圖模板;layout_params屬性用于定義視圖的詳細(xì)布局信息。
3.2 編輯
3.2.1 LAYOUT常量
為避免暴露后端文件名,F(xiàn)rontPage對(duì)象layout屬性的值應(yīng)使用代字,在后端進(jìn)行轉(zhuǎn)換。定義Hash類(lèi)型常量LAYOUT用于存儲(chǔ)模板數(shù)據(jù),鍵名為代字,作為layout屬性的值;值為Hash對(duì)象,用于存儲(chǔ)視圖模板的屬性。LAYOUT常量結(jié)構(gòu)如下:
LAYOUT= {
‘'index201801 => {
:title => '首頁(yè)模板201801',
:charset => 'UTF-8',
:view => 'index_201801',
:layout_file => 'index_201801.html',
:loop_list => true,
:img => 'data:image/gif;base64,(略……)',
:config => {:top => 5,:slide => 10,:affiche => 7,:topic => 3,},
:core_assets => ['/stylesheets/base.css','/stylesheets/index.css',(略……)],
:partial => {
'one_col_201801' => {
:title => '單行單列圖片鏈接帶標(biāo)題廣告',
:charset => 'UTF-8',
:view => 'one_col_201801',
:layout_file => 'one_col_201801.html',
:img => 'data:image/gif;base64,(略……)'
},
'two_cols_201801' => {
:title => '兩列對(duì)分圖片鏈接',
:charset => 'UTF-8',
:view => 'two_cols_201801',
:layout_file => 'two_cols_201801.html',
:img => 'data:image/gif;base64,(略……)'
},
(略……)
}
},
(略……)
}
LAYOUT常量主要鍵值對(duì)含義如表1:
partial的值為Hash結(jié)構(gòu),每個(gè)鍵值對(duì)定義一個(gè)父模板可使用的局部視圖。鍵名為代字,用于識(shí)別;值為Hash類(lèi)型,用于存儲(chǔ)局部視圖的屬性。主要鍵值對(duì)如表2:
3.2.2 編輯模塊
編輯模塊為一個(gè)html文件,與erb視圖文件一一對(duì)應(yīng),用于在后臺(tái)可視化編輯器中直觀展示頁(yè)面布局和編輯配置。編輯模塊用線框圖展示視圖的布局結(jié)構(gòu),并通過(guò)Dom屬性記錄不同區(qū)域可加載的信息類(lèi)型。
圖1中左側(cè)為實(shí)際頁(yè)面,右側(cè)的編輯模塊用線框圖模擬了視圖的實(shí)際布局。其中藍(lán)色區(qū)域不可自定義,白色區(qū)域中方括號(hào)內(nèi)的內(nèi)容為當(dāng)前配置到此區(qū)域的對(duì)象。channel表示對(duì)象為Channel類(lèi),其后表示具體選中的類(lèi)實(shí)例。
學(xué)習(xí) | 專(zhuān)題 |
上述代碼片段為某編輯模塊的局部,使用table結(jié)構(gòu)模擬一個(gè)左右布局的頁(yè)面區(qū)域。左側(cè)樣式類(lèi)名為full的TD標(biāo)簽,表示一個(gè)不可自定義的區(qū)域,右側(cè)表示一個(gè)可自定義的區(qū)域。title屬性的值是區(qū)域的唯一標(biāo)識(shí)符,在同一個(gè)編輯模塊中應(yīng)具有唯一性;注意,此處應(yīng)做好規(guī)劃并與erb視圖文件對(duì)應(yīng)位置的標(biāo)識(shí)符保持一致。class屬性的值用于定義該區(qū)域可展示的內(nèi)容,channel表示可選擇Channel類(lèi)對(duì)象,即頻道;obj表示Channel類(lèi)的子對(duì)象,即文章,其后的第一個(gè)方括號(hào)表示載入子對(duì)象時(shí)調(diào)用的局部視圖名,{className}表示子對(duì)象類(lèi)名,此處為News,意即此處調(diào)用_news.html.erb局部視圖并通過(guò)News對(duì)象實(shí)例渲染;方括號(hào)中的數(shù)字表示數(shù)量上限。
3.2.3 視圖編輯器
視圖編輯器是讀取、解析、生成、更新頁(yè)面配置數(shù)據(jù)的人機(jī)接口。
視圖編輯器根據(jù)LAYOUT常量加載可使用視圖,并生成可視化UI。選擇一個(gè)視圖模板后會(huì)自動(dòng)載入該模板的編輯模塊,頁(yè)面腳本逐個(gè)檢查編輯模塊的每個(gè)區(qū)域:如果該區(qū)域的className不為full,則該區(qū)域可自定義,根據(jù)前述規(guī)則解析className并添加腳本動(dòng)作;當(dāng)該區(qū)域觸發(fā)點(diǎn)擊事件時(shí),打開(kāi)類(lèi)選擇器,通過(guò)類(lèi)選擇器為該區(qū)域配置內(nèi)容。類(lèi)選擇器的配置受該區(qū)域class屬性的限值,以前述代碼片段為例:channel[5]限制類(lèi)選擇器只能選擇Channel實(shí)例,不可超過(guò)5個(gè)。
圖3所示類(lèi)選擇器關(guān)聯(lián)Channel類(lèi)對(duì)象,列出所有可選擇的對(duì)象實(shí)例;采用復(fù)選框進(jìn)行勾選,深色底為已選項(xiàng);此時(shí)已選擇5個(gè)選項(xiàng),達(dá)到該區(qū)域上限,其他選項(xiàng)的復(fù)選框已處于不可選狀態(tài)。
一個(gè)視圖模板除了固定的可編輯區(qū)域外,還可以根據(jù)LAYOUT常量中對(duì)應(yīng)partial鍵值載入可選擇的局部視圖;這些局部視圖可重復(fù)使用。點(diǎn)擊可視化按鈕可順序插入新的局部視圖;自動(dòng)生成唯一字符串作為該局部視圖的唯一標(biāo)識(shí)符。配置局部視圖內(nèi)容的方法同上,并可對(duì)局部視圖執(zhí)行刪除、排序等操作。
視圖編輯器同步更新頁(yè)面對(duì)象配置數(shù)據(jù)front_page_layout_params和可視化顯示信息。
3.2.4 配置數(shù)據(jù)
配置數(shù)據(jù)front_page_layout_params在頁(yè)面同時(shí)以JSON對(duì)象和表單元素的形式存在,并保持同步更新。
圖4為一個(gè)經(jīng)可視化處理的front_page_layout_params數(shù)據(jù),藍(lán)框中的鍵值對(duì)為該視圖模板的固定區(qū)域;這些區(qū)域的鍵名由其編輯模塊中的title屬性定義;不同視圖模板的鍵名及鍵值對(duì)數(shù)量均有差異。loop_list鍵值對(duì)中存儲(chǔ)插入的局部視圖配置數(shù)據(jù)。channel代表Channel類(lèi)對(duì)象,ad代表Ad類(lèi)對(duì)象,1780、308為實(shí)例ID;partial_id的值表示LAYOUT常量對(duì)應(yīng)視圖模板的partial值中由“one_col0_202001”鍵名指向的鍵值對(duì),由此可定位到局部視圖文件。
視圖編輯器提交時(shí),front_page_layout_params作為當(dāng)前FrontPage實(shí)例的layout_params屬性值實(shí)現(xiàn)持久化存儲(chǔ);該實(shí)例被再次編輯時(shí),視圖編輯器根據(jù)layout_params屬性值生成JSON對(duì)象front_page_layout_params,并按配置自動(dòng)載入相應(yīng)視圖的編輯模塊,以優(yōu)化使用體驗(yàn)。
4 應(yīng)用自定義布局
4.1 根據(jù)配置數(shù)據(jù)渲染視圖
當(dāng)用戶請(qǐng)求一個(gè)自定義布局的頁(yè)面時(shí),控制器首先根據(jù)頁(yè)面路由找到對(duì)應(yīng)的FrontPage實(shí)例,讀出視圖模板layout、配置數(shù)據(jù)layout_params等。使用JSON.parse方法將layout_params轉(zhuǎn)換為JSON對(duì)象front_page_layout_params,同時(shí)生成Hash對(duì)象@page。遍歷front_page_layout_params每個(gè)鍵值對(duì),讀取對(duì)應(yīng)實(shí)例信息并以鍵值對(duì)的形式存入@page中;鍵值對(duì)結(jié)構(gòu)與front_page_layout_params類(lèi)似,值中增加一個(gè)鍵名為data,值為ActiveRecord::Relation實(shí)例的鍵值對(duì)。
控制器處理完畢之后,載入視圖模板layout。按前述結(jié)構(gòu)在@page中獲取相應(yīng)位置的數(shù)據(jù)并渲染,代碼示例如下:
<% @page.get("data|headline|data").each do |c|%>
<%= render partial:@page.get("data|headline|view").gsub(/\{className\}/,c.get("data").first.class.to_s.downcase()) || 'news', collection:c.get("data") unless c.get("data").size == 0%>
<%end%>
對(duì)于插入的局部視圖,循環(huán)調(diào)用局部視圖并將對(duì)應(yīng)的data作為參數(shù)傳入即可,代碼示例如下:
<%@page.get("data|loop_list").each do |b|%>
<%= render partial:b.get("partial"), locals:{block: b.get("blocks")}%>
<%end%>
上述代碼中,get方法為自定義的Hash實(shí)例方法,用于根據(jù)鍵名取得值,支持嵌套。
4.2 自定義樣式文件
通過(guò)應(yīng)用自定義樣式文件可使頁(yè)面風(fēng)格更加多樣化,同時(shí)應(yīng)提供預(yù)覽功能便于調(diào)整,具體方法不再詳述。
4.3 優(yōu)化響應(yīng)
此方案的數(shù)據(jù)查詢需要在控制器中全部處理完成后再渲染視圖,易造成長(zhǎng)查詢,影響頁(yè)面響應(yīng)。可采用以下方法進(jìn)行優(yōu)化。
一是將同步請(qǐng)求改為異步請(qǐng)求。@page返回請(qǐng)求參數(shù)而非對(duì)象實(shí)例,頁(yè)面渲染完成后再通過(guò)異步請(qǐng)求完成內(nèi)容填充。二是采用緩存降低數(shù)據(jù)庫(kù)讀取頻率。如使用memcached服務(wù)器做頁(yè)面緩存,Redis服務(wù)器做隊(duì)列緩存,并開(kāi)啟數(shù)據(jù)庫(kù)緩存等,以提高系統(tǒng)響應(yīng)效率。
5 結(jié)束語(yǔ)
通過(guò)此方案,用戶可在一個(gè)系統(tǒng)中建立多個(gè)風(fēng)格迥異的站點(diǎn),使用系統(tǒng)后臺(tái)高自由度定義不同頁(yè)面的外觀布局,且不影響系統(tǒng)安全性。達(dá)到了最初提出的設(shè)計(jì)要求。
參考文獻(xiàn):
[1] 任中方,張華,閆明松,陳世福.MVC模式研究的綜述[J].計(jì)算機(jī)應(yīng)用研究,2004,21(10):1-4,8.
[2] 武漢長(zhǎng)樂(lè)未央網(wǎng)絡(luò)科技有限公司.Action View 概述-Ruby On Rails指南[EB/OL].(2020-10-23)[2022-03-16].https://clwy.cn/guide/pages/rails-v5-action-view-overview.
[3] 郝子希,王志軍,劉振宇.文件上傳漏洞的攻擊方法與防御措施研究[J].計(jì)算機(jī)技術(shù)與發(fā)展,2019,29(2):129-134.
[4] Hartl M.Ruby on Rails 教程[M].安道,譯.4版.北京:人民郵電出版社,2017.
[5] 李靜媛.基于Web的新聞管理系統(tǒng)的設(shè)計(jì)與實(shí)現(xiàn)[D].天津:天津大學(xué),2007.
【通聯(lián)編輯:謝媛媛】