三、奖励回调通知

三、奖励回调通知 #

#lfeng/wellplay/能力综述/奖励系统

回调通知是指开发者在发奖时,指定服务端在处理领奖请求时,通知某个特定服务器,在该服务器确认接收了该回调后才将领奖结果返回给客户端的机制。

因为加入了回调请求和响应的过程,使用回调通知机制一般会导致客户端花费更多的等待时间。

要启用回调功能,业务服务器在需要发奖设置奖励策略 (RewardPolicy)中的CallbackURLCallbackBody字段。

回调地址 #

通过设定奖励策略中的CallbackURL字段为一个有效的地址,即可让Wellplay在收到客户端领奖请求后向该地址发起POST回调请求。 该地址可以是一个HTTP或者HTTPS的URL,允许公网访问。 如果需要传递自定义的请求内容,开发者可以考虑配合使用奖励策略中的CallbackBody字段。如果只有CallbackURL而没有 CallbackBody,回调服务器收到的请求内容将为空。

回调内容 #

开发者可以控制回调中传递到游戏回调服务器的反馈信息。CallbackBody 的格式如下:

Hello=$(x:Hello)&Foo=Bar&RoleID=$(Role.ID)

上面的 CallbackBody 示例中,混合使用了 ~魔法变量~Role.ID,自定义变量Hello及常量Foo。 假设客户端的回调实现如下:

// 监听领奖状态
[LFPlay listenReward:^NSDictionary<NSString *,NSString *> *(NSString *awardBody) {
  // 请求领奖成功时触发
	// AwardBody是后台配置的奖励包体,客户端可以根据AwardBody的内容决定返回的参数。
	NSLog(@"awardBody == %@",awardBody);
	// 返回值即ReturnBody或CallbackBody中会使用到的自定义变量
  return @{@"Hello":@"World"};
}
// ...

Wellplay会构造出如下的回调信息:

Hello=World&Foo=Bar&RoleID=123456-654321-985214

再对该信息进行URL安全的Base64编码,得到结果如下:

SGVsbG89V29ybGQmRm9vPUJhciZSb2xlSUQ9MTIzNDU2LTY1NDMyMS05ODUyMTQ=

URL安全的Base64编码适用于以URL方式传递Base64编码结果的场景。该编码方式的基本过程是先将内容以Base64格式编码为字符串,然后检查该结果字符串,将字符串中的加号+换成中划线-,并且将斜杠/换成下划线_。

Wellplay将这组数据作为请求 Body 发送至用户指定的回调服务器,请求方式为 POST。 回调服务器将接收到以下格式的请求:

POST /callback  HTTP/1.1
Content-Type: application/x-www-form-urlencoded
User-Agent: go-agent
Host: api.examples.com
Authorization: WPlay f5p6mi5Le66LeDz6:eb74fc3e1277c0c9bf02976dce2cba91dd7f2fde:6ZS37zdri9hUNckKZkK-TuGSPX8=

SGVsbG89V29ybGQmRm9vPUJhciZSb2xlSUQ9MTIzNDU2LTY1NDMyMS05ODUyMTQ=

回调服务器接收到回调请求后,负责生成Wellplay返回给客户端的数据(json 格式),该数据作为此次回调请求的响应内容。如果回调成功,回调服务应对Wellplay作出类似如下的响应(注意:回调响应内容完全由回调服务器生成,Wellplay服务器只做透传,以下仅为示例):

HTTP/1.1 200 OK
Server: nginx/1.1.19
Date: Thu, 19 Dec 2018 06:27:30 GMT
Content-Type: application/json

{"some_thing_ok":true,"world":"hello","msg":"感谢老铁送出的火箭🚀"}

Wellplay将上面的回调结果返回给客户端,则客户端接收到以下响应:

// ......
 rewardBlock:^(NSDictionary *returnBody) {
	// 领奖成功时发生的回调
  // 由回调接口返回的JSON被处理过后的字典
  NSLog(@"returnBody == %@",returnBody);
	// returnBody == {"some_thing_ok":true,"world":"hello","msg":"感谢老铁送出的火箭🚀"}
}
// ......

如果回调失败,客户端会收到异常回调,可能如下所示:

// ...
rewardError:^(id errorDic) {
	// 领奖失败时发生的回调
  NSLog(@"rewardError == %@",errorDic);
  // rewardError == {"msg":"请求拒绝","Detials":{"CallbackError":{"Status":406,"Error":"回调拒绝请求:status = 400","CallbackURL":"http://api.example.com","CallbackBodyType":"x-www-form-urlencoded","CallbackBody":"Hello=World&Foo=Bar&RoleID=123456-654321-985214"}}
}];

安全性 #

由于回调地址是公网可任意访问的,那如何确认回调是合法的呢?

Wellplay在回调时会对请求数据签名,并将结果包含在请求头Authorization字段中,格式为WPlay $APPKey:$Signature:$Nonce,示例如下:

Authorization: WPlay f5p6mi5Le66LeDz6:eb74fc3e1277c0c9bf02976dce2cba91dd7f2fde:6ZS37zdri9hUNckKZkK-TuGSPX8=

其中WPlay为固定值,f5p6mi5Le66LeDz6为应用的$APPKey28c2b834704f77a79b41047bcfb6606dd9b50886为HEX编码的签名$Signature$Nonce为32位字符串随机值(用于避免回放攻击)。

回调服务器可以通过以下方法验证其合法性:

  1. 获取明文 data = Request.URL.Path +"\n" +Request.Body+ “\n” + Nonce,部分语言或框架无法直接获取请求 body 的原始数据,在自行拼接时应当注意,body 中的数据是经过URL 安全的 Base64 编码的。
  2. 采用 HMAC-SHA1 签名算法,使用APPSecret对明文 data 签名,并采用HEX对结果进行编码。
  3. 比较签名结果是否与Authorization 字段中的$Signature字段相同,如相同则表明这是一个合法的回调请求。

以Go语言为例,验证代码如下:

func validate(authorizationHeader string, requestURL string, requestBody string, myAPPKey string, myAPPSecret string) error {
	s := strings.TrimPrefix(authorizationHeader, "WPlay ")
	paramList := strings.Split(s, ":")
	if len(paramList) != 3 {
		return fmt.Errorf("Auth参数异常")
	}
	appKey := paramList[0]
	if appKey != myAPPKey {
		return fmt.Errorf("不是我的APP")
	}
	remoteSignature := paramList[1]
	nonce := paramList[2]

	strToSign := requestURL + "\n" + requestBody + "\n" + nonce
	hmacSHA1 := hmac.New(sha1.New, []byte(myAPPSecret))
	hmacSHA1.Write([]byte(strToSign))
	localSignature := hmacSHA1.Sum(nil)
	hexLocalSignature := hex.EncodeToString(localSignature)

	if hexLocalSignature != remoteSignature {
		return fmt.Errorf("签名不匹配")
	}
	return nil
}

注意 #

如果回调数据有用户的敏感数据,建议回调地址使用 HTTPS 协议。