第1章 億可控系統分析與設計
DownloadJavaEE精英進階課
提qu碼:xb2k
學習目標
- 了解物聯網應用領域及發展現狀
- 能夠說出億可控的核心功能
- 能夠畫出億可控的系統架構圖
- 能夠完成億可控環境的準備并了解億可控的功能結構
- 完成設備管理相關功能的開發
1.物聯網行業分析
1.1 什么是物聯網
物聯網(英文:Internet of Things,縮寫:IoT)起源于傳媒領域,是資訊科技產業的第三次革命,物聯網是指通過資訊傳感設備,按約定的協議,將任何物體與網路相連接,物體通過資訊傳播媒介進行資訊交換和通信,以實作智能化識別、定位、跟蹤、監管等功能, V(cmL46679910)
在物聯網應用中有三項關鍵技術,分別是感知層、網路傳輸層和應用層,
中國式物聯網定義:
最簡潔明了的定義:物聯網(Internet of Things)是一個基于互聯網、傳統電信網等資訊承載體,讓所有能夠被獨立尋址的普通物理物件實作互聯互通的網路,它具有普通物件設備化、自治終端互聯化和普適服務智能化3個重要特征,
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-M9buAq7M-1628513245731)(images/1-1.png)]
上圖中出現了四個概念,我們這里分別解釋一下:
兩化融合是資訊化和工業化的高層次的深度結合, 是指以資訊化帶動工業化、以工業化促進資訊化,走新型工業化道路;兩化融合的核心就是資訊化支撐,追求可持續發展模式,
M2M全稱Machine to Machine,是指資料從一臺終端傳送到另一臺終端,也就是機器與機器的對話,
射頻識別(RFID)是 Radio Frequency Identification 的縮寫,其原理為閱讀器與標簽之間進行非接觸式的資料通信,達到識別目標的目的,RFID 的應用非常廣泛,典型應用有動物晶片、汽車晶片防盜器、門禁管制、停車場管制、生產線自動化、物料管理,
傳感網是傳感器網路的簡稱,傳感器網路是集計算機、通信、網路、智能計算、傳感器、嵌入式系統、微電子等多個領域交叉綜合的新興學科,它將大量的多種類傳感器節點(集傳感、采集、處理、收發于一體)組成自治的網路,實作對物理世界的動態智能協同感知,
從上圖中可以看出,物聯網涵蓋了上邊所提到的四大領域,
“一句式”理解物聯網
把所有物品通過資訊傳感設備與互聯網連接起來,進行資訊交換,即物物相息,以實作智能化識別和管理,
歷史溯源
物聯網這個概念,中國在1999年提出來的時候叫傳感網,中科院早在1999年就啟動了傳感網的研究和開發,與其它國家相比,我國的技術研發水平處于世界前列,具有同發優勢和重大影響力,
2005年11月27日,在突尼斯舉行的資訊社會峰會上,國際電信聯盟(ITU)發布了《ITU互聯網報告2005:物聯網》,正式提出了物聯網的概念,
2009年8月24日,中國移動總裁王建宙在臺灣公開演講中,也提到了物聯網這個概念,
工信部總工程師朱宏任在中國工業運行2009年夏季報告會上表示,物聯網是個新概念,到2009年為止還沒有一個約定俗成的,大家公認的概念,他說,總的來說,“物聯網”是指各類傳感器和現有的“互聯網”相互銜接的一種新技術,
物聯網是在計算機互聯網的基礎上,利用RFID、無線資料通信等技術,構造一個覆寫世界上萬事萬物的“Internet of Things”,在這個網路中,物品(商品)能夠彼此進行“交流”,而無需人的干預,其實質是利用射頻自動識別(RFID)技術,通過計算機互聯網實作物品(商品)的自動識別和資訊的互聯與共享,
物聯網概念的問世,打破了之前的傳統思維,過去的思路一直是將物理基礎設施和IT基礎設施分開,一方面是機場、公路、建筑物,另一方面是資料中心,個人電腦、寬帶等,而在物聯網時代,鋼筋混凝土、電纜將與芯片、寬帶整合為統一的基礎設施,在此意義上,基礎設施更像是一塊新的地球,故也有業內人士認為物聯網與智能電網均是智慧地球的有機構成部分,
1.2 物聯網應用領域
1、智能家居
智能家居是利用先進的計算機技術,運用智能硬體(氦氪wifi、Zigbee、藍牙、NB-iot等),物聯網技術,通訊技術,將與家具生活的各種子系統有機的結合起來,通過統籌管理,讓家居生活更舒適,方便,有效,與安全,智能家居主要包括智能音箱、智能燈、智能插座、智能鎖、智能恒溫器、掃地機器人等,
2、智慧交通
智慧交通,是將物聯網、互聯網、云計算為代表的智能傳感技術、資訊網路技術、通信傳輸技術和資料處理技術等有效地集成,并應用到整個交通系統中,在更大的時空范圍內發揮作用的綜合交通體系 [2] ,智慧交通是以智慧路網、智慧出行、智慧裝備、智慧物流、智慧管理為重要內容,以資訊技術高度集成、資訊資源綜合運用為主要特征的大交通發展新模式,依托迪蒙科技在云計算、物聯網、大資料、金融科技等領域的豐富開發經驗和雄厚的技識訓累,歷時3年傾力打造的中國目前首家 一款集網約專車、智慧停車、汽車租賃、汽車金融,以及其他智慧出行領域創新商業模式于一體的高端智慧交通整體解決方案 [3] ,
4、智能電網
智能電網是在傳統電網的基礎上構建起來的集傳感、通信、計算、決策與控制為一體的綜合數物復合系統,通過獲取電網各層節點資源和設備的運行狀態,進行分層次的控制管理和電力調配,實作能量流、資訊流和業務流的高度一體化,提高電力系統運行穩定性,以達到最大限度地提高設備效利用率,提高安全可靠性,節能減排,提高用戶供電質量,提高可再生能源的利用效率,
4、智慧城市
智慧城市就是運用資訊和通信技術手段感測、分析、整合城市運行核心系統的各項關鍵資訊,從而對包括民生、環保、公共安全、城市服務、工商業活動在內的各種需求做出智能回應,其實質是利用先進的資訊技術,實作城市智慧式管理和運行,進而為城市中的人創造更美好的生活,促進城市的和諧、可持續成長,
隨著人類社會的不斷發展,未來城市將承載越來越多的人口,目前,我國正處于城鎮化加速發展的時期,部分地區“城市病”問題日益嚴峻,為解決城市發展難題,實作城市可持續發展,建設智慧城市已成為當今世界城市發展不可逆轉的歷史潮流,
智慧城市的建設在國內外許多地區已經展開,并取得了一系列成果,國內的如智慧上海、智慧雙流;國外如新加坡的“智慧國計劃”、韓國的“U-City計劃”等 ,
5、其它領域:智能汽車、智能建筑、智能水務、智能商業、智能工業、平安城市、智能農業、智能安防、智能醫療等,
1.3 物聯網發展現狀
消費級IOT蓬勃發展,仍處初級階段
物聯網通過相關設備將物與物、人與人進行聯網,
(1)規模:全球物聯網產業規模自 2008 年500億美元增長至 2018 年僅 1510 億美元,年均復合增速達 11.7%,我國物聯網產業規模2017年達 11500億元,自 2011 年起進一步加速,2009-2017 年均復合增速達 26.9%,我國物聯網發展速度較全球平均水平更快,
(2)滲透:全球物聯網行業滲透率 2013、2017 分別達 12%、29%,提升一倍多,預計2020年有超過 65%企業和組織將應用物聯網產品和方案,近年來,我國物聯網市場規模不斷擴大,2012年的 3650 億元增長到 2017 年的 11605 億元,年復合增長率高達 25%,
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-KRD9bsWJ-1628513245734)(images/1-2.png)]
2012-2017年我國物聯網市場規模(億元)
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-jLhCUwAK-1628513245737)(images/1-3.png)]
全球物聯網滲透率變化
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-VdeyotnC-1628513245739)(images/1-4.png)]
消費級物聯網:仍處于初級階段
消費級IOT預計快速增長,
(1)全球:2017全球消費級IOT硬體銷售額達4859億美元,同比增長29.5%,2015-2017 復合增速達 26.0%,2022 年銷售額望達 15502 億美元,2017-2022 年均復合增速達 26.1%,全球消費級 IOT 市場規模呈現進一步加速的趨勢,
(2)中國大陸: 2017 中國大陸消費級 IOT 硬體銷售額達 1188 億美元,同比增長 30.0%,2015-2017 復合增速達 28.9%,2022年銷售額望達 3118 億美元, 2017-2022 年均復合增速達 21.3%,2017年前因小米等公司的快速發展,中國消費級 IOT 發展整體快于全球平均水平,2017 年后在中國消費級 IOT 仍維持高速發展的狀況下,全球消費級 IOT 將發展更快,(3)連接設備:全球消費級 IOT 終端數量 2017年達 49 億個, 2015-2017 年均復合增速達 27.7%,預計 2022 年達 153億個, 2017-2022 年均復合增速達 25.4%, 2017 中國消費級 IOT 終端數量占世界達 26.5%,預計 2022 年占比提升至 29.4%, 2017-2022 預計復合增速達 28.2%,
全球消費級IOT市場規模:V(cmL46679910)
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-BrxpSZPn-1628513245741)(images/1-5.png)]
中國消費級IOT市場規模:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-jB5mJXP5-1628513245744)(images/1-6.png)]
全球及中國IOT終端數量:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-1f4gaz10-1628513245746)(images/1-7.png)]
2.億可控需求分析
2.1 需求概述
? 億可控作為一個中臺,對設備運行狀況進行實時在線監測、預警,不做業務相關的功能,
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-NTNA0JHO-1628513245749)(images/1-14.png)]
? 核心功能串列:
? (1)報文資料采集與指標決議 :整個系統的資料來源是通過接收設備發送過來的報文訊息,在系統中定義主題和訊息內容欄位的指標資料為過濾條件,從而對訊息進行收集和分析,
? (2)報警監控 : 通過和系統中定義的各種告警級別資料進行對比,一旦發現觸發到告警級別的訊息,就會通過和告警關聯配置的webhook來將告警資訊透傳到其它系統
? (3)GPS定位監控 :采集每臺設備的GPS定位,并提供設備位置查詢功能,
? (4)資料看板 : 提供豐富的自定義資料看板,
2.2 業務架構圖
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-qCiUBa83-1628513245752)(images/1-19.png )]
從上圖我們可以看到,真個系統從業務上分為6大功能模塊:圖形監控模塊、資料詳情展示模塊、看板管理模塊、設備管理模塊、報警管理模塊、系統管理模塊,
2.3 核心業務描述
產品原型地址:
https://app.mockplus.cn/run/prototype/yYVLQlJ-YN6/JhE4uVilt/4nw_LQ8n7
詳見資源提供的《億可控PRD檔案》
3.億可控系統架構
3.1 系統架構圖
整個系統的技術架構圖如下:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-lw4risdz-1628513245754)(images/1-10.png)]
預制資料將放入MySQL里進行存盤,設備上報的指標資料包括告警資料將存入influxDB中,設備的地理位置資訊資料存入到ES中以便后期搜索,為了提高系統的運行穩定性,有些頻繁訪問的資料儲存在redis中,因為考慮到設備上報的資料是非常頻繁的,如果單單只依靠MySQL資料庫的話,會很容易將MySQL服務器的CPU的占用率搞到100%,從而會引發整個系統的崩潰無法使用,
一些基本的配置放入到了consul的配置中心,考慮到系統的橫向擴展能力,將整個系統基于Consul做注冊中心來搭組建一個微服務,
3.2 資料庫設計
mysql資料庫有5個表:
管理員表tb_admin
| 列名 | 資料型別 | 說明 |
|---|---|---|
| id | int | 表主鍵id,自增 |
| login_name | varchar(50) | 登錄賬號 |
| password | varchar(60) | 密碼 |
| type | tinyint | 型別 1:超級管理員 2:普通用戶 目前作為保留欄位 |
| board | varchar(50) | 看板串列 |
指標配置表tb_quota
| 列名 | 資料型別 | 說明 |
|---|---|---|
| id | int | 表主鍵id |
| name | varchar(50) | 指標名稱 |
| unit | varchar(20) | 指標單位 |
| subject | varchar(50) | 報文主題 |
| value_key | varchar(50) | 指標值欄位 |
| sn_key | varchar(50) | 設備識別碼欄位 |
| webhook | varchar(1000) | web鉤子 |
| value_type | varchar(10) | 指標欄位型別,Double、Inteter、Boolean |
| reference_value | varchar(100) | 參考值 |
報警配置表tb_alarm
| 列名 | 資料型別 | 說明 |
|---|---|---|
| id | int | 表主鍵id,自增 |
| name | varchar(50) | 報警指標名稱 |
| quota_id | int | 關聯指標名稱 |
| operator | varchar(10) | 運算子 |
| threshold | int | 報警閾值 |
| level | int | 報警級別 1:一般 2:嚴重 |
| cycle | int | 沉默周期(以分鐘為單位) |
| webhook | varchar(1000) | web鉤子地址 |
面板配置表tb_board
| 列名 | 資料型別 | 說明 |
|---|---|---|
| id | int | 表主鍵id,自增 |
| admin_id | int | 管理員id |
| name | varchar(50) | 看板名稱 |
| quota | varchar(100) | 指標 |
| device | varchar(100) | 設備 |
| system | tinyint | 是否是系統看板 |
| disable | tinyint | 是否不顯示 |
GPS配置表tb_gps
| 列名 | 數據型別 | 說明 |
|---|---|---|
| id | bigint | 表主鍵id |
| subject | varchar(50) | 報文主題 |
| sn_key | varchar(50) | 設備識別碼欄位 |
| type | tinyint | 型別(單欄位、雙欄位) |
| value_key | varchar(50) | 經緯度欄位 |
| separation | varchar(10) | 經緯度分隔符 |
| longitude | varchar(20) | 經度欄位 |
| latitude | varchar(20) | 維度欄位 |
4.基礎代碼決議
4.1 環境準備
4.1.1 加載虛擬機鏡像
使用課程配套的虛擬機鏡像,
網路連接建議使用NAT模式,
本課程講義中提供的代碼,192.168.200.128為宿主機IP,如果你加載鏡像后不是此IP請自行調整,
已安裝好docker環境,并已拉取了所需鏡像,開箱即用,
4.1.2 MySQL建庫建表
連接虛擬機的mysql ,用戶名root ,密碼root123
創建資料庫ykk,創建表
create table if not exists tb_admin
(
id int auto_increment
primary key,
login_name varchar(50) null comment '登錄名',
password varchar(60) null comment '密碼',
type tinyint null comment '型別 1超級管理員 0普通用戶',
board varchar(50) null comment '看板'
);
create table if not exists tb_alarm
(
id int auto_increment comment 'id'
primary key,
name varchar(50) null comment '報警名稱',
quota_id int null comment '指標id',
operator varchar(10) null comment '運算子',
threshold int null comment '報警閾值',
level int null comment '報警級別 1一般 2嚴重',
cycle int null comment '沉默周期(分鐘)',
webhook varchar(1000) null comment 'web鉤子',
constraint tb_alarm_name_uindex
unique (name)
);
==V(cmL46679910)==
create table if not exists tb_board
(
id int auto_increment comment 'id'
primary key,
admin_id int default 1 null comment '管理員id',
name varchar(50) null comment '看板名稱',
quota varchar(100) default '0' null comment '指標(趨勢時設定)',
device varchar(100) null comment '設備(累計)',
`system` tinyint default 0 null comment '是否是系統看板',
disable tinyint default 0 null comment '是否不顯示',
constraint tb_board_name_uindex
unique (name)
);
create table if not exists tb_gps
(
id int not null comment 'id'
primary key,
subject varchar(50) null comment '主題',
sn_key varchar(50) null comment '設備識別碼欄位',
single_field tinyint null comment '型別(單欄位、雙欄位)',
value_key varchar(50) null comment '經緯度欄位',
separation varchar(10) null comment '經緯度分隔符',
longitude varchar(20) null comment '經度欄位',
latitude varchar(20) null comment '維度欄位',
constraint tb_gps_subject_uindex
unique (subject)
);
create table if not exists tb_quota
(
id int auto_increment comment 'id'
primary key,
name varchar(50) null comment '指標名稱',
unit varchar(20) null comment '指標單位',
subject varchar(50) null comment '報文主題',
value_key varchar(50) null comment '指標值欄位',
sn_key varchar(50) null comment '設備識別碼欄位',
webhook varchar(1000) null comment 'web鉤子',
value_type varchar(10) null comment '指標欄位型別,Double、Inteter、Boolean',
reference_value varchar(100) null comment '參考值',
constraint tb_quota_name_uindex
unique (name)
);
4.1.3 Consul添加配置
(1)進入Consul
打開瀏覽器,輸入地址 http://192.168.200.128:8500/
(2)創建配置 key為 config/backend-service/data value如下
spring:
datasource:
url: jdbc:mysql://192.168.200.128:3306/ykk?useUnicode=true&autoReconnect=true&autoReconnectForPools=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: root123
driver-class-name: com.mysql.jdbc.Driver
redis:
host: 192.168.200.128
port: 6379
database: 0
lettuce:
pool:
max-active: 10
max-wait: -1
max-idle: 5
min-idle: 1
shutdown-timeout: 100
timeout: 1000
password:
4.2 工程結構決議
專案主體框架截圖如下:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Pjp9PypR-1628513245757)(images/1-12.png)]
目前專案主要分為兩個部分:ykk-common和ykk-backend,
ykk-common模塊存放系統的一些基礎通用性定義,包括通用例外定義、資料庫聯接定義、還有一些常量定義,
ykk-backend模塊是我們后臺邏輯的實作代碼,里面按照具體的功能實作拆分到了具體的包里,
4.3 核心代碼決議
4.3.1 用戶登錄與JWT校驗
(1)用戶登錄業務邏輯
package com.yikekong.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.base.Strings;
import com.yikekong.entity.AdminEntity;
import com.yikekong.mapper.AdminMapper;
import com.yikekong.service.AdminService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
@Service
public class AdminServiceImpl extends ServiceImpl<AdminMapper,AdminEntity> implements AdminService{
@Override
public Integer login(String loginName, String password) {
if(Strings.isNullOrEmpty(loginName) || Strings.isNullOrEmpty(password)){
return -1;
}
QueryWrapper<AdminEntity> queryWrapper = new QueryWrapper<>();
queryWrapper
.lambda()
.eq(AdminEntity::getLoginName,loginName);
AdminEntity adminEntity = this.getOne(queryWrapper);
if(adminEntity == null)
return -1;
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
if(passwordEncoder.matches(password,adminEntity.getPassword())){
return adminEntity.getId();
}
return -1;
}
}
(2)用戶登錄控制器類
@RestController
public class AdminController{
@Autowired
private AdminService adminService;
@PostMapping("/login")
public LoginResultVO login(@RequestBody AdminVO admin){
LoginResultVO result = new LoginResultVO();
Integer adminId = adminService.login(admin.getLoginName(),admin.getPassword());
if(adminId < 0){
result.setLoginSuccess(false);
return result;
}
result.setAdminId(adminId);
String token = JwtUtil.createJWT(adminId);
result.setToken(token);
result.setLoginSuccess(true);
return result;
}
}
(3)登錄校驗
httpfilter包里AuthFilter是我們jwt的過濾器,主要來校驗jwt token,該類的實作如下:
package com.yikekong.httpfilter;
import org.elasticsearch.common.Strings;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
@WebFilter(urlPatterns = "/*",filterName = "authFilter")
public class AuthFilter implements Filter{
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest)servletRequest;
HttpServletResponse resp = (HttpServletResponse)servletResponse;
String path = ((HttpServletRequest) servletRequest).getServletPath();
//如果訪問的是login介面,不進行jwt token校驗
if(path.equals("/login")){
filterChain.doFilter(servletRequest, servletResponse);
return;
}
String authToken = ((HttpServletRequest) servletRequest).getHeader("Authorization");
//如何header中不存在Authorization的值,直接回傳校驗失敗
if(Strings.isNullOrEmpty(authToken)){
((HttpServletResponse) servletResponse).setStatus(HttpStatus.UNAUTHORIZED.value());
return;
}
try {
JwtUtil.parseJWT(authToken);
} catch (Exception e) {
//jwt校驗失敗,回傳
((HttpServletResponse) servletResponse).setStatus(HttpStatus.UNAUTHORIZED.value());
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
}
4.3.2 指標管理-創建指標
QuotaController的create方法用于創建指標
V(cmL46679910)
/**
* 創建指標
* @param vo
* @return
*/
@PostMapping
public boolean create(@RequestBody QuotaVO vo){
QuotaEntity quotaEntity = new QuotaEntity();
BeanUtils.copyProperties(vo,quotaEntity);
return quotaService.save(quotaEntity);
}
此方法接收的vo類,是前端的封裝視圖物件,有很多時候,前端傳遞過來的資料與我們后端資料庫對應的不一定完全一致,所以我們通常的做法是創建一個單獨的vo類,用于與前端進行資料的傳輸,這樣如果前端傳遞的資料物件發送結構變化,并不會影響到后端資料庫結構,
BeanUtils.copyProperties(vo,quotaEntity); 用于物件資料的拷貝,如果兩個物件有相同的屬性,會自動復制屬性,這樣可以避免在代碼中出現大量的setter方法,
5. 設備管理
5.1 設備添加
5.1.1 需求分析
在億可控系統中,我們不能也不需要從系統界面中添加設備,設備的添加,是在億可控接收到設備發過來的報文,決議后保存的,由于物聯網類的應用所使用的設備數量可能非常龐大,而對這部分資料的讀寫頻率又很頻繁,所以我們使用elasticsearch作為設備的資料庫,
5.1.2 索引庫結構設計
設備庫 device
| 列名 | 資料型別 | 說明 |
|---|---|---|
| deviceId | keyword | 設備編號 |
| alarm | boolean | 是否告警 |
| alarmName | keyword | 告警名稱 |
| level | integer | 告警級別 |
| online | boolean | 是否在線 |
| status | boolean | 開關 |
| tag | keyword | 標簽 |
5.1.3 代碼實作
(1)創建索引庫(打開kibana創建 http://192.168.200.128:5601/)
PUT /devices
{
"mappings": {
"properties": {
"deviceId": {
"type": "keyword"
},
"alarm": {
"type": "boolean"
},
"alarmName": {
"type": "keyword"
},
"level": {
"type": "integer"
},
"online": {
"type": "boolean"
},
"status": {
"type": "boolean"
},
"tag": {
"type": "keyword"
}
}
}
}
(2)pom.xml添加配置
<!--es相關依賴-->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.7.1</version>
</dependency>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.7.1</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>7.7.1</version>
</dependency>
<!--es相關依賴結束-->
(3)在組態檔中添加配置,以下配置添加到spring節點下
elasticsearch:
rest:
uris: http://192.168.200.128:9200
(4)創建包com.yikekong.dto , 創建用于封裝設備的DTO類
package com.yikekong.dto;
import lombok.Data;
import java.io.Serializable;
/**
* 設備DTO
*/
@Data
public class DeviceDTO implements Serializable {
private String deviceId;//設備編號
private Boolean alarm;// 是否告警
private String alarmName;//告警名稱
private Integer level;//告警級別
private Boolean online;//是否在線
private String tag;// 標簽
private Boolean status;//開關狀態
}
(5)創建com.yikekong.es包,包下創建ESRepository類,并撰寫添加設備的方法
package com.yikekong.es;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.yikekong.dto.DeviceDTO;
import com.yikekong.util.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.Map;
@Component
@Slf4j
public class ESRepository{
@Autowired
private RestHighLevelClient restHighLevelClient;
/**
* 添加設備
* @param deviceDTO
*/
public void addDevices(DeviceDTO deviceDTO){
if(deviceDTO==null ) return;
if(deviceDTO.getDeviceId()==null) return;
IndexRequest request=new IndexRequest("devices");
try {
String json = JsonUtil.serialize(deviceDTO);
Map map = JsonUtil.getByJson(json, Map.class);
request.source(map);
request.id(deviceDTO.getDeviceId());
restHighLevelClient.index(request, RequestOptions.DEFAULT);
} catch (JsonProcessingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
log.error("設備添加發生例外");
}
}
}
5.1.4 單元測驗
撰寫單元測驗
import com.yikekong.YkkApplication;
import com.yikekong.dto.DeviceDTO;
import com.yikekong.es.ESRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@SpringBootTest(classes = YkkApplication.class)
@RunWith(SpringRunner.class)
public class EsTest {
@Autowired
private ESRepository esRepository;
@Test
public void testAdd(){
DeviceDTO deviceDTO=new DeviceDTO();
deviceDTO.setDeviceId("123456");
deviceDTO.setStatus(true);
deviceDTO.setAlarm(false);
deviceDTO.setLevel(0);
deviceDTO.setAlarmName("");
deviceDTO.setOnline(true);
deviceDTO.setTag("");
esRepository.addDevices(deviceDTO);
}
}
查詢資料,驗證運行結果
GET devices/_search
{
"query": {
"match_all": {}
}
}
5.2 根據設備ID查詢設備
5.2.1 需求分析
根據id從elasticsearch中查詢設備資訊,在之后的報文決議的邏輯中需要呼叫此方法來實作設備的查詢,
5.2.2 代碼實作
ESRepository類添加方法
/**
* 根據設備id 查詢設備
* @param deviceId 設備id
* @return
*/
public DeviceDTO searchDeviceById(String deviceId){
SearchRequest searchRequest=new SearchRequest("devices");
SearchSourceBuilder searchSourceBuilder=new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.termQuery("_id",deviceId));
searchRequest.source(searchSourceBuilder);
try {
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
SearchHits hits = searchResponse.getHits();
long hitsCount = hits.getTotalHits().value;
if(hitsCount<=0) return null;
DeviceDTO deviceDTO=null;
for(SearchHit hit:hits){
String hitResult = hit.getSourceAsString();
deviceDTO=JsonUtil.getByJson(hitResult,DeviceDTO.class );
deviceDTO.setDeviceId(deviceId);
break;
}
return deviceDTO;
} catch (IOException e) {
e.printStackTrace();
log.error("查詢設備例外");
return null;
}
}
5.2.3 單元測驗
撰寫單元測驗方法,驗證代碼是否正確
@Test
public void testSearchById(){
DeviceDTO deviceDTO = esRepository.searchDeviceById("123456");
try {
String json = JsonUtil.serialize(deviceDTO);
System.out.println(json);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
5.3 設定設備狀態
5.3.1 需求分析
當我們不需要接收某設備的報文,可以將其關閉,已經關閉的設備對接收的報文指標不做任何處理,
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-A3vpfMkU-1628513245760)(images/1-15.png)]
5.3.2 API 介面
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-AMfqrtGl-1628513245764)(images/1-16.png)]
5.3.3 代碼實作
(1)ESRepository類添加方法實作對設備的開與關,
/**
* 更新設備狀態
* @param deviceId
* @param status
* @return
*/
public boolean updateStatus(String deviceId,Boolean status){
UpdateRequest updateRequest=new UpdateRequest("devices",deviceId)
.doc( "status",status );
try {
restHighLevelClient.update( updateRequest,RequestOptions.DEFAULT );
return true;
} catch (IOException e) {
e.printStackTrace();
log.error("更新設備狀態出錯");
return false;
}
}
(2)DeviceService新增方法定義
/**
* 更改設備狀態
* @param deviceId
* @param status
* @return
*/
boolean setStatus(String deviceId, Boolean status);
DeviceServiceImpl實作方法
@Autowired
private ESRepository esRepository;
@Override
public boolean setStatus(String deviceId, Boolean status) {
DeviceDTO deviceDTO = findDevice(deviceId);
if( deviceDTO==null ) return false;
return esRepository.updateStatus(deviceId,status);
}
/**
* 根據設備id查詢設備
* @param deviceId
* @return
*/
private DeviceDTO findDevice(String deviceId){
DeviceDTO deviceDTO = esRepository.searchDeviceById(deviceId);
return deviceDTO;
}
(3)DeviceController新增方法
/**
* 設定狀態的介面
* @param deviceVO
* @return
*/
@PutMapping("/status")
public boolean setStatus(@RequestBody DeviceVO deviceVO){
return deviceService.setStatus(deviceVO.getSn(),deviceVO.getStatus());
}
5.3.4 介面測驗
(1)運行YkkApplication啟動工程
(2)測驗介面,為了方便測驗,我們采用vscode的Rest Client插件來進行測驗,腳本是課程提供的“資料\測驗\yikekong.http” ,用vscode打開,找到以下腳本
####修改設備狀態########
PUT http://{{hostname}}:{{port}}/device/status HTTP/1.1
Authorization: {{Authorization}}
Content-Type: {{contentType}}
{
"sn":"123456",
"status":true
}
我們可以修改status值后 點擊 Send Request 鏈接進行測驗
5.4 設定設備標簽
5.4.1 需求分析 V(cmL46679910)
我們為了方便之后對設備進行查詢,我們可以對每個設備設定一個或多個標簽,在前端界面上沒有更新設備標簽的功能,此功能只對外部系統提供呼叫的介面,
5.4.2 代碼實作
(1)ESRepository類添加方法
/**
* 更新設備標簽
* @param deviceId
* @param tags
* @return
*/
public boolean updateDeviceTag(String deviceId,String tags){
UpdateRequest updateRequest=new UpdateRequest("devices",deviceId)
.doc( "tag",tags );
try {
restHighLevelClient.update( updateRequest,RequestOptions.DEFAULT );
return true;
} catch (IOException e) {
e.printStackTrace();
log.error("更新設備標簽出錯");
return false;
}
}
(2)DeviceService新增方法
/**
* 更新設備標簽
* @param deviceId
* @param tags
* @return
*/
boolean updateTags(String deviceId,String tags);
DeviceServiceImpl實作方法
@Override
public boolean updateTags(String deviceId, String tags) {
DeviceDTO deviceStatus = findDevice(deviceId);
if(deviceStatus == null) return false;
esRepository.updateDeviceTag(deviceId,tags);
return true;
}
(3)DeviceController新增方法
/**
* 設定標簽的介面
* @param deviceVO
* @return
*/
@PutMapping("/tags")
public boolean setTags(@RequestBody DeviceVO deviceVO){
return deviceService.updateTags(deviceVO.getSn(),deviceVO.getTags());
}
(4)AuthFilter類的doFilter新增代碼,對tags放行
//tag介面不校驗token
if(path.contains("/device/tags")){
filterChain.doFilter(servletRequest, servletResponse);
return;
}
5.4.3 介面測驗
(1)啟動工程
(2)找到以下腳本,進行測驗
####設定設備標簽############
PUT http://{{hostname}}:{{port}}/device/tags HTTP/1.1
Content-Type: {{contentType}}
{
"sn":"123456",
"tags":"學校"
}
5.5 更新設備告警資訊
5.5.1 需求分析
當設備發送過來的報文中的指標資訊達到告警級別,我們應該更新elasticsearch中的更新設備告警資訊(是否告警、告警級別、告警名稱)
5.5.2 代碼實作
ESRepository類添加方法
/**
* 更新設備告警資訊
* @param deviceDTO
* @return
*/
public boolean updateDevicesAlarm(DeviceDTO deviceDTO){
UpdateRequest updateRequest=new UpdateRequest("devices",deviceDTO.getDeviceId())
.doc( "alarm",deviceDTO.getAlarm(),//是否告警
"level",deviceDTO.getLevel(),//告警級別
"alarmName",deviceDTO.getAlarmName() );//告警名稱
try {
restHighLevelClient.update( updateRequest,RequestOptions.DEFAULT );
return true;
} catch (IOException e) {
e.printStackTrace();
log.error("更新設備告警資訊出錯");
return false;
}
}
5.5.3 單元測驗
撰寫單元測驗,在TestES中新增測驗方法
@Test
public void testAlarm(){
DeviceDTO deviceDTO=new DeviceDTO();
deviceDTO.setDeviceId("123456");
deviceDTO.setAlarm(true);
deviceDTO.setLevel(1);
deviceDTO.setAlarmName("溫度過高");
esRepository.updateDevicesAlarm(deviceDTO);
}
5.6 更新在線狀態
5.6.1 需求分析
在線狀態是指這個設備是否在線,如果設備存在網路故障就會導致設備離線,億可控系統可以監測設備的在線和離線狀態
5.6.2 代碼實作
我們這里需要在ESRepository類添加方法用于更新在線狀態,
/**
* 更新在線狀態
* @param deviceId
* @param online
* @return
*/
public boolean updateOnline(String deviceId,Boolean online){
UpdateRequest updateRequest=new UpdateRequest("devices",deviceId)
.doc( "online",online );
try {
restHighLevelClient.update( updateRequest,RequestOptions.DEFAULT );
return true;
} catch (IOException e) {
e.printStackTrace();
log.error("更新在線狀態出錯");
return false;
}
}
5.6.3 單元測驗
撰寫單元測驗,在TestES中新增測驗方法
@Test
public void testOnline(){
esRepository.updateOnline("123456",false);
}
5.7 分頁查詢設備
5.7.1 需求分析
有兩個頁面需要實作分頁查詢設備
(1)設備管理,如下圖效果,需要設備編號、標簽作為查詢條件分頁查詢
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-M9xsfZNI-1628513245768)(images/1-13.png)]
(2)設備詳情,如下圖效果,需要設備狀態、標簽、設備編號作為查詢條件分頁查詢
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-E3RFMZf2-1628513245771)(images/1-18.png)]
設備詳情頁比設備管理多了一個“設備狀態”的查詢條件,設備狀態有四個值:在線(0)、離線(1)、一般告警(2)、嚴重告警(3) ,
為了不讓代碼冗余,我們這兩個功能可以用同一個方法實作,
5.7.2 代碼實作
ESRepository類添加方法
/**
* 分頁查詢設備
* @param page 頁碼
* @param pageSize 頁大小
* @param deviceId 設備編號
* @param tags 標簽
* @param state 狀態
* @return
*/
public Pager<DeviceDTO> searchDevice(Long page,Long pageSize,String deviceId,String tags,Integer state){
SearchRequest searchRequest=new SearchRequest("devices");
SearchSourceBuilder sourceBuilder=new SearchSourceBuilder();
//條件查詢
BoolQueryBuilder boolQueryBuilder=QueryBuilders.boolQuery();
//設備編號
if(!Strings.isNullOrEmpty(deviceId)) {
boolQueryBuilder.must(QueryBuilders.wildcardQuery("deviceId", deviceId + "*"));
}
//標簽
if(!Strings.isNullOrEmpty(tags) ){
boolQueryBuilder.must(QueryBuilders.wildcardQuery("tag","*"+tags+"*"));
}
//狀態(在線狀態和告警狀態) 0:在線 1:離線 2:一般告警 3:嚴重告警
if(state!=null){
if(state.intValue()==0){
boolQueryBuilder.must( QueryBuilders.termQuery("online",true));
}
if(state.intValue()==1){
boolQueryBuilder.must( QueryBuilders.termQuery("online",false));
}
if(state.intValue()==2){
boolQueryBuilder.must( QueryBuilders.termQuery("level",1));
}
if(state.intValue()==3){
boolQueryBuilder.must( QueryBuilders.termQuery("level",2));
}
}
sourceBuilder.query(boolQueryBuilder);
//分頁
sourceBuilder.from( (page.intValue()-1)*pageSize.intValue() );
sourceBuilder.size( pageSize.intValue() );
sourceBuilder.trackTotalHits(true);
//排序
sourceBuilder.sort("level", SortOrder.DESC);//告警級別高的排前面
searchRequest.source(sourceBuilder);
try {
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
SearchHits hits = searchResponse.getHits();
SearchHit[] searchHits = hits.getHits();
List<DeviceDTO> devices= Lists.newArrayList();
for(SearchHit hit: searchHits){
String hitResult = hit.getSourceAsString();
DeviceDTO deviceDTO = JsonUtil.getByJson(hitResult, DeviceDTO.class);
devices.add(deviceDTO);
}
Pager<DeviceDTO> pager=new Pager<>( searchResponse.getHits().getTotalHits().value,pageSize );
pager.setItems(devices);
return pager;
} catch (IOException e) {
e.printStackTrace();
log.error("查詢設備失敗");
return null;
}
}
(2)DeviceService新增方法定義
/**
* 搜索設備
* @param page
* @param pageSize
* @param sn
* @param tag
* @return
*/
Pager<DeviceDTO> queryPage(Long page, Long pageSize, String sn, String tag, Integer status);
DeviceServiceImpl實作方法
@Override
public Pager<DeviceDTO> queryPage(Long page, Long pageSize, String sn, String tag, Integer status) {
return esRepository.searchDevice(page,pageSize,sn,tag,status);
}
(3)DeviceController新增方法
/**
* 分頁搜索設備
* @param page
* @param pageSize
* @param sn
* @param tag
* @return
*/
@GetMapping
public Pager<DeviceDTO> findPage(@RequestParam(value = "page",required = false,defaultValue = "1") Long page,
@RequestParam(value = "pageSize",required = false,defaultValue = "10") Long pageSize,
@RequestParam(value = "sn",required = false) String sn,
@RequestParam(value = "tag",required = false) String tag){
return deviceService.queryPage(page,pageSize,sn,tag,null);
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/293039.html
標籤:其他
