拦截器HandlerInterceptor+方法参数解析器HandlerMethodArgumentResolver用于统一获取当前登录用户信息


前言

需求:很多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/");
}

三、总结

成为一个小胖子,没事摸摸小肚子~

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值