理解:比如秒杀或抢购场景中,如果用户提前知道了接口地址,就可能出现狂刷接口的现象,为了防止这种情况发生,最好的方法就是不让用户获取到秒杀接口地址。有一种实现方式就是隐藏接口地址,所谓的隐藏是指,秒杀地址每次都是不同的,而且在地点秒杀按钮后,会先调用后端接口获取一个pathId,然后传回前端,拼接在秒杀接口上,再请求秒杀接口地址,这样每次点击按钮后,才会生成真正的秒杀接口地址。
好处:可以防止接口泄露,或者用户提前知道接口,而出现的狂刷接口的现象。
实现:
1、点击秒杀按钮,先请求一个后端服务,生成随机数(进行MD5加密)作为pathId,存入缓冲,设置过期时间,然后传回前端。
// 调用后台接口获取秒杀接口的入口地址
function getMiaoshaPath(){
var goodsId = $("#goodsId").val();
g_showLoading();
$.ajax({
url:"/miaosha/path",
type:"GET",
data:{
goodsId:goodsId,
verifyCode:$("#verifyCode").val()
},
success:function(data){
if(data.code == 0){
//相当于是产生的uuid(获取到的秒杀接口的入口地址)
var path = data.data;
doMiaosha(path);
}else{
layer.msg(data.msg);
}
},
error:function(){
layer.msg("客户端请求有误");
}
});
}
后端方法
@RequestMapping(value="/path", method=RequestMethod.GET)
@ResponseBody
public Result<String> getMiaoshaPath(HttpServletRequest request, MiaoshaUser user,
@RequestParam("goodsId")long goodsId,
@RequestParam(value="verifyCode", defaultValue="0")int verifyCode
) {
if(user == null) {
return Result.error(CodeMsg.SESSION_ERROR);
}
String str = MD5Util.md5(UUIDUtil.uuid()+"123456");
redisService.set(MiaoshaKey.getMiaoshaPath, ""+user.getId() + "_"+ goodsId, str);
return Result.success(str);
}
2、获得pathId后,前端用这个pathId拼接在秒杀接口上作为参数。
//秒杀的时候,需要向服务端传递参数
function doMiaosha(path){
$.ajax({
//进行秒杀
url:"/miaosha/"+path+"/do_miaosha",
type:"POST",
data:{
goodsId:$("#goodsId").val()
},
success:function(data){
if(data.code == 0){
//window.location.href="/order_detail.htm?orderId="+data.data.id;
getMiaoshaResult($("#goodsId").val());
}else{
layer.msg(data.msg);
}
},
error:function(){
layer.msg("客户端请求有误");
}
});
}
3、后端接收到这个pathId,并且与缓冲的pathId比较,如果
@RequestMapping(value="/{path}/do_miaosha", method=RequestMethod.POST)
@ResponseBody
public Result<Integer> miaosha(Model model,MiaoshaUser user,
@RequestParam("goodsId")long goodsId,
@PathVariable("path") String path) {
model.addAttribute("user", user);
if(user == null) {
return Result.error(CodeMsg.SESSION_ERROR);
}
//验证path------------------------------------------
String pathOld = redisService.get(MiaoshaKey.getMiaoshaPath, ""+user.getId() + "_"+ goodsId, String.class);
//如果通过比较,进行秒杀逻辑
if(!path.equals(pathOld)){
//如果通不过,抛出业务异常,非法请求
return Result.error(CodeMsg.REQUEST_ILLEGAL);
}
//验证path------------------------------------------
//内存标记,减少redis访问
boolean over = localOverMap.get(goodsId);
if(over) {
return Result.error(CodeMsg.MIAO_SHA_OVER);
}
//预减库存
long stock = redisService.decr(GoodsKey.getMiaoshaGoodsStock, ""+goodsId);//10
if(stock < 0) {
localOverMap.put(goodsId, true);
return Result.error(CodeMsg.MIAO_SHA_OVER);
}
//判断是否已经秒杀到了
MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
if(order != null) {
return Result.error(CodeMsg.REPEATE_MIAOSHA);
}
//入队
MiaoshaMessage mm = new MiaoshaMessage();
mm.setUser(user);
mm.setGoodsId(goodsId);
sender.sendMiaoshaMessage(mm);
return Result.success(0);//排队中
}