0.前言
前段時間忙了其他事了,感覺利用周末的時間效率好低哦。沒有平時上班時間的效率高。哈哈哈。這篇博客,主要是物聯網業務服務器前期的一些簡單設計。主要是設備如何進行登錄,從業務服務器那里獲取Token后,登陸到MQTT服務器。業務服務器對設備的登錄驗證,ACL權限驗證這兩方面。主要是把業務服務器與MQTT服務器聯系起來。
距離上次EMQ學習已經有一段時間了,測試服務器也已經換了。由於一些原因,只能用自己的服務器了。博客中出現的IP或者域名,由於只是測試環境,沒有進行過多的限制,有幸看到博客的,請不要搞破壞哦。哈哈哈。/滑稽
每個人對業務的設計的不同的,我只是根據自己的了解來設計的。有更好的想法,可以在下面評論區,跟我討論哦。
1.EMQ服務器配置
本次使用2.3.5版本。直接是從github.com上拉取下來的,然后進行源代碼進行安裝的。前期開發不用涉及到插件開發,可以直接使用官網提供的二進制包。不然的話,就用源代碼自己編譯。注意這里如果自己編譯的話,Erlang要 R20+版本才可以。自己到http://www.erlang.org 下載新版的Erlang/OTP。
一切的編譯步驟,我在前面的博客有提到,不清楚的可以看我前面的博客。雖然EMQ進行了小版本更新,但是大體的編譯流程還是差不多了。這里就不多說了。
編譯后,會在emq-relx目錄下生成_rel/emqttd目錄。
我覺得MQTT通信服務器與業務服務器是要盡量分開的,特別是不要在MQTT服務器里做過多的業務處理。所以使用EMQ服務器里面的帳號驗證和ACL權限驗證。采用的是Redis中間件驗證方式。
由於默認的EMQ服務器是不多帳號密碼進行驗證和主題的驗證。這里我們要開啟驗證。下面的這些配置是對默認的配置進行的修改。
(1)data/loaded_plugins 文件
這里要增加一行emq_auth_redis
(2)etc/emq.conf
由於現在是單服務器,性能配置什么都是默認的就可以。現在我還沒有了解所有的配置,性能調優以后看有沒有機會了解。
這里要修改的是可以修改node.name 和 node.cookie,兩個也可以使用默認就可以。
由於默認的EMQ服務器是不檢查帳號密碼和ACL權限的。這里需要修改emq.conf使其要進行驗證。還有cache_acl這個默認是true, 測試的時候最好是設置為false,這里如果是true的話,那么EMQ同樣會對ACL進行驗證。但是只驗證一次,就是在第一次發布或者訂閱對應的Topic時會去判斷ACL,如果通過了,那么下次對這個Topic進行sub/pub是不再進行判斷驗證的,同理不通過了,下次也不會進行驗證。如果有cache是可以提高性能,但是對於那些需要對權限動態修改的業務場景,這個功能就不能用了。對應的配置項是
1 mqtt.allow_anonymous = false 2 mqtt.acl_nomatch = deny 3 mqtt.cache_acl = false
(3)etc/plugins/emq_auth_redis.conf
修改對應的redis地址和密碼, 分別對應的配置項是 auth.redis.server=127.0.0.1:6379 和 auth.redis.password=password 剩下的cmd不用修改,使用默認的,后續開發如果需要自定義的,可以修改這里的cmd。
嗯,剩下的就不用再配置了。
./bin/emqttd start 啟動EMQ服務器就可以。然后通過Web管理界面進行查看,服務是否啟動。然后在里面提供的WebSocket界面測試連接。

2.業務代碼
如果在EMQ服務器里做業務處理的話,不是不行,但是呢,Erlang這個語言,處理業務不好,我也不熟悉,作為一個項目來說,招人也不好招。
我還是使用較為通用的Java來做業務處理。Java的生態會好很多。本次使用Spring Boot 2.0框架。由於本篇博客主要講的是物聯網業務方面的,關於Spring Boot的一些配置,這里就不展開了。以后有機會再寫成博客說明。
從上面的圖可以看到,現在沒有帳號密碼是不能登錄到MQTT服務器了。EMQ是通過查詢Redis然后進行驗證的。所以我只需要在業務服務器增加一段往Redis服務器寫入權限控制的數據記錄即可。關於默認的格式,參考EMQ文檔。
Login代碼段
1 @Autowired 2 private StringRedisTemplate stringredisTemplate; 3 4 @RequestMapping(value="login") 5 public @ResponseBody IOTDeviceModel login(HttpSession session, 6 @RequestParam("username") String username, @RequestParam("password") String password){ 7 //檢驗帳號密碼 8 IOTDeviceModel dev = new IOTDeviceModel(); 9 if(username.equals("demo")){ 10 dev = getIOTDeviceModelDemo(); 11 }else if(username.equals("test")){ 12 dev = getIOTDeviceModelTest(); 13 }else{ 14 dev.setMsg("用戶名,密碼錯誤."); 15 return dev; 16 } 17 session.setAttribute("session_user", dev); 18 //設置到 mqtt-redis 並返回token 19 stringredisTemplate.opsForHash().put("mqtt_user:" + dev.getUUID(), "password", dev.getToken()); 20 stringredisTemplate.expire("mqtt_user:" + dev.getUUID(), 100, TimeUnit.SECONDS); 21 //這里查詢模擬數據數據庫,允許當前用戶可以發布和訂閱的Topic 22 stringredisTemplate.opsForHash().put("mqtt_acl:" + dev.getUUID(), "/publicroom", "3"); 23 //返回成功 24 dev.setMsg("創建成功, 請使用uuid,token 登陸到mqtt服務器"); 25 return dev; 26 }
GetModel代碼段
1 //測試用戶 Demo 2 private IOTDeviceModel getIOTDeviceModelDemo(){ 3 IOTDeviceModel model = new IOTDeviceModel(); 4 model.setDevid(1); 5 model.setUsername("demo"); 6 model.setPassword("demo"); 7 model.setUUID("5a53a33d-98af-4f87-bb57-cc2e21450b36"); 8 model.setToken(UUID.randomUUID().toString()); //生成臨時的Token 9 return model; 10 } 11 //測試用戶Test 12 private IOTDeviceModel getIOTDeviceModelTest(){ 13 IOTDeviceModel model = new IOTDeviceModel(); 14 model.setDevid(2); 15 model.setUsername("test"); 16 model.setPassword("test"); 17 model.setUUID("f9a06e81-3f12-425b-b58d-21fca17b9932"); 18 model.setToken(UUID.randomUUID().toString()); 19 return model; 20 }
上面的代碼很簡單了,就是判斷當前帳號密碼是否正確,如果正確的,就寫入Redis。可能會有疑問,為什么設置到Redis的密碼,不直接使用password字段,而是隨機生成Token。這是由於業務中數據庫保存的密碼一般都是加密后的。而加密算法也是各不相同,默認的MD5可能不滿足,當然也有基於一些其他的原因。這里就是返回Token,然后用戶、設備通過業務服務器獲取到Token,登錄到MQTT服務器上。

這樣就登錄成功了。現在這個帳號只能發布和訂閱/publicroom這個主題。通過自帶的測試工具進行測試如下

可以發現,分別對/World 和 /publicroom這兩個主題進行訂閱和發布,都是成功的,但是真正在服務器發送數據通信的只有/publicroom這個主題,因為只有這個主題是被允許的,/World這個主題不允許。
其實按照正常邏輯來說,這里的/World最好是連訂閱都是不允許的,但是好像默認的這里WebSocket插件沒有做處理。如果以后我基於自己開發插件的話,這里可以進行限制處理了。
3.測試結果
主要重新梳理一下流程。
(1)一個簡答的測試界面
1 <!DOCTYPE html> 2 <html xmlns:th="http://www.thymeleaf.org" 3 xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> 4 <head> 5 <meta name="viewport" content="width=device-width,initial-scale=1"/> 6 <meta charset="UTF-8"/> 7 <title>EMQ-測試</title> 8 </head> 9 <body> 10 以下是模擬用戶/設備登錄業務服務獲取Token<br> 11 用戶名: <input id="username" /> 12 密碼: <input id="password" /> 13 <button id="login-btn">登錄業務服務器</button> <br> 14 登陸后信息: <div id='login-ret'></div> 15 16 <hr> 17 以下是用戶/設備獲取Token后登錄到MQTT服務器<br> 18 用戶名: <input id="mqtt-username"/> 19 密碼: <input id="mqtt-password"/> 20 <button id="mqtt-login-btn">登錄MQTT</button> <br> 21 登陸后信息: <div id="mqtt-login-ret"></div> 22 23 <hr> 24 登錄到MQTT后 25 <script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js" type="text/javascript"></script> 26 <script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.min.js" type="text/javascript"></script> 27 <script th:inline="javascript"> 28 jQuery(function($){ 29 bindBtnLogin(); 30 }); 31 32 function bindBtnLogin(){ 33 $("#login-btn").bind('click', function(){ 34 var username = $("#username").val(); 35 var password = $("#password").val(); 36 $.post("/login", { 37 username: username, 38 password: password 39 }, function(ret){ 40 console.log(ret); 41 $("#login-ret").html(JSON.stringify(ret)); 42 }); 43 }); 44 } 45 </script> 46 </body> 47 </html>

輸入demo demo 進行登錄,然后返回Token,然后我使用MQTT客戶端去連接。這里的客戶端使用的是 Eclipse Paho MQTT Utility 工具。
填寫對應的服務器地址,然后在選項里寫入帳號密碼,這里的帳號就是上圖的UUID,密碼就是上圖的Token了。然后登錄成功。

(2)查看Redis數據
這里使用Redis客戶端進行查看,方便調試

這里的mqtt_user:** 由於設置了TTL,所以會在一段時間后自動刪除掉。
(3)客戶端訂閱主題
依次在下面訂閱/publicroom和/TTT兩個主題,會發現兩個都訂閱成功(具體原因,上面有說到)。然后在下面的發布,分別對/publicroom和/TTT主題進行發布信息。下圖是運行結果。注意這里的訂閱要分開進行訂閱,不要兩個選中然后訂閱。這里的/TTT訂閱不成功了?這么說,EMQ服務器應該是有處理返回的。

(4)再用另外的帳號
這里同理,按照上面的流程,再用另外的帳號,進行登錄,然后兩個帳號互發信息。經測試,基本符合預期的。
(5)Web管理界面
這里看一下幾個管理界面。


兩個用戶,ClientID分別就是上面業務服務器模擬的兩台設備。
4.簡單流程圖

5.其他
最后多說兩句,一個產品的業務邏輯和業務需求的前期確認是跟一開始的Topic設計是相關的。Topic如果一開始設計的不好,后面的業務就很難進行擴展了。只能往EMQ增加邏輯判斷,我覺得是很沒有必要的。很多可以靠Topic的巧妙設計來避開復雜的業務邏輯。
另外,需要很多對設備的統計,操作的處理.例如統計當前主題下訂閱的用戶,踢出主題下的某個用戶,系統推送下發消息等.比如統計用戶,這個我覺得是業務相關的,需要從業務服務器中獲取,但是業務服務器一般沒有這些數據,所以就需要業務服務器Hook到MQTT服務獲取實時在線數.又比如消息下發推送,這個肯定是業務服務器下發消息,比如廣播等,同樣要寫MQTT插件.
當然,這個是個人的想法。期待下一篇博客 業務系統設計2。
