李思睿,鄭大翔,李志芳
(海南醫(yī)學院,海南 ???571199)
847520303@qq.com;2505663420@qq.com;19143212@qq.com
當前絕大多數(shù)的醫(yī)院信息系統(tǒng)都采用三層架構(gòu)的開發(fā)方式,將整個應(yīng)用程序劃分為界面層、業(yè)務(wù)邏輯層、數(shù)據(jù)訪問層。在界面層,通常結(jié)合DevExpress控件庫進行開發(fā),其內(nèi)容雖然豐富,但加載速度較慢,且樣式較為單一。處理業(yè)務(wù)邏輯時,因為分層需要使用中間層方式(數(shù)據(jù)訪問層)訪問數(shù)據(jù)庫,需要對數(shù)據(jù)進行各種轉(zhuǎn)換和計算,降低了系統(tǒng)性能。當用戶訪問量增大,處于高并發(fā)時,會導(dǎo)致系統(tǒng)響應(yīng)慢,經(jīng)常出現(xiàn)未響應(yīng)狀態(tài),甚至崩潰。三層架構(gòu)實際上不只是三層,隨著業(yè)務(wù)復(fù)雜度的提升,少則劃分四五層,多至八九層。層級之間缺乏統(tǒng)一的標準,不同的開發(fā)者對各個層級的理解不一致,當有新功能需求時,需要開發(fā)者在各個層級進行相應(yīng)開發(fā),同時接口方面也存在銜接不一致的問題。
隨著互聯(lián)網(wǎng)技術(shù)的快速發(fā)展,架構(gòu)技術(shù)數(shù)不勝數(shù)。國內(nèi)外IT公司在業(yè)務(wù)層次追求強大功能的同時,更加注重用戶體驗和系統(tǒng)性能。在開發(fā)層次,采用團隊協(xié)作方式,利用工程化思想,采取前后端分離的開發(fā)模式,前端負責對數(shù)據(jù)的渲染,關(guān)注與用戶的交互;后端處理業(yè)務(wù)邏輯,為前端提供數(shù)據(jù)接口。前端工程化是當今主流的項目開發(fā)方案。本文通過對現(xiàn)有醫(yī)院信息系統(tǒng)的研究,采用解耦的編程思想,設(shè)計并實現(xiàn)了藥房藥庫管理系統(tǒng)。系統(tǒng)以藥品管理規(guī)范化、科學化為目標,提高了醫(yī)院藥房藥庫整體的工作效率,促進了醫(yī)院信息化發(fā)展。系統(tǒng)主要包括藥房管理、藥庫管理、門診藥房、數(shù)據(jù)統(tǒng)計、后臺管理功能模塊。
系統(tǒng)采用前后端分離的架構(gòu)模式,前端主要運用HTML、CSS制作靜態(tài)頁面,結(jié)合JavaScript實現(xiàn)頁面動態(tài)化并對數(shù)據(jù)進行渲染展示,讓頁面運行更加流暢,追求更高的用戶體驗;后端處理業(yè)務(wù)邏輯,提供數(shù)據(jù)接口,實現(xiàn)服務(wù)的高性能和高并發(fā)。前端通過Axios調(diào)用后端的Api接口,對后端返回數(shù)據(jù)進行處理并渲染。這種開發(fā)方式降低了前后端代碼之間的耦合性,架構(gòu)體系清晰,便于代碼的上線部署和后續(xù)維護。
系統(tǒng)的桌面應(yīng)用程序基于Electron,它是由GitHub開發(fā)的一款跨平臺桌面應(yīng)用開發(fā)框架。Electron將Google的Chromium和Node.js合并到一個運行環(huán)境中,所以能使用Node.js中幾乎所有的模塊,通過JavaScript操作系統(tǒng)原生的Api。Electron有且僅只有一個主進程(Main Process),Main Process通過BrowserWindow實例創(chuàng)建頁面并通過Chromium展示,每個獨立的Web頁面都運行在它自身的渲染進程(Render Process)中,能夠完美地使用Vue.js,在各個渲染進程中獨立開發(fā)Web頁面。Electron原理如圖1所示。
圖1 Electron原理圖示Fig.1 Diagram of Electron principle
在傳統(tǒng)開發(fā)中,頁面都是靜態(tài)渲染的,頁面的更新必須要直接操作DOM,大量DOM操作不僅消耗性能且十分煩瑣。Vue.js通過MVVM模式實現(xiàn)數(shù)據(jù)驅(qū)動視圖,實際上是從數(shù)據(jù)劫持到發(fā)布訂閱的一種模式。通過Object.defineProperty這個Api在對象上定義一個新屬性或者修改現(xiàn)有的屬性,使用該Api下的get方法讀取屬性獲取data,使用set方法寫入屬性監(jiān)聽data的變化。組件中data一旦發(fā)生變化,就會根據(jù)修改后的data重新渲染視圖,由MVVM去自動更新DOM,開發(fā)者就無須直接操作DOM,節(jié)約了開發(fā)成本,提高了程序的性能。
雖然MVVM能幫助開發(fā)者自動更新視圖,減少了對DOM的直接操作,但實際上也是要對DOM進行操作的,依然非常耗時。但Virtual Dom先用執(zhí)行速度更快的JS來模擬DOM結(jié)構(gòu),在多次的DOM操作中計算出最小的變更,避免了一些毫無意義的操作,最后操作DOM,其本質(zhì)上是DOM和JS之間的一個緩存。
Virtual Dom通過diff算法比較DOM操作前后的差別,計算出最小變更。對于操作前后的DOM樹,diff算法只對它們之間同級比較,若兩棵樹的tag不相同,刪除重建,不再進行下一層級的對比。若一棵樹的tag和key都相同,則認為是相同的節(jié)點,同樣不再進行下一層級的對比。此算法的時間復(fù)雜度為O(n),執(zhí)行算法時的工作量是十分理想的。
在修改model中的data時,重新編譯渲染。在編譯過程中會將template模板轉(zhuǎn)變成一個render函數(shù),通過render函數(shù)生成Virtual Dom,調(diào)用vm._render方法生成一個新的虛擬節(jié)點(Vnode)。對原來的Vnode和新生成的Vnode進行diff算法計算出最小變更。這個過程中會實例化一個Watcher,Watcher是Dep實例中的一個對象,Watcher會調(diào)用Dep下的Notify方法遍歷Dep的Watcher實例數(shù)組,通過對應(yīng)的update方法來更新視圖。Vue.js使用了Virtual Dom,避免了回流和重繪的DOM操作,提高了性能。Virtual Dom流程相關(guān)的模板渲染過程如圖2所示。
圖2 模板渲染過程Fig.2 Template rendering process
組件化設(shè)計的優(yōu)勢如下:(1)組件標準化。每個組件都有統(tǒng)一的標準,有了標準和規(guī)范,各個組件才能更好地結(jié)合在一起使用。(2)功能分治。將系統(tǒng)的各個功能都封裝到不同的組件中,目的是讓每個組件可以獨立進行開發(fā),達到解耦。(3)組件復(fù)用。當系統(tǒng)中因為某個功能的改變導(dǎo)致組件不可以使用時,對組件進行回收,更換一個新的組件,或者對原先組件進行優(yōu)化。(4)組件組合。組件之間通過組合的方式,構(gòu)成一個功能或者一類功能模塊。組件化設(shè)計最終的目的是達到高效、協(xié)作以及復(fù)用。
模塊是指在系統(tǒng)開發(fā)中,用自執(zhí)行函數(shù)將多個函數(shù)包裹起來形成閉包,自執(zhí)行函數(shù)通過return將內(nèi)部函數(shù)暴露,這種方式也稱單例設(shè)計模式。模塊化就是把一個或多個函數(shù)封裝到一個JS中,各個JS之間互不影響,提供方通過export的方式進行暴露,使用方通過import的方式進行導(dǎo)入。模塊化最終的目的是解決開發(fā)中的命名沖突,避免因各個JS文件中存在同名變量導(dǎo)致的變量同名覆蓋問題;同時解決了各個JS之間的依賴問題,并達到代碼的復(fù)用,提高函數(shù)的可維護性,從而提高整體項目的開發(fā)效率,降低維護成本。
一般情況下,組件只需要操作自己的私有數(shù)據(jù)就能夠滿足某一功能的需求,但有些功能比較復(fù)雜,需要多個組件共同完成。父組件可以通過屬性傳遞的方式向子組件傳遞狀態(tài)(數(shù)據(jù)),子組件使用props接收父組件傳遞的數(shù)據(jù),通過this.$emit調(diào)用父組件的事件。父組件通過event.$on綁定自定義事件,子組件使用event.$emit調(diào)用自定義事件。但是,在組件之間不是父子或兄弟這種簡單關(guān)系,而是相隔多層的祖孫關(guān)系或者沒有任何關(guān)系,甚至多個組件之間共用一個狀態(tài)的情況下,使用上述組件通信方式就顯得特別笨拙。
采用Vuex對組件的全局狀態(tài)進行統(tǒng)一管理,實現(xiàn)了組件之間的數(shù)據(jù)共享。第一步,組件改變數(shù)據(jù)后通過執(zhí)行$store.dispatch()方法觸發(fā)action。第二步,在action處理異步或同步操作后執(zhí)行$store.commit()方法觸發(fā)mutation。第三步,在mutation中處理同步操作后更新狀態(tài)(數(shù)據(jù)),存儲在state中。組件可以通過$store.getters()方法獲取state中的全局對象,也可以通過import將mapGetters輔助函數(shù)導(dǎo)入并映射到組件的計算屬性(computed)中,在computed中使用擴展運算符...mapGetters將獲取的全局對象解構(gòu)。
Vuex可以集中管理組件間共享的狀態(tài)(數(shù)據(jù)),充分利用Vue.js的細粒度數(shù)據(jù)響應(yīng)機制來進行高效的狀態(tài)管理。存儲的數(shù)據(jù)是響應(yīng)式的,能夠保證在實時存儲、視圖更新的操作后,存儲數(shù)據(jù)與頁面數(shù)據(jù)保持同步。各步操作采用模塊化的方法,分工明確,提高了開發(fā)效率,易于后期維護。Vuex組件狀態(tài)管理如圖3所示。
圖3 Vuex組件狀態(tài)管理Fig.3 Vuex component status management
為避免用戶密碼信息泄露,危害系統(tǒng)安全,系統(tǒng)采用md5加密算法。md5是哈希算法中的一種信息摘要算法,不同長度的用戶密碼都會被加密成固定長度的32 位字符,并存儲在數(shù)據(jù)庫中。md5加密具有雪崩效應(yīng),明文的一丁點修改,都會造成密文的大幅變化,且密文到明文具有不可逆性。
但如果用戶密碼比較簡單,通過彩虹表仍可能被快速破解,故將明文密碼拆解為三部分,結(jié)合密鑰在特定部分加Salt,再進行多次md5加密。Salt具有動態(tài)性,用戶名具有唯一性,可將用戶名作為Salt(如明文拆解1+username+明文拆解2+密鑰+明文拆解3)。這樣處理前后的密文截然不同,即使Salt和密鑰泄露,彩虹表也無法使用,只能根據(jù)Salt和密鑰對彩虹表進行重新生成,且處理后的密碼具有超長位數(shù)和復(fù)雜度。在原始密碼只是純數(shù)字的情況下,分布列范圍至少為10的20次方;如果是數(shù)字加字母的密碼,其分布列范圍則達到了62的20次方,幾乎不可能破解。
普通的接口只需要攜帶指定參數(shù)就可以訪問后端服務(wù),多次惡意的訪問會導(dǎo)致服務(wù)器繁忙,甚至崩潰。采用JSON Web Token的認證機制,用戶登錄成功后會自動生成一個JWT Token,返回并存儲到Cookie中,用戶訪問接口時,附帶在請求頭中協(xié)同用戶提交的參數(shù)一起發(fā)送到服務(wù)端,服務(wù)端對發(fā)來的Token用密鑰解析,驗證請求是否合法以及判斷用戶的身份。
JSON Web Token包括JWT頭部(Header)、JWT主體(Playload)、JWT簽名(Veify Signature)。Header規(guī)定了所采用的加密算法和Token的類型,Playload中包含需要傳遞的數(shù)據(jù),Veify Signature對Playload中的數(shù)據(jù)進行運算,返回一串字符串,再使用Header中規(guī)定的加密算法進行加密,生成加密字符串。
本系統(tǒng)采用用戶名和姓名作為主體數(shù)據(jù),加密算法為HMAC SHA256,Token類型為JWT類型,自設(shè)密鑰。服務(wù)端接收到用戶發(fā)來的Token信息,使用密鑰進行解密,判斷Token有效期及Token對應(yīng)的用戶身份,身份驗證通過允許訪問接口。JWT驗證過程如圖4所示。
圖4 JWT驗證過程Fig.4 JWT validation process
JWT驗證過程:(1)可以指定Secure來確保Token只在Https協(xié)議下傳輸,防止傳輸過程中被竊聽。(2)在Cookie中設(shè)置HttpOnly,禁止通過JS獲取Cookie信息,一定程度上防御了XSS攻擊。(3)服務(wù)端可以檢查用戶的Refer和Origin,在Response中缺少Access-Control-Allow-Origin字段或者原始資源URI不明的情況下,可以拒絕處理該請求,防止惡意請求,一定程度上防御了CSRF攻擊,減輕服務(wù)器的壓力。
(1)設(shè)置授權(quán)機制。對授權(quán)碼采用md5加密方式,只有設(shè)置授權(quán)碼的管理員和被授予權(quán)限的用戶才知道真正的授權(quán)碼。
(2)防御跨站腳本攻擊(XSS)。對輸入文本的特殊字符進行轉(zhuǎn)義,例如將“<”變?yōu)椤?lt”,這樣被非法嵌入JavaScript的腳本就不能運行。
(3)防御跨偽造攻擊(CSRF)。重要請求接口采用POST請求方式,對重要操作增加了驗證措施。
(4)采用路由守衛(wèi)的導(dǎo)航模式。通過函數(shù)鉤子beforeEach對路由進行全局前置守衛(wèi),對登錄后才能訪問的頁面設(shè)置了攔截操作,登錄后服務(wù)端設(shè)置Token信息,只有Token存在時才能訪問。
系統(tǒng)用戶主要是醫(yī)務(wù)人員,群體較為固定。根據(jù)業(yè)務(wù)需求,功能模塊包括藥房管理、藥庫管理、門診藥房、數(shù)據(jù)統(tǒng)計、后臺管理等。(1)藥房管理實現(xiàn)了藥品報損、效期管理、藥品請領(lǐng);(2)藥庫管理實現(xiàn)了藥品入庫、藥品出庫、藥品盤點、庫存查詢、藥品移庫;(3)門診藥房如圖5所示,實現(xiàn)了處方發(fā)藥、狀態(tài)查詢、退藥處理;(4)數(shù)據(jù)統(tǒng)計包括訪問量、年度藥房藥庫銷售金額、當前庫存量、分類藥品銷量排行、年度藥房報損藥品金額、年度藥品報損原因、年度盤盈盤虧等統(tǒng)計;(5)后臺管理包括票據(jù)管理、調(diào)價管理、用戶管理、權(quán)限管理。
圖5 門診藥房功能模塊Fig.5 Functional modules of outpatient pharmacy
系統(tǒng)采用可視化面板(GUI)對代碼進行測試,自動生成測試報告,在4G環(huán)境下只需1.59 秒就可以加載完成,但ECharts和Element-UI的依賴體積過大,在一定程度上影響了系統(tǒng)的性能。同時在資源模塊中,出現(xiàn)對打包后的JS文件和部分圖片文件發(fā)出警告的問題。針對這些問題我們做了如下優(yōu)化:
(1)在依賴方面,通過import方式導(dǎo)入的所有依賴項都會被打包合并到一個文件中,導(dǎo)致文件體積過大。使用config.set方法創(chuàng)建白名單,白名單中的依賴項將不會被合并打包,而在window全局對象查找使用;在public下的index.html文件中添加以link、script的src方式引用CDN資源的配置項,通過修改Webpack的externals節(jié)點加載外部的CND資源。但完整引入Element-UI依賴項導(dǎo)致依賴包體積過大,采用按需加載的方式將所需的組件從Element-UI中解構(gòu)出來,僅對使用的組件進行打包。
(2)在路由方面,使用了路由懶加載技術(shù)。安裝babel插件,用箭頭函數(shù)的形式,通過webpackChunkName對路由進行分組,打包到不同的JS中,避免了一次性加載全部路由,只有當用戶訪問的時候才進行加載。通過Vue.js的內(nèi)置組件Keep-alive對路由進行緩存,避免了重復(fù)渲染,提高了頁面的加載效率,增加了用戶體驗感。
(3)在圖片方面,將較大的圖片壓縮成base64格式,以減少圖片體積。對于大量的圖片,可以通過v-lazy指令對圖片進行懶加載,圖片分多批次加載,用戶訪問時才觸發(fā)下一次加載。
(4)在代碼層面,進行組件通信時,通過event.$on生成自定義事件,框架提供的默認事件在組件生命周期結(jié)束時會被自動回收,而開發(fā)者定義的自定義事件會一直存在。本系統(tǒng)在組件的生命周期beforeDestory函數(shù)中通過evnent.$off銷毀自定義事件。此外,還要對全局變量進行回收,避免內(nèi)存泄漏。系統(tǒng)分配給應(yīng)用程序的內(nèi)存資源是有限的,當應(yīng)用程序向系統(tǒng)申請內(nèi)存時,系統(tǒng)會在堆內(nèi)存中開辟一塊內(nèi)存空間,如果應(yīng)用程序中的全局變量、自定義事件沒有被回收和銷毀,久而久之,內(nèi)存就會被占滿,發(fā)生內(nèi)存泄漏的現(xiàn)象,導(dǎo)致軟件卡頓直至崩潰。
(5)對于首屏加載過慢的問題或頁面組件過多難以一次性加載的情況,采用異步組件的方式,只有用戶觸發(fā)了該組件,才會對該組件進行渲染。對加載過的組件同樣進行緩存處理,避免二次加載造成資源浪費。
本文以藥房藥庫管理系統(tǒng)為例,運用前端工程化思想,采用前后端分離架構(gòu),前端采用了Vue.js,以組件和模塊的方式對視圖部分進行開發(fā);后端采用了Node.js,負責處理業(yè)務(wù)邏輯,提供數(shù)據(jù)接口,并結(jié)合MVVM模式、組件化、模塊化等解決方案,達到組件間高效、協(xié)作、復(fù)用的效果,模塊間互不影響,視圖與數(shù)據(jù)分離,細化了開發(fā)者的分工協(xié)作,并從用戶隱私安全、接口安全、系統(tǒng)安全三大角度進行了分析。提出組件狀態(tài)的管理機制以及錯誤信息的控制措施,從整體上提升了項目可維護性和拓展性,同時提高了開發(fā)效率,降低了開發(fā)成本。解決了傳統(tǒng)醫(yī)院信息系統(tǒng)分層開發(fā)方式的系統(tǒng)性能差、維護難、開發(fā)效率低下、層級間協(xié)作不一致的問題,提高了醫(yī)院的藥品管理水平,促進了醫(yī)院的信息化發(fā)展。