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;
	}

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Springboot中,我们可以通过配置拦截器实现对请求的拦截和放行。通常情况下,我们会在拦截器中编写一些逻辑来判断是否需要放行请求。 在你提供的引用中,可以看到配置拦截器用来拦截不经过登录页面就放行的页面的需求。为了实现这个需求,你可以通过在拦截器中编写逻辑,判断当前请求是否是登录页面所需的静态资源,如果是的话,就允许请求继续往下执行。 关于拦截器放行的具体实现,一种常见的做法是在拦截器的preHandle()方法中进行判断。在这个方法中,我们可以通过检查请求的URL或者其他一些条件来决定是否放行请求。如果判断为需要拦截且未登录,可以通过request.getRequestDispatcher("/")将请求重定向到登录页,并设置相应的提示信息,然后返回false表示拦截请求。否则,返回true表示放行请求。 以上是一种处理拦截器放行的方式,你可以根据具体情况进行适当的修改和调整。同时,你还可以参考引用中提供的Springboot引入拦截器并放行Swagger的代码示例,来更好地理解和实践拦截器放行的过程。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [springboot\springMVC配置拦截器以及放行资源](https://blog.csdn.net/weixin_44200024/article/details/121289570)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [Springboot引入拦截器并放行swagger代码实例](https://download.csdn.net/download/weixin_38675506/12745293)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小安灬

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值