应用类型及免登方式

在物联市场的卖家后台,可以创建不同类型的应用

  • 实例(分发)型:也称为单租户应用、独占式应用,ISV定义应用之后。每次交付行为都是以这份定义为基础,平台为客户分配一份独立的云资源,并部署一份应用到该云资源中,并将访问节点信息交付给客户。
  • 账号(分发)型:也称为多租户应用、共享式应用,或者SaaS应用。每次交付行为,会导致平台向该SaaS应用发起一个开通新租户的请求,并将访问入口交付给客户。
  • 一次性交付型:ISV定义的一份应用,本身就是一次交付。所以,不存在分发给第二个客户的行为。如果有第二个客户需要相同的能力,ISV需要再次定义一个应用。

1、SSO免登

对于账号分发模式的应用,需要应用实现三个api,分别是:

租户创建接口:用户购买应用时,由托管平台调用这个接口,通知应用有新的购买行为,参数里面会有购买者的信息

租户回收接口:用户删除应用时,由托管平台调用这个接口,通知应用有用户删除应用

免登地址获取:这个接口即是对接SSO的api,用于获取免登的地址。接口参数如下:

tenantSubUserId对应员工的标识,appId是应用实例的标识

具体可参考SaaS应用对接

2、OAuth免登

这种免登方式,用户通过数字工厂访问应用页面时,会在原页面路径后面加上code,类似于

https://a.b.c/page/index.html?code=XXXXXX

应用再通过open api从code中获取当前登录者的信息。具体可以参考OAuth2.0对接

用于应用对接的开放api中,像主数据、工厂建模、工艺路径的api中,会需要appId和employeeId两个参数:

(1 )参数appId来确定当前调用相关的租户。

appId是客户在市场上购买应用时生成的一个实例id,用于标识应用实例,同时也与购买者的租户信息对应。对于多租户的SaaS应用,appKey和appId对应的目标租户可能是不一样的,因此需要appId确认目标租户信息;同时为了兼容老版本,当appId没有指定时,使用appKey所属的租户信息。

(2 )参数employeeId用于操作鉴权。

employeeId是阿里云的子账号id。每一种主数据在系统内部都被当做一个资源,并且给不同的子账号授予不同的操作权限,employeeId标识当前api调用者的身份。用户登录数字工厂之后,可能会通过集成进来的应用页面查询主数据,这种场景下会有employeeId的信息。如果没有登录态,例如后台的定时任务,这个参数可不传,认为是主账号操作。

这个过程大致如下

注:

① 图中第3、4步是对接SSO方式才有的步骤,会传一个tenantSubUserId给应用,这个值可以做为employeeId调用open api

② 对于oauth的方式,在第5步中,应用收到请求后,根据code换取当前登录者的信息,会拿到一个open_id,这个值可以做为employeeId调用open api

数据变更通知订阅

应用可以通过订阅消息的方式来接收数据记录变化的通知。

数字工厂采用http2的方式推送消息,使用QoS1,每条消息只能被一个客户端消费并且只能被消费一次。

举例说明如下:

① 有一个appkey注册了消息通知,多个程序使用同一个appkey进行消息接收,当有数据变化时产生一条消息,只有一个程序能收到这条消息

② 有多个不同的appkey并且都注册了消息通知,当有数据变化时会对每一个appkey产生内容同的一条消息,每个appkey都会收到单独的一条消息通知

可查看其它的服务端订阅使用限制

1、订阅消息

通过开放api订阅消息,http2方式数据推送,注册订阅的消息类型

独享实例托管场景

package demo.masterdata;

import com.alibaba.cloudapi.sdk.model.ApiResponse;
import com.alibaba.fastjson.JSON;
import com.aliyun.iotx.api.client.IoTApiClientBuilderParams;
import com.aliyun.iotx.api.client.IoTApiRequest;
import com.aliyun.iotx.api.client.SyncApiClient;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

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

/**
 * 订阅消息通知的demo,使用open api
 */
public class SubscribeDemoApplication {
    public static void main(String[] args) {
        try {
            IoTApiClientBuilderParams ioTApiClientBuilderParams = new IoTApiClientBuilderParams();

            ioTApiClientBuilderParams.setAppKey("");
            ioTApiClientBuilderParams.setAppSecret("");

            SyncApiClient syncApiClient = new SyncApiClient(ioTApiClientBuilderParams);

            IoTApiRequest request = new IoTApiRequest();
            //设置api的版本
            request.setApiVer("1.0.0");

            List<Integer> msgTypes = Lists.newArrayList();
            // 主数据
            msgTypes.add(1);
            // 工厂建模
            msgTypes.add(4);

            //设置参数
            request.putParam("msgTypes", msgTypes);

            // 多租户SaaS应用的appId,新用户购买后该SaaS应用后,就要重新订阅消息
            // request.putParam("appId", "xxxxx");

            Map<String, String> properties = Maps.newHashMap();
            properties.put("http_method_type", "POST");
            request.putParam("properties", JSON.toJSONString(properties));

            // 根据需要设置http header
            Map<String, String> headers = new HashMap<>();

            //请求参数域名、path、request
            String path = "/industry/notification/http2/register";
            ApiResponse response = syncApiClient.postBody("api.link.aliyun.com", path, request, true, headers);
            String responseString = new String(response.getBody(), "UTF-8");
            System.out.println("response code = " + response.getCode() + " response = " + responseString);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

多租户SaaS场景

对于多租户SaaS应用,每次有用户购买时都需要用appId订阅一次消息。这个appId是调用生产租户接口时传入的

package demo.masterdata;

import com.alibaba.cloudapi.sdk.model.ApiResponse;
import com.alibaba.fastjson.JSON;
import com.aliyun.iotx.api.client.IoTApiClientBuilderParams;
import com.aliyun.iotx.api.client.IoTApiRequest;
import com.aliyun.iotx.api.client.SyncApiClient;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

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

/**
 * 订阅消息通知的demo,使用open api
 */
public class SubscribeDemoApplication {
    public static void main(String[] args) {
        try {
            IoTApiClientBuilderParams ioTApiClientBuilderParams = new IoTApiClientBuilderParams();

            ioTApiClientBuilderParams.setAppKey("");
            ioTApiClientBuilderParams.setAppSecret("");

            SyncApiClient syncApiClient = new SyncApiClient(ioTApiClientBuilderParams);

            IoTApiRequest request = new IoTApiRequest();
            //设置api的版本
            request.setApiVer("1.0.0");

            List<Integer> msgTypes = Lists.newArrayList();
            // 主数据
            msgTypes.add(1);

            // 工厂建模
            msgTypes.add(4);

            //设置参数
            request.putParam("msgTypes", msgTypes);

            // 多租户SaaS应用的appId,新用户购买后该SaaS应用后,就要重新订阅消息
            request.putParam("appId", "xxxxx");

            Map<String, String> properties = Maps.newHashMap();
            properties.put("http_method_type", "POST");
            request.putParam("properties", JSON.toJSONString(properties));

            // 根据需要设置http header
            Map<String, String> headers = new HashMap<>();

            //请求参数域名、path、request
            String path = "/industry/notification/http2/register";
            ApiResponse response = syncApiClient.postBody("api.link.aliyun.com", path, request, true, headers);
            String responseString = new String(response.getBody(), "UTF-8");
            System.out.println("response code = " + response.getCode() + " response = " + responseString);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }
}

2、接收消息

pom依赖。

<dependency>
  <groupId>com.aliyun.api.gateway</groupId>
  <artifactId>sdk-core-java</artifactId>
  <version>1.1.0</version>
  <exclusions>
    <exclusion>
      <groupId>commons-codec</groupId>
      <artifactId>commons-codec</artifactId>
    </exclusion>
  </exclusions>
</dependency>
<dependency>
  <groupId>com.aliyun.openservices</groupId>
  <artifactId>iot-client-message</artifactId>
  <version>1.1.5</version>
</dependency>

java demo

public static void main(String[] args) {
        String endPoint = "https://${appKey}.iot-as-http2.cn-shanghai.aliyuncs.com:443";

        // 托管应用appKey
        String appKey = "";
        // 托管应用appSecret
        String appSecret = "";

        // 连接配置
        Profile profile = Profile.getAppKeyProfile(endPoint, appKey, appSecret);

        // 构造客户端
        MessageClient client = MessageClientFactory.messageClient(profile);

        // 数据接收
        client.connect(messageToken -> {
            Message m = messageToken.getMessage();
            System.out.println("receive message from " + m);
            return MessageCallback.Action.CommitSuccess;
        });
    }

注:目前主数据通知的消息是通过下面这个topic推送的,可以在代码中给该topic设置listener专门处理它的数据

/sys/appkey/<appKey>/digfty/data/change
String dataChangeTopicFormat = "/sys/appkey/%s/digfty/data/change";
String dataChangeTopic = String.format(dataChangeTopicFormat, appKey);
client.setMessageListener(dataChangeTopic, messageToken ->  {
  System.out.println("receive data change message from " + messageToken.getMessage());
  return MessageCallback.Action.CommitSuccess;
});