目录
1、注册参数解析器和拦截器实现
package com.miaosha.config;
import com.miaosha.access.AccessInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import java.util.List;
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
@Autowired
private UserArgumentResolve resolve;
@Autowired
AccessInterceptor accessInterceptor;
/**
* 注册拦截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(accessInterceptor);
}
/**
* 注册自定义参数解析器
* @param argumentResolvers
*/
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(resolve);
}
}
2、参数解析器实现
package com.miaosha.config;
import com.miaosha.model.entity.MiaoshaUser;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
@Service
public class UserArgumentResolve implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
Class<?> clazz = methodParameter.getParameterType();
return clazz == MiaoshaUser.class;
}
@Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
return UserContext.getUser();
}
}
3、通过ThreadLocal保存用户信息(如果创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题)
package com.miaosha.config;
import com.miaosha.model.entity.MiaoshaUser;
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();
}
}
4、拦截器实现
package com.miaosha.access;
import com.alibaba.fastjson.JSON;
import com.miaosha.common.ConstantPool;
import com.miaosha.config.UserContext;
import com.miaosha.model.entity.MiaoshaUser;
import com.miaosha.result.CodeMsg;
import com.miaosha.result.Result;
import com.miaosha.service.MiaoshaService;
import com.miaosha.tool.redis.AccessKey;
import com.miaosha.tool.redis.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.IOException;
import java.io.OutputStream;
@Service
public class AccessInterceptor extends HandlerInterceptorAdapter {
@Autowired
private MiaoshaService miaoshaService;
@Autowired
RedisService redisService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
MiaoshaUser miaoshaoUser = this.getUser(request, response);
UserContext.setUser(miaoshaoUser);
HandlerMethod handlerMethod = (HandlerMethod) handler;
//通过注解标记要登录的接口
AccessLimit accessLimit = handlerMethod.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 (miaoshaoUser == null) {
render(response, CodeMsg.SESSION_ERROR);
return false;
}
key += "_" + miaoshaoUser.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;
}
/**
* 通过数据流方式将结果写入客户端
* @param response
* @param sessionError
* @throws IOException
*/
private void render(HttpServletResponse response, CodeMsg sessionError) throws IOException {
response.setContentType("application/json;charset=UTF-8");
OutputStream outputStream = response.getOutputStream();
String msg = JSON.toJSONString(Result.error(sessionError));
outputStream.write(msg.getBytes("UTF-8"));
outputStream.flush();
outputStream.close();
}
/**
* 获取用户token信息
* @param request
* @param response
* @return
* @throws IOException
*/
private MiaoshaUser getUser(HttpServletRequest request, HttpServletResponse response) throws IOException {
String paramToken = request.getParameter(ConstantPool.TOKEN);
String paramCookie = this.getCookieVale(request, ConstantPool.TOKEN);
if (StringUtils.isEmpty(paramToken) || StringUtils.isEmpty(paramCookie)) {
return null;
}
String token = StringUtils.isEmpty(paramToken) ? paramCookie : paramToken;
return miaoshaService.getByToken(response, token);
}
/**
* 获取用户cookie信息
* @param request
* @param cookieName
* @return
*/
private String getCookieVale(HttpServletRequest request, String cookieName) {
Cookie[] cookies = request.getCookies();
if (cookies == null || cookies.length == 0) {
return null;
}
for (Cookie cookie : cookies) {
if (cookie.getName().equals(cookieName)) {
return cookie.getValue();
}
}
return null;
}
}
5、注解实现
package com.miaosha.access;
import org.omg.SendingContext.RunTime;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AccessLimit {
//访问时长
int seconds();
//最多可在访问时长内访问多少次
int maxCount();
//接口是否需要登录才能访问
boolean needLogin() default true;
}
6、接口中使用注解实现方式如下
package com.miaosha.contorller;
import com.miaosha.access.AccessLimit;
import com.miaosha.model.entity.MiaoshaUser;
import com.miaosha.result.Result;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/miaosha")
public class MiaoshaController {
@AccessLimit(seconds = 100, maxCount = 10, needLogin = true)
@RequestMapping(value = "/doMiaosha", method = RequestMethod.POST)
@ResponseBody
public Result doMiaosha(MiaoshaUser miaoshaUser, @RequestParam("goodsId") String goodsId) {
return Result.success(0);
}
}