在前面的一些文章中我们有讲到,通过拦截器我们可以做很多的事情,包括接口统一的 参数校验、 登录校验、权限校验等,也可以做一些HTTP响应体写入逻辑,本篇我们也就是讲解下,使用拦截器对开放的接口做公共参数校验功能实现。
下面我以我们实际开发中所遇到的问题,来举例说明。
需求描述
在对外开放接口的时候,我们的调用端是很多的,比如:APP/PC/公众号H5/小程序 等等,当线上环境某一个用户出现问题时,如果这个问题仅仅是后端还好,但是如果是前后端需要配合解决的错误,我们就需要更多的调用客户端的一些信息,这个时候你去问客户 APP 什么版本、什么手机这显然是不妥的,所以我们应该收集更多的调用信息,以便我们做后续业务处理、日志记录等等的一些操作,通常需要对客户端统一收集的信息比如 调用来源、APP版本号、API的版本号、安全验证信息 等等。
解决方式
我们将这些信息放入头信息(HTTP HEAD中),下面给出在参数命名的例子:
X-Token
用户的登录token(用于获取用户登录信息)
Api-Version
api的版本号
App-Version
app版本号
Call-Source
调用来源(IOS、ANDROID、PC、WECHAT、WEB)
Authorization
安全校验参数
然后我们将该些信息在拦截器中做统一的参数校验,下面我们给出响应的代码,给大家一个写法上的参考。
我们为这些请求头参数变量定义常量类。
定义请求头参数常量类
package cn.notemi.demo.constant;
import cn.notemi.demo.enums.ApiStyleEnum;
import cn.notemi.demo.enums.CallSourceEnum;
/**
* @desc Header的key罗列
*/
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 CallSourceEnum}
*/
public static final String CALL_SOURCE = "Call-Source";
/**
* API的返回格式 {@link ApiStyleEnum}
*/
public static final String API_STYLE = "Api-Style";
}
对于调用的来源我们可以定义一个枚举类:
package cn.notemi.demo.enums;
/**
* @desc 调用来源枚举类
*/
public enum CallSourceEnum {
/**
* WEB网站
*/
WEB,
/**
* PC客户端
*/
PC,
/**
* 微信公众号
*/
WECHAT,
/**
* IOS平台
**/
IOS,
/**
* 安卓平台
*/
ANDROID;
public static boolean isValid(String name) {
for (CallSourceEnum callSource : CallSourceEnum.values()) {
if (callSource.name().equals(name)) {
return true;
}
}
return false;
}
}
定义拦截器
package cn.notemi.demo.intercepter;
import cn.notemi.demo.constant.HeaderConstants;
import cn.notemi.demo.enums.CallSourceEnum;
import cn.notemi.demo.enums.ResultCode;
import cn.notemi.demo.exceptions.BusinessException;
import cn.notemi.demo.util.StringUtil;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @desc 统一参数校验:HEADER头参数校验
*/
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 ((CallSourceEnum.ANDROID.name().equals(callSource) || CallSourceEnum.IOS.name().equals(callSource)) && StringUtil.isEmpty(appVersion)) {
throw new BusinessException(ResultCode.PARAM_NOT_COMPLETE);
}
if (!CallSourceEnum.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 才会有所以我们根据这种特殊情况做了下判断。
拦截器配置类
package cn.notemi.demo.config;
import cn.notemi.demo.intercepter.HeaderParamsCheckInterceptor;
import cn.notemi.demo.intercepter.LoginAuthInterceptor;
import cn.notemi.demo.intercepter.ResponseResultInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
public class InterceptorConfig extends WebMvcConfigurerAdapter {
@Bean
public HeaderParamsCheckInterceptor headerParamsCheckInterceptor() {
return new HeaderParamsCheckInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
//请求头参数拦截
registry.addInterceptor(headerParamsCheckInterceptor());
}
}
最后
至此,所有端传不同的信息,然后将这些信息整合到日志中,就能更清晰的定位问题了。
读完文章你会发现,对拦截器的使用上基本上没有什么创新,只是在使用例子也就是业务的逻辑上代码的变化,其实是的,拦截器使用起来还是蛮简单的,但是该在什么地方去使用它,这个还是要过考虑下的,所以在这篇文章中我们主要是以实际开发中所用到的问题做举例,这可能引出你对拦截器更好使用的一些想法,希望你在以后的使用中能够更好的利用拦截器的特性,写出更美观、更好维护的代码。
GitHub地址:https://github.com/FlickerMi/notemi-demo
Comments (暂无评论)