
# 创建自定义策略函数
#### 了解函数接口定义
函数服务对函数有明确的接口定义。
![](https://support.huaweicloud.com/usermanual-iotps/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-iotps/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-iotps/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-iotps/figure/zh-cn_image_0000001193540295.png "点击放大")
   进入函数工作流服务控制台，创建函数并上传包含工程Jar包和第三方Jar包的Zip包。
   ![](https://support.huaweicloud.com/usermanual-iotps/figure/zh-cn_image_0000001147700484.png "点击放大")
   为避免调试过程中函数因内存不足或超时导致调用失败，视实际使用情况调整内存大小和超时时间。
   ![](https://support.huaweicloud.com/usermanual-iotps/figure/zh-cn_image_0000001147540684.png "点击放大")
   
   
3. 配置测试事件，调试函数。 
   创建测试事件，填写满足设备发放约束的函数参数的JSON格式。
   ![](https://support.huaweicloud.com/usermanual-iotps/figure/zh-cn_image_0000001193540297.png "点击放大")
   单击"保存并测试"，查看函数运行结果，确认函数逻辑的正确性。
   ![](https://support.huaweicloud.com/usermanual-iotps/figure/zh-cn_image_0000001147700490.png "点击放大")
   日志显示，已调用了发放设备接口，函数主要逻辑正确。至此，您已完成自定义策略函数的编写，可进行后续步骤。
   
   
 
#### 后续操作
完成自定义策略函数的编写后，创建关联该函数的自定义策略实例以及设备或注册组，通过设备南向操作触发函数，将设备发放到指定目的接入点（设备接入实例）。
后续操作包括：
- [添加自定义策略实例](https://support.huaweicloud.com/usermanual-iotps/iot_01_0006.html)；
- [注册设备](https://support.huaweicloud.com/usermanual-iotps/iot_01_0008.html)或[创建注册组](https://support.huaweicloud.com/usermanual-iotps/iot_01_0009.html)；
- 南向接口：[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)。
最佳实践：
- 结合函数服务通过自定义策略发放证书认证的设备。
 
 #### 设备发放对返回参数的约束
表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"
  }
}
```
