智能小程序OPENCARD
接入指南

接入

整体流程图

接入条件

资质要求

  • 小程序主面向C端用户,且不属于医疗、资讯、招聘、购物平台等类目。
  • 实际服务范围应与企业经营范围一致;对于国家及相关部门要求的特殊资质类目,需拥有相关资质证明。

线上核心功能体验标准

登录

  • 支持百度授权登录
  • 登录状态长久保持,无需多次登录
  • 个人中心可顺畅地切换账号

客服电话(必备)/在线人工客服/留言咨询

  • 在线客服3分钟内须有回应
  • 客服电话真实有效,有工作人员应答
  • 留言后15分钟内回复,咨询服务要有效解决用户问题

线上支付

  • 下单支付前无强制登录

消息通知

  • 订单重要状态变动时有短信通知,或有百度app消息通知/push

订单查询

  • 小程序内&手百app端内,在登录状态可查询所有历史交易订单

定位服务

  • 提供方位信息可自动定位服务,定位准确
  • 明确提示可服务区域

信息填写

  • 用户信息输入一次后无需重复提交(如地址簿、乘机人信息)

线上界面交互体验标准

页面质量

  • 无作弊页、无效页、低质页

广告体验

排版布局

  • 字体不可明显过大过小
  • 主体内容占比不低于50%
  • 排版清晰合理,避免内容辨识成本偏大。如行间距过大、页面出现大面积空白、段落/图片排版错乱不整齐、主体内容展示不全等

加载速度

  • 首屏内容全部加载完成时间<=1.5s间

操作交互

  • 视频-可全屏、进度条可拖拽、不卡顿、竖屏视频支持竖屏播放
  • 音频-进度条可拖拽,且歌词需与音频同步
  • 图片-点击图片可全屏,且在全屏下手势操作顺畅
  • 嵌套-页面不可出现PC页/H5页嵌套
  • 嵌套-页面不可出现PC页/H5页嵌套
  • 展开正文的功能键需明示且可用,不可出现在首屏、不可超过1次、不可与其他区域距离过近
  • 禁止调起/下载APP
  • 咨询类功能区域-可在底部悬浮,面积≤10%;或在左右单侧悬浮,面积≤1cm²。咨询功能不可弹窗,不可重复,不可抖动

线下体验标准

合法合规

  • 企业应具备相关经营资格,部分行业须拥有国家及相关部门要求的专业资质证明
  • 不存在不法经营、勒索强迫交易、提供黄赌毒服务等违法现象

安全可靠

  • 到店服务环境干净卫生,无危害用户身心的风险问题
  • 上门服务保证服务安全,不可造成人身、财险危害
  • 保护用户隐私信息
  • 不可高频回访

质量保障

  • 产品质量保障,无假冒伪劣
  • 服务能力优,服务佳

一致性

  • 不允许存在产品线上线下信息不一致(如虚假打折、价格诈欺、误导、虚报服务范围等)的恶劣问题
  • 不抬高价格,不得高于在其他平台/app的定价
  • 退换货能力与同行业竞品对齐

及时性

  • 举报/投诉反馈及时受理,用户问题高效解决
  • 预约/发货需及时受理,时间上给予用户明确预期,信息进度可查,可实时跟踪反馈

选择卡片

通过链接:https://open.baidu.com/#/smartapp/select ,或者在“百度数据开放平台”首页选择:“小程序开放->申请加入”,进入“选择卡片”页。

在这一步,开发者(资源方)选择要申请合作的开放类目卡片,并选择在要与这个卡片绑定的小程序或 HTML5 站点,即作为卡片点击跳转的目标小程序或 HTML5 站点。

在确定绑定关系后,卡片开发过程中资源方返回的所有跳转 URL,均为对应小程序或 HTML5 站点下的 PATH 和 PARAMS,无需重复提供小程序 AppKey 或 HTML5 站点域名等信息。

步骤 1:选择类目及卡片

资源方可以在这一步选择要申请的开放类目卡片。

步骤 2:选择名下的小程序

资源方可以在这一步选择卡片要绑定的小程序(和 HTML5 站点)。绑定关系一旦完成不可修改,卡片中所有跳转链接,均为已绑定小程序(和 HTML5 站点)的内部链接,不可跳转到其它小程序(和 HTML5 站点)。请谨慎选择设置!

情况 1:非适配卡片(仅支持绑定小程序)

情况 2:适配卡片(支持绑定小程序和适配的 HTML5)

如果卡片类型为适配卡片,那么资源方可以在绑定的小程序之外,额外绑定一个 HTML5 站点,作为小程序结果的适配站点。如果资源方没有对应的 HTML5 站点,也可以选择“否”。

开放平台将通过 Webhook 请求协议中的“surface”字段来指示开发者应该返回小程序 URL 还是 HTML5 站点 URL。具体内容详见“实现卡片接口”章节。

如果已开放类目不能满足您的需求,可以申请开放新类目

上传intent数据

什么是intent?
intent 是搜索需求(搜索词)的正规化和结构化的 JSON 表示,在每个卡片的 intent 有其特定的结构。例如,”景点门票“卡片下,intent 的结构是 {“scenic_spot”:””}。那么,”故宫怎么买票“这个搜索词表达成 intent 就是:{“scenic_spot”:”故宫”}

intent有什么用?
用户在搜索框中输入的搜索词表达方式多种多样,需要经过复杂的语义分析才能确定用户的意图。例如同样是查故宫门票,有人搜”故宫门票“,有人搜”故宫买票“,可能还有人搜”故宫怎么买票“。
如果我们将原始搜索词发送给开发者,那就意味着所有的开发者都需要实现复杂的语义分析,才能确定用户的意图。为了降低开发者的实现代价,我们将分析出来的正规化 intent 而不是原始搜索词发送给开发者服务。开发者只需要用 intent 中正规化的参数进行简单的 KV 或者数据库检索即可命中需求数据。
例如原始搜索词: “故宫门票“ 、 “故宫买票“和”故宫怎么买票“,经过我们意图分析它都是景点门票类需求,对应到”景点门票“卡片;通过语义分析出来的 intent都是 “{“scenic_spot”:”故宫”}“。这样开发者收到的请求参数就很简单,对于这些搜索词都只需要用”故宫“做数据库检索就好。

由于各类目的 intent 结构不同,开发者在上传 intent 前需要阅读对应的接入细则,确定该类目 intent 的数据字段。同时,开发者需要根据小程序本身的资源覆盖情况,确定能够提供的所有 intent 字段组合。

当前支持使用 txt 文件上传,开发者需要将所有 intent 序列化后的 JSON 串写入 txt 文件中。

为避免解析出错,我们对 txt 文件格式有以下要求:

  • 编码采用 UTF-8 无 BOM 编码;
  • 文件大小不得超过 4MB,超过 4MB 的请选择使用 API 上传;
  • 换行符使用 Linux 风格,使用 \n 表示一行的结束;
  • 每行一个完整的 JSON 序列化串,JSON 串中不得有换行、TAB 等不可打印字符;
  • 当 intent 要求多个 key 时,JSON 里必须含有所有 key,部分 value 内容可以为空;
  • 内容不得有重复的行,除尾部外不得有空行。

例如:景点门票卡片下的 intent 仅要求一个景点名字段,开发者需要将小程序覆盖的所有景点名都列入到上传的 txt 文件里,形如:

{“scenic_spot”:”故宫”}
{“scenic_spot”:”故宫博物院”}
{“scenic_spot”:”天坛”}
{“scenic_spot”:”颐和园”}

实现卡片接口

基本概念

下面这张图描述了开发者接入卡片后的搜索请求数据流:

数据流图

在开发者作为一个资源方接入一个卡片后,当用户搜索卡片相关的搜索词,开放平台会根据 intent 配置对该搜索词进行语义分析。分析得到的 intent 会发送到资源方的 Webhook URL,开发者根据自己数据和业务逻辑生成符合开放平台要求的资源数据返回给开放平台。开放平台将会用该资源数据结合卡片的配置渲染生成搜索结果中的卡片,并插入到搜索结果页中的适当位置。

出于对搜索速度,结果稳定性和减轻开发者服务器压力考虑,开放平台将会采取一定的缓存策略。也就是说,在一部分情况下用户的搜索请求是通过缓存来满足的。

Webhook API 协议(公共字段)

开放平台发送给 Webhook URL 的请求 JSON object,遵照以下形式。其中的 intent 字段对于每个卡片来说,内容是不同的,即 srcid 决定了 intent 的内部数据结构。

1
2
3
4
5
6
7
8
9
10
11
12
{
"type": "sp_ala", // 智能小程序阿拉丁请求,以便与其它请求区分
"srcid": "123", // 卡片的 ID,每个卡片不同
"surface": "mobile", // 卡片展示在哪个产品上, mobile: 支持小程序的移动搜索,web_h5: 支持 H5 的移动搜索
"intent": { // 卡片的 intent,每个卡片不同
...
},
"location": { // 定位信息,非 LBS 卡片不提供
"province": "浙江", // 省级行政单位短名(不含行政区划单位,例如"市、省")
"city": "杭州" // 城市名(不含行政区划单位,例如"市")
}
}

对于适配卡片,需要特别关注“surface”字段。适配类卡片的 Webhook 接口,需要对不同的场景返回不同的跳转 URL:

  1. 当“surface”字段值为“mobile”时,Webhook 返回的“data”中所有 URL,均应为小程序的内部 URL(即 PATH 之后的部分);
  2. 当“surface”字段值为“web_h5”时,Webhook 返回的“data”中所有 URL,均应为小程序适配 HTML5 站点的内部 URL(也是 PATH 之后的部分,不含 http(s):// 和域名)。

Webhook 返回的响应 JSON object,遵照以下形式。其中的 data 字段对于每个卡片来说,内容是不同的,即请求的 srcid 决定了响应消息中 data 的内部数据结构。

1
2
3
4
5
6
7
8
{
"status": 0, // 结果状态码,0 代表正确,1 代表无结果,2 代表请求参数错误,3 代表内部服务错误
"msg": "", // 出错消息
"data": { // 资源的结果内容,每个资源分类不同
...
},
"lifetime": 1559705004 // 可选字段,秒级 Unix 时间戳,指示该数据的有效期
}

以上内容主要是卡片 API 接口的公共字段。至于每个卡片的 srcid编号,以及 intentdata字段的数据格式和含义,请参考”开放类目卡片列表“下对应卡片的接口说明。

Webhook 加密协议

为保护开放平台和开发者双方的数据安全,以上请求和响应消息都不能通过明文发送,需要对消息进行加密。加密算法的具体实现细节请参考”API 加密协议“一节中的说明。

Webhook 服务端 Python 示例代码

开发者可以参考以下 Python 示例代码,来实现 Webhook 服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#!/usr/bin/env python
# encoding: utf-8

import BaseHTTPServer
import time

# jwcrypto 不是标准库,需额外安装: pip install jwcrypto
from jwcrypto import jwk, jwe
from jwcrypto.common import json_encode, json_decode

# 配置项,测试时请修改以下配置
HOST_NAME = 'localhost'
PORT_NUMBER = 8000
PSK_TABLE = {'0':jwk.JWK.from_json('{"kty":"oct","k":"MDEyMzQ1Njc4OWFiY2RlZg"}')}

# Webhook 基本协议实现
#
# 以下代码为示例,未妥善处理业务字段或异常等因素
class Webhook(BaseHTTPServer.BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(404)

def do_POST(self):
content_length = int(self.headers['Content-Length'])
req_body = self.rfile.read(content_length)
jwetoken = jwe.JWE()
# 从 protected 字段中解出来 kid
jwetoken.deserialize(req_body)
kid = jwetoken.jose_header["kid"]
# 通过 kid 确定要用哪个解密 key
key = PSK_TABLE[kid]
# 可以将 rid 打印到日志中,以便问题追查时 join 日志
print time.asctime(), "Req head %s" % json_encode(jwetoken.jose_header)
# 用 key 解密取得解密后的内容
jwetoken.decrypt(key)
print time.asctime(), "Req json %s" % jwetoken.payload.decode("unicode_escape")
req = json_decode(jwetoken.payload)
# 如果资源方有多张卡片,从 req 中解析出来 srcid,处理相应的业务逻辑
if req["srcid"] == "123":
# do something
res = {"status":0,"msg":""}
res["data"] = {
"item_list":[{"title":"故宫博物院"}],
"jump_url":"/path/to/page3"
}
else:
res = {"status":2,"msg":"Invalid srcid"}
# 处理业务响应结果
payload = json_encode(res)
print time.asctime(), "Res json %s" % payload.decode("unicode_escape")
# 用请求的 header 和 key 生成加密结果,保持加密算法和头内容(kid/rid)与请求一致
jwetoken = jwe.JWE(plaintext=payload,
protected=json_encode(jwetoken.jose_header),
recipient=key)
res_body = jwetoken.serialize(compact=True)
self.send_response(200)
self.end_headers()
self.wfile.write(res_body)

if __name__ == '__main__':
httpd = BaseHTTPServer.HTTPServer((HOST_NAME, PORT_NUMBER), Webhook)
print time.asctime(), "Webhook Starts - http://%s:%s" % (HOST_NAME, PORT_NUMBER)
try:
httpd.serve_forever()
except KeyboardInterrupt:
pass
httpd.server_close()
print time.asctime(), "Webhook Stops - http://%s:%s" % (HOST_NAME, PORT_NUMBER)

数据处理和交互示例

下面我们以景点门票为例说明各阶段数据的处理过程。

开放平台生成请求 JSON 明文

1
{"intent":{"scenic_spot":"\u6545\u5bab"},"srcid":"123","surface":"mobile","type":"sp_ala"}

开放平台根据开发者配置的 PSK 对请求进行加密,生成 JWE 密文

加密参数

  • PSK: "0123456789abcdef"
  • base64url(PSK): "MDEyMzQ1Njc4OWFiY2RlZg"
  • kid: "0"

会话参数

  • rid: "1559123682789-315431431"
1
eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoiMCIsInJpZCI6IjE1NTkxMjM2ODI3ODktMzE1NDMxNDMxIn0.1vDnKkf50N3piN9sLgr87h2maEm61IdJIC39WEhHB5N99P1JjNUMhQ.fnj_JIk0aYbkGKkGaT1QsA.HpabSZGitPw3CTmIuwpDS-XCN1Yxf2N9CLKBtJprC2q5qSMEHicubEV4jjcIYgctp8F1jYFSu3yhvWuxGtA7h4p_Ek07jmQRjZRE7GB9GhX_uE3IdoJavWm0cYEgJ7gF.B7iwwd5Eh4KaLdNID2f4UQ

由于 JWE 协议每次加密使用的 Sesion Key 不同,即使对同样的明文,每次加密生成的密文部分也不同。开发者无需将自己生成的密文与上述内容进行逐字节一一比对。

开放平台将密文 HTTP POST 到 webhook

我们以 webhook http://localhost:8000 为例,用 curl 模拟发送 POST 请求:

1
curl -X POST "http://localhost:8000" -H "Content-Type:application/jwt" -d'eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoiMCIsInJpZCI6IjE1NTkxMjM2ODI3ODktMzE1NDMxNDMxIn0.1vDnKkf50N3piN9sLgr87h2maEm61IdJIC39WEhHB5N99P1JjNUMhQ.fnj_JIk0aYbkGKkGaT1QsA.HpabSZGitPw3CTmIuwpDS-XCN1Yxf2N9CLKBtJprC2q5qSMEHicubEV4jjcIYgctp8F1jYFSu3yhvWuxGtA7h4p_Ek07jmQRjZRE7GB9GhX_uE3IdoJavWm0cYEgJ7gF.B7iwwd5Eh4KaLdNID2f4UQ'

开发者根据 PSK 对请求进行解密,获得 JSON 明文

如果解密失败,开发者需返回 400 HTTP 状态码,并在 HTTP body 中注明解密出错原因。

对 JWE 解密时,除获得解密后的 JSON 明文之外,还可以从 protected header 中获得其它加密参数和会话参数。protected header 将会作为参数用于构造返回消息的 JWE 对象,其中的 rid可以用来作为会话的唯一标识打印日志,以及在追查问题时提供给开放平台作为参考。

开发者根据 intent 生成返回结果 JSON 明文

1
{"data":{"item_list":[{"title":"\u6545\u5bab\u535a\u7269\u9662"}],"jump_url":"/path/to/page3"},"msg":"","status":0}

开发者使用同样的参数对结果进行加密,返回 JWE 密文

可以注意到,JWE 的第一段 protected header 与请求完全一致,这代表结果的加密参数(除 PSK 以外)与请求保持了一致。

1
eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoiMCIsInJpZCI6IjE1NTkxMjM2ODI3ODktMzE1NDMxNDMxIn0.a4JDrnuVbcn7C00siEKwODneowk5hwpDmqZtzKycHcW2z-NimkzAJQ.0vEJlCWY7pUG400R9-KNCg.S0eNjmJUl9wUbJg_AB-sZw9LOQov27pa7HuoIR7_pdRUkxYZefzDbUzVpgelXtgAKpXSaZdYxwY_RSxCWbfUtfp1gzW_2pqnYhSOLNs8wBV3tReNdlxmjZocz_Tm_ePG2RNv3yo5H6IzJLEuEvBBp5xqtF-kGvsVF-kBLteHqDQ.-6EJe2xKhrlTB60K3m86Qg

提交测试

提测前准备

完成自测

在开发过程中,开发者首先要完成服务的自测。开发者可以自己实现发送加密 intent 请求的桩程序(参考下面 Python 客户端代码),也可以使用我们提供的一个调试页面来给开发中的服务发送测试请求:点击打开调试页面

服务要求:接口耗时不超过300ms

Webhook 客户端示例 (Python)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#!/usr/bin/env python
# encoding: utf-8
# Webhook 简单客户端,可用于构造和发送测试请求,未妥善考虑所有异常

import time
import random
import sys

# jwcrypto 不是标准库,需额外安装: pip install jwcrypto
from jwcrypto import jwk, jwe
from jwcrypto.common import json_encode, json_decode

# requests 不是标准库,需额外安装: pip install requests
import requests

# 配置项,测试时请修改以下配置
URL = 'http://localhost:8000'
PSK_TABLE = {'0':jwk.JWK.from_json('{"kty":"oct","k":"MDEyMzQ1Njc4OWFiY2RlZg"}')}
KID = '0'

# 自动生成内容
rid = "%d-%d" % (round(time.time() * 1000), random.randint(0, 10**6))
jwe_header = {"alg": "A128KW", "enc":"A128CBC-HS256", "kid":KID, "rid":rid}
req = {"type":"sp_ala","surface":"mobile"}

# 对请求加密,发送加密后的内容到服务器,测试时请修改请求内容
req["srcid"] = "123"
req["intent"] = {"scenic_spot":"故宫"}
req_token = jwe.JWE(plaintext=json_encode(req),
protected=json_encode(jwe_header),
recipient=PSK_TABLE[KID])
r = requests.post(URL,
data = req_token.serialize(compact=True),
headers = {"Content-Type":"application/jwt"})

# 检查返回结果
print "Check http_status:", "PASS" if r.status_code == 200 else "FAIL"
res_token = jwe.JWE()
res_token.deserialize(r.text)
print "Check jose_header:", "PASS" if jwe_header == res_token.jose_header else "FAIL"
res_token.decrypt(PSK_TABLE[KID])
res = json_decode(res_token.payload)
print "Check status:", "PASS" if res["status"] == 0 else "FAIL"
print "Response JSON:", json_encode(res).decode("unicode_escape")

配置 Webhook

在提交开放平台测试之前,开发者需要在开放平台配置 Webhook URL 和对应的加密参数 PSK。

  • Webhook URL:开发者设置的用于接收 intent 请求的 URL;
  • Webhook PSK:用于开放平台、开发者双方之间通信加密、认证的预共享密钥;

PSK base64url 编码生成方法

平台要求开发者提供的是 base64url 编码的 PSK,开发者可以通过在线 base64url 编码生成器生成,注意去掉末尾补齐的 “=” 即可。为避免泄密,开发者也可以调用本地命令来生成 PSK 的 base64url 编码,以下是部分语言的示例:

Python:

1
2
pip install jwcrypto
python -c 'from jwcrypto.common import base64url_encode; print base64url_encode("0123456789abcdef")'

Node:

1
2
npm install base64url
node -e 'const base64url = require("base64url"); console.log(base64url("0123456789abcdef"))'

PHP:

1
php -r 'echo rtrim(strtr(base64_encode("0123456789abcdef"),"+/","-_"),"=");'

完成上线

开发者需要将正式的服务部署到在平台中配置的 Webhook URL。

完成服务自检

开发者需要检查 Webhook 服务的正确性,以及更重要地,检查所有的 intent 参数是否有正确的返回结果。

如果开发者选择的卡片有图片字段,要确认:

  1. 图片 URL 为 HTTPS;
  2. 已经将图片服务器 HTTPS 域名提交到平台中;
  3. 图片服务器对百度域没有防盗链限制,访问频率控制要较高,避免审核不通过或触发线上无图导致违规;

完成内部性能测试

为避免性能测试的压力压垮 webhook 服务导致业务损失,开发者在提测前应该对生产环境 webhook 服务进行内部性能测试。内部性能测试的响应时间要考虑到跨网络的请求延时,例如在性能测试要求 300ms 时,内部性能测试卡线在 150ms - 180ms 为宜。

触发测试

完成提测准备后,开发者可以到开放平台:个人中心->资源管理->小程序开放里 在对应资源中点击”去实现”按钮后,填写相关信息,并提交测试申请。相关操作步骤如下图:

测试内容

开放平台对开发者提供的 Webhook 服务测试主要有以下两项:

接口测试

开放平台会用开发者在上一步上传 intent 数据中提供的所有 intent 对 Webhook 进行检查。

当请求中 surface 参数为 mobile时,所有的 intent 必须有正确的返回结果,即返回 status0data 字段非空且符合对应卡片的返回数据格式要求。此时返回的 data 中所有跳转链接均为小程序内链接。任一 intent 返回失败或者出错,均视为接口接口测试未通过。测试结果会以开放平台消息形式发送给开发者,不通过时开发者需要修正错误后重新提交测试。

对于适配卡片,请求中 surface 参数为 web_h5 时,webhook 可以对 intent 响应 “无结果”,即返回 "status: 1"。但如果 webhook 响应了 "status: 0",要求data 字段非空且符合对应卡片的返回数据格式要求,并且 data 中所有跳转链接均为绑定的 HTML5 站点内链接。对 intent 请求响应 status 为 0/1 以外的值,视为出错。

性能测试

开放平台会用开发者在上一步上传 intent 数据中提供的所有 intent,以及开放平台自己构造的扩展和错误 intent,对开发者的服务进行性能测试。开放平台对外的性能测试主要利用服务器空闲时间进行,而且需要排队,因此不能保证准确地测试发起时间。开发者需要在提测前完成服务器容量准备和内部性能测试,以免发压时宕机造成业务损失。

性能测试标准(暂定)

性能测试要求在 QPS 100 的情况下,压测 30 分钟,98% 的请求在 300 ms 内返回,包含开放平台到开发者 Webhook 服务器的网络传输时间。

性能测试结果会以开放平台消息形式发送给开发者,不通过时开发者需要优化服务性能或扩容服务器后重新提交测试。

审核上线

当开发者通过数据校验与压测后,搜索将以数据校验通过的内容(含intent内容、卡片展现内容、落地页内容)为对象,从相关性、正确性、一致性、时效性等多方面进行审核,详见通用内容规范。
通过效果审核的卡片会直接进入到待上线环节,具体上线时间会通过开放平台消息通知开发者。

反 馈帮 助 回 到顶 部