秒杀接口地址的隐藏可以防止恶意用户通过频繁调用接口来请求的操作,但是无法防止利用按键精灵或者机器人频繁点击按钮来刷接口的操作。
而且,对于高并发下,在某一个时间段,也就是刚刚开始秒杀的那一瞬间,迎来的并发量是最大的,如何做到并发量分流也是一种减少数据库以及系统压力的措施。
数学图形验证码:
1.防机器人刷接口
2.分散用户请求
思路:点击秒杀按钮之前,需要输入验证码验证
1.生成验证码的接口
2.在获取秒杀路径的同时,验证验证码
步骤
1.改造前端页面,验证码只在秒杀进行中的时候存在,<img id > 用id控制属性,一开始验证码和输入框是不可见的(未开始的时候)。当倒计时结束,开始秒杀的时候,使其可见并且src="/getVerifyCode" 请求后端,生成图片。
/**加入秒杀数学验证码 功能
* 1.一开始图形验证码和输入框都是隐藏的
* 2.当秒杀进行的时候,显示验证码和输入框
* */
$("#verifyCodeImg").attr("src", "/miaosha/verifyCode?goodsId="+$("#goodsId").val());//访问验证码接口
$("#verifyCodeImg").show();
$("#verifyCode").show();
2.传参为goodsid。根据用户id和goodsid 生成数学公式验证码,
图片是利用BufferedImage 类生成,生成大小长80 宽50像素的灰底图片。 利用是Graphics 类作为画笔,swing),
/**
* 秒杀验证码 接口
*/
@RequestMapping(value = "/verifyCode", method = RequestMethod.GET)// 只允许POST 提交
@ResponseBody
public Result<Integer> Miaosha(HttpServletResponse response,MiaoshaUser user, Model model,
@RequestParam("goodsId") long goodsId) {
/*判断是否登陆*/
if (user == null) {
return Result.error(CodeMsg.SESSION_ERROR);
}
BufferedImage image = miaoshaService.createVerifyImg(user, goodsId);
try {
OutputStream out = response.getOutputStream();//用response的输出流输出这个图片
ImageIO.write(image,"JPEG",out);//图片写入输出流
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
return Result.error(CodeMsg.SERVER_ERROR);//失败就失败
}
return null;
}
public BufferedImage createVerifyImg(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();//获取图像的graphics 对象,利用它就可以画图
// 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);//在图片上生成 50个 干扰的点
}
// 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);//将 这个String 类型验证码 写在 图片上
g.dispose();//关掉这个画笔
//把验证码存到redis中
int rnd = calc(verifyCode);//计算这个数学公式验证码的值
//将这个计算的值放入 redis中等待 对比
redisService.set(MiaoshaKey.getMiaoshaVerifyCode, user.getId()+","+goodsId, rnd);
//输出图片
return image;
}
对于数学公式的生成,专门有一个方法:
生成3 个 0到9之间的数,然后在生成一个字符数组,用于存放 + - * (加减乘)三个数学运算符,
然后对其进行拼接,一个字+运算符+字+运算符+字 生成一个字符串
private static char [] chars = {'+','-','*'};
//生成这个验证码
private String generateVerifyCode(Random rdm) {
int num1 = rdm.nextInt(10);
int num2 = rdm.nextInt(10);
int num3 = rdm.nextInt(10);//生成三个随机数(10以内)
char charOne = chars[rdm.nextInt(3)];//生成两个运算符// n 表示 10以内,不包括10的整数 0-9
char charTwo = chars[rdm.nextInt(3)];
//对其进行拼接,获得数学表达式
String verifyCode = ""+num1+charOne+num2+charTwo+num3;
return verifyCode;
}
然后利用drawString 方法将这个字符串写在生成的图片上。
同时,我们利用scriptEngine类,调用javascript的eval() 方法,计算这个公式的值,将这个值,以验证vericode 的key存入缓存,
然后将这个验证码图片 用输出流 作为response输出。
3.前端得到这个验证码图片,显示该验证码,然后在用户需要在输入验证码 将这个验证码作为参数,将这个verifyCode 作为参数一同传输给后端(这个操作是在/getPath 这个接口中进行校验),后端接收的时候,接收到参数,再秒杀之前,进行验证码比对,缓存中取出该验证码进行校验。如果不通过,不生成秒杀接口地址。
注意:Bug eval() 计算得到的是double 值,但我们需要的int 值,需要强转
以及一个刷新验证码的操作,
在 图片上定义个oncilck 操作,点击后在请求 获取图片验证码的接口,但是浏览器会有缓存,要加上timestamp 这个参数,浏览器才会送请求中取数据,而不是读直接缓存(对于相同url ,浏览器会选择读缓存,而不是请求)