目录
思考过程
遇到的问题
过滤器Filter使用步骤
全局去除入参前后空格代码实现
处理过程中自己造成的一些问题
需求背景:
前端所有的条件查询去除前后空格,如搜 【" 测试 "】后端将其转为【测试】。之前都是前端统一处理的,但是这次要后端处理,那么就得考虑全局对入参进行去前后空格再进一步去查询,所以通过滤器Filter来实现。
思考过程
最开始想到的是拦截器Interceptor,但是拦截器一般用于对接口参数进行取值的一些操作,对于GET请求,如果进行入参修改好像更复杂,个人暂时定论拦截器对于GET入参只能取不能改(不确定),但是对于POST请求,不管是拦截器还是过滤器,都会面临请求体丢失问题,所以用到拦截器最终还是用到过滤器来配合解决这个问题,下文再详细说明,所以本文采用拦截器Filter进行解决
遇到的问题
1.POST请求 Required request body is missing
对于GET请求,通过request.getParameterMap()获取所有参数,进行处理后能正常传给接口,但是对于POST请求,通过request.getInputStream()获取流从而读取流获取参数。但request.getInputStream()只能读取一次,所以到接口的时候,@RequestBody注解也是通过该方法获取参数,所以此时如果没有做处理就没参数可以读,就会报错。所以自定义HttpServletRequestWrapper,重写getInputStream方法,把处理完的数据流保存下来传给接口获取
2.@WebFilter中urlPatterns = {"/test/*","/test2/*"}配置过滤规则失效
原因是同时添加了@Component与@WebFilter导致拦截不起作用,就默认所有接口都进过滤器。解决方法去掉@Component保留@WebFilter,在启动类添加@ServletComponentScan即可解决。如果保留@Component去除@WebFilter,则需要单独建一个配置类配置拦截规则。
过滤器Filter使用步骤
实现Filter接口,重写doFilter方法,通过@WebFilter进行拦截路径配置即可
全局去除入参前后空格代码实现
@WebFilter中urlPatterns表示需要过滤得接口,如果是全部则用/*,
单个规则写法urlPatterns = "/test/*", 多个规则用{}
filterName 表示过滤器名字,可采用@WebFilter配置也可以单独写个配置类
/**
* 请求参数去除前后空格过滤器
*/
@Log4j2
@WebFilter(urlPatterns = {"/test/*","/test2/*"}, filterName = "ParameterTrimFilter")
public class ParameterTrimFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
log.info("进入请求参数去前后空格过滤器");
HttpServletRequest request = (HttpServletRequest) servletRequest;
//防止流读取一次就没,自定义HttpServletRequestWrapper处理
ParameterTrimWrapper parameterTrimWrapper = new ParameterTrimWrapper(request);
filterChain.doFilter(parameterTrimWrapper, servletResponse);
log.info("参数去空格过滤器处理完成");
}
@Override
public void destroy() {
}
}
/**
* 请求参数去除前后空格HttpServletRequestWrapper
*/
@Log4j2
public class ParameterTrimWrapper extends HttpServletRequestWrapper {
//用于存放post请求体数据
private byte[] body;
//用于存放get请求参数
private Map<String, String[]> params = new HashMap<>();
private static final String CONTENT_TYPE = "Content-Type";
private static final String GET = "GET";
private static final String POST = "POST";
public ParameterTrimWrapper(HttpServletRequest request) throws IOException {
super(request);
//处理get请求 url路径携带参数情况
if(GET.equals(request.getMethod())){
//获取到所有入参
Map<String, String[]> requestMap = request.getParameterMap();
if(ObjectUtil.isNotEmpty(requestMap)){
this.params.putAll(requestMap);
//对每个入参进行去除前后空格
this.dealUrlParameter();
}
}
//处理post请求 对请求体进行情况
if(POST.equals(request.getMethod())){
//getInputStream()读取请求体数据流
String bodyJson = new String(IOUtils.toByteArray(super.getInputStream()), StandardCharsets.UTF_8);
if(StringUtils.isNotEmpty(bodyJson)){
//处理每个参数去除前后空格
Map<String, Object> finalMap = dealJson(bodyJson);
//将处理完的结果再次塞进body
this.body = JSON.toJSONString(finalMap).getBytes(StandardCharsets.UTF_8);
}
}
}
/**
* get请求参数处理 将参数中的值去除前后空格后写回
*/
public void dealUrlParameter(){
params.forEach((k,v) ->{
for(int i = 0 ;i < v.length ; i++ ){
v[i] = v[i].trim();
}
params.put(k,v);
});
}
/**
* post请求参数处理 处理json参数 去除前后空格
* @param bodyJson json
* @return map
*/
public static Map<String, Object> dealJson(String bodyJson){
Map<String, Object> jsonMap = new HashMap<>();
//将body字符串转为json对象
JSONObject jsonObject = JSONObject.parseObject(bodyJson);
if (jsonObject != null) {
//对键值对进行处理
jsonObject.forEach((k,v) -> {
//根据字段类型进行处理
if(v instanceof JSONArray){
//如果第一层字段为数组,再对数组中每个元素类型判断递归处理
List<Object> list = new ArrayList<>();
for (Object obj : (JSONArray) v){
if(obj instanceof JSONObject){
list.add(dealJson(obj.toString()));
}else if(obj instanceof String){
list.add(obj.toString().trim());
}else {
list.add(obj);
}
}
jsonMap.put(k , list);
}else if(v instanceof JSONObject){
//第一层字段为对象 对象内部进行递归处理
jsonMap.put(k , dealJson(v.toString()));
}else if(v instanceof String){
//第一层字段为字符串 直接处理
jsonMap.put(k , v.toString().trim());
}else {
//其他
jsonMap.put(k , v);
}
});
}
return jsonMap;
}
//重写getInputStream(),最后程序回来调用这个方法
@Override
public ServletInputStream getInputStream() throws IOException {
//bais处理过后的最新数据流
final ByteArrayInputStream bais =new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() {
return bais.read();
}
};
}
//已下几个方法是设计参数读写处理 照着写就行
public String getBody() {
return new String(this.body, StandardCharsets.UTF_8);
}
@Override
public String[] getParameterValues(String name) {
return params.get(name);
}
@Override
public Map<String,String[]> getParameterMap(){
return this.params;
}
@Override
public String getParameter(String name) {
String[]values = params.get(name);
if(values == null || values.length == 0) {
return null;
}
return values[0];
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
}
处理过程中自己造成的一些问题
1.本地测试get请求与post请求都可以,但是一部署到测试环境,还是会造成body丢失
解决:在自定义的ParameterTrimWrapper.getInputStream() 方法中,为了之过滤application/json的post请求,加了判断
public ServletInputStream getInputStream() throws IOException {
//其他非application/json类型不做处理
if(!super.getHeader("Content-Type").equalsIgnoreCase("application/json")){
return super.getInputStream();
}else{
ByteArrayInputStream bais =new ByteArrayInputStream(this.body);......
本地测试正常,但是测试环境中,Content-Type传的是application/json;charset=UTF-8导致一直调用原来的getInputStream(),所以导致到达接口的时候body丢失