# 自定义策略
#### 了解函数接口定义
函数服务对函数有明确的接口定义。
![](https://support.huaweicloud.com/usermanual-iothub/public_sys-resources/note_3.0-zh-cn.png)
以Java语言为例，接口定义为：*作用域* *返回参数* *函数名（函数参数，* *Context* *参数）*。
- 作用域：提供给FunctionGraph调用的用户函数必须定义为public。
- 返回参数：用户定义，FunctionGraph负责转换为字符串，作为HTTP Response返回。对于返回参数对象类型，HTTP Response该类型的JSON字符串。
- 函数名：用户定义函数名称。
- 用户定义参数：当前函数只支持一个用户参数。对于复杂参数，建议定义为对象类型，以JSON字符串提供数据。FunctionGraph调用函数时，解析JSON为对象。
- Context参数：runtime提供函数执行上下文，其接口定义在[SDK接口](https://support.huaweicloud.com/devg-functiongraph/functiongraph_02_0430.html#functiongraph_02_0430__section1845018519242)说明。
创建Java函数时，函数入口参数需要提供函数完整的名字空间，参数格式为：包名.类名.函数名。
设备发放在此基础上，要求函数代码满足如下条件：
- 返回参数：需满足[设备发放对返回参数的约束]；
- 函数参数：需满足[设备发放对函数参数的约束]；
- 函数接口实现：从函数参数中的备选接入点中选择一个接入点，调用[发放设备接口](https://support.huaweicloud.com/api-iotps/StartAllocateDevice.html)，根据接口响应拼接参数返回。
 
#### 编写自定义函数
1. 创建函数工程，编写函数。 
   首先建立一个普通的Java项目，添加FunctionGraph函数[JavaSDK](https://support.huaweicloud.com/devg-functiongraph/functiongraph_02_0430.html#section1)为工程依赖，可下载设备发放提供[函数Demo](https://iodps-file.obs.cn-north-4.myhuaweicloud.com/TdpFunctionDemo-1.0.0.zip)，参照Demo创建工程编写函数。
   ![](https://support.huaweicloud.com/usermanual-iothub/public_sys-resources/note_3.0-zh-cn.png)
   FunctionGraph函数JavaSDK提供了Event事件接口、Context接口和日志记录接口。其中Runtime-x.x.x.jar包含了函数执行上下文，其他Jar包为附带的第三方库，可按需添加。
   Java函数开发指南参见[函数工作流 FunctionGraph \> 开发指南 \> 如何开发函数 \> Java函数开发指南](https://support.huaweicloud.com/devg-functiongraph/functiongraph_02_0430.html)。
   参照如下代码编写函数，实现必要逻辑。
   ```
   package com.huawei.demo;
    
   import com.huawei.demo.common.logger.DefaultLogger;
   import com.huawei.demo.model.AccessPointPara;
   import com.huawei.demo.model.FunctionGraphPara;
   import com.huawei.demo.model.TdpFuncResult;
   import com.huawei.services.runtime.Context;
    
   import java.util.Optional;
    
   /**
    * 实现该类。
    * WARNING：{@link #apiHandle(FunctionGraphPara, Context)} 该方法必须在子类中定义，否则函数无法触发该函数接口！！！
    * <pre>
    * {@code
    *     @Override
    *     public TdpFuncResult apiHandle(FunctionGraphPara para, Context context) {
    *         return super.apiHandle(para, context);
    *     }
    * }
    * </pre>
    */
   public abstract class TdpFunction {
       protected static final DefaultLogger LOGGER = new DefaultLogger(TdpFunction.class);
    
       /**
        * 函数定义
        */
       public TdpFuncResult apiHandle(FunctionGraphPara para, Context context) {
           // 获取日志
           DefaultLogger.init(Optional.ofNullable(context)
               .map(Context::getLogger)
               .orElse(null));
    
           // 校验入参
           TdpFuncResult result = checkPara(para, context);
           if (result != null) {
               return result;
           }
    
           // 确定接入点
           AccessPointPara accessPointPara = determineAccessPoint(para);
    
           // 发放设备
           result = provisionDevice(para, accessPointPara);
    
           LOGGER.info("result:{}", result);
           return result;
       }
    
       /**
        * 校验入参
        */
       protected abstract TdpFuncResult checkPara(FunctionGraphPara para, Context context);
    
       /**
        * 从备选的接入点中选择合适的接入点
        */
       protected abstract AccessPointPara determineAccessPoint(FunctionGraphPara para);
    
       /**
        * 调用设备发放的发放接口发放设备
        */
       protected abstract TdpFuncResult provisionDevice(FunctionGraphPara para, AccessPointPara accessPointPara);
   }
   ```
   
   
2. 打包函数，上传代码。 
   ![](https://support.huaweicloud.com/usermanual-iothub/public_sys-resources/note_3.0-zh-cn.png)
   函数打包规范参见[函数工作流 FunctionGraph \> 开发指南 \> 概述 \> 函数工程打包规范](https://support.huaweicloud.com/devg-functiongraph/functiongraph_02_0103.html)。
   如函数工程未使用到第三方库，则可将工程打成一个Jar包上传。如使用到第三方库，则需将工程Jar包和第三方Jar包打包成一个Zip包。
   本文使用到了多个第三方库，因此，如下图所示，将工程Jar包和第三方Jar包打包成一个Zip包。
   ![](https://support.huaweicloud.com/usermanual-iothub/figure/zh-cn_image_0000001193540295.png "点击放大")
   进入函数工作流服务控制台，创建函数并上传包含工程Jar包和第三方Jar包的Zip包。
   ![](https://support.huaweicloud.com/usermanual-iothub/figure/zh-cn_image_0000001147700484.png "点击放大")
   为避免调试过程中函数因内存不足或超时导致调用失败，视实际使用情况调整内存大小和超时时间。
   ![](https://support.huaweicloud.com/usermanual-iothub/figure/zh-cn_image_0000001147540684.png "点击放大")
   
   
3. 配置测试事件，调试函数。 
   创建测试事件，填写满足设备发放约束的函数参数的JSON格式。
   ![](https://support.huaweicloud.com/usermanual-iothub/figure/zh-cn_image_0000001193540297.png "点击放大")
   单击"保存并测试"，查看函数运行结果，确认函数逻辑的正确性。
   ![](https://support.huaweicloud.com/usermanual-iothub/figure/zh-cn_image_0000001147700490.png "点击放大")
   日志显示，已调用了发放设备接口，函数主要逻辑正确。至此，您已完成自定义策略函数的编写，可进行后续步骤。
   
   
 
#### 后续操作
完成自定义策略函数的编写后，创建关联该函数的自定义策略实例以及设备或注册组，通过设备南向操作触发函数，将设备发放到指定目的接入点（设备接入实例）。
后续操作包括：
- [添加自定义策略实例]；
- [注册设备](https://support.huaweicloud.com/usermanual-iothub/iot_09_0177.html#ZH-CN_TOPIC_0000001938891445__section645154422218)或[创建注册组](https://support.huaweicloud.com/usermanual-iothub/iot_09_0177.html#ZH-CN_TOPIC_0000001938891445__section1842895018302)；
- 南向接口：[MQTT CONNECT连接鉴权](https://support.huaweicloud.com/qs-iotps/iot_03_0003.html)、[设备请求引导消息](https://support.huaweicloud.com/qs-iotps/iot_03_0004.html)、[设备接收引导消息](https://support.huaweicloud.com/qs-iotps/iot_03_0005.html)。
最佳实践：
- [结合函数服务通过自定义策略发放证书认证的设备](https://support.huaweicloud.com/bestpractice-iotps/iot_bp_0041.html)。
 
 #### 添加自定义策略
1. 进入"策略"界面，单击展开"自定义策略"，单击"添加函数"。 
   图1添加自定义策略   
   ![](https://support.huaweicloud.com/usermanual-iothub/figure/zh-cn_image_0000001836144161.png "点击放大")
   
   
2. 按照下方参数说明填写关键参数信息后，单击"确定"。 
   
   | 参数名称 | 说明                                                                                                                                                                        | 示例                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
   |:---|:---|:---|
   | 函数    | 即在[函数工作流控制台](https://console.huaweicloud.com/functiongraph)中实现的自定义策略。如果下拉框没有您想要的函数，可以单击创建新函数来实现您的自定义策略需求。                                                                  | 将需要通过函数"function"发放的设备发放至华北-北京四的物联网平台。 - 需通过函数"function"发放的设备：WaterMeter-Beijing0001、WaterMeter-Beijing0002  - 函数：function  - 发放区域：华北-北京四   |
   | 发放区域 | 发放到指定区域后，设备将接入对应区域的设备接入服务。 所选区域未开通设备接入服务时，如果确定添加实例，系统将自动为您开通设备接入服务。不同区域设备接入服务价格不同，收费详情请参考价格说明。 | 将需要通过函数"function"发放的设备发放至华北-北京四的物联网平台。 - 需通过函数"function"发放的设备：WaterMeter-Beijing0001、WaterMeter-Beijing0002  - 函数：function  - 发放区域：华北-北京四   |
      
   
   
 
 #### 设备发放对返回参数的约束
表1TdpFuncResult 
| **名称**       | **说明** | **类型**                                 |
|:---|:---|:---|
| statusCode | String  | 函数执行状态码，用于标识函数执行过程是否出现异常，遵循HTTP状态码含义。 |
| body        | String  | 字符串，但格式为JSON，结构为TdpFuncBody。           |
   
表2TdpFuncBody 
| **名称**    | **说明**              | **类型**                                                       |
|:---|:---|:---|
| status | String              | 设备发放业务功能标识，allow表示发放成功，deny表示发放失败，设备发放使用此标识判断函数内业务功能执行成功与否。 |
| context   | TdpFuncBodyContext | 扩展字段，用于承载函数执行结果。                                          |
   
表3TdpFuncBodyContext 
| **名称**          | **说明** | **类型**                  |
|:---|:---|:---|
| allocationResult | String | 发放结果，存放发放接口返回的响应结构体。 |
| errorCode        | String  | 错误码，如发放失败，则此值需不为空。     |
| errorMsg       | String  | 错误描述，如发放失败，则此值需不为空。  |
   
**发放设备成功的返回参数样例**
```
{
  "statusCode": 200,
  "body": "{\"status\":\"allow\",\"context\":{\"allocationResult\":\"dps返回的下发结果\"}}"
}
```
**发放设备失败的返回参数样例**
```
{
  "statusCode": 200,
  "body": "{\"status\":\"deny\",\"context\":{\"errorCode\":\"错误码\",\"errorMsg\":\"错误描述\"}}"
}
```
 #### 设备发放对函数参数的约束
表4FunctionGraphPara 
| **名称**        | **类型**                | **说明**               |
|:---|:---|:---|
| iamToken    | String                | 委托Token，便于用户调用发放接口。 |
| deviceId     | String               | 设备ID。                |
| deviceName    | String                 | 设备名称。               |
| accessPoints | List\<AccessPointPara\> | 备选接入点列表。             |
| authInfo       | AuthInfoPara           | 设备认证信息。           |
   
表5AccessPointPara 
| **名称**                | **类型** | **说明**                 |
|:---|:---|:---|
| regionId            | String   | 备选接入点的区域ID。              |
| accessPointId       | String  | 备选接入点的接入点ID。             |
| authorizingDomainId | String  | 备选接入点的接入点授权租户，仅在授权场景下使用。 |
   
表6AuthInfoPara 
| **名称**    | **类型**      | **说明**                        |
|:---|:---|:---|
| authType  | String       | 认证类型。可选 SECRET 和 CERTIFICATES。 |
| secret     | String       | 设备密钥信息，当认证类型为 SECRET 时携带。      |
| certInfo | CertInfoPara | 证书信息，当认证类型为 CERTIFICATES 时携带。 |
   
表7CertInfoPara 
| **名称**      | **类型**                | **说明**     |
|:---|:---|:---|
| caCert     | CaCertPara              | 设备CA证书信息。  |
| deviceCert | List\<DeviceCertPara\> | 设备证书信息。 |
   
表8CaCertPara 
| **名称**     | **类型**   | **说明**       |
|:---|:---|:---|
| subjectCnName | String | 证书使用者CN NAME。 |
| caCertName     | String   | 证书名。        |
| fingerprint | String | 证书指纹。        |
   
表9DeviceCertPara 
| **名称**          | **类型** | **说明**              |
|:---|:---|:---|
| subjectCnName   | String  | 证书使用者CN NAME。        |
| issuerCnName    | String | 证书颁发者CN NAME。       |
| sha1Fingerprint  | String   | 证书指纹（使用SHA-1算法计算）。  |
| sha256Fingerprint | String | 证书指纹（使用SHA-256算法计算）。 |
   
**证书认证设备触发函数策略的函数参数样例**：
```
{
  "iamToken": "MIIT3gYJKoZIhvcNAQcCoIIT...",
  "deviceId": "testDeviceId",
  "deviceName": "testDeviceName",
  "accessPoints": [
    {
      "regionId": "cn-north-4",
      "accessPointId": "cn-north-4",
      "authorizingDomainId": null
    },
    {
      "regionId": "cn-north-4",
      "accessPointId": "86a45e59-3003-24b8-4e81-df0a9a694639",
      "authorizingDomainId": "84fd2c95ddf03b840f18100a000d45c2"
    }
  ],
  "authInfo": {
    "authType": "CERTIFICATES",
    "certInfo": {
      "caCert": {
        "subjectCnName": "serverCommonName20200922204814",
        "caCertName": "ca20200922204814",
        "fingerprint": "dc0f1016f495157344ac5f1296335cff725ef22f"
      },
      "deviceCert": [
        {
          "subjectCnName": "deviceCommonName20200922204814",
          "issuerCnName": "serverCommonName20200922204814",
          "sha1Fingerprint": "b2217bd882968b0bc15b1c2b132ba8a598f11879",
          "sha256Fingerprint": "734326bb5f3065d8e90d95ce1910b831d3856a1318770768bf9b03923d641ddd"
        }
      ]
    }
  }
}
```
**密钥认证设备触发函数策略的函数参数样例**：
```
{
  "iamToken": "MIIT3gYJKoZIhvcNAQcCoIIT...",
  "deviceId": "testDeviceId",
  "deviceName": "testDeviceName",
  "accessPoints": [
    {
      "regionId": "cn-north-4",
      "accessPointId": "cn-north-4",
      "authorizingDomainId": null
    },
    {
      "regionId": "cn-north-4",
      "accessPointId": "86a45e59-3003-24b8-4e81-df0a9a694639",
      "authorizingDomainId": "84fd2c95ddf03b840f18100a000d45c2"
    }
  ],
  "authInfo": {
    "authType": "SECRET",
    "secret": "29ccf3ff01247d731fef"
  }
}
```
