赞
踩
隐藏秒杀接口地址后,确保秒杀开始前没有人知道地址。http是明文传输的,访问的url,参数都可见,若不做隐藏,恶意用户可以在秒杀活动开始前就访问秒杀接口地址。
秒杀项目完整代码地址:https://github.com/yang-mou/miaosha.git
思路:
1、第一次请求后台先验证用户是否登录和验证码是否正确,生成随机地址存入redis并且返回
2、带着地址请求后台,后台从redis中取出地址验证是否正确
3、正确继续执行秒杀逻辑,错误直接返回
秒杀按钮
<button class="btn btn-primary" type="button" id="buyButton"onclick="getMiaoshaPath()">立即秒杀</button>
获取秒杀地址
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){ //获取到秒杀地址 var path = data.data; //请求秒杀方法 doMiaosha(path); }else{ layer.msg(data.msg); } }, error:function(){ layer.msg("客户端请求有误"); } }); }
请求执行秒杀逻辑
function doMiaosha(path){//秒杀的时候,需要向服务端传递参数 $.ajax({ url:"/miaosha/"+path+"/do_miaosha",//进行秒杀 type:"POST", data:{ goodsId:$("#goodsId").val() }, success:function(data){ if(data.code == 0){ //判断秒杀结果 getMiaoshaResult($("#goodsId").val()); }else{ layer.msg(data.msg); } }, error:function(){ layer.msg("客户端请求有误"); } }); }
第一次请求获取秒杀地址
@RequestMapping(value="/path", method=RequestMethod.GET) @ResponseBody public Result<String> getMiaoshaPath(HttpServletRequest request, HttpServletResponse response, MiaoshaUser user, @RequestParam("goodsId")long goodsId, @RequestParam(value="verifyCode", defaultValue="0")int verifyCode ) { //判断用户是否登录 if(user == null) { return Result.error(CodeMsg.SESSION_ERROR); } //判断验证码是否正确 boolean check = miaoshaService.checkVerifyCode(user, goodsId, verifyCode); if(!check) { return Result.error(CodeMsg.REQUEST_ILLEGAL); } //生成秒杀地址 String path =miaoshaService.createMiaoshaPath(user, goodsId); return Result.success(path); }
生成秒杀地址
MiaoshaService 生成随机数再用MD5加密来作为 该用户秒杀该商品的path,并将存入缓存,客户端获得path后会立马进行秒杀,所以path的有效期设置很短(有效期1分钟)
服务端收到秒杀请求时,根据用户id和商品id,检查对应的path是否正确后,再进行后面的秒杀逻辑。
//隐藏原有的秒杀地址 @RequestMapping(value="/{path}/do_miaosha", method=RequestMethod.POST) @ResponseBody public Result<Integer> miaosha(HttpServletRequest request, HttpServletResponse response, 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); } //从redis中取出验证path boolean check = miaoshaService.checkPath(user, goodsId, path); if(!check){ return Result.error(CodeMsg.REQUEST_ILLEGAL); } //内存标记,从map取值判断,减少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); //返回0代表排队中 return Result.success(0); }
验证秒杀地址是否正确
/**
* 验证秒杀地址
* @param user
* @param goodsId
* @param path
* @return
*/
public boolean checkPath(MiaoshaUser user, long goodsId, String path) {
if(user == null || path == null) {
return false;
}
String pathOld = redisService.get(MiaoshaKey.getMiaoshaPath, ""+user.getId() + "_"+ goodsId, String.class);
return path.equals(pathOld);
}
验证码图片
<img id="verifyCodeImg" width="80" height="32" style="display:none" onclick="refreshVerifyCode()"/>
<input id="verifyCode" class="form-control" style="display:none"/>
请求函数
function refreshVerifyCode(){
$("#verifyCodeImg").attr("src", "/miaosha/verifyCode?goodsId="+$("#goodsId").val()+"×tamp="+new Date().getTime());
}
生成验证码
@RequestMapping(value="/verifyCode", method=RequestMethod.GET) @ResponseBody public Result<String> getMiaoshaVerifyCod(HttpServletRequest request, HttpServletResponse response, MiaoshaUser user, @RequestParam("goodsId")long goodsId) { if(user == null) { return Result.error(CodeMsg.SESSION_ERROR); } try { BufferedImage image = miaoshaService.createVerifyCode(user, goodsId); OutputStream out = response.getOutputStream(); ImageIO.write(image, "JPEG", out); out.flush(); out.close(); return null; }catch(Exception e) { e.printStackTrace(); return Result.error(CodeMsg.MIAOSHA_FAIL); } }
生成验证码以及验证加减乘除
/** * 生成验证码 * @param user * @param goodsId * @return */ public BufferedImage createVerifyCode(MiaoshaUser user, long goodsId) { if(user == null || goodsId <=0) { return null; } int width = 80; int height = 32; //create the image BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics g = image.getGraphics(); // set the background color g.setColor(new Color(0xDCDCDC)); g.fillRect(0, 0, width, height); // draw the border g.setColor(Color.black); g.drawRect(0, 0, width - 1, height - 1); // create a random instance to generate the codes Random rdm = new Random(); // make some confusion for (int i = 0; i < 50; i++) { int x = rdm.nextInt(width); int y = rdm.nextInt(height); g.drawOval(x, y, 0, 0); } // generate a random code String verifyCode = generateVerifyCode(rdm); g.setColor(new Color(0, 100, 0)); g.setFont(new Font("Candara", Font.BOLD, 24)); g.drawString(verifyCode, 8, 24); g.dispose(); //把验证码存到redis中 int rnd = calc(verifyCode); redisService.set(MiaoshaKey.getMiaoshaVerifyCode, user.getId()+","+goodsId, rnd); //输出图片 return image; } private static char[] ops = new char[] {'+', '-', '*'}; /** * 生成 加减乘 的算式 * + - * * */ private String generateVerifyCode(Random rdm) { int num1 = rdm.nextInt(10); int num2 = rdm.nextInt(10); int num3 = rdm.nextInt(10); char op1 = ops[rdm.nextInt(3)]; char op2 = ops[rdm.nextInt(3)]; String exp = ""+ num1 + op1 + num2 + op2 + num3; return exp; } /** * 将算式进行计算 * @param exp * @return */ private static int calc(String exp) { try { ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName("JavaScript"); return (Integer)engine.eval(exp); }catch(Exception e) { e.printStackTrace(); return 0; } }
end…
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。