初识小游戏- 开放能力
1.用户授权
部分接口需要经过用户授权同意才能调用。这些接口按使用范围分成多个 scope ,用户选择对 scope 来进行授权,当授权给一个 scope 之后,其对应的所有接口都可以直接使用。
调用接口发起授权
第一次使用某个 scope 下的接口时,会弹窗询问用户,“XXX申请获得以下权限:(权限描述)”。如果用户点击允许,则可获得此 scope 的接口权限。并且接口调用成功,否则接口调用失败。
wx.login({
success: function () {
wx.getUserInfo()
}
})
提前发起授权
如果需要提前发起授权获得用户同意,则可调用 [wx.authorize()] 来提前发起授权。
wx.authorize({
scope: 'scope.record'
})
处理用户拒绝授权
用户有可能拒绝小程序发起的授权申请,需要处理这种情况。
wx.login({
success: function () {
wx.getUserInfo({
fail: function (res) {
// iOS 和 Android 对于拒绝授权的回调 errMsg 没有统一,需要做一下兼容处理
if (res.errMsg.indexOf('auth deny') > -1 || res.errMsg.indexOf('auth denied') > -1 ) {
// 处理用户拒绝授权的情况
}
}
})
}
})
wx.authorize({
scope: 'scope.record',
fail: function (res) {
// iOS 和 Android 对于拒绝授权的回调 errMsg 没有统一,需要做一下兼容处理
if (res.errMsg.indexOf('auth deny') > -1 || res.errMsg.indexOf('auth denied') > -1 ) {
// 处理用户拒绝授权的情况
}
}
})
获取用户授权设置
通过调用 [wx.getSetting()]接口可以获取用户当前的授权处理信息。
wx.getSetting({
success: function (res) {
var authSetting = res.authSetting
if (authSetting['scope.userInfo'] === true) {
// 用户已授权,可以直接调用相关 API
} else if (authSetting['scope.userInfo'] === false){
// 用户已拒绝授权,再调用相关 API 或者 wx.authorize 会失败,需要引导用户到设置页面打开授权开关
} else {
// 未询问过用户授权,调用相关 API 或者 wx.authorize 会弹窗询问用户
}
}
})
引导用户重新授权
如果用户拒绝过某个 scope
的授权申请,则后续这个 scope
下的相关 API 调用都会直接失败,用 [wx.authorize()]申请此 scope
也会直接失败,而不会弹窗询问用户。这种情况下,需要引导用户主动到设置页面打开相应的 scope
权限。
授权页面的进入路径为:右上角菜单->关于(小程序名字)->右上角菜单->设置
Scope 列表
scope | 对应 API | 描述 |
---|---|---|
scope.userInfo | [wx.getUserInfo()] | 用户信息 |
scope.userLocation | [wx.getLocation()] | 地理位置 |
scope.werun | [wx.getWeRunData()] | 微信运动步数 |
scope.record | [wx.startRecord()]、[RecorderManager.start()] | 录音功能 |
scope.writePhotosAlbum | [wx.saveImageToPhotosAlbum()] | 保存到相册 |
2.用户登录态签名
小程序的一部分后台(HTTP)接口要求验证用户登录态。开发者在调用时需提供以session_key为密钥生成的签名。其中session_key是指通过wx.login 获得的登录态。
签名算法
目前支持的签名算法是 hmac_sha256。 对于POST请求,开发者生成签名的算法是:
signature = hmac_sha256( post_data, session_key )
其中post_data为本次POST请求的数据包。特别地,对于GET请求,post_data等于长度为0的字符串。
signature = hmac_sha256( "", session_key )
签名示例
例如开发者需要请求的HTTP(POST)接口,其中请求包为一个json字符串。
curl -d '{"foo":"bar"}' 'https://api.weixin.qq.com/some_api?access_token=xxx&openid=xxx&signature=???&sig_method=hmac_sha256'
开发者需要计算出signature参数。假设用户当前有效的session_key 为 :
'o0q0otL8aEzpcZL/FT9WsQ=='
则开发者生成签名应该是
hmac_sha256('{"foo":"bar"}', 'o0q0otL8aEzpcZL/FT9WsQ==') = 654571f79995b2ce1e149e53c0a33dc39c0a74090db514261454e8dbe432aa0b
开发者服务器发起的HTTP请求
curl -d '{"foo":"bar"}' 'https://api.weixin.qq.com/some_api?access_token=xxx&openid=xxx&signature=654571f79995b2ce1e149e53c0a33dc39c0a74090db514261454e8dbe432aa0b&sig_method=hmac_sha256'
session_key 合法性校验
我们提供接口供开发者校验服务器所保存的登录态session_key是否合法。 为了保持session_key私密性,我们提供的校验接口本身不直接明文session_key,而是通过校验登录态签名完成。
接口地址
请求方法:GET
https://api.weixin.qq.com/wxa/checksession?access\_token=ACCESS\_TOKEN&signature=SIGNATURE&openid=OPENID&sig\_method=SIG\_METHOD
调用示例
curl -G 'https://api.weixin.qq.com/wxa/checksession?access_token=OsAoOMw4niuuVbfSxxxxxxxxxxxxxxxxxxx&signature=fefce01bfba4670c85b228e6ca2b493c90971e7c442f54fc448662eb7cd72509&openid=oGZUI0egBJY1zhBYw2KhdUfwVJJE&sig_method=hmac_sha256'
参数说明
参数 | 必填 | 说明 |
---|---|---|
access_token | 是 | 用凭证 |
openid | 是 | 用户唯一标识符 |
signature | 是 | 用户登录态签名 |
sig_method | 是 | 用户登录态签名的哈希方法 |
buffer | 是 | 托管数据,类型为字符串,长度不超过1000字节 |
返回结果
//正确时的返回JSON数据包如下:
{"errcode":0,"errmsg":"ok"}
//错误时的返回JSON数据包如下(示例为签名错误):
{"errcode":87009,"errmsg":"invalid signature"}
3.米大师支付签名
以查询余额的接口为例,原始请求信息:
米大师密钥:zNLgAGgqsEWJOg1nFVaO5r7fAlIQxr1u
HTTP请求方式: POST
请求的URI:/cgi-bin/midas/getbalance
sig签名
参与米大师签名请求参数
"openid":"odkx20ENSNa2w5y3g_qOkOvBNM1g",
"appid":"wx1234567",
"offer_id":"12345678",
"ts":1507530737,
"zone_id":"1",
"pf":"iap"
对参与米大师签名的参数按照key=value的格式,并按照参数名ASCII字典序升序排序如下:
stringA="appid=wx1234567&offer_id=12345678&openid=odkx20ENSNa2w5y3g_qOkOvBNM1g&pf=iap&ts=1507530737&zone_id=1"
拼接uri、method和米大师密钥:
stringSignTemp=stringA+"&org_loc=/cgi-bin/midas/getbalance&method=POST&secret=zNLgAGgqsEWJOg1nFVaO5r7fAlIQxr1u"
把米大师密钥作为key,使用HMAC-SHA256得到签名。
sig=hmac_sha256(key,stringSignTemp)
="d1f0a41272f9b85618361323e1b19cd8cb0213f21b935aeaa39c160892031e97"
mp_sig签名
1 参与开平签名请求参数
"access_token":"ACCESSTOKEN",
"openid":"odkx20ENSNa2w5y3g_qOkOvBNM1g",
"appid":"wx1234567",
"offer_id":"12345678",
"ts":1507530737,
"zone_id":"1",
"pf":"iap",
"sig":"d1f0a41272f9b85618361323e1b19cd8cb0213f21b935aeaa39c160892031e97"
2 对参与开平签名的参数按照key=value的格式,并按照参数名ASCII字典序升序排序如下:
stringA="access_token=ACCESSTOKEN&appid=wx1234567&offer_id=12345678&openid=odkx20ENSNa2w5y3g_qOkOvBNM1g&pf=iap&sig=d1f0a41272f9b85618361323e1b19cd8cb0213f21b935aeaa39c160892031e97&ts=1507530737&zone_id=1"
3 拼接uri、method和session_key:
stringSignTemp=stringA+"&org_loc=/cgi-bin/midas/getbalance&method=POST&session_key=V7Q38/i2KXaqrQyl2Yx9Hg=="
4 把session_key作为key,使用HMAC-SHA256得到签名。
mp_sig=hmac_sha256(key,stringSignTemp) ="f7fc0198b1bf795892bed804d145206105eb5835d6ac53fd745834b4a1236c78"
4.关系链数据使用指南
一个微信用户的关系链数据包括两部分:
- 该用户好友的用户数据
- 该用户所在的某个群的群成员的用户数据。
获取关系链数据的 API:
- [wx.getFriendCloudStorage()]获取当前用户也玩该小游戏的好友的用户数据
- [wx.getGroupCloudStorage()]获取当前用户在某个群中也玩该小游戏的成员的用户数据
这两个 API 的返回结果都是一个对象数组,数组的每一个元素都是一个表示用户数据的对象,其结构如下:
属性 | 类型 | 说明 |
---|---|---|
openId | string | 用户的 openId |
avatarUrl | string | 用户的微信头像 url |
nickName | string | 用户的微信昵称 |
data | Object | 用户的游戏数据 |
用户的 游戏数据
指的是用户的段位、战绩等游戏业务特有的数据,通过调用 [wx.setUserCloudStorage()]可以将当前用户的游戏数据托管在微信后台。只有被托管过数据的用户,才会被视为 玩过
该小游戏的用户,才会出现在 [wx.getFriendCloudStorage()] 和 [wx.getGroupCloudStorage()] 返回的对象数组中。
还提供了以下 API:
- [wx.removeUserCloudStorage()] 删除用户托管数据中指定字段的数据
- [wx.getUserCloudStorage()]获取当前用户的托管数据
[wx.getUserCloudStorage]、[wx.getFriendCloudStorage()]和 [wx.getGroupCloudStorage()] 只能在 开放数据域
中调用。
[wx.setUserCloudStorage()]和 [wx.removeUserCloudStorage()]可以同时在 主域
和开放数据域中调用。
开放数据域
开放数据域
是一个封闭、独立的 JavaScript 作用域。要让代码运行在开放数据域,需要在 game.json 中添加配置项 openDataContext
指定开放数据域的代码目录。添加该配置项表示小游戏启用了开放数据域,这将会导致一些 [限制]。
{
"deviceOrientation": "portrait",
"openDataContext": "src/myOpenDataContext"
}
同时还需要在该目录下创建 index.js 作为开放数据域的入口文件,其代码运行在开放数据域。game.js 是整个游戏的入口文件,其代码运行在 主域
。
主域和开放数据域中的代码不能相互 require。
主域和开放数据域的通信
开放数据域不能向主域发送消息。
主域可以向开放数据域发送消息。调用 wx.getOpenDataContext()方法可以获取开放数据域实例,调用实例上的 OpenDataContext.postMessage()]方法可以向开放数据域发送消息。
// game.js
let openDataContext = wx.getOpenDataContext()
openDataContext.postMessage({
text: 'hello',
year: (new Date()).getFullYear()
})
在开放数据域中通过 wx.onMessage()方法可以监听从主域发来的消息。
// src/myOpenDataContext/index.js
wx.onMessage(data => {
console.log(data)
/* {
text: 'hello',
year: 2018
} */
})
展示关系链数据
如果想要展示通过关系链 API 获取到的用户数据,如绘制排行榜等业务场景,需要将排行榜绘制到 sharedCanvas 上,再在主域将 sharedCanvas 渲染上屏。
// src/myOpenDataContext/index.js
let sharedCanvas = wx.getSharedCanvas()
function drawRankList (data) {
data.forEach((item, index) => {
// ...
})
}
wx.getFriendUserGameData({
success: res => {
let data = res.data
drawRankList(data)
}
})
sharedCanvas 是主域和开放数据域都可以访问的一个离屏画布。在开放数据域调用 wx.getSharedCanvas() 将返回 sharedCanvas。
// src/myOpenDataContext/index.js
let sharedCanvas = wx.getSharedCanvas()
let context = sharedCanvas.getContext('2d')
context.fillStyle = 'red'
context.fillRect(0, 0, 100, 100)
在主域中可以通过开放数据域实例访问 sharedCanvas,通过 drawImage() 方法可以将 sharedCanvas 绘制到上屏画布。
// game.js
let openDataContext = wx.getOpenDataContext()
let sharedCanvas = openDataContext.canvas
let canvas = wx.createCanvas()
let context = canvas.getContext('2d')
context.drawImage(sharedCanvas, 0, 0)
5. 虚拟支付
小游戏为开发者提供游戏内虚拟物品的购买服务。
注:目前小游戏虚拟支付能力只支持在安卓Android系统内使用,暂不开放苹果iOS系统内虚拟支付功能。
在开通虚拟支付功能前,开发者需完成:
- 开通小程序微信支付
- 申请开通小游戏虚拟支付
wx.requestMidasPayment()是我们提供购买游戏币的API:
wx.requestMidasPayment(Object options)
示例代码
// 游戏币
wx.requestMidasPayment({
mode: 'game',
offerId: '',
buyQuantity: 10,
zoneId: 1,
success() {
// 支付成功
},
fail({ errMsg, errCode }) {
// 支付失败
console.log(errMsg, errCode)
}
})
提供用于测试验证与调试的沙箱测试环境,并相应提供以下API:
接口名 | 功能 | 说明 |
---|---|---|
[midasGetBalance] | 查询余额 | 查看某个用户的游戏币余额 |
[midasPay] | 扣除游戏币 | 扣除某个用户的游戏币 |
[midasCancelPay] | 取消支付 | 在有效期内的订单,可以通过本接口取消该笔扣除游戏币的订单 |
[midasPresent] | 游戏币赠送 | 向某个用户赠送游戏币 |
6.获取二维码
小游戏的二维码与小程序有着相同的样式和获取方式。通过后台接口可以获取小游戏的二维码,扫描该二维码可以直接进入小游戏。目前微信支持两种二维码;
7. 转发
用户在使用小游戏过程中,可转发消息给其他用户或群聊。
转发菜单
点击右上角按钮,会弹出菜单,菜单中的“转发”选项默认不展示。通过 [wx.showShareMenu()] 和 [wx.hideShareMenu()]可动态显示、隐藏这个选项。
被动转发
用户点击右上角菜单中的“转发”选项后,会触发转发事件,如果小游戏通过 [wx.onShareAppMessage()] 监听了这个事件,可通过返回自定义转发参数来修改转发卡片的内容,否则使用默认内容。
wx.onShareAppMessage(function () {
// 用户点击了“转发”按钮
return {
title: '转发标题'
}
})
主动转发
游戏内可通过 [wx.shareAppMessage()]接口直接调起转发界面,与被动转发类似,可以自定义转发卡片内容。
wx.shareAppMessage({
title: '转发标题'
})
使用 Canvas 内容作为转发图片
如果不指定转发图片,默认会显示一个小程序的 logo。如果希望转发的时候显示 Canvas 的内容,可以使用 [Canvas.toTempFilePath()]或 [Canvas.toTempFilePathSync()]来生成一张本地图片,然后把图片路径传给 imageUrl
参数。
转发出来的消息卡片中,图片的最佳显示比例是 5:4。
wx.onShareAppMessage(function () {
return {
title: '转发标题',
imageUrl: canvas.toTempFilePathSync({
destWidth: 500,
destHeight: 400
})
}
})
withShareTicket 模式
通过 [wx.updateShareMenu] 接口可修改转发属性。如果设置 withShareTicket
为 true
,会有以下效果
- 选择联系人的时候只能选择一个目标,不能多选
- 消息被转发出去之后,在会话窗口中无法被长按二次转发
- 消息转发的目标如果是一个群聊,则
- 会在转发成功的时候获得一个
shareTicket
- 每次用户从这个消息卡片进入的时候,也会获得一个
shareTicket
,通过调用 [wx.getShareInfo()] 接口传入shareTicket
可以获取群相关信息
- 会在转发成功的时候获得一个
修改这个属性后,同时对主动转发和被动转发生效。
// 设置 withShareTicket: true
wx.updateShareMenu({
withShareTicket: true
})
8.用户数据的签名验证和加解密
数据签名校验
为了确保开放接口返回用户数据的安全性,微信会对明文数据进行签名。开发者可以根据业务需要对数据包进行签名校验,确保数据的完整性。
- 签名校验算法涉及用户的session_key,通过 [wx.login] 登录流程获取用户session_key,并自行维护与应用自身登录态的对应关系。
- 通过调用接口(如 wx.getUserInfo]获取数据时,接口会同时返回 rawData、signature,其中 signature = sha1( rawData + session_key )
- 开发者将 signature、rawData 发送到开发者服务器进行校验。服务器利用用户对应的 session_key 使用相同的算法计算出签名 signature2 ,比对 signature 与 signature2 即可校验数据的完整性。
加密数据解密算法
接口如果涉及敏感数据(如[wx.getUserInfo]当中的 openId 和unionId ),接口的明文内容将不包含这些敏感数据。开发者如需要获取敏感数据,需要对接口返回的加密数据( encryptedData )进行对称解密。 解密算法如下:
- 对称解密使用的算法为 AES-128-CBC,数据采用PKCS#7填充。
- 对称解密的目标密文为 Base64_Decode(encryptedData)。
- 对称解密秘钥 aeskey = Base64_Decode(session_key), aeskey 是16字节。
- 对称解密算法初始向量 为Base64_Decode(iv),其中iv由数据接口返回。
另外,为了应用能校验数据的有效性,我们会在敏感数据加上数据水印( watermark )
watermark参数说明:
参数 | 类型 | 说明 |
---|---|---|
watermark | OBJECT | 数据水印 |
appid | String | 敏感数据归属appid,开发者可校验此参数与自身appid是否一致 |
timestamp | DateInt | 敏感数据获取的时间戳, 开发者可以用于数据时效性校验 |