在前面的一些文章中我们有讲到,通过拦截器我们可以做很多的事情,包括接口统一的 参数校验、 登录校验、权限校验等,也可以做一些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(欢迎加群)
欢迎关注我们的公众号或加群,等你哦!