springboot拦截器配置案例

解决问题

  1. 如何编写并配置一个或多个拦截器
  2. 如何对返回前端数据做转换
  3. 如何做权限拦截

1、全局拦截器

import com.tryonce.config.auth.UserInfoHolder;
import com.tryonce.config.exception.AuthException;
import com.tryonce.dto.WebUserBase;
import com.tryonce.type.OsType;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Slf4j
@Component
public class GlobalInterceptor implements HandlerInterceptor {

    /**
     * 业务处理前执行(controller代码执行前)
     * 1、ThreadLocal赋值:请求时间戳、获取Header信息等
     * 2、打印请求参数信息
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws AuthException {
        // 1、设置请求开始时间
        log.info("[set-ThreadLocal] GlobalThreadLocal.reqStartTime:{}", Thread.currentThread());
        GlobalThreadLocal.reqStartTime.set(System.currentTimeMillis());
        // 2、打印请求信息
        log.info("Interceptor.preHandle:{}", Thread.currentThread());
        log.info("Request URL            : {}", request.getRequestURL().toString());
        log.info("HTTP Method            : {}", request.getMethod());
        log.info("Request IP             : {}", request.getRemoteAddr());
        // 3、从Header获取数据并存到ThreadLocal
        WebUserBase webUserBase = WebUserBase.builder()
                .os(OsType.android.getType())
                .product(request.getHeader("product"))
                .androidId(request.getHeader("androidId"))
                .deviceId(request.getHeader("deviceId"))
                .oaid(request.getHeader("oaid"))
                .appFlag(null == request.getHeader("appFlag") ? null : Integer.valueOf(request.getHeader("appFlag")))
                .build();
        UserInfoHolder.setWebUserBase(webUserBase);
        log.info("WebUserBase             : {}", UserInfoHolder.getWebuserBase());
        return true;
    }

    /**
     * 业务处理后,返回浏览器之前
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {

    }

    /**
     * 返回浏览器时执行
     * 1、清除ThreadLocal数据
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 1、移除ThreadLocal - 请求时间
        log.info("[remove-ThreadLocal] GlobalThreadLocal.reqStartTime:{}", Thread.currentThread());
        GlobalThreadLocal.reqStartTime.remove();
        // 2、移除ThreadLocal - 用户信息
        log.info("[remove-ThreadLocal] UserInfoHolder {} . ", Thread.currentThread());
        UserInfoHolder.remove();
    }

}

2、登录拦截器

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.alibaba.fastjson.JSON;
import com.tryonce.config.auth.UserInfo;
import com.tryonce.config.auth.UserInfoHolder;
import com.tryonce.config.exception.AuthException;
import com.tryonce.service.AuthService;
import com.tryonce.tools.IP;
import com.tryonce.vo.ResponseEnum;
import com.tryonce.vo.ResponseVo;
import com.tryonce.vo.auth.AuthLoginDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Slf4j
@Component
public class LoginAuthInterceptor implements HandlerInterceptor {
    @Autowired
    private AuthService authService;

    /**
     * 业务处理前执行(controller代码执行前)
     * 1、权限校验
     * 2、刷新token缓存
     * 3、记录日志
     * ......
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws AuthException {
        // 1、权限校验
        String token = request.getHeader("Authorization");
        AuthLoginDto authLoginDto = authService.validLoginUser(token);
        if (null == authLoginDto) {
            log.info("不存在该用户或用户登录已过期,请重新登录!token : {},url : {}", token, request.getRequestURI());
            ServletUtil.write(response, JSON.toJSONString(ResponseVo.buildResponse(ResponseEnum.AUTH.getCode(), "不存在该用户或用户登录已过期,请重新登录!", null)), MediaType.APPLICATION_JSON_UTF8_VALUE);
            return false;
        } else {
            // 2、设置用户信息
            UserInfoHolder.set(new UserInfo(authLoginDto, IP.get(request), UserInfoHolder.getWebuserBase()));
        }
        // 3、若 sa-token 未登录,则进行登录
        if (!StpUtil.isLogin()) {
            StpUtil.login(UserInfoHolder.getUserCode());
        }
        log.info("[set-ThreadLocal] UserInfoHolder:{} - ", Thread.currentThread(), UserInfoHolder.get());
        return true;
    }

    /**
     * 业务处理后,返回浏览器之前
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {

    }

    /**
     * 返回浏览器时执行(全局拦截器已清除,故此处不做处理)
     * 1、清除threadLocal
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }

}

3、其他拦截器

4、将拦截器加入拦截

import cn.dev33.satoken.interceptor.SaAnnotationInterceptor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.List;

@Slf4j
@Configuration
public class InterceptorConfig extends WebMvcConfigurationSupport {
	@Autowired
	private LoginAuthInterceptor loginAuthInterceptor;
	@Autowired
	private GlobalInterceptor globalInterceptor;

	/**
	 * 无需权限拦截的请求
	 */
	private static List<String> EXCLUDE_PATH_AUTH = Arrays.asList(
			// 登录、退出
			"/auth/login", "/auth/logout",
			// swagger2
			"/doc.html", "/v2/api-docs", "/v2/api-docs-ext", "/swagger-resources", "/swagger-ui.html", "/configuration/ui", "/configuration/security",
			// 静态资源
			"/*.css", "/*.js", "/*.png", "/*.jpg", "/*.jpeg", "/webjars/**", "/favicon.ico", "/error", "/static/**"
	);

	@Override
	public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
		configurer.enable();
	}

	/**
	 * 添加拦截器
	 */
	@Override
	protected void addInterceptors(InterceptorRegistry registry) {
		log.info("********************* 加入拦截器 LoginAuthInterceptor ******************");
		// 注册全局拦截器
		registry.addInterceptor(globalInterceptor).addPathPatterns("/**");
		// 注册权限拦截器
		registry.addInterceptor(loginAuthInterceptor).addPathPatterns("/**").excludePathPatterns(EXCLUDE_PATH_AUTH);
		// 注册注解拦截器,并排除不需要注解鉴权的接口地址 (与登录拦截器无关) - Sa-Token
		registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns("/**").excludePathPatterns(EXCLUDE_PATH_AUTH);
		super.addInterceptors(registry);
	}

	@Override
	protected void configurePathMatch(PathMatchConfigurer configurer) {
		// 如果为true 会识别 xx.* 后缀的内容
		configurer.setUseSuffixPatternMatch(false);
		super.configurePathMatch(configurer);
	}

	/**
	 * 数据转换
	 * 1、解决Long类型返回前端精度丢失
	 * 2、统一对日期做日期格式化
	 */
	@Override
	public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
		MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
		ObjectMapper objectMapper = new ObjectMapper();
		// 时间格式化
		objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
		objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
		// Long转json精度丢失的配置
		SimpleModule simpleModule = new SimpleModule();
		simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
		simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
		objectMapper.registerModule(simpleModule);
		jackson2HttpMessageConverter.setObjectMapper(objectMapper);
		converters.add(0, jackson2HttpMessageConverter);
	}

}

5、ResponseBodyAdvice

import com.alibaba.fastjson.JSON;
import com.tryonce.vo.ResponseVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

@ControllerAdvice
@Slf4j
public class ResponseControllerAdvice implements ResponseBodyAdvice<ResponseVo> {

	@Override
	public boolean supports(MethodParameter returnType, Class converterType) {
		if (returnType.getParameterType() == ResponseVo.class) {
			return true;
		}
		return false;
	}

	/**
	 * 返回前端前记录响应信息及耗时
	 */
	@Override
	public ResponseVo beforeBodyWrite(ResponseVo body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
		Long startTime = GlobalThreadLocal.reqStartTime.get();
		String res = JSON.toJSONString(body);
		log.info("[END] uri:[{}] , costTime:[{}] , return:[{}]", request.getURI().toString(), (startTime == null ? 0 : System.currentTimeMillis() - startTime), res.length() > 1000 ? res.substring(0, 1000) + "......" : res);
		GlobalThreadLocal.reqStartTime.remove();
		return body;
	}

}

你可能感兴趣的:(java)