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