秒杀系统 安全优化 数学公式验证码
秒杀接口地址的隐藏可以防止恶意用户通过频繁调用接口来请求的操作,但是无法防止机器人,刷票软件恶意频繁点击按钮来刷请求秒杀地址接口的操作。
高并发下场景,在刚刚开始秒杀的那一瞬间,迎来的并发量是最大的,减少同一时间点的并发量,将并发量分流也是一种减少数据库以及系统压力的措施(使得1s中来10万次请求过渡为10s中来10万次请求)
思路:点击秒杀之前,先输入验证码,分散用户的请求。具体实现是服务端生成类似1+2-3的验证码,把结果计算出来存至服务端(缓存),把验证码图片发至客户端,此后客户端在请求秒杀地址前输入验证码值发请求验证,(去缓存里面取得值验证是否与用户输入相同),验证通过才会动态生成秒杀地址给前端。
步骤:
在商品详情页面加入验证码图片标签,指定Id,再加入验证码输入框input组件,并初始化它们的属性为不可见的,因为一开始验证码和输入框是不可见的(只有秒杀开始才会可见),图片可以点击刷新图片,所以定义refreshVCode方法来刷新图片。
在倒计时方法里面正在进行秒杀分支判断中加入显示验证码以及验证码输入框的代码逻辑,开始秒杀的时候,设置其可见并且指定attr()方法动态指定src,发送请求到后端,动态生成图片。注意:秒杀结束之后,又需要将其设置为不可见的。
请求中传参为goodsId,然后可以根据用户id和goodsId生成数学公式验证码,然后将这个验证码图片response的输出流输出至前端。
后端接收生成图片请求接口:
/**
* 生成图片验证码
*/
@RequestMapping(value ="/vertifyCode")
@ResponseBody
public Result<String> getVertifyCode(Model model,MiaoshaUser user,
@RequestParam("goodsId") Long goodsId,HttpServletResponse response) {
model.addAttribute("user", user);
//如果用户为空,则返回至登录页面
if(user==null){
return Result.error(CodeMsg.SESSION_ERROR);
}
BufferedImage img=miaoshaService.createMiaoshaVertifyCode(user, goodsId);
try {
OutputStream out=response.getOutputStream();
ImageIO.write(img,"JPEG", out);
out.flush();
out.close();
return null;
} catch (IOException e) {
e.printStackTrace();
return Result.error(CodeMsg.MIAOSHA_FAIL);
}
}
图片是利用BufferedImage 类生成,指定高度与宽度,利用Graphics做画笔,填充颜色,画出边界线等操作,然后利用drawString方法将我们随机拼接成字符串写在生成的图片上,还要计算出字符串的值存在缓存里面。
createMiaoshaVertifyCode生成验证码图片方法:
public BufferedImage createMiaoshaVertifyCode(MiaoshaUser user, Long goodsId) {
if(user==null||goodsId<=0) {
return null;
}
int width=80;
int height=30;
BufferedImage img=new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
Graphics g=img.getGraphics();
g.setColor(new Color(0xDCDCDC));
g.fillRect(0, 0, width, height);
g.setColor(Color.BLACK);
g.drawRect(0, 0, width-1, height-1);
Random rdm=new Random();
for(int i=0;i<50;i++) {
int x=rdm.nextInt(width);
int y=rdm.nextInt(height);
g.drawOval(x, y, 0, 0);
}
//生成验证码
String vertifyCode=createVertifyCode(rdm);
g.setColor(new Color(0,100,0));
g.setFont(new Font("Candara",Font.BOLD,24));
//将验证码写在图片上
g.drawString(vertifyCode, 8, 24);
g.dispose();
//计算存值
int rnd=calc(vertifyCode);
//将计算结果保存到redis上面去
redisService.set(MiaoshaKey.getMiaoshaVertifyCode, ""+user.getId()+"_"+goodsId, rnd);
return img;
}
注意:对于数学公式的生成,方法createVertifyCode实现,生成3个0到9之间的随机数,然后在生成一个字符数组,用于存放 + - * (加减乘)三个数学运算符,随机选中两个字符,然后对其进行拼接 成一个字符串,数+运算符+数+运算符+数,返回这个字符串。
private static char[]ops=new char[] {'+','-','*'};
private String createVertifyCode(Random rdm) {
//生成10以内的
int n1=rdm.nextInt(10);
int n2=rdm.nextInt(10);
int n3=rdm.nextInt(10);
char op1=ops[rdm.nextInt(3)];//0 1 2
char op2=ops[rdm.nextInt(3)];//0 1 2
String exp=""+n1+op1+n2+op2+n3;
return exp;
}
利用scriptEngine类,调用JavaScript的eval() 方法,计算这个字符串公式的值,将这个值保存到redis上面去(用户下次发送验证请求的时候,直接去缓存里面取出并验证即可)注意:eval() 计算得到的是double 值,但我们需要的int 值,需要强转
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;
}
}
前端得到这个验证码图片,显示该验证码,然后用户需要输入验证码将这个验证码作为参数,与获取秒杀地址请求一起传输给后端(校验的操作在获取秒杀地址之前),后端接收到参数,进行验证码比对,缓存中取出该验证码进行校验。如果不通过,不生成秒杀接口地址,直接返回验证码错误信息。
注意:在图片上定义个oncilck 操作,点击后在请求获取图片验证码的接口,但是浏览器会有缓存,要加上timestamp 这个参数,浏览器才会真正发送请求,不然只是去缓存里面拿。
//刷新验证码,浏览器具有缓存---所以加一个参数timestamp
function refreshVCode(){
$("#vertifyCodeImg").attr("src","/miaosha/vertifyCode?goodsId="+$("#goodsId").val()+"×tamp="+new Date().getTime());
}
验证逻辑
/**
* 验证验证码,取缓存里面取得值,验证是否相等
*/
public boolean checkVCode(MiaoshaUser user, Long goodsId, int vertifyCode) {
Integer redisVCode=redisService.get(MiaoshaKey.getMiaoshaVertifyCode, user.getId()+"_"+goodsId, Integer.class);
if(redisVCode==null||redisVCode-vertifyCode!=0) {
return false;
}
//删除缓存里面的数据
redisService.delete(MiaoshaKey.getMiaoshaVertifyCode, user.getId()+"_"+goodsId);
return true;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?