文章目录
前言
需求:很多Controller方法,刚进来要先获取当前登录用户的信息,以便做后续的用户相关操作。
准备工作:前端每次请求都传token,后端封装一方法redisUtil.get(token),根据token解析得到currentUser。
这是一个常见的业务需求,为实现这个需求有以下三种解决方案:
一、最原始直接的方式:即在每个Controller开始,先调用redisUtil.get(token),获取用户的信息,不够优雅。
二、AOP:AOP可以解决很多切面类问题,思路同Spring AOP来自定义注解实现审计或日志记录(完整代码),将currentUser放到request里;比起拦截器稍重。
三、拦截器+方法参数解析器:使用mvc拦截器HandlerInterceptor+方法参数解析器HandlerMethodArgumentResolver最合适。
本篇博客讲解第三种的具体实现步骤~
一、拦截器+方法参数解析器 是什么?
SpringMVC提供了mvc拦截器HandlerInterceptor,包含以下3个方法:
①:preHandle
②:postHandle
③:afterCompletion
HandlerInterceptor经常被用来解决拦截事件,如用户鉴权等。
另外,Spring也向我们提供了多种解析器Resolver,如用来统一处理异常HandlerExceptionResolver,以及今天的主角HandlerMethodArgumentResolver。HandlerMethodArgumentResolver是用来处理方法参数的解析器,包含以下2个方法:
①:supportsParameter(满足某种要求,返回true,方可进入resolveArgument做参数处理)
②:resolveArgument
二、具体实现步骤
1.自定义权限拦截器LoginInterceptor拦截所有request请求,并将token解析为currentUser,最终放到request中。
自定义权限拦截器LoginInterceptor,需实现HandlerInterceptor。在preHandle中调用redisUtil.get(token),获取到当前用户,最后塞进request中,如下:
public class LoginInterceptor implements HandlerInterceptor {
//@Autowired
//private ShoConfigService shoConfigService;
@Autowired
private RedisUtil redisUtil;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
System.out.println(request.getRequestURL());
String tokenHeader = request.getHeader("token");
if (StringUtils.isBlank(tokenHeader)) {
throw new AppException("openid为空", ResultCode.PERMISSION_NO_ACCESS.getCode());
}
CurrentUser currentUser = (CurrentUser) redisUtil.get(tokenHeader);
if (currentUser == null) {
throw new AppException("openid为空", ResultCode.PERMISSION_NO_ACCESS.getCode());
}
request.getSession().setAttribute("currentUser", currentUser);
// 调用接口的时候查询系统维护的状态
/*JsonResult result = shoConfigService.selStatue();
if (result.getData().equals(0)) {
throw new AppException("系统维护中", ResultCode.SYSTEM_MAINTENANCE.getCode());
}*/
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
2.自定义参数注解@User,添加至controller的方法参数currentUser之上。
自定义方法参数上使用的注解@User,代表被它注解过的参数的值都需要由方法参数解析器CurrentUserHandlerMethodArgReslover来“注入”,如下:
package com.boduo.config.aop;
import java.lang.annotation.*;
/**
* @Description:@User自定义注解
* @Author nanyi
* @Date 2021/4/16 20:03
**/
@Target({ElementType.PARAMETER})//Annotation所修饰的对象范围:方法参数
@Retention(RetentionPolicy.RUNTIME)//Annotation被保留时间:运行时保留(有效)
@Documented//标记注解:java工具文档化
public @interface User {
}
给各Controller类中方法的参数添加此注解,案例如下:
/**
* @Description:对应品牌的通知
* @Author nanyi
* @Date 2021/4/16 10:28
**/
@RestController
@RequestMapping("/api/shop/notify")
@Api(tags = "首页")
@ApiSort(1)
public class NotifyController {
@Autowired
NotifyService notifyService;
/**
* @Description:未查看通知列表
*/
@GetMapping("/list")
@ApiOperation("通知列表")
@ApiOperationSupport(order = 11)
public JsonResult<NotifyDTO> noticesAll(@ApiIgnore @User CurrentUser currentUser) {
if (currentUser.getIsVisitory()) {
// 如果是游客,根据Openid和品牌id进行查询
return notifyService.selNotifyByBrandId(currentUser.getUsername(), currentUser.getBrand().getBrandId());
}
// 如果是VIP,根据AI系统里面的用户编号和品牌id进行查询
return notifyService.selNotifyByBrandId(currentUser.getUsername(), currentUser.getBrand().getBrandId());
}
}
3.自定义方法参数解析器CurrentUserHandlerMethodArgReslover,取出request中的currentUser,并赋值给添加了@User注解的参数currentUser。
自定义方法参数解析器CurrentUserHandlerMethodArgReslover,需实现HandlerMethodArgumentResolver。
package com.boduo.config.aop;
import com.boduo.entity.CurrentUser;
import org.springframework.core.MethodParameter;
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;
import javax.servlet.http.HttpServletRequest;
/**
* @Description:@User 注解解析器
* @Author nanyi
* @Date 2021/4/16 10:28
**/
public class CurrentUserHandlerMethodArgReslover implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
//如果该参数注解有@User且参数类型是User
return methodParameter.getParameterAnnotation(User.class) != null;
}
@Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
//取得HttpServletRequest
HttpServletRequest request= (HttpServletRequest) nativeWebRequest.getNativeRequest();
//取出session中的User
return (CurrentUser)request.getSession().getAttribute("currentUser");
}
}
拦截器定义好以后,在SpringMVC项目中,需要去SpringMVC的配置文件springmvc.xml添加该拦截器;但是在SpringBoot中,省去了很多配置文件,取而代之的是被注解@Configuration标识的配置类,SpringMVC配置文件对应的配置类需继承WebMvcConfigurer。同理,解析器定义好以后,也需被添加到SpringMVC的配置文件或配置类中。最后,额外的一步,配置mvc。
4.配置MVC
package com.boduo.config.aop;
import com.boduo.config.properties.MyProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
/**
* @Description:@User 注解解析器
* @Author nanyi
* @Date 2021/4/16 10:40
**/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Autowired
MyProperties myProperties;
@Bean
public LoginInterceptor LoginInterceptor() {
return new LoginInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
/**
*此处拦截路径(/**)
* 注意两个**。一个*号只拦截一级路径下,两个*号拦截所有
* 将LoginInterceptor()给添加进来
*/
registry.addInterceptor(LoginInterceptor()).addPathPatterns("/**")
.excludePathPatterns(myProperties.getIgnoreUrls());
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
//注册@User注解的实现类
argumentResolvers.add(new CurrentUserHandlerMethodArgReslover());
}
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//将templates目录下的CSS、JS文件映射为静态资源,防止Spring把这些资源识别成thymeleaf模版
registry.addResourceHandler("/static/**.js").addResourceLocations("classpath:/static/");
}
三、总结
成为一个小胖子,没事摸摸小肚子~