應用開發手冊
1 基本概念說明
本文提到的邊緣應用,是指部署在物業管理一體機的邊緣應用,是基于IoT的領域服務對接方案實現的邊緣應用。領域服務對接方案里有服務模型定義和數據模型定義,下面介紹一些相關的基本概念:
服務模型:
是一組可提供完整業務功能的HTTP/HTTPS接口。邊緣應用開發者可以根據對應的場景需求和業務需求進行這套接口定義。
服務提供方:
服務提供方即是可以提供服務模型里定義的服務功能的應用。服務提供方即可以是云端應用,也可以是邊緣端應用
服務依賴方:
服務依賴方即是使用服務模型里定義的服務功能的應用。服務依賴方也可以是云端應用或邊緣端應用
數據模型:
使用場景:本地系統/設備上報的消息可以基于數據模型進行定義,例如人臉通行事件
通過數據模型和IoT數據總線機制,可實現應用間的數據信息流轉
(數據提供方)應用可以通過數據添加的API接口,將本地系統或設備的消息送入數據總線。
(數據消費方)應用可以通過查詢數據模型的API接口獲取消息,也可以通過數據變更消息訂閱方式獲取消息。
數據模型支持圖片文件上傳,例如人臉通行事件的人臉照片
物業管理一體機框架:
物業管理一體機是基于K8s框架實現的,底層是托管底座,之上是基于docker的各種應用和服務。
LE組件是基于docker的服務程序,里面包含了支持各種設備接入驅動,例如門禁驅動,車行驅動,EBA設備驅動等
邊緣應用也是基于docker的應用程序。邊緣應用可通過編譯出的jar包,打包成鏡像,然后通過IoT云端平臺將應用下發到指定的物業管理一體機。邊緣應用的啟動入口,可以通過應用jar包里的docker file指定。
2 整體架構
2.1 核心模塊功能說明
云端應用:即SaaS應用,一般由ISV提供,是服務模型依賴方,負責服務模型的調用和數據訂閱。
物聯網云平臺:即IoT云端平臺。在領域服務對接方案里,與邊緣端核心服務一起提供服務總線和數據總線框架服務。
邊緣托管應用:即運行在物業管理一體機的邊緣應用,是服務模型的提供方。該應用一方面接受云端應用通過IoT平臺的服務模型調用,然后將調用轉換成本地系統支持的接口調用,另外一方面該應用處理本地的事件上報,然后通過邊緣數據總線,將消息安裝數據模型格式要求,上傳到IoT云端平臺。
2.2 核心流程說明
基于邊緣適配器應用的領域服務對接方案,核心流程包括兩個:至上而下的服務模型調用,至下而上的數據上報。下面分別說明。下面的序號,與框架圖中的序號一一對應。
服務調用:
(1) 云端SaaS應用調用服務模型提供的HTTP服務。 發起服務調用時,需提供項目Appkey, 路徑名稱(path)里需包含服務模型ID+接口方法名稱。
(2) 邊緣托管應用,偵聽到對應的服務調用后,進行業務處理。
數據上報:
(5) 邊緣托管應用處理事務后,可以將事件內容轉化成數據模型要求的格式,然后根據(邊緣)IoT平臺提供的數據插入接口,上報數據到邊緣數據總線,然后內部流轉到云端數據總線。
(6) 云端SaaS應用,可以通過IoT平臺提供的查詢數據模型的API接口獲取數據,也可以通過數據變更消息訂閱方式獲取數據。
3 開發指導
這里介紹的邊緣應用,是服務提供方應用。
3.1 對接服務模型
邊緣應用偵聽到對應的服務調用后,進行適當的適配轉換,再調用本地系統提供的HTTP服務。
邊緣應用對接服務模型,可以參考IoT公開鏈接:服務總線:服務提供的開發示例:
3.2 對接數據模型
具體對接可參考IoT公開鏈接:邊緣應用數據總線對接:http://bestwisewords.com/document_detail/145523.html?spm=a2c4g.11186623.6.595.81bc5ee4wgidUt
這里只補充說明一些需要關注的內容:
通過本地系統環境變量獲取appkey and appSecrect:
public static final String appkey =System.getenv("iot.hosting.appKey");
public static final String appSecret =System.getenv("iot.hosting.appSecret");
邊緣端數據模型服務路由
private static final String DATA_EDGE_PATH ="api.link.aliyun.com";
請求參數:
請求參數里的modelId,就是對應的數據模型Id。
request.putParam("modelId","value1");
對于邊緣端應用,下面兩個請求參數可以忽略掉
request.putParam("scopeId","value2");
request.putParam("appId","value3");
上傳文件數據模型接口說明:
下面鏈接文檔提供的接口只是獲得了需要上傳的文件名稱和URL:
如返回接口示例:
{
"id":"6fr2c332-c1db-417c-aa15-8c5trg3r5d92",
"code":200,
"message":null,
"localizedMsg":null,
"data":{
"fileName":"5269712352e5.jpg",
"url":"https://xxxxx.xxx.xx.com/xxx/file/5269712352e5.jpg?Expires=1557902379&OSSAccessKeyId=uyedjYL******&Signature=sotMFFIq4RP%2BWJSDScE8SxvO******"
}
開發者還需將真正需要上傳的文件,上傳到接口返回值里指定的fileName 和 url,示例代碼如下:
// response為上傳文件數據模型接口的返回
result = new String(response.getBody(), "UTF-8");
UploadResult uploadResult = JSON.parseObject(result, UploadResult.class);
data nameAndPath = uploadResult.getData();
String url = nameAndPath.getUrl();
HttpClient httpClient = HttpClients.createDefault();
HttpResponse response;
HttpPut put = new HttpPut(url);
// byte[] fileBytes:為準備要上傳的圖片文件
HttpEntity reqEntity = EntityBuilder.create().setBinary(fileByte).build();
put.setEntity(reqEntity);
response = httpClient.execute(put);
4 應用自測
4.1 物聯網應用服務平臺應用調試接口
應用通過AIoT開放平臺創建部署成功后,可以基于平臺的應用調試接口,進行服務接口的調用驗證:
?
進入應用管理,點擊需調試的應用。注意這個應用需要處于已發布狀態,如下圖所示
?
點擊實例管理-測試:
點擊服務提供測試,選擇一個要調試驗證的接口,點擊右側的進入接口調試界面
?
接口請求里,可以根據輸入參數要求補充相關參數,然后點擊發送,然后查看接口返回信息,如下圖所示:
4.2 模擬本地系統事件上報:Postman
前置條件:
PC機安裝了Postman軟件
物業管理一體機部署了適配器應用
PC機需要與物業管理一體機在相同的局域網內
?
模擬事件上報:
下面示例,以《停車場系統領域模型V3.1-數據模型定義》-車輛通行為例
路徑:物業一體機ip:port/具體路徑
請求Body內容示例如下:
{
"carCode": "浙A5C393",
"inTime": "2016-10-18 16:44:44",
"passTime": "2016-10-28 16:44:44",
"parkID": "88",
"inOrOut": "1",
"GUID": "134589c1d68d44d38dcb7f084b9cf8a1",
"channelID": "1",
"channelName": "北大門出口",
"imagePath": "https://ss1.bdstatic.com\\70cFuXSh_Q1YnxGkpoWK1HF6hhy\\it\\u=3854694535,624476780&fm=11&gp=0.jpg"
}
Postman示例截圖如下。其中192.168.1.40是物業一體機的內網IP地址,需根據實際情況填寫,10060是固定的端口號。
4.3 模擬本地服務調用:Postman
前置條件:
PC機安裝了Postman軟件
物業管理一體機部署了適配器應用
PC機需要與物業管理一體機在相同的局域網內
本地部署了相應的系統,例如部署了立方停車系統
?
模擬服務調用:
下面示例,以《停車場系統領域服務V3.1-服務模型定義》-1.1 查詢停車場信息為例
路徑: 物業一體機IP:port/服務模型ID/服務接口path
特別注意Header內容:定義 Content-Type 為 application/octet-stream
請求Body內容示例如下:
{
"id":"UniqueRequestId",
"version":"1.0",
"request":{
"apiVer":"1.0"
},
"params":{
}
}
Postman示例截圖如下。其中192.168.1.40是物業一體機的內網IP地址,需根據實際情況填寫,10060是固定的端口號。
4.4 如何查看邊緣應用日志
應用開發完成并部署到物業一體機進行調試,可以登錄到物業一體機,查看對應的應用日志:
進入應用管理,點擊需調試的適配器應用。注意這個應用需要處于已發布狀態,如下圖所示
?
選擇實例管理-管理
選擇節點運維
點擊SSH終端,選擇容器(一般只有一個,點擊選擇即可):
選擇完容器,將自動登錄到物業一體機。當前目錄即保持有該應用的日志
5 示例代碼
5.1 對接服務模型
以車輛加入安全黑/白名單服務為例
?
1、入口層
package com.aliyun.iotx.parkinglot.adapter.web.servicemodelcontroller;
import com.alibaba.fastjson.JSON;
import com.aliyun.iotx.common.base.service.IoTxResult;
import com.aliyun.iotx.parkinglot.adapter.enums.ParkingLotAdapterEnum;
import com.aliyun.iotx.parkinglot.adapter.service.BlackWhiteListService;
import com.aliyun.iotx.parkinglot.adapter.service.dto.VehiclePermissionalDTO;
import com.aliyun.iotx.parkinglot.adapter.service.serviceImpl.logicjudge.LogicJudge;
import com.aliyun.iotx.parkinglot.adapter.utils.IoTxResultUtils;
import com.aliyun.iotx.parkinglot.adapter.vo.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* 黑白名單
*/
@Slf4j
@RestController
@RequestMapping(value = "/iotx_parking_service_model", method = RequestMethod.POST)
public class BlackWhiteListController {
@Autowired
private BlackWhiteListService blackWhiteListService;
/**
* 7.1車輛加入安全黑/白名單
* @param request
* @return
*/
@RequestMapping(value = "/parkingLotVehicleListAdd")
public IoTxResult vehicleAddList(HttpServletRequest request) throws Exception {
String json="";
json= new String(readInputStream(request.getInputStream()),"UTF-8");
//將json數據解析成vo對象接收
BWListVo bwListVo = JSON.parseObject(json, BWListVo.class);
BlackWhiteListVo blackWhiteList = bwListVo.getParams();
//自己業務的處理邏輯
blackWhiteListService.vehicleAddList(blackWhiteList);
IoTxResult<Object> ioTxResult = new IoTxResult<>();
ioTxResult.setData(null);
IoTxResultUtils.ioTxResultSet(ParkingLotAdapterEnum.SUCCDESS_VEHICLEADDLIST, ioTxResult);
return ioTxResult;
}
}
/**
依賴類:
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
class BWListVo implements Serializable {
private static final long serialVersionUID = -8067179280515471493L;
/**
* request里的全局唯一id透傳
*/
private String id;
/**
* 請求協議版本
*/
private String version;
private Map<String, Object> request;
private BlackWhiteListVo params;
}
/**
* 車輛加入安全
* 黑白名單入參
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
@ValidateBean
class BlackWhiteListVo implements Serializable {
private static final long serialVersionUID = 831783475630914****L;
@NotBlank(message = "parkingLotId不能為空")
private String parkingLotId;
@JsonFormat(with = ACCEPT_SINGLE_VALUE_AS_ARRAY)
@EachValidate(constraint = NotBlank.class,message = "車牌號不能為空,不能有空字符串")
private List<String> plateNumber;
@JsonFormat(with = ACCEPT_SINGLE_VALUE_AS_ARRAY)
private List<String> areaId;
@NotBlank(message = "type不能為空")
private String type;
private String effectiveDate;
private String expiryDate;
}
5.2 對接數據模型
以車輛通行數據模型為例:
車輛通行接口入口
package com.aliyun.iotx.parkinglot.adapter.web.datamodelcontroller;
import com.alibaba.cloudapi.sdk.model.ApiResponse;
import com.alibaba.fastjson.JSON;
import com.aliyun.iotx.parkinglot.adapter.service.datamodelservice.IoTParkPassRecordService;
import com.aliyun.iotx.parkinglot.adapter.utils.UrlFormatUtil;
import com.aliyun.iotx.parkinglot.adapter.vo.InOutRecordVo;
import com.aliyun.iotx.parkinglot.adapter.vo.LFResultVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
* 數據模型
* 車輛通行
*/
@Slf4j
@RestController
public class IoTParkPassRecordController {
@Autowired
private IoTParkPassRecordService parkPassRecordService;
/**
* 上傳進出記錄
* 當代項目的圖片地址格式是
* @param inOutRecordVo
* @return
*/
@RequestMapping(value = "/reportInAndOutRecord", method = RequestMethod.POST)
public LFResultVo inOutRecordReport(@RequestBody InOutRecordVo inOutRecordVo) {
String newUrl = UrlFormatUtil.change(inOutRecordVo.getImagePath());
inOutRecordVo.setImagePath(newUrl);
//把記錄轉換為標準數據模型后上傳,需要引用后一段通用的DOP上傳代碼片段。參考下面的邊緣應用數據總線參考使用:
ApiResponse apiResponse = parkPassRecordService.inOutRecordReport(inOutRecordVo);
LFResultVo lfResultVo = new LFResultVo();
if (apiResponse.getCode() == 200) {
lfResultVo.setResCode(0);
lfResultVo.setResMsg("數據上報成功");
} else {
//立方上報數據失敗返回數字1
lfResultVo.setResCode(1);
lfResultVo.setResMsg("數據上報失敗");
}
return lfResultVo;
}
}
?
邊緣應用數據總線參考使用:
public class BlackWhiteList {
/**
* 車輛通行上傳
* @param request
* @return
*/
public static void main(String[] args) {
IoTApiRequest ioTApiRequest = new IoTApiRequest();
String uuid = UUID.randomUUID().toString();
String uuidOne = uuid.replace("-", "");
ioTApiRequest.setId(uuidOne);
ioTApiRequest.setApiVer("1.0");
ioTApiRequest.putParam("modelId", "iot_park_pass_record");
JSONObject properties = new JSONObject();
properties.put("direction", "獲取入參的對應的值");
properties.put("openType", "獲取入參的對應的值");
properties.put("plateNumber","獲取入參的對應的值");
//首先獲取到要上傳的文件名和上傳的路徑
ApiResponse response = getUploadFileNameAndPath();
String result = null;
try {
result = new String(response.getBody(), "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
//將JSON字符串轉化成對象
UploadResult uploadResult = JSON.parseObject(result, UploadResult.class);
data nameAndPath = uploadResult.getData();
properties.put("plateNumberImage", nameAndPath.getFileName());
//根據底層停車系統上報的文件路徑下載文件
String lfFileUrl = inOutRecordVo.getImagePath();
//下載后文件的名字
String name = nameAndPath.getFileName();
//文件保存路徑
String savePath = fsp.getSavePathOne();
if(!StringUtil.isEmpty(lfFileUrl)){
String url = FileUtil.getURL(lfFileUrl);
FileUtil.downLoadFromUrl(url,name,savePath);
}
//根據dop接口獲取到的path將文件上傳
//上傳文件的路徑
String url = nameAndPath.getUrl();
//要上傳的文件
File file = new File(savePath + name);
//判斷上傳是否成功
//首先判斷文件是否存在,存在才上傳文件
if(file.exists()){
byte[] fileBytes = FileUtil.getFileBytes(file);
boolean b1 = FileUtil.uploadFile(url, fileBytes);
if (!b1) {
log.info("文件上傳失敗");
throw new ParkingLotAdapterException(502, "文件上傳失敗", "File upload failed");
}
}
//文件上傳成功后,清除本地緩存的文件
FileUtil.deleteFile(savePath, name);
properties.put("typePermission", "獲取入參的對應的值");
properties.put("plateColor", "獲取入參的對應的值");
properties.put("plateType", "獲取入參的對應的值");
properties.put("vehicleColor", "獲取入參的對應的值");
properties.put("vehicleType", "獲取入參的對應的值");
properties.put("barrierId", "獲取入參的對應的值");
if (StringUtils.isNotBlank(inOutRecordVo.getChannelName())) {
properties.put("barrierName", "獲取入參的對應的值");
}
properties.put("parkingLotId","獲取入參的對應的值");
properties.put("areaId", "未知");
properties.put("orderNumber", "獲取入參的對應的值");
properties.put("recordId", "未知");
//數據上報的時間
Date date = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String eventTime = dateFormat.format(date);
properties.put("eventTime",eventTime); //應該產生一個時間上報
ioTApiRequest.putParam("properties", properties);
ApiResponse apiResponse = syncApiClient.postBody(host, path, ioTApiRequest, "https".equalsIgnoreCase(schema));
}
public static ApiResponse getUploadFileNameAndPath() {
IoTApiRequest request = new IoTApiRequest();
//設置api的版本
request.setApiVer("0.0.1");
// 接口參數
String uuid = UUID.randomUUID().toString();
request.setId(uuid.replace("-", ""));
JSONObject param = new JSONObject();
param.put("appId", "應用id");
param.put("version", "應用版本");
request.putParam("properties", param);
//這個參數對應于數據模型中需要上傳文件的字段
request.putParam("attrName", "plateNumberImage");
//這個參數對應于數據模型的模型id
request.putParam("modelId", "iot_park_pass_record");
request.putParam("fileType", "文件類型");
request.putParam("version", "版本);
request.putParam("fileSize", "文件大小");
try {
ApiResponse apiResponse = syncApiClient.postBody(DATA_EDGE_PATH,
"/data/model/data/upload", request, false);
return apiResponse;
} catch (IOException e) {
log.info("上傳文件獲取文件名和上傳路徑接口出現異常:{}", e.getMessage());
}
return null;
}
}
pom依賴:
<dependency>
<groupId>com.aliyun.iotx</groupId>
<artifactId>iotx-api-gateway-client</artifactId>
</dependency>
<dependency>
<groupId>com.aliyun.iotx</groupId>
<artifactId>common-base</artifactId>
</dependency>