秒杀系统 - 隐藏地址/图片验证码/redis实现接口限流防刷

隐藏地址+图形验证码

目的

这么做主要是为了防止客户端提前拿到接口地址,在秒杀开始的时候直接调用秒杀接口。

思路

本次实验做法:/getPath获取秒杀链接,之后访问新链接进行秒杀。getPath中要验证图形验证码的结果是否正确。
本次实验缺点:因为真正的秒杀地址为/miaosha+{/getPath获取的data},仍然可以通过get请求获得真正秒杀地址。

更好的做法:调用/getPath接口可以返回一个页面的url,让浏览器跳转到这个新的url页面,新的页面才是真正的秒杀页面。

实现

获取图形验证码图片请求

	@RequestMapping(value="/verifyCode", method=RequestMethod.GET)
	@ResponseBody
	public Result<String> getMiaoshaVerifyCod(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 Result.success("获取验证码成功");
		}catch(Exception e) {
    
    
			e.printStackTrace();
			return Result.error(CodeMsg.MIAOSHA_FAIL);
		}
	}

图片生成,把图片运算结果存入redis。

 public BufferedImage createVerifyCode(MiaoshaUser user, long goodsId) {
    
    
        if(user == null || goodsId <=0) {
    
    
            return null;
        }
        int width = 80;
        int height = 32;
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);  //该图像具有整数像素的 8 位 RGB 颜色
        Graphics g = image.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);
        }
        // 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 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;
        }
    }

    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;
    }

http请求获取path。

	@RequestMapping(value="/getPath", 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);
		}
		boolean check = miaoshaService.checkVerifyCode(user, goodsId, verifyCode);
		if(!check) {
    
    
			return Result.error(CodeMsg.REQUEST_ILLEGAL);
		}
		String md5Str = MD5Util.md5(UUIDUtil.uuid() +"123456");
		redisService.set(MiaoshaKey.getMiaoshaPath,""+user.getId()+"_"+goodsId,md5Str);
		return Result.success(md5Str);
	}

秒杀接口

	@RequestMapping(value="/{pathParam}/do_miaosha", method=RequestMethod.POST)
	@ResponseBody
	public Result<Integer> miaosha(Model model,MiaoshaUser user,
								   @RequestParam("goodsId")Long goodsId,
								   @PathVariable("pathParam") String pathParam ) {
    
    
		if(user == null) {
    
    
			return Result.error(CodeMsg.SESSION_ERROR);
		}
		//验证path
		boolean check = miaoshaService.checkPath(user, goodsId, pathParam);
		if(!check){
    
    
			return Result.error(CodeMsg.REQUEST_ILLEGAL);
		}
		//验证正确进行秒杀
}

限流防刷

在这里插入图片描述
定义注解AccessLimit ,这样就可以在需要限流的接口上@AccessLimit(seconds = 3,maxCount = 5,needLogin = true)就可以对下面的http请求进行限制:3秒内改用户最多请求5次。

@Retention(RUNTIME)
@Target(METHOD)
public @interface AccessLimit {
    
    
	int seconds();
	int maxCount();
	boolean needLogin() default true;
}

定义ThreadLocal线程内保存用户信息。

public class UserContext {
    
    
	
	private static ThreadLocal<MiaoshaUser> userHolder = new ThreadLocal<MiaoshaUser>();
	
	public static void setUser(MiaoshaUser user) {
    
    
		userHolder.set(user);
	}
	
	public static MiaoshaUser getUser() {
    
    
		return userHolder.get();
	}

}

定义拦截器

@Service
public class AccessInterceptor  extends HandlerInterceptorAdapter {
    
    
	
	@Autowired
	MiaoshaUserService userService;
	
	@Autowired
	RedisService redisService;
	
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
    
    
		if(handler instanceof HandlerMethod) {
    
    
			//获取用户信息
			MiaoshaUser user = getUser(request, response);
			UserContext.setUser(user);
			//如果无AccessLimit注解,直接返回true
			HandlerMethod hm = (HandlerMethod)handler;
			AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);
			if(accessLimit == null) {
    
    
				return true;
			}
			//根据需求生成key
			int seconds = accessLimit.seconds();
			int maxCount = accessLimit.maxCount();
			boolean needLogin = accessLimit.needLogin();
			String key = request.getRequestURI();
			if(needLogin) {
    
    
				if(user == null) {
    
    
					render(response, CodeMsg.SESSION_ERROR);
					return false;
				}
				key += "_" + user.getId();
			}
			AccessKey ak = AccessKey.withExpire(seconds);
			//验证时间,累加
			Integer count = redisService.get(ak, key, Integer.class);
	    	if(count  == null) {
    
    
	    		 redisService.set(ak, key, 1);
	    	}else if(count < maxCount) {
    
    
	    		 redisService.incr(ak, key);
	    	}else {
    
    
	    		render(response, CodeMsg.ACCESS_LIMIT_REACHED);
	    		return false;
	    	}
		}
		return true;
	}
	
	private void render(HttpServletResponse response, CodeMsg cm)throws Exception {
    
    
		response.setContentType("application/json;charset=UTF-8");
		OutputStream out = response.getOutputStream();
		String str  = JSON.toJSONString(Result.error(cm));
		out.write(str.getBytes("UTF-8"));
		out.flush();
		out.close();
	}

	private MiaoshaUser getUser(HttpServletRequest request, HttpServletResponse response) {
    
    
		String paramToken = request.getParameter(MiaoshaUserService.COOKI_TOKEN_NAME);
		String cookieToken = getCookieValue(request, MiaoshaUserService.COOKI_TOKEN_NAME);
		if(StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)) {
    
    
			return null;
		}
		String token = StringUtils.isEmpty(paramToken)?cookieToken:paramToken;
		return userService.getByToken(response, token);
	}
	
	private String getCookieValue(HttpServletRequest request, String cookiName) {
    
    
		Cookie[]  cookies = request.getCookies();
		if(cookies == null || cookies.length <= 0){
    
    
			return null;
		}
		for(Cookie cookie : cookies) {
    
    
			if(cookie.getName().equals(cookiName)) {
    
    
				return cookie.getValue();
			}
		}
		return null;
	}
	
}

猜你喜欢

转载自blog.csdn.net/weixin_44532671/article/details/119965503