问题重现
在我们的项目中,有一个商店购买的页面,流程是这样的: 选择道具 -----> 点击购买 -------> 获得道具 ------> 扣除银币
如果玩家在一秒钟并发的请求这个URL, 如果他有100个银币,只可以买10个道具,由于并发的问题,他还可以买100个道具,到时候,他的银币的数量可能扣除为负数。这样,对游戏来说,是非常严重的问题。
并发的原理是这样的: 当玩家第一次购买道具的的时候,读取玩家的余额,如果大于道具的价格,系统把道具给玩家,然后在扣除玩家的余额。在我我把道具给了玩家过程中,由于大量的请求过来,还没有来得及把玩家的钱扣除,所以,导致了玩家买到了更多的东西
问题所在
如果扣“除银币”在“获得道具”之前完成,就基本上可以避免了这种问题,但是,由于我们道具的特殊设定,不能先扣除银币,所以不能这样做
这样,就只能避免玩家重复的提交同一个买道具的URL,产生了大量的并发。方法是,URL上面加一个formHash,每次都生成一个新的formHash.那么就防止了页面多次提交了。
formHash的生成代码
formHash保存在memcache中
// 生成新的 formHash
public
function
refreshFormHash(
$formName
)
{
// 生成随机码
$formHash
= md5(
'VoyageMobile:'
.
date
(
'md'
) .
':'
. mt_rand() .
':'
.
$formName
);
$cacheObj
= Com_Cache::getInstance(
'Memcache'
);
$cacheKey
=
'formHash:'
.
$formName
;
// 将 formHash 写入到 Memcache 中
$cacheObj
->set(
$cacheKey
,
$formHash
);
// 传出到模板中
$this
->assign(
'formHash'
,
$formHash
);
return
$formHash
;
}
|
formHash的验证
/**
* 验证 formHash 是否正确
*
* @param string $formName
* @param mixed $formHashGet
* @param bool $clear 是否取完即清除
* @return bool
*/
public
function
validFormHash(
$formName
,
$formHashGet
= null,
$clear
= true)
{
// 页面中传过来的formHash
if
(
$formHashGet
=== null) {
$formHashGet
=
$this
->getx(
'formHash'
);
}
$cacheObj
= Com_Cache::getInstance(
'Memcache'
);
$cacheKey
=
'formHash:'
.
$formName
;
$formHash
=
$cacheObj
->get(
$cacheKey
);
// 取完即清除(formHash只能用一次)
$clear
&&
$cacheObj
->
delete
(
$cacheKey
);
return
$formHash
==
$formHashGet
? true : false;
}
|
formHash的使用
在道具购买的首页中,系统生成一个formHash.
public
function
shopIndex()
{
// 生成道具的列表
// 生成formHash
$this
->refreshFormHash();
}
|
formHash的验证
在购买道具的函数中,必须验证formHash,而且,必须生成新的formHash,并分配到页面中
/**
* 购买提交
*/
public
function
purchaseAction()
{
// 验证请求合法性
if
(!
$this
->validFormHash(
'bmItem'
)) {
$this
->vRedirect(
'/main/?err=Invalid_Request_FormHash'
);
}
// 重新刷新 formHash
$this
->refreshFormHash(
'bmItem'
);
// 购买流程
}
|
注意问题
某些页面是ajax请求,不刷新页面,这时候把新生成的formHash 动态的在页面中生成。
当客户端有传值,但服务端查不到时(可能是 Memcache 意外重启),为保证玩家体验,则直接当成验证通过
ajax提交失败也要更新formHash 见我文章xxxx