Seckill System - Hidden address/picture verification code/redis implements interface current limiting and anti-swiping

Hidden address + graphic verification code

Purpose

This is mainly to prevent the client from obtaining the interface address in advance, and directly calling the seckill interface when the seckill starts.

train of thought

The method of this experiment: /getPath to get the seckill link, and then access the new link to perform the seckill. In getPath, it is necessary to verify whether the result of the graphic verification code is correct.
Disadvantages of this experiment: Because the real seckill address is /miaosha+{data obtained by/getPath}, the real seckill address can still be obtained through the get request.

A better way: call the /getPath interface to return the url of a page, and let the browser jump to this new url page, and the new page is the real second kill page.

accomplish

Get graphic verification code image request

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

The image is generated, and the image operation result is stored in 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 request to get 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);
	}

Spike interface

	@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);
		}
		//验证正确进行秒杀
}

Current limiting anti-brush

insert image description here
Define the annotation AccessLimit, so that @AccessLimit(seconds = 3, maxCount = 5, needLogin = true) can limit the following http requests on the interface that needs current limiting: change the user to request up to 5 times within 3 seconds.

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

Define the ThreadLocal thread to save user information.

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

}

define interceptor

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

Guess you like

Origin blog.csdn.net/weixin_44532671/article/details/119965503