目录
SpringMvc优雅接收数据
一个这样的场景,前端用户进行登录,这时候后台随机生成一个UUID作为token写入前端的Cookie,而后端则放入Redis中存储,用户之后的访问带上token,如何好的去对这个token进行解析呢,可以考虑使用HandlerMethodArgumentResolver
接口
通过看该接口的继承图可知,@PathVariable,@RequestParam也是实现了该接口
所以,接下来可以实现该接口
@Service
public class UserArgumentResolver implements HandlerMethodArgumentResolver {
@Autowired
UserService userService;
// 仅对User这个类 有效
public boolean supportsParameter(MethodParameter parameter) {
Class<?> clazz = parameter.getParameterType();
return clazz==User.class;
}
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
String paramToken = request.getParameter(UserService.COOKI_NAME_TOKEN);
String cookieToken = getCookieValue(request, UserService.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;
}
}
public MiaoshaUser getByToken(HttpServletResponse response, String token) {
if(StringUtils.isEmpty(token)) {
return null;
}
User user = redisService.get(UserKey.token, token, User.class);
//延长有效期
if(user != null) {
addCookie(response, token, user);
}
return user;
}
private void addCookie(HttpServletResponse response, String token, User user) {
redisService.set(UserKey.token, token, user);
Cookie cookie = new Cookie(COOKI_NAME_TOKEN, token);
cookie.setMaxAge(UserKey.token.expireSeconds());
cookie.setPath("/");
response.addCookie(cookie);
}
之后就是注入SpringMvc的config
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter{
@Autowired
UserArgumentResolver userArgumentResolver;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(userArgumentResolver);
}
}
使用
@RequestMapping("/info")
@ResponseBody
public Result<MiaoshaUser> info(Model model,MiaoshaUser user) {
return Result.success(user);
}
结果:
{"code":0,"msg":null,"data":{"id":18670138519,"nickname":"张三","password":"62833290495de70f541422fbededf769","salt":"123456","head":"","registerDate":1570697329000,"lastLoginDate":null,"loginCount":1}}
扩展
当然,除了HandlerMethodArgumentResolver
这个接口,抽象类HandlerInterceptorAdapter
同时也能做许多功能,比如:限制访问某个接口过于频繁记录用于是否异地登录,这里就拿限制用户访问某个接口频繁来演示
大概思路:
每当用户进行访问时,就将用户与访问数记录下来,放入Redis中,当发现Redis中访问的次数超过某个阙值,就抛出异常
@Retention(RUNTIME)
@Target(METHOD)
public @interface AccessLimit {
// 访问时间
int seconds();
// 最大次数
int maxCount();
// 是否要登录
boolean needLogin() default true;
}
@Service
public class AccessInterceptor extends HandlerInterceptorAdapter{
@Autowired
UserService userService;
@Autowired
RedisService redisService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// 处理器方法的处理
if(handler instanceof HandlerMethod) {
User user = getUser(request, response);
// 这里利用ThreadLocal进行绑定
UserContext.setUser(user);
HandlerMethod hm = (HandlerMethod)handler;
// 得到方法上的AccessLimit注解
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 User getUser(HttpServletRequest request, HttpServletResponse response) {
String paramToken = request.getParameter(UserService.COOKI_NAME_TOKEN);
String cookieToken = getCookieValue(request, UserService.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;
}
}
public class UserContext {
private static ThreadLocal<User> userHolder = new ThreadLocal<User>();
public static void setUser(User user) {
userHolder.set(user);
}
public static MiaoshaUser getUser() {
return userHolder.get();
}
}
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter{
@Autowired
UserArgumentResolver userArgumentResolver;
@Autowired
AccessInterceptor accessInterceptor;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(userArgumentResolver);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(accessInterceptor);
}
}