本章内容出自《小程序开发不求人》电子书,点击下载完整版
支付宝小程序 API
简介
支付宝小程序开发框架提供了丰富的 JSAPI(原生 API) 和 OpenAPI(开放能力 API),开发者可方便快捷地调用这些 API,详情请参见 API 概览。
- OpenAPI 是支付宝开放平台在小程序上开放的开放能力 API。通过 OpenAPI,小程序可以轻松实现用户授权、获取会员基础信息、获取用户手机号、小程序唤起支付、跳转支付宝卡包、会员开卡授权等多种多样的功能。
- JSAPI 按实现的功能分类,可分为界面、多媒体、缓存、文件、位置、网络、设备、数据安全、分享、收藏、自定义通用菜单、小程序当前运行版本类型、自定义分析、更新管理等14 个大类。
本章我们将讲述支付宝小程序比较常用的几个 JSAPI,带你走进 JSAPI 的奇妙世界。
如何秘密告白:小程序 HTTPS 网络请求实现
木心的《从前慢》里说:
记得早先少年时
大家诚诚恳恳
说一句 是一句
……
从前的日色变得慢
车,马,邮件都慢
一生只够爱一个人
从前的锁也好看
钥匙精美有样子
你锁了 人家就懂了
木心怀念着过去那个通过邮件传递信息的简单时代。
当下的网络时代虽然瞬息万变,但传递信息也是一样的“说一句,是一句”,“你锁了,人家就懂了”。
小程序经常需要往服务器传递数据或者从服务器拉取信息。
当用户通过小程序加载服务器传来的信息时,整个网络过程如下:
- 用户通过小程序向服务器发出 GET 请求,
- 服务器发送一个响应,响应信息包含一个数据文件。
该过程叫做 HTTP。
上面的流程也许过于简化,其实用户与服务器之间不可能面对面直接通话,因为它们相隔不是很近,甚至服务器是在浏览器的千里之外,而客户端浏览器不可能直通服务器。
每一次的网络请求,小程序传递给服务器的信息,中间经过多重的信息转达。同理服务器回应小程序的响应也是同样的路径。
通俗点说,就是传纸条的原理。写字条的同学需要把字条递给旁边第一个同学,然后第二个同学递给第三个同学,以此类推,一直传递到最后的信息接收者。
让我们看看,在传递字条的过程中,如果信息发出者想要给信息最末尾的接受者告白,会发生什么呢?
在 HTTP 状态下,传递者都可以打开字条,查看里面的内容。而且发送信息者无法知道传输路径,一旦发生信息窃取,甚至不知道是谁窃取的。
还是就是当信息落入心怀不轨的人手中,或者篡改信息内容,其后果不可设想。比如,把“你喜欢我吗?”篡改成“你不喜欢我吗?”。
为了避免这些情况发生,HTTP 安全版本应运而生,即 HTTPS。通过 HTTPS,传送的每次信息都被加上一个锁。
该锁配套的公钥和密钥仅小程序和服务器知道,其他传递者无法获取。因此,无论客户端发送的信息经过多个路由器,他人都无法读取信息内容。
客户端发送初始信息到服务器时,在信息内容中包含服务器的名称(在名为“服务器名称指示”的字段中)。而服务器运行商可以在同一台计算机上运行多个站点,因此运行商可以跟踪到客户端的访问轨迹。虽然初始的信息已设置了加密,但是初始请求是仍未加密的。
这就是通过小程序 my.request 安全传递告白信息的故事。本节将介绍如何使用my.request API 实现网络请求,并介绍一些使用注意事项。
版本要求:基础库 1.11.0 或更高版本;支付宝客户端 10.1.32 或更高版本,若版本较低,建议做 兼容处理。
发起网络请求:
- my.request 目前支持 GET/POST。
- my.request 目前只支持 HTTPS 协议的请求。
使用说明:
- 请预先在 支付宝小程序管理中心 > 小程序详情 > 设置 > 开发设置 > 服务器域名白名单中配置域名白名单。小程序在以下 API 调用时只能与白名单中的域名进行通讯:HTTP 请求(my.request)、上传文件(my.uploadFile)、下载文件(my.downloadFile)和WebSocket(my.connectSocket)。
- 添加服务器域名白名单后,需要重新打包上传生成体验版,服务器域名才会生效。
- 在 IDE 上进行调试时,请使用真机预览调试。
- 支付宝客户端已不再维护 my.httpRequest,建议使用 my.request。另外,钉钉客户端尚不支持 my.request。若在钉钉客户端开发小程序,则需要使用 my.httpRequest。
扫码体验
重要:
- 小程序开发过程中,可在开发工具内 详情 > 域名信息 > 忽略 httpRequest 域名合法性检查 中选择是否忽略域名合法性检查,如果选择忽略,则在模拟器、预览以及真机调试场景不会校验域名合法性,但小程序上线前必须确保通讯域名在白名单内,否则在正式版本无法调用。
- my.request 的请求头默认值为 {'content-type': 'application/json'},而不是{'contenttype': 'application/x-www-form-urlencoded'}。此外,请求头对象里面的 key 和value 必须是 String 类型。
示例代码
// dataType 为 json 示例
my.request({
url: 'https://httpbin.org/post',
method: 'POST',
data: {
from: '支付宝',
production: 'AlipayJSAPI',
},
dataType: 'json',
success: function(res) {
my.alert({content: 'success'});
},
fail: function(res) {
my.alert({content: 'fail'});
},
complete: function(res) {
my.hideLoading();
my.alert({content: 'complete'});
}
});
// dataType 为 base64 示例
my.request({
url:
'https://gw.alipayobjects.com/mdn/miniapp_de/afts/img/A*G1kWSJbe2zEAAAAA
AAAAAABjARQnAQ',
method: 'GET',
dataType: 'base64',
success: (resp) => {
console.log('resp data length', resp.data.length);
console.log('resp data', resp.data); // 返回格式类似于:
data:image/png;base64,iVBORw0KG...
},
fail: (err) => {
console.log('error', err);
},
});
入参
Object 类型,属性如下:
data 参数说明
传给服务器的数据最终会是 String 类型,如果 data 不是 String 类型,会被转换成 String 。转换规则如下:
- 若方法为 GET,会将数据转换成 query string:
encodeURIComponent(k)=encodeURIComponent(v)&encodeURIComponent(k)=encodeURIComponent(v)... - 若方法为 POST 且 headers['content-type'] 为 application/json ,会对数据进行JSON 序列化
- 若方法为 POST 且 headers['content-type'] 为 application/x-www-formurlencoded ,会将数据转换成 query string:encodeURIComponent(k)=encodeURIComponent(v)&encodeURIComponent(k)=encodeURIComponent(v)...
success 回调函数
入参为 Object 类型,属性如下:
属性 | 类型 | 描述 |
---|---|---|
data | String | 响应数据,格式取决于请求时的dataType 参数,如果 dataType 值为 base64时,返回的是符合 data URIscheme 规范的内容字符串。 |
status | Number | 响应码。 |
headers | Object | 响应头。 |
返回值 RequestTask
网络请求任务对象。调用 my.request 后返回的请求对象。
RequestTask.abort()
中断请求任务。
示例代码
// 返回 RequestTask,可以调用 abort 方法取消请求
const task = my.request({url: 'https://httpbin.org/post'})
task.abort();
“抱歉,不是我的菜”:小程序扫码点餐化解尴尬
月上柳梢头,人约黄昏后。
周五到了,小心翼翼约她出来吃晚饭,她欣然应约。
餐厅位于徐汇区闹中取静的华山路,法式梧桐的点缀让餐厅更显典雅,也更富有异国情调。踏入餐厅,灯光是橘色的,餐具是蓝的,桌椅也是蓝的,让人恍惚之间有到了爱琴海边的错觉,唯美的装修风格、充满欧洲风味的精致美食,处处洋溢着地中海风情,真浪漫啊。
她翩翩而至,裙裾飞扬。
见到她我脸红了。我紧张地问她要吃些什么,又手忙脚乱地叫来服务员点完了菜,
脸上冒出了小汗珠。
窗外的小雨滴滴答答,窗内的我们显得格外安静。
我鼓起勇气,打破沉默,小声问道:“你……你对我印象如何?”
“抱歉,不是我的菜……”
此刻,我如同五雷轰顶,只觉天旋地转,眼前华光溢彩的餐厅瞬间变得黯淡了。
“你是不是点错菜了?还是上错菜了呢?”她指着桌上的法式田螺和奶油蘑菇汤,瞪大了眼睛问我。旁边站着满脸疑惑的上菜员。
如何化解点错菜的尴尬呢?这时就需要使用支付宝小程序扫码点餐的功能了。
为了让用户减少输入,我们可以把复杂的信息编码成一个二维码,利用 my.scanAPI 调起支付宝扫一扫,用户扫码之后,my.scan 的 success 回调会收到这个二维码所对应的字符串信息。
例如餐厅点餐的小程序,我们给餐厅中每个餐桌编号 1-100 号,把这个数字编码到二维码中,扫码获得编号之后,就可以知道是哪一桌点的菜,大大提高点餐体验和效率。
// 利用 my.scanCode 获取二维码的数据
//page.js
Page({
// 点击“扫码订餐”的按钮,触发 tapScan 回调
tapScan: function() {
// 调用 my.login 获取微信登录凭证
my.scanCode({
success: function(res) {
var num = res.result // 获取到的 num 就是餐桌的编号
}
})
}
})
还有很多场景可以结合支付宝 App 扫码能力做到很好的体验,例如通过扫商品上的一维码做一个商品展示的小程序;通过扫共享单车上的二维码去开启单车。我们可以多思考如何利用这个扫码能力去替代一些繁琐的输入操作,让我们的小程序变得更加便捷。
扫码体验
示例代码
// API-DEMO page/API/scan-code/scan-code.json
{
"defaultTitle": "Scan"
}
<!-- API-DEMO page/API/scan-code/scan-code.axml-->
<view class="page">
<view class="page-section">
<form onSubmit="scanCode">
<view>
<button type="primary" onTap="scan">扫码</button>
</view>
</form>
</view>
</view>
// API-DEMO page/API/scan-code/scan-code.js
Page({
scan() {
my.scan({
type: 'qr',
success: (res) => {
my.alert({ title: res.code });
},
});
}
})
入参
Object 类型,属性如下:
success 回调函数
入参为 Object 类型,属性如下:
定格甜蜜回忆:小程序上传图片功能实现
还记得你们第一次手牵手出门吗?
还记得你们第一次共享浪漫的烛光晚餐吗?
还记得你们第一次依偎在电影院中约会吗?
还记得你们第一次去迪士尼约会圆她一个公主梦吗?
甜蜜的回忆,多想定格在时光里。不如开发一个小程序,用于上传存储这些甜甜的照片吧!
本节将介绍如何通过 my,uploadFile API 实现小程序上传图片的功能。
使用前提:
请预先在 支付宝小程序管理中心 > 小程序详情 >设置 > 开发设置 > 服务器域名白名单 中配置域名白名单。小程序在以下 API 调用时只能与白名单中的域名进行通讯:HTTP 请求(my.request)、上传文件(my.uploadFile)、下载文件(my.downloadFile)和 WebSocket(my.connectSocket)。
扫码体验
示例代码
// API-DEMO page/upload-file/upload-file.json
{
"defaultTitle": "Upload File"
}
<!-- API-DEMO page/upload-file/upload-file.axml -->
<view class="page">
<button type="primary" onTap="uploadFile">上传图片</button>
</view>
// API-DEMO page/API/upload-file/upload-file.js
Page({
uploadFile() {
my.chooseImage({
chooseImage: 1,
success: res => {
const path = res.apFilePaths[0];
console.log(path);
my.uploadFile({
url: 'http://httpbin.org/post',
fileType: 'image',
fileName: 'file',
filePath: path,
success: res => {
my.alert({ title: '上传成功' });
},
fail: function(res) {
my.alert({ title: '上传失败' });
},
});
},
});
},
});
上传文件的后端代码,相关 openAPI 请参考
alipay.offline.material.image.upload(上传门店照片和视频接口) 。
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException {
String path = req.getParameter("filePath");
//得到要下载的文件名
String fileName = URLEncoder.encode(req.getParameter("fileName"),"utf-8");
String fileType = path.substring(path.lastIndexOf('.')+1,path.length());
FileInputStream fis = new FileInputStream(path);
System.out.println("debugFileName: "+ fileName);
//下载文件存放路径
String localPath = "";
FileOutputStream fs = new FileOutputStream(localPath + fileName
+"."+fileType);
resp.setHeader("content-disposition", "attachment;filename="+fileName);
resp.setHeader("content-type", fileType );
//执行 fileOutputStream 的输出操作
int len = 1;
byte[] b = new byte[1024];
while((len=fis.read(b))!=-1){
fs.write(b, 0, len);
}
fs.close();
fis.close();
}
入参
Object 类型,属性如下:
success 回调函数
入参为 Object 类型,属性如下:
爱情不掉线:小程序获取设备的网络状态
地球不爆炸,爱情不掉线。宇宙不重启,我们不分离。
不想要和手机里的女朋友掉线的你,如何获取到她的手机网络状态呢?
大家都知道,手机连接到互联网有几种方式:WiFi、2G、3G、4G,包括很快到来的 5G。每种方式的上传速度和下载速度差异很大,它们的计费方式的差异也导致用户在使用互联网服务的时候有不同的使用习惯。
WiFi 相对于其他几种网络连接方式,其速度会更快。WiFi 一般情况下,都是免费供用户使用,而移动数据网络是需要根据使用流量进行计费的。
考虑这样一个场景,小程序需要下载一些文档,然后通过小程序的能力去预览这个文档,这些文档可能文件体积比较大,对于某些用户来说,他们并不想耗费太多的数据流量去预览文档。这种情况下,可以通过小程序提供的获取网络状态 APImy.getNetworkType,做一些更友好的体验提示。
扫码体验
入参
Object 类型,属性如下:
success 回调函数
入参为 Object 类型,属性如下:
示例代码
// API-DEMO page/API/get-network-type/get-network-type.json
{
"defaultTitle": "获取手机网络状态"
}
<!-- API-DEMO page/API/get-network-type/get-network-type.axml-->
<view class="page">
<view class="page-section">
<view class="page-section-demo">
<view class="page-body-title">网络状态</view>
<block a:if="{{hasNetworkType === false}}">
<text class="page-body-text">未获取</text>
<text class="page-body-text">点击按钮可获取网络状态</text>
</block>
<block a:if="{{hasNetworkType === true}}">
<text class="page-body-text-network-type">{{networkType}}</text>
</block>
</view>
<view class="page-section-btns">
<view onTap="getNetworkType">获取手机网络状态</view>
<view onTap="clear">清空</view>
</view>
</view>
</view>
// API-DEMO page/API/get-network-type/get-network-type.js
Page({
data: {
hasNetworkType: false
},
onLoad() {
this.onChange = this.onChange.bind(this);
// my.onNetworkChange(this.onChange);
},
onChange(res){
console.log('onNetworkChange', res);
this.setData({
hasNetworkType: true,
networkType: res.networkType
});
},
onUnload() {
// my.offNetworkChange(this.onChange);
},
getNetworkType() {
my.getNetworkType({
success: (res) => {
this.setData({
hasNetworkType: true,
networkType: res.networkType
})
}
})
},
clear() {
this.setData({
hasNetworkType: false,
networkType: ''
})
},
});
/* API-DEMO page/API/get-network-type/get-network-type.acss */
.page-body-info {
height: 200rpx;
}
.page-body-text-network-type {
font-size: 80rpx;
font-family: Helvetica;
}