适配器应用

更新时间:

本章节主要讲述适配器应用的开发、部署、调试等功能。

修改记录及版本信息

序号

版本号

修改内容

修改时间

1

Rev1.0

文档创建

2021-05-20

2

Rev1.1

补充 4.2

2021-05-25

3

Rev1.2

补充 6

2021-05-27

4

Rev1.3

更新附录2:查看日志

2021-05-28

5

Rev1.4

更新6:示例代码

2021-06-03

1 基本概念说明

本文提到的边缘应用,是指部署在物业管理一体机的边缘应用,是基于IoT的领域服务对接方案实现的边缘应用。领域服务对接方案里有服务模型定义和数据模型定义,下面介绍一些相关的基本概念:

服务模型:

针对某项业务,可提供一组完整功能的HTTP接口。例如停车场领域服务模型。 边缘应用开发者可以根据对应的场景需求和业务需求进行这套接口定义。

服务提供方:

服务提供方即是可以提供服务模型里定义的服务功能的应用。服务提供方既可以是云端应用,也可以是边缘端应用。

服务依赖方:

服务依赖方即是使用服务模型里定义的服务功能的应用。服务依赖方也可以是云端应用或边缘端应用。

数据模型:

  • 使用场景:本地系统/设备上报的消息可以基于数据模型进行定义,例如人脸通行事件。

  • 通过数据模型和IoT数据总线机制,可实现应用间的数据信息流转。

  • IoT平台提供了对数据进行增删改查的4个API,以及HTTP2方式的消息订阅机制。

  • (数据提供方)应用可以通过数据添加的API接口,将本地系统或设备的消息送入数据总线。

  • (数据消费方)应用可以通过查询数据模型的API接口获取消息,也可以通过数据变更消息订阅方式获取消息。

  • 数据模型支持图片文件上传,例如人脸通行事件的人脸照片。

物业管理一体机框架:

image

物业管理一体机是基于K8s系统实现的,底层是EdgeBox底座,上面是基于docker的各种应用和服务。

LE组件也是基于docker的服务程序,里面包含了支持各种设备接入驱动,例如门禁驱动,车行驱动,EBA设备驱动等。

边缘应用也是基于docker的应用程序。边缘应用可通过编译出的jar包,打包成镜像,然后通过IoT云端平台将应用下发到指定的物业管理一体机。边缘应用启动入口,可以通过应用jar包里的docker file指定。

2 整体架构

下面将介绍边缘端适配器应用在整体架构里的位置以及上下游模块的关系,便于更好的理解边缘应用开发方法。

image

2.1 核心模块功能说明

云端应用:即SaaS应用,一般由ISV提供,即服务模型依赖方,负责服务模型的调用和数据订阅。

物联网云平台:即IoT云端平台。在领域服务对接方案里,与边缘端核心服务一起提供服务总线和数据总线框架服务。

适配器应用:即运行在物业管理一体机的边缘应用,是服务模型的提供方。一方面该应用接受云端应用通过IoT平台的服务调用,然后将调用转换成本地系统支持的接口调用,另外一方面该应用接受本地系统的事件上报,然后通过边缘数据总线,将消息安装数据模型格式要求,上传到IoT云端平台。

本地系统:即项目现场的本地各种系统,例如立方停车场系统。

2.2 核心流程说明

基于边缘适配器应用的领域服务对接方案,核心流程包括两个:至上而下的服务模型调用,至下而上的数据上报。下面分别说明。下面的序号,与框架图中的序号一一对应。

服务调用:

(1)云端SaaS应用调用服务模型提供的HTTP服务。 发起服务调用时,需提供项目Appkey, 路径名称(path)里需包含服务模型ID+接口方法名称。

(2)边缘适配器应用,侦听到对应的服务调用后,进行适当的适配转换,再调用本地系统提供的HTTP服务。

(3)本地系统有一套对外开放的HTTP服务。侦听到来自边缘适配器应用调用后,完成相应的功能并将结果返回。

数据上报:

(4)本地系统发生事件,可以将事件内容通过HTTP接口发送给边缘适配器应用。接口的URL可以双方(适配器应用/本地系统)约定。

(5)边缘适配器应用侦听到事件请求后,可以将事件内容转化成数据模型要求的格式,然后根据(边缘)IoT平台提供的数据插入接口,上报数据到边缘数据总线,然后内部流转到云端数据总线。

(6)云端SaaS应用,可以通过IoT平台提供的查询数据模型的API接口获取数据,也可以通过数据变更消息订阅方式获取数据。

3 边缘应用开发指导

这里介绍的边缘应用,是服务提供方应用。

3.1 边缘应用对接服务模型

边缘应用侦听到对应的服务调用后,进行适当的适配转换,再调用本地系统提供的HTTP服务。

边缘应用对接服务模型,可以参考IoT公开链接:服务总线:服务提供的开发示例:

https://help.aliyun.com/document_detail/114863.html?spm=a2c4g.11186623.6.579.30243aa12KgeDk#h2-7-2-8

3.2 边缘应用对接数据模型

具体对接可参考IoT公开链接:边缘应用数据总线对接:https://help.aliyun.com/document_detail/145523.html?spm=a2c4g.11186623.6.595.81bc5ee4wgidUt

这里只补充说明一些需要关注的内容:

通过本地系统环境变量获取appkey and appSecrect:

  1. public static final

    String

    appkey =

    System

    .getenv(

    "iot.hosting.appKey"

    );

  2. public static final

    String

    appSecret =

    System

    .getenv(

    "iot.hosting.appSecret"

    );

  3. //边缘端数据模型服务路由

private static finalStringDATA_EDGE_PATH =System.getenv("iot.hosting.api.domain");

请求参数:

请求参数里的modelId,就是对应的数据模型Id。

  1. request.putParam(

    "modelId"

    ,

    "value1"

    );

对于边缘端应用,下面两个请求参数可以忽略掉。

  1. request

    .

    putParam

    (

    "scopeId"

    ,

    "value2"

    );

  2. request.putParam(

    "appId"

    ,

    "value3"

    );

上传文件数据模型接口说明:

下面链接文档提供的接口只是获得了需要上传的文件名称和URL:

https://help.aliyun.com/document_detail/145523.html?spm=a2c4g.11186623.6.595.56c2610adevJLp#h2-2-5-5

如返回接口示例:

{
"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=uyedjYLHD****&Signature=sotMFFIq4RP%2BWJSDScE8SxvOlvo%3D"
}

开发者还需将真正需要上传的文件,上传到接口返回值里指定的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 服务依赖方应用开发指导

服务依赖方,例如云端SaaS应用,需要进行服务调用和订阅数据。这里也是列出需要特别关注的点。详细内容请参考:附录1 参考链接:1:边缘应用服务总线对接;3:服务总线。

4.1 APPkey and AppSecrect

云端SaaS应用访问服务模型时,请求参数里的Appkey and AppSecrect, 可以从IoT平台的数字园区引擎的项目里查看得到,如下图所示:

image

4.2 下载文件数据模型接口

可以参考边缘应用数据总线对接的下载文件数据模型接口:

https://help.aliyun.com/document_detail/145523.html?spm=a2c4g.11186623.6.595.1b1e610aWUV5Bv#h2-2-6-6

需要注意下面几点:

a: Appkey and Appsecrect:

请参考4.1 里的说明获取

b: filename:

入参列表里缺失了filename,实际上是需要的:

request.putParam("fileName","value1");

filename是从订阅的数据模型消息里获取到的

c: scopeid:

scopeid就是项目id。在数字园区引擎里选择打开某个实例后,浏览器地址栏里projectId就是scopeid,如下图所示:

image

下载文件数据模型接口返回参数里,提供了需下载的文件URL:

{
   "id": "6fr2c332-c1db-417c-aa15-8c5trg3r5d92",
    "code": 200,
    "message": null,
    "localizedMsg": null,
    "data": {
        "URL": "https://xxxxx.xxx.xx.com/xxx/file/5269712352e5.jpg?Expires=1557902379&OSSAccessKeyId=uyedjYLHD****&Signature=sotMFFIq4RP%2BWJSDScE8SxvOlvo%3D"
    }
}

开发者还需要根据这个URL,进行文件下载操作。

4.3 数据查询/数据订阅

前面提到过,云端应用可以通过IoT平台提供的查询数据模型的API接口获取数据,也可以通过数据变更消息订阅方式获取数据。

可以分别参考下面内容实现,只是注意Appkey and Appsecrect需要参考4.1 里的说明得到

数据查询:请参考:https://help.aliyun.com/document_detail/145523.html?spm=a2c4g.11186623.6.595.1b1e610aWUV5Bv#h2-2-4-4

数据订阅:请参考:https://help.aliyun.com/document_detail/145523.html?spm=a2c4g.11186623.6.595.1b1e610aWUV5Bv#h2-2-7-7

5 边缘端应用自测

5.1 模拟本地系统事件上报:Postman

前置条件:

  • PC机安装了Postman软件

  • 物业管理一体机部署了适配器应用

  • PC机需要与物业管理一体机在相同的局域网内

模拟事件上报:

下面示例,以《停车场系统领域模型V3.1-数据模型定义》-车辆通行为例

路径:物业一体机ip:port/具体路径

请求Body内容示例如下:

{
    "carCode": "浙A5****",
    "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示例截图如下:

00 文档图片.png

5.2 模拟本地服务调用: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示例截图:

image

image

6 示例代码

6.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;
}

6.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;
    }
}

边缘应用数据总线文档:https://help.aliyun.com/document_detail/145523.html?spm=a2c4g.11186623.6.595.4eaf2577JupxLJ#h2-2-1-1

边缘应用数据总线参考使用:

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>

6.3 云端应用

服务模型调用示例代码:

* Copyright 2017 Alibaba Group
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
    
package com.test.demo;/*

import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import com.alibaba.cloudapi.sdk.model.ApiResponse;

*/
public class RequestDemo {

    public static void main(String[] args) throws UnsupportedEncodingException {
        postRequestDemo();
    }

    public static void postRequestDemo() throws UnsupportedEncodingException {
        
        IoTApiClientBuilderParams builderParams = new IoTApiClientBuilderParams();
        
        builderParams.setAppKey("32570812"); //appKey
        builderParams.setAppSecret("9c991848cd11f932bcddae23cc8a25d3"); //appSecret

        SyncApiClient syncClient = new SyncApiClient(builderParams);

        IoTApiRequest request = new IoTApiRequest();

        //设置api的版本
        request.setApiVer("1.0");
        request.setId("42423423");

        //如果需要登录,设置当前的会话的token
        //request.setIotToken("xxxxxxxxxxxxxxx");

        //设置参数 --看具体路径下是否需要参数
        //request.putParam("", "");
        
        //请求参数域名、path、request
        String host = "service-mesh.api-iot.cn-shanghai.aliyuncs.com"; //阿里云服务器域名
        String path = "/iotx_parking_service_model/parkingLotInfoGet"; //适配器具体路径
        
        ApiResponse response = syncClient.postBody(host, path, request);
        System.out.println(
            "response code = " + response.getCode() + " response content = " + new String(response.getBody(),
                "utf-8"));
    }
}

数据模型订阅消费示例代码:

package com.test.demo;


import com.alibaba.cloudapi.sdk.model.ApiResponse;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.openservices.iot.api.Profile;
import com.aliyun.openservices.iot.api.message.MessageClientFactory;
import com.aliyun.openservices.iot.api.message.api.MessageClient;
import com.aliyun.openservices.iot.api.message.callback.MessageCallback;
import com.aliyun.openservices.iot.api.message.entity.Message;
import com.aliyun.openservices.iot.api.message.entity.MessageToken;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.concurrent.TimeoutException;
@Slf4j
public class Subscribe {

    public static void main(String[] args) throws InterruptedException {

        String appKey = "32393627";
        String appSecret = "f578d197746f9857d3b4e937f0817375";
        String endpoint = String.format("https://%s.iot-as-http2.cn-shanghai.aliyuncs.com:443", appKey);
        
        // 连接配置
        Profile profile = Profile.getAppKeyProfile(endpoint, appKey, appSecret);

        // 构造客户端
        MessageClient client = MessageClientFactory.messageClient(profile);
        
        // 数据接收
        MessageCallback messageCallback = new MessageCallback() {
            @Override
            public Action consume(MessageToken messageToken) {
                Message m = messageToken.getMessage();
                System.out.println("receive : " + new String(messageToken.getMessage().getPayload()));
                
                // 你的处理消息逻辑
                //监听后会传入
                try {
                   getData("补充id信息");
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
                
                // 处理完一定要发送CommitSuccess
                return MessageCallback.Action.CommitSuccess;
            }
        };
        
        String topic = String.format("/sys/appkey/%s/dop/model/data/change", appKey);
        client.setMessageListener(topic,messageCallback);

        client.connect(messageToken -> {
            System.out.println(messageToken.getMessage());
            return MessageCallback.Action.CommitSuccess;
        });
        
        
        
    }
    
    
    public static void getData() throws UnsupportedEncodingException {

        IoTApiClientBuilderParams ioTApiClientBuilderParams =
                new IoTApiClientBuilderParams();
        ioTApiClientBuilderParams.setAppKey("32393627");
        ioTApiClientBuilderParams.setAppSecret("f578d197746f9857d3b4e937f0817375");
        SyncApiClient syncClient = new SyncApiClient(ioTApiClientBuilderParams);
        IoTApiRequest request = new IoTApiRequest();
        //设置api的版本
        request.setApiVer("0.0.3");
        
        // 接口参数
        request.putParam("modelId","iot_park_pass_record");
        
        //需要的数据模型的字段
        List<String> returnFields = Lists.newArrayList("plateNumber");
        request.putParam("returnFields", returnFields);
        
        //监听后会返回dataids,里面的值作为查询条件
        request.putParam("conditions", JSON.parseArray("[{\"fieldName\": \"id\",\"operate\": \"eq\",\"value\": 49649}]"));
        //49649,49656
        //请求参数域名、path、request
        ApiResponse response = syncClient.postBody("api.link.aliyun.com",
                "/data/model/data/query", request, true);
        System.out.println( "response code = " + response.getCode()
                + " response = " + new String(response.getBody(), "UTF-8"));

    }
    
}

pom依赖:

<dependency>
            <groupId>com.aliyun.openservices</groupId>
            <artifactId>iot-client-message</artifactId>
            <version>1.1.3</version>
        </dependency>

        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-core</artifactId>
            <version>3.7.1</version>
        </dependency>

        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.11</version>
        </dependency>

监听后的返回结果示例:

image

getData方法返回的结果:就可以得到刚刚上传的数据了

image

重复消费的问题:

本地客户端的业务代码处理消息的时间过长,大于等于30s。服务端认为你消费失败了,就会向客户端重复推送数据。

附录 1 参考链接

1:边缘应用服务总线对接:

https://help.aliyun.com/document_detail/145527.html?spm=a2c4g.11186623.6.596.7fed5ee4ubEs4R

2:边缘应用数据总线对接:

https://help.aliyun.com/document_detail/145523.html?spm=a2c4g.11186623.6.595.1b1e610aWUV5Bv

3:服务总线

https://help.aliyun.com/document_detail/114863.html?spm=a2c4g.11186623.6.579.30243aa12KgeDk#h1-7-isv-7

4:数据总线

https://help.aliyun.com/document_detail/114862.html?spm=a2c4g.11186623.6.642.60a7a463DkcwKm

5:API调用对接指引:

https://help.aliyun.com/document_detail/144661.html?spm=a2c4g.11174283.6.674.3eb152d0w6QxBu

6:数据模型对接指引:

https://help.aliyun.com/document_detail/140317.html?spm=a2c4g.11174283.6.656.3eb152d0LBZ7EM#h1-2-1

附录 2 如何查看边缘应用日志

应用开发完成并部署到物业一体机进行调试,可以登录到物业一体机,查看对应的应用日志:

1:选择项目

点击数字园区引擎项目列表,选择一个项目,如下图所示:

image

2:应用管理

行业能力-AB应用集成,找到要查看的应用,点击右侧的三个点,点击管理。如下图所示:

image

选择节点运维

image

点击SSH终端,选择容器(一般只有一个,点击选择即可):

image

选择完容器,将自动登录到物业一体机。当前目录即保持有该应用的日志

image

附录 3 边缘应用部署手册

修改记录及版本信息

序号

版本号

修改内容

修改时间

1

Rev1.0

文档创建

2021-06-01

1 适配器应用接入操作

1.1 创建镜像仓库

使用阿里云账号登录AIoT能力中心的镜像管理页面,如下图所示:

image

选择新建镜像仓库,这个仓库用于存放制作好的镜像,每次往仓库推送一次镜像都是一个新的版本。

image

镜像仓库名称:填写: lifang_parkinglot_adaptor

摘要:填写:立方停车系统适配器

创建好镜像仓库之后,需要制作镜像并推送到这个镜像仓库。

首先在列表中找到刚新建完成的镜像仓库 lifang_parkinglot_adaptor,然后选择查看,如下图所示:

image

其中操作指南指示上传镜像的步骤,镜像版本管理列出了仓库中推送上来的所有镜像。

image

下面操作以Linux操作环境为例

前置条件1:Linux环境下安装了Docker工具

前置条件2:在Linux环境下将要操作的目录下,将下面两个文件拷贝到该目录。这两个文件在项目交付时,请联系项目组中阿里云技术负责人获取。

(1) parkinglot_adapter-1.0-SNAPSHOT.jar

(2) Dockerfile

在Linux目录下执行下面命令:

sudo docker build -f Dockerfile -t lifang_adaptor_image:v1.0.

其中:v1.0是版本号; lifang_adaptor_imag是镜像名称

image

sudo docker images

image

找到lifang_adaptor_image对应的image id:b176b64a5fad

说明:下面命令,必须是本文1.1节开始找到的镜像仓库lifang_parkinglot_adaptor 页面里找到拷贝出的命令,如下图所示:

image

1:登录registry

从上面红框里拷贝1对应的命令,最后添加镜像登录密码

sudo docker login --username=iotx_homelink registry.cn-hangzhou.aliyuncs.com -p镜像登录密码

image

镜像登录密码是从镜像管理右侧修改Registry登录密码设置并获取

image

2: 打标签:

从上面红框里拷贝2对应的命令,修改image id 和版本号。其中image id在2.1.3.2提供,版本号在2.1.3.1指定

sudo docker tagb176b64a5fadregistry.cn-shanghai.aliyuncs.com/iot-1102025233338260-drzc46k7/lifang_parkinglot_adaptor:1.0

如下图所示:

image

3:将镜像推送到Registry

从上面红框里拷贝3对应的命令,修改镜像版本号:

sudo docker push registry.cn-shanghai.aliyuncs.com/iot-1102025233338260-drzc46k7/lifang_parkinglot_adaptor:1.0

image

推送成功后,点击该镜像仓库的镜像版本管理,可以看见刚才推送成功的1.0版本

image

1.2 应用创建与发布

在左边菜单栏选择应用管理,然后选择创建应用,如下图所示:

image

弹出页面如下图所示:

image

选择园区适配器应用

应用名称:填写:lifang_parkinglot_adaptor

应用描述:填写:立方停车系统适配器

image

子系统模板:固定选择停车库(场)管理系统

子系统品牌:填写:立方停车系统。这里要求至少输入4个字符

子系统型号:填写立方提供的系统版本信息:acs 4.2.0

点击创建应用,弹出下面页面,点击开发新版本,如下图所示:

image

版本说明:初始版本

image

点击确定,完成应用创建

点击托管配置编排。鼠标左键点中自研节点,拖拽到右侧,如下图所示:

image

下面将填写配置信息:

服务名称:iotxparkingservice

是否有状态:不用更改,使用缺省:false

镜像选择:选择1.1.2创建的镜像:lifang_parkinglog_adaptor

镜像版本:可以选择最新版本:1.0

是否启用初始化容器:不用更改,使用缺省:false

如下图所示:

image

下面进行环境变量配置,点击环境变量右侧的导入,如下图所示

image

请将下面内容填写到导入框里:其中IP地址192.168.100.100是立方停车系统的IP地址,要根据实际情况填写:

[
    {
        "name": "adapter_port",
        "value": "10060"
    },
    {
        "name": "lfDataBase_ip",
        "value": "192.168.100.100"
    },
    {
        "name": "lfDataBase_port",
        "value": "3306"
    },
    {
        "name": "lfDataBase_un",
        "value": "root"
    },
    {
        "name": "lfDataBase_pwd",
        "value": "lf0507"
    },
    {
        "name": "lf_ip",
        "value": "192.168.100.100"
    },
    {
        "name": "lf_port",
        "value": "9988"
    },
    {
        "name": "lf_subPath",
        "value": "/Parking/Handheld/"
    }
]

image

确定后内容如下图所示:

image

点击Network下面的自研节点边缘端口对,如下图所示

image

协议:选择TCP

服务端口:固定填写 10060

应用自身端口:固定填写 10060

勾选固定主机端口,如上图所示。主机端口:固定填写 10060

服务类型:从下拉列表选择NodePort

其他直接使用默认参数。点击左下角保存:

image

可视化编排完成之后,点击右侧的模型与权限,如下图所示:

image

点击添加数据模型,如下图所示:

image

立方停车系统的适配器目前支持下面三种数据模型,需分别添加:

1车辆通行: iot_park_pass_record

2收费事件:iot_park_fee

4黑名单车辆识别:iot_park_blacklist_detect

添加车辆通行数据模型:在数据模型名称搜索框里输入iot_park_pass_record,点击搜索

image

在搜索出对应数据模型,版本选择最新版本,数据权限选择增删改查,特别注意勾选订阅。最后点击批量添加

然后依次添加收费事件/黑名单车辆识别数据模型,操作与上面类似。最后结果如下:

image

点击服务模型添加服务模型,如下图所示

image

在服务模型标识符搜索框里输入iotx_parking_service_model,这个是立方停车适配器支持的停车服务模型标识符,点击搜索,如下图所示

image

搜索出停车系统领域服务,版本选择最新的,“提供服务的节点:端口”直接使用自动填充内容,不用修改。最后点击左下角批量添加。添加完成后如下图所示:

image

服务模型添加完成以后,云端可以通过服务模型调用应用的接口,从而应用向云端提供服务。

点击版本概览,点击右上角发布版本,如下图所示

image

1.3 应用部署

应用创建和发布是在AIoT能力中心平台。应用部署是在数字园区引擎平台。

特别提醒,部署适配器应用的前提条件:

  • 物业管理一体机已激活

  • 边缘集群组件和云边协同组件都已部署到了物业管理一体机

登录数字园区引擎项目管理页面,选择适配器应用需要部署的项目,如下图所示:

image

找到基础能力-物业管理一体机-主机管理,找到对应主机右侧的主机详情,如下图所示:

image

进入主机详情页面后,点击应用实例分配容器应用,如下图所示:

image

选择自定义应用,搜索框里输入在1.1节里创建的应用名称 lifang_parkinglot_adaptor。选择搜查出的应用,点击下一步,如下图所示:

image

选择版本名称,点击下一步,如下图所示:

image

应用实例名称:可以输入:立方停车系统适配器应用。点击自检,如下图所示:

image

自检通过后,点击开始部署,应用部署大概需要1~2分钟,可以刷新查看状态,如下图所示

image

所有容器应用都安装完成,点击右上角部署,这个是将云端的应用真正部署到物业管理一体机,如下图所示。

image