企业实战之Spring拦截器《统一参数校验》

在前面的一些文章中我们有讲到,通过拦截器我们可以做很多的事情,包括接口统一的 参数校验、 登录校验、权限校验等,也可以做一些HTTP响应体写入逻辑,比如我们另一篇文章所说的《解决跨域问题》,本篇我们也就是讲解下,使用拦截器对开放的接口做公共参数校验功能实现。

下面我以我们实际开发中所遇到的问题,来举例说明。

需求描述

在对外开放接口的时候,我们的调用端是很多的,比如:APP/PC/WECHAT公众号or小程序 等等,当线上环境某一个用户出现问题时,如果这个问题仅仅是后端还好,但是如果是前后端需要配合解决的错误,我们就需要更多的调用客户端的一些信息,这个时候你去问客户app什么版本、什么手机这显然是不妥的,所以我们应该收集更多的调用信息,以便我们做后续业务处理、日志记录等等的一些操作,通常需要对客户端统一收集的信息比如 调用来源、app版本号、api的版本号、安全验证信息 等等。

解决方式

我们将这些信息放入头信息(HTTP HEAD中),下面给出在参数命名的例子:

  • X-Token 用户的登录token(用于兑换用户登录信息)
  • Api-Version api的版本号
  • App-Version app版本号
  • Call-Source 调用来源(IOS、ANDROID、PC、WECHAT、WEB)
  • Authorization 安全校验参数(后面会有文章详细介绍该如何做安全校验)

然后我们将该些信息在拦截器中做统一的参数校验,下面我们给出响应的代码,给大家一个写法上的参考O(∩_∩)O~

我们为这些请求头参数变量定义常量类:

package com.zhuma.demo.constant;

import com.zhuma.demo.enums.CallSource;


/**
 * @desc Header的key罗列
 * 
 * @author zhumaer
 * @since 8/31/2017 3:00 PM
 */
public class HeaderConstants {

	/**
	 * 用户的登录token
	 */
	public static final String X_TOKEN = "X-Token";

	/**
	 * api的版本号
	 */
	public static final String API_VERSION = "Api-Version";

	/**
	 * app版本号
	 */
	public static final String APP_VERSION = "App-Version";

	/**
	 * 调用来源 {@link CallSource}
	 */
	public static final String CALL_SOURCE = "Call-Source";

}

对于调用的来源我们可以定义一个枚举类:

package com.zhuma.demo.enums;

/**
 * @desc 调用来源枚举类
 * 
 * @author zhumaer
 * @since 8/31/2017 3:00 PM
 */
public enum CallSource {
	WEB,
	PC,
	WECHAT,
	IOS,
	ANDROID;

	public static boolean isValid(String name) {
		for (CallSource callSource : CallSource.values()) {
			if (callSource.name().equals(name)) {
				return true;
			}
		}
		return false;
	}

}

拦截器类:

package com.zhuma.demo.interceptor;

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

import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import com.zhuma.demo.constant.HeaderConstants;
import com.zhuma.demo.enums.CallSource;
import com.zhuma.demo.enums.ResultCode;
import com.zhuma.demo.exception.BusinessException;
import com.zhuma.demo.util.StringUtil;

/**
 * @desc HEADER头参数校验
 * 
 * @author zhumaer
 * @since 8/29/2017 3:00 PM
 */
@Component
public class HeaderParamsCheckInterceptor implements HandlerInterceptor {

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		if (handler instanceof HandlerMethod) {
			String callSource = request.getHeader(HeaderConstants.CALL_SOURCE);
			String apiVersion = request.getHeader(HeaderConstants.API_VERSION);
			String appVersion = request.getHeader(HeaderConstants.APP_VERSION);

			if (StringUtil.isAnyBlank(callSource, apiVersion)) {
				throw new BusinessException(ResultCode.PARAM_NOT_COMPLETE);
			}

			try {
				Double.valueOf(apiVersion);
			} catch (NumberFormatException e) {
				throw new BusinessException(ResultCode.PARAM_IS_INVALID);
			}

			if ((CallSource.ANDROID.name().equals(callSource) || CallSource.IOS.name().equals(callSource)) && StringUtil.isEmpty(appVersion)) {
				throw new BusinessException(ResultCode.PARAM_NOT_COMPLETE);
			}

			if (!CallSource.isValid(callSource)) {
				throw new BusinessException(ResultCode.PARAM_IS_INVALID);
			}

		}

		return true;
	}

	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
		// nothing to do
	}

	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
		// nothing to do
	}

}

说明
上述校验逻辑其实很简单,如果校验不通过直接以一个异常抛出,交由统一的异常处理器去处理后续事情,对于app版本信息可能只有android、ios才会有所以我们根据这种特殊情况做了下判断。

拦截器配置类(以spring boot为例):

package com.zhuma.demo.config.web;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import com.zhuma.demo.interceptor.HeaderParamsCheckInterceptor;
import com.zhuma.demo.interceptor.LoginedAuthInterceptor;

@Configuration
public class InterceptorConfig extends WebMvcConfigurerAdapter {

	@Autowired
	private LoginedAuthInterceptor loginedAuthInterceptor;

	@Autowired
	private HeaderParamsCheckInterceptor headerParamsCheckInterceptor;

	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		String apiUri = "/**";
		//请求头参数校验
		registry.addInterceptor(headerParamsCheckInterceptor).addPathPatterns(apiUri);
		//登录拦截(这个上一篇文章讲过了,这里说明下两个拦截器前后调用顺序就是本配置类代码的配置顺序)
		registry.addInterceptor(loginedAuthInterceptor).addPathPatterns(apiUri);
	}

}

最后

读完文章你会发现,文章跟前两遍文章对拦截器的使用上基本上没有什么创新,只是在使用例子也就是业务的逻辑上代码的变化,其实是的,拦截器使用起来还是蛮简单的,但是该在什么地方去使用它,这个还是要过考虑下的,所以在这篇文章中我们主要是以实际开发中所用到的问题做举例,这可能引出你对拦截器更好使用的一些想法,希望你在以后的使用中能够更好的利用拦截器的特性,写出更美观、更好维护的代码O(∩_∩)O~

源码github地址:https://github.com/zhumaer/zhuma
QQ群号:629446754(欢迎加群)

欢迎关注我们的公众号或加群,等你哦!

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值