秒杀接口地址隐藏

思路:秒杀开始之前,先去请求接口获取秒杀地址

1.接口改造,带上 PathVariable参数

2.添加接口生成地址的接口

3.秒杀手动请求,先验证PathVariable

随机生成一个字符串,作为地址加在url上,然后生成的时候

存入 redis缓存中,根据前端请求的url获取path。

判断与缓存中的字符串是否一致,一致就认为对的。就正常

藐视,否则失败。

先请求下获取path。之后拼接成秒杀地址

@RequestMapping(value = "/path",method = RequestMethod.GET)

@ResponseBody

public Result getMiaoshaPath(Model model, MiaoshaUser user, @RequestParam("goodsId") long goodsId) {

//没登录就跳转到登录页面

model.addAttribute("user", user);

if (user == null) {

return Result.error(CodeMsg.SESSION_ERROR);

}

String path = miaoshaService.createMiaoshaPath(user,goodsId);

return Result.success(path);

}

存入和取出 redis

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

}

public String createMiaoshaPath(MiaoshaUser user,long goodsId) {

String str = MD5Util.md5(UUIDUtil.uuid() + "123456");

//利用用户id,商品id拼接为key同时也是不同的路径

redisService.set(MiaoshaKey.getMiaoshaPath, "" + user.getId() + "_" + goodsId, str);

return str;

}

控制器中验证

//验证path

boolean over = miaoshaService.checkPath(user,goodsId,path);

if (!over){

return Result.error(CodeMsg.REQUEST_ILLEGAL);

}

数据公式验证码添加

思路:点击秒杀之前,先输入验证码,分散用户的请求

1.添加生成验证码的接口

2.在获取秒杀路径的时候,验证码证码

3.ScriprEngine使用

前端增加获取验证码显示验证码输入验证码上传。

增加返回验证码的接口

在每次秒杀的时候,要先判断这个验证码是否正确

生成数字验证码并存入redis中,判断也是从redis中取出来判断

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;

}

public boolean checkVerifyCode(MiaoshaUser user, long goodsId, int verifyCode) {

if(user == null || goodsId <=0) {

return false;

}

Integer codeOld = redisService.get(MiaoshaKey.getMiaoshaVerifyCode, user.getId()+","+goodsId, Integer.class);

if(codeOld == null || codeOld - verifyCode != 0 ) {

return false;

}

redisService.delete(MiaoshaKey.getMiaoshaVerifyCode, user.getId()+","+goodsId);

return true;

}

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;

}

接口限流防刷

思路:对接口做限流

可以把用户访问这个url的次数存入 redis中

做次数限制

key是 前缀+url路径+用户id

使用拦截器,拦截器中判断次数

实现只写一个注解,就可以对这个url判断

多少秒,多少次数,是否需要登录

如何实现:

先定义注解

import static java.lang.annotation.ElementType.METHOD;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;

import java.lang.annotation.Target;

@Retention(RUNTIME)

@Target(METHOD)

public @interface AccessLimit {

int seconds();

int maxCount();

boolean needLogin() default true;//默认需要定义

}

接下来定义拦截器

import com.alibaba.fastjson.JSON;

import com.example.demo.domain.MiaoshaUser;

import com.example.demo.redis.AccessKey;

import com.example.demo.result.CodeMsg;

import com.example.demo.result.Result;

import com.example.demo.service.MiaoshaUserService;

import com.example.demo.service.RedisService;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;

import org.springframework.util.StringUtils;

import org.springframework.web.method.HandlerMethod;

import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.Cookie;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import java.io.OutputStream;

/**

* Created by Administrator on 2018/5/8.

*/

@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);//将用户保存到 ThreadLocal中

HandlerMethod hm = (HandlerMethod)handler;

//获取方法注解

AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);

//如果没有不做任何限制

if(accessLimit == null) {

return true;

}

//获取注解定义的几个参数

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

}else {

//do nothing

}

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_NAME_TOKEN);

String cookieToken = getCookieValue(request, MiaoshaUserService.COOKI_NAME_TOKEN);

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;

}

}

定义完拦截器要注册

@Configuration

public class WebConfig extends WebMvcConfigurerAdapter{

@Autowired

UserArgumentResolver userArgumentResolver;

@Autowired

AccessInterceptor accessInterceptor;

@Override

public void addArgumentResolvers(List argumentResolvers) {

argumentResolvers.add(userArgumentResolver);

}

@Override

public void addInterceptors(InterceptorRegistry registry) {

registry.addInterceptor(accessInterceptor);

}

}