# 自定义拦截器
iFLYOS
为用户提供了大量的常用技能,比如音乐播放、天气查询、股票查询等等。但如果你不想使用其中的某些技能或者希望iFLYOS
的技能无法处理某些用户请求时,对用户的语料进行自己的特殊处理,那么拦截器
将会是你最好的选择。关于拦截器的配置,请前往设备接入控制台 (opens new window)进行配置:设备接入控制台 > 设备 > 云端语义
# 拦截器类型
# 前拦截器
前拦截器
可以在用户请求进入iFLYOS技能
之前,对用户的请求进行拦截。当你配置了前拦截器,我们会生成一个PreInterceptorRequest
,发送到拦截器的地址,根据拦截器的响应做进一步的端指令处理。拦截器响应请查看:拦截器响应说明
# 后拦截器
当iFLYOS技能
无法处理用户请求(即用户语料未能命中任意技能)时,如果你配置了后拦截器,那么我们会生成一个PostInterceptorRequest
发送到你的拦截器,再根据拦截器的响应做进一步的端指令处理。拦截器响应请查看:拦截器响应说明
拦截器要求
- 发布后的产品的拦截器链接必须使用
https
协议。在测试调试阶段我们支持http
的链接,为了保证产品上线后拦截器可用,请在提交审核前把链接改为https
,并保证拦截器服务可用。 - 拦截器需要对请求进行安全校验,以确认是来自于
iFLYOS
的请求,请参考:校验说明 - 拦截器需要在
800ms
内返回,否则iFLYOS
将会当做无响应,继续下一步的处理
# 回调地址
根据拦截器语义处理类型不同,回调地址的含义也不相同。
使用私有技能 for iFLYOS 作为拦截器,回调地址用于接收设备上报的自定义事件。如果你的设备未定义自定义事件,回调地址可不填。
使用 AIUI webAPI 应用作为拦截器,此处填写你在 AIUI 应用配置中的后处理地址,iFLYOS 服务将把 AIUI webAPI 应用的语义理解结果 (opens new window)发送至你配置的回调地址。
使用自定义语义理解服务作为拦截器,此处填写你的自定义语义理解服务地址,用于接收用户请求。
# 拦截器请求校验
回调地址服务需要校验请求是否来源于 iFLYOS,所有发送给技能的请求Header都包含Signature
字段
# 校验步骤
校验该签名的步骤:
- 从企业平台获取校验签名的公钥
public_key
- 对
Signature
的值进行Base64-decode,得到decoded_signature
- 使用SHA-1摘要算法(十六进制编码)对请求Body生成
hash
- 使用RSA算法, 使用公钥
public_key
对decoded_signature
,hash
进行校验,摘要类型为sha256
假设用户从企业平台拿到的密钥为
-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlN9BU3eBo9YbR/KaH42W\nmgkE3j/Sm+WkXHDOeP5IDmehq0yTlWQtfUpoAj6T0/KIQgnhQm6MULXlRtvYIam4\nW5I4gRSx1Yk4dpBTpJ8z6/QJG6DqywjuATfZgyEiEr9Nc6sjW2bXILHOLlCvMT+5\n8aX9+QNB+WRqMSNkHN06Fa9aIfE7fbrjASlfZB4oYlr+ldTM1Q6pUOhLDJtZw906\nVNqfgdZUPOBU7D9bYonBZrMCZN//YMr7jxSo9p6H4a0v9HNAvKPWFgPs7SmM/mC2\ndWsF+A2TaA+znshWbmYPzNMphrBul+oDbYtOi6zP7Co00Xgg+ivNf3PdEhMuiJ6E\nbQIDAQAB\n-----END PUBLIC KEY-----\n
IVS的请求body为
"{\"message\":\"ok\"}"
Signature为
LG9565Z7KF92BKXWUdihbJ10oSelQg0YeR6QGYF4n4dd1QtP+2Gig8nWFkQaev06fJ2t30+Jh7ZmEdlZaoKJFEXxjXaG00mcVlc2VI0C7HJ/XXahBRcGt9guVrkDAfS0BEihN2hnsPev4QZ2WHVX/RLG+JnkA2j+eUKJnnMNIEjkgWJ8U17yWd9Etdn2Zj/8l/4TMqhvtG/5qB8ILkB7633agOj7z1ShD6eb9+blMYwx209pXPZomQ6E8QA0vYw8AcK7BFbcw7ikU1Ii2LLKxDg6aYRg82nFGfNQZftNmb4AR60g55ZsFPo9aSfWYADDR1YczUI5hPeLZkDoL9BaLw==
校验示例(Ruby)
public_key = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlN9BU3eBo9YbR/KaH42W\nmgkE3j/Sm+WkXHDOeP5IDmehq0yTlWQtfUpoAj6T0/KIQgnhQm6MULXlRtvYIam4\nW5I4gRSx1Yk4dpBTpJ8z6/QJG6DqywjuATfZgyEiEr9Nc6sjW2bXILHOLlCvMT+5\n8aX9+QNB+WRqMSNkHN06Fa9aIfE7fbrjASlfZB4oYlr+ldTM1Q6pUOhLDJtZw906\nVNqfgdZUPOBU7D9bYonBZrMCZN//YMr7jxSo9p6H4a0v9HNAvKPWFgPs7SmM/mC2\ndWsF+A2TaA+znshWbmYPzNMphrBul+oDbYtOi6zP7Co00Xgg+ivNf3PdEhMuiJ6E\nbQIDAQAB\n-----END PUBLIC KEY-----\n"
body = "{\"message\":\"ok\"}"
hash = OpenSSL::Digest.hexdigest("SHA1", body)
signature = "LG9565Z7KF92BKXWUdihbJ10oSelQg0YeR6QGYF4n4dd1QtP+2Gig8nWFkQaev06fJ2t30+Jh7ZmEdlZaoKJFEXxjXaG00mcVlc2VI0C7HJ/XXahBRcGt9guVrkDAfS0BEihN2hnsPev4QZ2WHVX/RLG+JnkA2j+eUKJnnMNIEjkgWJ8U17yWd9Etdn2Zj/8l/4TMqhvtG/5qB8ILkB7633agOj7z1ShD6eb9+blMYwx209pXPZomQ6E8QA0vYw8AcK7BFbcw7ikU1Ii2LLKxDg6aYRg82nFGfNQZftNmb4AR60g55ZsFPo9aSfWYADDR1YczUI5hPeLZkDoL9BaLw=="
decoded_signature = Base64.decode64(signature)
pub_key = OpenSSL::PKey::RSA.new(public_key)
pub_key.verify("SHA256", decoded_signature, hash) # => true
校验示例(Java)
public static void main(String[] args){
String publicKey = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlN9BU3eBo9YbR/KaH42W\nmgkE3j/Sm+WkXHDOeP5IDmehq0yTlWQtfUpoAj6T0/KIQgnhQm6MULXlRtvYIam4\nW5I4gRSx1Yk4dpBTpJ8z6/QJG6DqywjuATfZgyEiEr9Nc6sjW2bXILHOLlCvMT+5\n8aX9+QNB+WRqMSNkHN06Fa9aIfE7fbrjASlfZB4oYlr+ldTM1Q6pUOhLDJtZw906\nVNqfgdZUPOBU7D9bYonBZrMCZN//YMr7jxSo9p6H4a0v9HNAvKPWFgPs7SmM/mC2\ndWsF+A2TaA+znshWbmYPzNMphrBul+oDbYtOi6zP7Co00Xgg+ivNf3PdEhMuiJ6E\nbQIDAQAB\n-----END PUBLIC KEY-----\n";
publicKey = publicKey.replace("-----BEGIN PUBLIC KEY-----", "").replace("\n", "").replace("-----END PUBLIC KEY-----", "");
String sign = "LG9565Z7KF92BKXWUdihbJ10oSelQg0YeR6QGYF4n4dd1QtP+2Gig8nWFkQaev06fJ2t30+Jh7ZmEdlZaoKJFEXxjXaG00mcVlc2VI0C7HJ/XXahBRcGt9guVrkDAfS0BEihN2hnsPev4QZ2WHVX/RLG+JnkA2j+eUKJnnMNIEjkgWJ8U17yWd9Etdn2Zj/8l/4TMqhvtG/5qB8ILkB7633agOj7z1ShD6eb9+blMYwx209pXPZomQ6E8QA0vYw8AcK7BFbcw7ikU1Ii2LLKxDg6aYRg82nFGfNQZftNmb4AR60g55ZsFPo9aSfWYADDR1YczUI5hPeLZkDoL9BaLw==";
String body = "{\"message\":\"ok\"}";
System.out.println("verify: " + verify(publicKey, body, sign));
}
public static boolean verify(String publicKey, String body, String sign){
String hash = SHA1(body);
try {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] encodedKey = Base64.getDecoder().decode(publicKey);
PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
java.security.Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(pubKey);
signature.update(hash.getBytes("utf-8"));
return signature.verify(Base64.getDecoder().decode(sign));
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
public static String SHA1(String decript) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.update(decript.getBytes());
byte messageDigest[] = digest.digest();
StringBuffer hexString = new StringBuffer();
for (int i = 0; i < messageDigest.length; i++) {
String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
if (shaHex.length() < 2) {
hexString.append(0);
}
hexString.append(shaHex);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
# 拦截器语义处理类型
# 使用私有技能 for iFLYOS 作为拦截器
你需要:
- 在技能工作室 (opens new window)创建【私有技能 for iFLYOS】。
- 在技能控制台配置你的技能交互模型,参考文档 (opens new window)。
- 编写你的技能后处理 (opens new window),用于处理技能收到的通用技能请求 (opens new window)。
- 在设备端实现自定义技能,参考文档 (opens new window)
使用此方式时,服务回调地址将能够接收设备上报的自定义事件。如果你的设备未定义自定义事件,回调地址可不填。
提示
- 若你的拦截器选择的语义处理服务为【AIUI WebAPI应用】,拦截器应用处理请求的语义,应用后处理会收到 处理结果协议 (opens new window)
- 若你的拦截器选择的语义处理服务为【其他语义服务】,iFLYOS 会把用户请求直接转发至你的回调地址。
# 使用AIUI webAPI 应用作为拦截器
建议
如果你之前已经通过AIUI应用平台 (opens new window)开发过应用,我们建议你选择这种方式来实现快速开发。
iFLYOS 和 AIUI webAPI 应用实现了打通,你只需要在拦截器配置处填写APPID
和APIKey
,用户的语音请求即可发送至你的AIUI webAPI 应用进行语义理解。APPID
和APIKey
在AIUI应用控制台 (opens new window)中获取。
iFLYOS 支持对AIUI webAPI 应用的返回结果进行云端处理或设备处理。
# 云端处理
- 请求:使用云端处理的方式,iFLYOS 服务将把 AIUI webAPI 应用的语义理解结果 (opens new window)发送至你配置的回调地址。
- 处理:你的服务需要接收对应的请求,进行完善的处理。
- 返回:你的回调服务处理完成后,需要按照返回符合iFLYOS交互协议2.1的内容。可参考文档:技能response_v2.1 (opens new window)。除了标准回复中可用的指令外,你也可以返回技能自定义指令。
# 设备处理
使用设备处理的方式,iFLYOS 服务将把 AIUI webAPI 应用的语义理解结果按以下格式下发送至设备:
# EVS transfer_semantic 指令
{
"iflyos_responses": [
...,
{
"header": {
"name": "interceptor.custom"
},
"payload": {//AIUI 应用返回内容透传
...
}
}
]
}
# IVS TransferSemantic 指令
消息格式
{
"directive": {
"header": {
"namespace": "Custom",
"name": "TransferSemantic",
"dialogRequestId": "...",
"messageId": "...",
},
"payload": {//AIUI 应用返回内容透传
}
}
}
# 使用自定义语义理解服务作为拦截器
如果你已经自己定义了语义理解服务,或使用第三方语义理解服务,你可以选择自定义语义理解服务。
此时回调地址必填,用户请求时,你的回调地址会收到以下请求,你需要在处理后返回以下回复。
提示
如果自定义语义理解服务选择不处理该语料,只需要返回http.statusCode
为204,http.body
为空即可。
# 请求
# HTTP请求头
POST / HTTP/1.1
Content-Type : application/json;charset=UTF-8
Host : your.application.endpoint
Content-Length :
Accept : application/json
Accept-Charset : utf-8
Signature: xxxxxxxxxxxxxxx
# Body格式
{
"version": "1.0",
"session": {
"new": true,
"sessionId": "f78b7d68...",
"attributes": {
"key": "value"
}
},
"context": {
"System": {
"device": {
"deviceId": "f78b7d68...",
"supportedInterfaces": {
"AudioPlayer": {},
"Display":{}
}
},
"application": {
"applicationId": "f78b7d68..."
},
"user": {
"userId": "f78b7d68...",
"accessToken": "23bf653f..."
}
},
"AudioPlayer": {
"playerActivity": "PLAYING",
"token": "audioplayer-token",
"offsetInMilliseconds": 0
},
"Custom": {
"key": "value" //自定义数据,参考自定义上下文
}
},
"request": {}
}
关于参数的具体说明请参考:技能请求Body说明 (opens new window)
拦截器文本请求
消息示例
{
"type": "PreInterceptorRequest", // 后拦截器为:PostInterceptorRequest
"requestId": "f78b7d68...",
"timestamp": "2018-08-06T16:13Z ",
"query": {
"type": "TEXT",
"original":"今天天气怎么样?"
}
}
参数说明
参数 | 描述 | 类型 | 必须出现 |
---|---|---|---|
type | 请求类型,这里取值 PreInterceptorRequest /PostInterceptorRequest | String | 是 |
requestId | 代表请求的唯一标识符。 | String | 是 |
timestamp | 请求时间戳,以ISO 8601格式发送 | String | 是 |
query | 请求信息。 - type:请求类型,取值 TEXT - original:用户语音经过IVS理解后生成的文本 | String | 是 |
拦截器事件请求
{
"type": "PreInterceptorEventRequest",
"requestId": "f78b7d68...",
"timestamp": "2018-08-06T16:13Z ",
"payload": {
"key": "value" // 自定义数据
}
}
参数说明
参数 | 描述 | 类型 | 必须出现 |
---|---|---|---|
type | 请求类型,这里取值 PreInterceptorEventRequest | String | 是 |
requestId | 代表请求的唯一标识符。 | String | 是 |
timestamp | 请求时间戳,以ISO 8601格式发送 | String | 是 |
payload | 请求数据,与设备端上报事件的payload 相同,详情请查看自定义设备能力 | String | 是 |
# 响应
标准响应 响应请参考:技能响应说明 (opens new window)。
自定义指令响应
厂商除了可以返回标准响应 (opens new window),还可以返回以下自定义指令,IVS在收到自定义指令响应后,我们将把payload
中的数据直接放入Custom
指令透传至设备。
自定义指令中可以包含一个自定义的json
数据,比如:
{
"version": "1.0",
"sessionAttributes": {
"key": "value"
},
"response": {
"directives": [
{
"type": "Custom",
"payload": {
"key": "value" // 自定义数据
}
}
],
"expectSpeech": true,
"shouldEndSession": true
}
}
# 自定义设备能力
若你拦截器中选择的语义理解服务为私有技能for iFLYOS 或 其他语义服务,拦截器返回至 IVS 的数据会经过处理后,下发对应指令至设备。如果你的设备使用拦截器时有定义自定义指令,则你的设备需要实现对自定义指令的执行逻辑。
# EVS自定义设备能力
EVS
是以response
的形式与客户端进行交互的。EVS 设备要实现的自定义指令可参考自定义拦截器
# IVS自定义设备能力
IVS
是以指令的形式与客户端进行交互的,当你的设备希望通过一些自定义的指令对设备进行控制,那么,我们为拦截器提供了这样的能力。技能可以在技能返回中将指令发送给IVS,IVS将把数据传送给设备。
IVS协议请参考:设备与IVS的交互约定
# Custom Context
如果你有需要从设备端获取特殊的设备状态时,你应该在发起语音请求时,在请求的上下文中,具体格式请参考:设备状态 Context
{
"header": {
"namespace": "Custom",
"name": "Custom"
},
"payload": {
// 自定义状态
}
}
# Custom 指令
消息格式
{
"directive": {
"header": {
"namespace": "Custom",
"name": "Custom",
"dialogRequestId": "...",
"messageId": "...",
},
"payload": {
"key": "value" // 自定义数据
}
}
}
# Custom 事件
客户端通过上报事件与iFLYOS
进行交互,当你的设备希望主动上报信息到iFLYOS
,我们会将事件转发到对应的拦截器和技能。IVS协议请参考:设备与IVS的交互约定
消息格式
{
"context": [// 参考设备上下文
],
"event": {
"header": {
"namespace": "Custom",
"name": "Custom",
"messageId": "...",
},
"payload": {
"key": "value" // 自定义数据
}
}
}