摘要:所幸,鄙人所在的硬件專業(yè),指導(dǎo)老師并不懂軟件,他只是想要一個農(nóng)業(yè)物聯(lián)網(wǎng)的監(jiān)測系統(tǒng),能提供給我的就是一個數(shù)據(jù)庫,帶著一個物聯(lián)網(wǎng)系統(tǒng)運行一年所保存的傳感器數(shù)據(jù)。該物聯(lián)網(wǎng)監(jiān)測系統(tǒng)整體上可分為三層數(shù)據(jù)庫層,服務(wù)器層和客戶端層。的執(zhí)行是同步的。
畢設(shè)大概是大學(xué)四年里最坑爹之一的事情了,畢竟一旦選題不好,就很容易浪費一年的時間做一個并沒有什么卵用,又不能學(xué)到什么東西的雞肋項目。所幸,鄙人所在的硬件專業(yè),指導(dǎo)老師并不懂軟件,他只是想要一個農(nóng)業(yè)物聯(lián)網(wǎng)的監(jiān)測系統(tǒng),能提供給我的就是一個Oracle 11d數(shù)據(jù)庫,帶著一個物聯(lián)網(wǎng)系統(tǒng)運行一年所保存的傳感器數(shù)據(jù)...That"s all。然后,因為他不懂軟件,所以他顯然以結(jié)果為導(dǎo)向,只要我交出一個移動客戶端和一個服務(wù)端,并不會關(guān)心我在其中用了多少坑爹的新技術(shù)。
那還說什么?上!我以強烈的惡搞精神,決定采用業(yè)界最新最坑爹最有可能爛尾的技術(shù),組成一個 Geek 大雜燴,幻想未來那個接手我工作的師兄的一臉懵逼,我露出了邪惡的笑容,一切只為了滿足自己的上新欲。
全部代碼在 GPL 許可證下開源:
服務(wù)端代碼:https://github.com/CauT/the-wall
客戶端代碼:https://github.com/CauT/Night...
由于數(shù)據(jù)庫是學(xué)校實驗室所有,所以不能放出數(shù)據(jù)以供運行,萬分抱歉~。理論上應(yīng)該有一份文檔,但事實上太懶,不知道什么時候會填坑~。
總體架構(gòu)OK,上圖說明技術(shù)框架。
?
該物聯(lián)網(wǎng)監(jiān)測系統(tǒng)整體上可分為三層:數(shù)據(jù)庫層,服務(wù)器層和客戶端層。
數(shù)據(jù)庫層除了原有的Oracle 11d數(shù)據(jù)庫以外,還額外增加了一個Redis數(shù)據(jù)庫。之所以增加第二個數(shù)據(jù)庫,原因為:
Node.js 的 Oracle 官方依賴 node-oracledb 沒有ORM,也就是說,所有的對數(shù)據(jù)庫的操作,都是直接執(zhí)行SQL語句,簡單粗暴,我擔心自己孱弱的數(shù)據(jù)庫功底(本行是 Android 開發(fā))會引發(fā)鎖表問題,所以通過限制只讀來避開這個問題。
由于該系統(tǒng)服務(wù)于農(nóng)業(yè)企業(yè)的內(nèi)部管理人員,因此其賬號數(shù)量和總體數(shù)據(jù)量必然有限,因此使用 redis 這種內(nèi)存型數(shù)據(jù)庫,可以不必考慮非關(guān)系型數(shù)據(jù)庫在容量占用上的劣勢。讀取速度反而較傳統(tǒng)的 SQL 數(shù)據(jù)庫有一定的優(yōu)勢。
使用非關(guān)系型數(shù)據(jù)庫比關(guān)系型數(shù)據(jù)庫好玩多了(霧
之所以寫了右邊的Git部分,是因為原本打算利用docker技術(shù)搞一個持續(xù)集成和部署的程序,實現(xiàn)提交代碼=>自動測試=>更新服務(wù)器部署更新=>客戶端自動更新 這樣一整套持續(xù)交付的流程,然而最后并沒有時間寫。
服務(wù)器層服務(wù)器層,采用 Node.js 的 Express 框架作為客戶端的 API 后臺。因為 Node.js 的單線程異步并發(fā)結(jié)構(gòu)使之可以輕松實現(xiàn)較高的 QPS,所以非常適合 API 后端這一特點。其框架設(shè)計和主要功能如下圖所示:
像網(wǎng)關(guān)層:鑒權(quán)模塊這么裝逼的說法,本質(zhì)也就是app.use(jwt({secret: config.jwt_secret}).unless({path: ["/signin"]}));一行而已。因為是直接從畢業(yè)論文里拿下來的圖,畢業(yè)論文都這尿性你們懂的,所以一些故弄玄虛敬請諒解。
客戶端層?客戶端層絕大部分是 React Native 代碼,但是監(jiān)控數(shù)據(jù)的圖表生成這一塊功能(如下圖),由于 React Native 目前沒有開源的成熟實現(xiàn);試圖通過 Native 代碼來畫圖表,需要實現(xiàn)一個 Native 和 React Native 互相嵌套的架構(gòu),又面臨一些可能的困難;故而最終選擇了內(nèi)嵌一個 html 頁面,前端代碼采用百度的 Echarts 框架來繪制圖表。最終的結(jié)構(gòu)就是大部分 React Native + 少部分 Html5 的客戶端結(jié)構(gòu)。
另外就是采用了 Redux 來統(tǒng)一應(yīng)用的事件分發(fā)和 UI 數(shù)據(jù)管理了??梢哉f,React Native 若能留名青史,Redux 必定是不可或缺的一大原因。這一點我們后文再述。
細節(jié)詳述 服務(wù)端層服務(wù)端接口表:
?
服務(wù)端程序的編寫過程中,往往涉及到了大量的異步操作,如數(shù)據(jù)庫讀取,網(wǎng)絡(luò)請求,JSON解析等等。而這些異步操作,又往往會因為具體的業(yè)務(wù)場景的要求,而需要保持一定的執(zhí)行順序。此外,還需要保證代碼的可讀性,顯然此時一味嵌套回調(diào)函數(shù),只會使我們陷入代碼幾乎不可讀的回調(diào)地獄(Callback Hell)中。最后,由于JavaScript單線程的執(zhí)行環(huán)境的特性,我們還需要避免指定不必要的執(zhí)行順序,以免降低了程序的運行性能。因此,我在項目中使用Promise模式來處理多異步的邏輯過程。如下代碼所示:
function renderGraph(req, res, filtereds) { var x = []; var ys = []; var titles = []; filtereds[0].forEach(function(row) { x.push(getLocalTime(row.RECTIME)); }); filtereds.forEach(function(filtered){ if (filtered[0] == undefined) // even if at least one of multi query was succeed // fast-fail is essential for secure throw new Error("數(shù)據(jù)庫返回結(jié)果為空"); var y = []; filtered.forEach(function(row) { y.push(row.ANALOGYVALUE); }); ys.push(y); titles.push(filtered[0].DEVICENAME + ": " + filtered[0].DEVICECODE); }); res.render("graph", { titles: titles, dataX: x, dataY: ys, height: req.query.height == undefined ? 200 : req.query.height, width: req.query.width == undefined ? 300 : req.query.width, }); } function resFilter(resolve, reject, connection, resultSet, numRows, filtered) { resultSet.getRows( numRows, function (err, rows) { if (err) { console.log(err.message); reject(err); } else if (rows.length == 0) { resolve(filtered); process.nextTick(function() { oracle.releaseConnection(connection); }); } else if (rows.length > 0) { filtered.push(rows[0]); resFilter(resolve, reject, connection, resultSet, numRows, filtered); } } ); } function createQuerySingleDeviceDataPromise(req, res, device_id, start_time, end_time) { return oracle.getConnection() .then(function(connection) { return oracle.execute( "SELECT DEVICE.DEVICEID, DEVICECODE, DEVICENAME, UNIT, ANALOGYVALUE, DEVICEHISTROY.RECTIME FROM DEVICE INNER JOIN DEVICEHISTROY ON DEVICE.DEVICEID = DEVICEHISTROY.DEVICEID WHERE DEVICE.DEVICEID = :device_id AND DEVICEHISTROY.RECTIME BETWEEN :start_time AND :end_time ORDER BY RECTIME", [ device_id, start_time, end_time ], { outFormat: oracle.OBJECT, resultSet: true }, connection ) .then(function(results) { var filtered = []; var filterGap = Math.floor( (end_time - start_time) / (120 * 100) ); return new Promise(function(resolve, reject) { resFilter(resolve, reject, connection, results.resultSet, filterGap, filtered); }); }) .catch(function(err) { res.status(500).json({ status: "error", message: err.message }); process.nextTick(function() { oracle.releaseConnection(connection); }); }); }); } function secureCheck(req, res) { let qry = req.query; if ( qry.device_ids == undefined || qry.start_time == undefined || qry.end_time == undefined ) { throw new Error("device_ids或start_time或end_time參數(shù)為undefined"); } if (req.query.end_time < req.query.start_time) { throw new Error("終止時間小于起始時間"); } }; router.get("/", function(req, res, next) { try { var device_ids; var queryPromises = []; secureCheck(req, res); device_ids = req.query.device_ids.toString().split(";"); for(let i=0; i這是生成指定N個傳感器在一段時間內(nèi)的折線圖的邏輯。顯然,剖析業(yè)務(wù)可知,我們需要在數(shù)據(jù)庫中查詢N次傳感器,獲得N個值對象數(shù)組,然后才能去用N組數(shù)據(jù)渲染出圖表的HTML頁面。 可以看到,外部核心的Promise控制的流程只集中于下面的幾行之中:Promise.all(queryPromises()).then(renderGraph()).catch()。即,只有獲取完N個傳感器的數(shù)值之后,才會去渲染圖表的HTML頁面,但是這N個傳感器的獲取過程卻又是并發(fā)進行的,由Promise.all()來實現(xiàn)的,合理地利用了有限的機器性能資源。
然而,推入queryPromises數(shù)組中的每個Promise對象,又構(gòu)成了自己的一條Promise邏輯鏈,只有這些子Promise邏輯鏈被處理完了,才可以說整個all()函數(shù)都被執(zhí)行完了。子Promise邏輯鏈大致地可以總結(jié)為以下形式:
function() { return new Promise().then().catch(); }其中的難點在于:
合理地切分整套業(yè)務(wù)邏輯到不同的then()函數(shù)中,且一個then()中只能有一個異步過程。
函數(shù)體內(nèi)的異步過程所產(chǎn)生的新的Promise邏輯鏈必須被通過return的方式掛載到父函數(shù)的Promise邏輯鏈中,否則即可能形成一個有先有后的控制流程。
catch()函數(shù)必須要做好捕捉和輸出錯誤的處理,否則代碼編寫過程中的錯誤即不可能被發(fā)現(xiàn),異步編程的整個過程也就無從繼續(xù)下去了。
值得一提的是Promise模式的引入。Node.js 自身不帶有Promise,可以引入標準的ECMAScript的Promise實現(xiàn),然而其功能較為簡陋,對于各種API的實現(xiàn)過于匱乏,因此最后選擇了bluebird庫來引入Promise模式的語言支持。
由此我們可以看到,沒有無緣無故的高性能。Node.js 的高并發(fā)的優(yōu)良表現(xiàn),是用異步編程的高復(fù)雜度換來的。當然,你也可以選擇不要編程復(fù)雜度,即不采用 Promise,Asnyc 等等異步編程模式,任由代碼淪入回調(diào)地獄之中,那么這時候的代價就是維護復(fù)雜度了。其中取舍,見仁見智。
客戶端層客戶端主要功能如下表所示:
?
接下來簡單介紹下幾個主要頁面??梢园l(fā)現(xiàn) iOS 明顯比 Android 要來的漂亮,因為只對 iOS 做了視覺上的細調(diào),直接遷移到 Android 上,就會由于屏幕顯示的色差問題,顯得非常粗糙。所以,對于跨平臺的 React Native App 來說,做兩套色值配置文件,以供兩個平臺使用,還是很有必要的。
?
上圖即是土壤墑情底欄的當前數(shù)據(jù)頁面,分別在Android和iOS上的顯示效果,默認展示所有當前的傳感器的數(shù)值,可以通過選擇傳感器種類或監(jiān)測站編號進行篩選,兩個條件可以分別設(shè)置,選定后再點擊查找,即向服務(wù)器發(fā)起請求,得到數(shù)據(jù)后刷新頁面。由于React Native 的組件化設(shè)計,刷新將只刷新下側(cè)的DashBoard部分,且,若有上次已經(jīng)渲染過的MonitorView,則會復(fù)用他們,不再重復(fù)渲染,從而實現(xiàn)了降低CPU占用的性能優(yōu)化。MonitorView,即每一個傳感器的展示小方塊,自上至下依次展示了傳感器種類,傳感器編號,當前的傳感器數(shù)值以及該傳感器顯示數(shù)值的單位。MonitorView和Dashboard均被抽象為一個一般化,可復(fù)用的組件,使之能夠被利用在氣象信息、病蟲害監(jiān)測之中,提升了開發(fā)效率,降低了代碼的重復(fù)率。
?
上圖是土壤墑情界面的歷史數(shù)據(jù)界面,分別在Android和iOS上的展示效果,默認不會顯示數(shù)據(jù),直到輸入了傳感器種類和監(jiān)測站編號,選擇了年月日時間后,再點擊查找,才會得到結(jié)果并顯示出來。該界面并非如同當前數(shù)據(jù)界面一樣,Android和iOS代碼完全共用。原因在于選擇月日和選擇時間的控件,Android和iOS系統(tǒng)有各自的控件,它們也被封裝為React Native中不同的控件,因此,兩條綠色的選擇時間的按鈕,被封裝為HistoricalDateSelectPad,分別放在componentIOS和componentAndroid文件夾中。界面下側(cè)的數(shù)據(jù)監(jiān)測板,即代碼中的Dashboard,是復(fù)用當前數(shù)據(jù)中的Dashboard。
?
上圖是土壤墑情界面的圖表生成界面,分別在Android和iOS上的展示效果。時間選擇界面,查找按鈕,選擇框,均可復(fù)用前兩個界面的代碼,因此無需多提。值得說的是,生成的折線圖,事實上是通過內(nèi)嵌的WebView來顯示一個網(wǎng)頁的。圖表網(wǎng)頁的生成,則依靠的百度Echarts 第三方庫,然后服務(wù)端提供了一個預(yù)先寫好的前端模板,Express框架填入需要的數(shù)據(jù),最后下發(fā)到移動客戶端上,渲染生成圖表。圖表支持了多曲線的刪減,手指選取查看具體數(shù)據(jù)點,放大縮小等功能。
?
上圖則是實際項目應(yīng)用中的Redux相關(guān)文件的結(jié)構(gòu)。stores中存放全局數(shù)據(jù)store相關(guān)的實現(xiàn)。
actions中則存放根據(jù)模塊切割開的各類action生成函數(shù)集合。在 Redux 中,改變 State 只能通過 action。并且,每一個 action 都必須是 Javascript Plain Object。事實上,創(chuàng)建 action 對象很少用這種每次直接聲明對象的方式,更多地是通過一個創(chuàng)建函數(shù)。這個函數(shù)被稱為Action Creator。
reducers中存放許多reducer的實現(xiàn),其中RootReducer是根文件,它負責把其他reducer拼接為一整個reducer,而reducer就是根據(jù) action 的語義來完成 State 變更的函數(shù)。Reducer 的執(zhí)行是同步的。在給定 initState 以及一系列的 actions,無論在什么時間,重復(fù)執(zhí)行多少次 Reducer,都應(yīng)該得到相同的 newState。
性能測試 服務(wù)端測試工具:OS X Activity Monitor(http_load)
?
客戶端iOS
測試工具:Xcode 7.3
?
Android
測試工具:Android Studio 1.2.0
?
代碼量相關(guān)?
簡單總結(jié)React Native 盡管在開發(fā)上具有這樣那樣的坑,但是因其天生的跨平臺,和優(yōu)于 Html5的移動性能表現(xiàn),使得他在寫一些不太復(fù)雜的 App 的時候,開發(fā)速度非??欤詭杀?buff。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/80619.html
摘要:本文分享幾種典型具有實際應(yīng)用過的智慧農(nóng)業(yè)物聯(lián)網(wǎng)解決方案,供大家參考。智慧農(nóng)業(yè)物聯(lián)網(wǎng)解決方案由三部分組成智慧農(nóng)業(yè)物聯(lián)網(wǎng)平臺智慧農(nóng)業(yè)物聯(lián)網(wǎng)網(wǎng)關(guān)無線節(jié)點。 智慧農(nóng)業(yè)是現(xiàn)代農(nóng)業(yè)發(fā)展的必然趨勢,也是科學(xué)技術(shù)發(fā)展的必然結(jié)果。本文分享幾種典型具有實際應(yīng)用過的智慧農(nóng)業(yè)物聯(lián)網(wǎng)解決方案,供大家參考。 結(jié)合多年與...
摘要:建立面向中小企業(yè)的信息化服務(wù)云計算平臺,為中小企業(yè)提供生產(chǎn)管理財務(wù)管理營銷管理人力資源管理等云計算服務(wù)?! 逗颖笔⌒畔⒎?wù)業(yè)十三五發(fā)展規(guī)劃》明確,十三五期間,河北省將重點推進云計算服務(wù)能力促進工程、云計算創(chuàng)新能力提升工程、云計算服務(wù)應(yīng)用示范工程、電子政務(wù)集約化建設(shè)工程、數(shù)據(jù)資源開發(fā)共享工程、云計算產(chǎn)業(yè)鏈發(fā)展培育工程、云計算基礎(chǔ)設(shè)施建設(shè)工程、云計算安全保障建設(shè)工程等八大工程,統(tǒng)籌規(guī)劃建設(shè)云上...
摘要:首先很遺憾的一點是,雖然是最好的語言,但是它不是最流行的語言。屬于配置比較高的硬件,而低配的呢三星設(shè)計了引擎,它能夠運行在小于內(nèi)存上,且全部代碼能夠存儲在不足的只讀存儲上。你覺得還能做什么 首先很遺憾的一點是,PHP雖然是最好的語言,但是它不是最流行的語言。showImg(https://segmentfault.com/img/bVvqTs);同時對不起的還有剛剛在4月TIOBE編程...
摘要:首先很遺憾的一點是,雖然是最好的語言,但是它不是最流行的語言。屬于配置比較高的硬件,而低配的呢三星設(shè)計了引擎,它能夠運行在小于內(nèi)存上,且全部代碼能夠存儲在不足的只讀存儲上。你覺得還能做什么 首先很遺憾的一點是,PHP雖然是最好的語言,但是它不是最流行的語言。showImg(https://segmentfault.com/img/bVvqTs);同時對不起的還有剛剛在4月TIOBE編程...
摘要:在考慮宇航員的生命安全時,輕微的打嗝或者服務(wù)中斷都會釀成生死事故。也許最大的挑戰(zhàn)來自谷歌主導(dǎo)的簡稱。在最近的開發(fā)者峰會,以及今年的會議上,谷歌都為安排了大量討論。由微軟提供,是廣受歡迎的編輯器,到月份已經(jīng)獲得了超過五百萬用戶。 譯者:安冬 (滬江Web前端開發(fā)工程師)本文原創(chuàng)翻譯,轉(zhuǎn)載請注明作者及出處。原文地址:http://developer.telerik.com/... 技術(shù)世界...
閱讀 1034·2019-08-30 15:55
閱讀 3478·2019-08-30 13:10
閱讀 1299·2019-08-29 18:45
閱讀 2384·2019-08-29 16:25
閱讀 2145·2019-08-29 15:13
閱讀 2459·2019-08-29 11:29
閱讀 582·2019-08-26 17:34
閱讀 1523·2019-08-26 13:57