过滤器对前端请求参数进行解码URLDecoder,接口接收参数类型为map,解码无效问题

news2024/11/15 12:04:01

文章目录

    • 一、前言
    • 二、设计思路
    • 三、代码实现
    • 四、启动测试
    • 五、过滤器解码无效
    • 六、源码跟踪
    • 七、解决方案
    • 八、再次重启测试
    • 九、总结

一、前言

最近做的一个公司项目,因为客户需要对特殊字符做搜索,但是前端的请求参数无法传递到后端,所以前端对所有列表请求的请求参数做了统一的URL编码,那么后端也需要做一个统一的解码操作。

二、设计思路

既然是统一解码操作,那么只需要拦截所有的GET请求,然后对GET请求里的参数进行解码就可以了,这里就可以用到过滤器来解决。

三、代码实现

1.编写过滤器MonitorDataFilter

  • 过滤器处于客户端和服务器端资源之间,对所有的请求或者响应进行拦截操作
  • 我们这里的过滤器主要就是拦截所有GET请求,然后对GET请求的请求参数解码
package org.***.***.filter;


import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;

/**
 * 过滤器
 * 修改请求体:对请求参数解码
 */
@Component
@WebFilter(urlPatterns = {"/*"}, filterName = "monitorDataFilter")
public class MonitorDataFilter implements Filter {

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {

	}

	@Override
	public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
		//此处可设置http请求和响应的字符编码格式
		servletRequest.setCharacterEncoding("utf-8");
		servletResponse.setCharacterEncoding("utf-8");
		HttpServletRequest request = (HttpServletRequest) servletRequest;
		HttpServletResponse response = (HttpServletResponse) servletResponse;
		String method = request.getMethod();
		if ("GET".equals(method)) {
			Map<String, String[]> parameterMap = request.getParameterMap();
			Map<String, Object> param = parameterMapDecode(parameterMap);
			ParameterRequestWrapper parameterRequestWrapper = new ParameterRequestWrapper(request, param);
			filterChain.doFilter(parameterRequestWrapper, response);
		} else {
			filterChain.doFilter(request, response);
		}
	}

	@Override
	public void destroy() {

	}

	/**
	 * 请求体解码
	 */
	private Map<String, Object> parameterMapDecode(Map<String, String[]> parameterMap) throws UnsupportedEncodingException {
		Map<String, Object> formData = new HashMap<>();
		for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
			String value = (entry.getValue())[0];
			String decode = URLDecoder.decode(value, "UTF-8");
			formData.put(entry.getKey(), decode);
		}
		return formData;
	}

}

2.编写ParameterRequestWrapper拦截器

  • ServletRequest和HttpServletRequest中的请求参数是不能进行修改的,因此有了ServletRequestWrapper和HttpServletRequestWrapper可以进行修改请求参数,controller中的请求参数,都是通过getParameter(String name) 或者 getParameterValues(String name)这两个方法类赋值转换的,因此重新定义类来修改请求参数,我们继承HttpServletRequestWrapper,然后重写getParameter(String name)、getParameterValues(String name)——取自(https://blog.csdn.net/qq_31289187/article/details/87097008/)
package org.***.***.filter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;

/**
 * 请求参数拦截
 */
public class ParameterRequestWrapper extends HttpServletRequestWrapper {

	private Map<String, String[]> params = new HashMap<>();

	public ParameterRequestWrapper(HttpServletRequest request) {
		super(request);
		this.params.putAll(request.getParameterMap());
	}

	/**
	 * 重写构造方法
	 *
	 * @param request
	 * @param extendParams
	 */
	public ParameterRequestWrapper(HttpServletRequest request, Map<String, Object> extendParams) {
		this(request);
		addAllParameters(extendParams);
	}


	/**
	 * 在获取所有参数名,必须重写此方法。
	 *
	 * @return
	 */
	@Override
	public Enumeration<String> getParameterNames() {
		return new Vector(params.keySet()).elements();
	}

	/**
	 * 根据params,修改请求体
	 * @param name
	 * @return
	 */
	@Override
	public String getParameter(String name) {
		String[] values = params.get(name);
		if (values == null || values.length == 0) {
			return null;
		}
		return values[0];
	}

	/**
	 * 根据params,修改请求体
	 * @param name
	 * @return
	 */
	@Override
	public String[] getParameterValues(String name) {
		String[] values = params.get(name);
		if (values == null || values.length == 0) {
			return null;
		}
		return values;
	}

	/**
	 * 添加多个参数
	 *
	 * @param otherParams
	 */
	public void addAllParameters(Map<String, Object> otherParams) {
		for (Map.Entry<String, Object> entry : otherParams.entrySet()) {
			addParameter(entry.getKey(), entry.getValue());
		}
	}

	/**
	 * 添加参数
	 *
	 * @param name  参数名
	 * @param value 参数值
	 */
	public void addParameter(String name, Object value) {
		if (value != null) {
			if (value instanceof String[]) {
				params.put(name, (String[]) value);
			} else if (value instanceof String) {
				params.put(name, new String[]{(String) value});
			} else {
				params.put(name, new String[]{String.valueOf(value)});
			}
		}
	}
}

四、启动测试

经过以上几步,基本就算是写好了这个功能,就可以进入测试阶段了
当请求参数为’&‘,过滤器中解码前的参数为’%26’,解码后为’&‘。
在这里插入图片描述
在这里插入图片描述
接口的请求参数也是解码后的’&’
在这里插入图片描述
那么到这里,其实已经就可以发布到线上用起来了。但是,后面在测试的时候,遇到一个问题,使得过滤器的解码无效。

五、过滤器解码无效

公司项目是个微服务项目,当我把过滤器发布上线后,发现有的服务的列表,输入搜索条件后,无法找到数据,经过一段时间的查找,才发现问题所在,解码失效了。
在这里插入图片描述
这里可以看到dictValue的值还是编码后的值,这里唯一不同的点就是,上面是对象接收,这里是map接收,我尝试改为对象接收,发现是可以解码的,但为什么map接收就失效了呢?然后就开始断点调试,发现过滤器也走了,过滤器中的解码也解成功了,好像也没有哪儿出了问题,但接收参数始终是未解码前的,这我就百思不得其解了。既然找到不问题,就度娘嘛,但是查了半天,网上也没有遇到同样的问题。那就没办法了,那就只能走小白最困难的源码跟踪环节,既然是已经解码了,但是请求参数又变回解码前的,那么肯定就是在过滤器放行后到调这个接口的其中某个环节出了问题。这里提一嘴,源码跟踪一直是我比较弱的点,感觉源码方法调来调去,一个接口多个实现,你都不知道往哪儿断点,把人都给绕晕了,所以我很少看源码,这块也一直没有得到提升,这次我非得把这个问题找出来。

六、源码跟踪

其实源码跟踪很简单,你不需要完全了解源码到底写了什么,你只需要抓住你的问题,然后针对性的跟踪即可。
例如,我这里就是请求参数变为未解码前的了,那么只需要知道到底是哪一步把请求参数改变了即可。
1.在接口处打上断点
在这里插入图片描述
2.然后查看方法执行链路

小知识:之前我一直不知道如何断点源码,就是不清楚到底是咋执行的,也不知道怎么看,其实很简单,idea的断点有相应的执行链路,它可以显示调用了哪些方法,这里的doFilter()以及parentList()分别是我过滤器的继续执行方法以及接口方法。那么中间这些就是源码调用的方法(很明显,从下往上,就是方法的调用顺序)

在这里插入图片描述
3.接下来就是一个方法一个方法的点,找请求体是哪个方法改变的。
经过一番查找发现,在这里,请求参数还是’&’
在这里插入图片描述
但是经过这个方法后,就变为‘%26’
在这里插入图片描述
4.进入this.getMethodArgumentValues()方法
进入这个方法后,继续断点走,可以发现,经过第一次循环,参数就被更改了。
在这里插入图片描述
这里可以看出进入方法前,参数还是对的。
在这里插入图片描述

5.进入this.resolvers.resolveArgument()
这里直接再进入resolver.resolveArgument()方法
在这里插入图片描述
6.进入resolver.resolveArgument()方法
ok,这里到了源码跟踪最麻烦的一点,到底进入哪个实现?
在这里插入图片描述
在这里插入图片描述

小知识:其实这里也很简单,只需要再接口打上断点,它会自动跳往下一个实现的方法(之前一直不知道这块,这就是没经常源码断点调试吃的亏)

7.断点后,按f9,进入了resolveArgument()方法
在这里插入图片描述

在这里插入图片描述
8.继续往下执行
可以看到,当执行webRequest.getParameterMap()方法前,参数还是对的,继续往下。
在这里插入图片描述
噢 ~ 噢 ~噢 !参数变了,答案近在咫尺(别看我只写了几步就找到了,因为一次跟,我可跟了好久才发现是这里改变了)。
在这里插入图片描述
ok,继续往下,看看是为什么会被改变。

9.进入webRequest.getParameterMap()方法,打上断点
在这里插入图片描述
10.进入前面的方法,this.getRequest()。
在这里插入图片描述
发现这里的参数是对的,没被改变。接着进入后面的方法,getParameterMap()
![在这里插入图片描述](https://img-blog.csdnimg.cn/0d17bd856c514fbda63e1eac676603fa.pn
11.在getParameterMap()方法,打上断点
在这里插入图片描述
12.继续往下执行,进入了getParameterMap()方法
哎嘿,可以看到,这个request的参数就是’%26’,所以返回的也就是未解码的,并没有用我们解码后的请求体,这是为什么呢?
在这里插入图片描述
经过我多次断点调试,发现代码会先执行ServletRequestWrapper这个类,这个ServletRequest会先初始化为前端传递的参数,所以它的参数是未解码前的。而我们用map类型作为接受参数,请求参数会从图8webRequest.getParameterMap()的方法获取,而这个方法又会从已经初始化好的ServletRequest里取,就造成了我们解码无效的情况。到这里,源码跟踪结束。
在这里插入图片描述

七、解决方案

方式一:最直接的方法就是将请求参数类型改为对象接收,但这种方式,如果接口多了,改起来特别麻烦。
方式二:重写源码,我们只需要重写参数被改变的哪个类即可,也就是图8哪个类。在parameterMap = webRequest.getParameterMap();这行代码之后调用解码的方法即可。

package org.springframework.web.method.annotation;

import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartRequest;
import org.springframework.web.multipart.support.MultipartResolutionDelegate;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.Part;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.*;

public class RequestParamMapMethodArgumentResolver implements HandlerMethodArgumentResolver {
	public RequestParamMapMethodArgumentResolver() {
	}

	public boolean supportsParameter(MethodParameter parameter) {
		RequestParam requestParam = (RequestParam)parameter.getParameterAnnotation(RequestParam.class);
		return requestParam != null && Map.class.isAssignableFrom(parameter.getParameterType()) && !StringUtils.hasText(requestParam.name());
	}

	public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
		ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
		Class valueType;
		HttpServletRequest servletRequest;
		Collection parts;
		Iterator var10;
		Part part;
		Map<String, String[]> parameterMap;
		MultipartRequest multipartRequest;
		if (!MultiValueMap.class.isAssignableFrom(parameter.getParameterType())) {
			valueType = resolvableType.asMap().getGeneric(new int[]{1}).resolve();
			if (valueType == MultipartFile.class) {
				multipartRequest = MultipartResolutionDelegate.resolveMultipartRequest(webRequest);
				return multipartRequest != null ? multipartRequest.getFileMap() : new LinkedHashMap(0);
			} else if (valueType == Part.class) {
				servletRequest = (HttpServletRequest)webRequest.getNativeRequest(HttpServletRequest.class);
				if (servletRequest != null && MultipartResolutionDelegate.isMultipartRequest(servletRequest)) {
					parts = servletRequest.getParts();
					LinkedHashMap<String, Part> result = new LinkedHashMap(parts.size());
					var10 = parts.iterator();

					while(var10.hasNext()) {
						part = (Part)var10.next();
						if (!result.containsKey(part.getName())) {
							result.put(part.getName(), part);
						}
					}

					return result;
				} else {
					return new LinkedHashMap(0);
				}
			} else {
				parameterMap = webRequest.getParameterMap();
				parameterMapDecode(parameterMap);
				Map<String, String> result = new LinkedHashMap(parameterMap.size());
				parameterMap.forEach((key, values) -> {
					if (values.length > 0) {
						result.put(key, values[0]);
					}

				});
				return result;
			}
		} else {
			valueType = resolvableType.as(MultiValueMap.class).getGeneric(new int[]{1}).resolve();
			if (valueType == MultipartFile.class) {
				multipartRequest = MultipartResolutionDelegate.resolveMultipartRequest(webRequest);
				return multipartRequest != null ? multipartRequest.getMultiFileMap() : new LinkedMultiValueMap(0);
			} else if (valueType != Part.class) {
				parameterMap = webRequest.getParameterMap();
				MultiValueMap<String, String> result = new LinkedMultiValueMap(parameterMap.size());
				parameterMap.forEach((key, values) -> {
					String[] var3 = values;
					int var4 = values.length;

					for(int var5 = 0; var5 < var4; ++var5) {
						String value = var3[var5];
						result.add(key, value);
					}

				});
				return result;
			} else {
				servletRequest = (HttpServletRequest)webRequest.getNativeRequest(HttpServletRequest.class);
				if (servletRequest != null && MultipartResolutionDelegate.isMultipartRequest(servletRequest)) {
					parts = servletRequest.getParts();
					LinkedMultiValueMap<String, Part> result = new LinkedMultiValueMap(parts.size());
					var10 = parts.iterator();

					while(var10.hasNext()) {
						part = (Part)var10.next();
						result.add(part.getName(), part);
					}

					return result;
				} else {
					return new LinkedMultiValueMap(0);
				}
			}
		}
	}

	/**
	 * 请求体解码
	 */
	public void parameterMapDecode(Map<String, String[]> parameterMap) throws UnsupportedEncodingException {
		Map<String, Object> formData = new HashMap<>();
		for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
			String value = (entry.getValue())[0];
			String decode = URLDecoder.decode(value, "UTF-8");
			String[] result = new String[1];
			result[0] = decode;
			entry.setValue(result);
		}
	}
}

八、再次重启测试

再次重启,解码成功。
在这里插入图片描述

九、总结

  • 学到了如何找接口的实现方法,直接再接口断点即可
  • 对源码跟踪有了更深的认识,提高了定位问题的能力

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/506628.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VS2017中Qt项目数据库连接——包含报错比如QMYSQL driver not loaded(细心看到最后,一定能解决你想解决的问题)

我把爆的错误QMYSQL driver not loaded写在文章末尾了&#xff0c;大家看一看&#xff01;前面是配置数据库 一、测试 VS2017 中 Qt 项目数据库连接 打开 VS2017&#xff0c;文件——新建项目&#xff0c;右侧输入框输入 Qt 确定后点击下一步&#xff0c;勾选模块 基类也是默…

RSU路测单元,你知道多少?

一、什么是RSU路测单元&#xff1f; RSU路测单元是实现智慧的路、车路协同的关键设备&#xff0c;设置在路侧&#xff0c;与附近过往车辆进行双向通信、交互数据&#xff0c;是智能交通系统中的一种重要设备。RSU可以连接路面原有电子设备&#xff0c;比如信号灯和摄像头&…

一文解决Xshell无法连接vmware上的centos

问题描述 win10系统上安装VMware workstation16 pro&#xff0c;装好后安装centos虚拟机&#xff0c;在设置network & hostname时选择的NAT模式&#xff0c;即使用自定义的网关和IPv4地址&#xff0c;最后配置完成后centos主机地址信息如下&#xff0c;在虚拟机内部进行pi…

【一览无余】Vue框架下Cesium加载遥感地图使用GeoServer切割TIF大文件对外发布WMS服务进行地图绘制(科普篇2/2)

【一览无余】Vue框架下Cesium加载遥感地图使用GeoServer切割TIF大文件对外发布WMS服务进行地图绘制&#xff08;科普篇2/2&#xff09; 二、Cesium是弄啥嘞2.1 WebGL2.1.1 WebGL是什么2.1.2 WebGL优点 2.2 Cesium是什么2.3 Cesium能干什么2.4 Cesium相关工具有哪些2.5 相关案例…

Echarts 3D散点图

文章目录 以下是一个 html echarts的案例 <!DOCTYPE html> <html> <head><meta charset"utf-8"><title>ECharts 3D Scatter Plot Demo</title><!-- 引入 ECharts --><script src"https://cdnjs.cloudflare.com/…

急吗?光急可没用呀!满满干货,两小时速成,别搁那干瞪眼了!

全球产业链加速重构&#xff0c;各种不确定性加大&#xff01;数字经济规模不断提升&#xff0c;为企业转型与发展创造大量机会&#xff01;企业亟须以数字化为工具或手段&#xff0c;再造组织流程和业务流程&#xff0c;以数字化确定性应对外部环境变化的不确定性&#xff0c;…

中检集团:把数智化转型作为“1号工程”

2018年4月&#xff0c;中国检验认证集团&#xff08;以下简称“中检集团”&#xff09;启动信息化建设“1号工程”&#xff0c;5年时间&#xff0c;从“数字中检1.0”升级到“2.0”再到“2.1”“2.2”&#xff0c;中检集团在数智化转型过程中&#xff0c;可谓是打了一场攻坚战。…

智能ai文章伪原创工具-智能ai文章原创处理系统

智能AI文章伪原创工具 您好&#xff0c;智能AI文章伪原创工具是一种通过机器学习和自然语言处理技术&#xff0c;帮助用户生成“看起来像是”原创文章的人工智能工具。该工具的原理是将原始文章分解为各个句子和段落&#xff0c;然后对其中的一些单词、短语或句子进行修改或替…

MySQL高级(进阶)SQL语句

#显示一个字段或者多个字段的所有内容 SELECT “字段” FROM 表名 &#xff1b; #distinct对字段去重查询 &#xff08;最好只对单个字段进行去重&#xff09; SELECT DISTINCT 字段名 FROM 表名 #where 有条件查询 SELECT “字段” FROM 表名 WHERE 条件&#xff08;例如&a…

安卓开发:使用可为null性

Kotlin园地 地址&#xff1a;Kotlin 园地 | Android 开发者 | Android Developershttps://developer.android.google.cn/training/kotlinplayground?hlzh-cn 以下简称 “K园” 先在K园执行以下代码&#xff1a; fun main() {var fa: String "sandra on";prin…

服务攻防-协议漏洞-FTPRDPSSHRsyncProFTPDlibsshOpenssh-Hydra工具使用口令猜解未授权访问

目录 一、导图 二、口令猜解-Hydra-FTP&RDP&SSH 1、协议介绍 2、Hydra工具介绍 3、实例演示 三、配置不当-未授权访问-Rsync 文件备份 1、Rsync介绍 2、漏洞成因——配置不当 3、实例演示 四、协议漏洞-应用软件-FTP&ProFTPD搭建 1、引入 2、ProFTPD介…

IDEA 搭建 Maven模块化项目

目录 1.前言 2. 软硬件环境 3.项目搭建 3.1.创建 SpringBoot 父项目 3.2. 构建子项目centerdao 3.3. 构建子项目centerweb 4. 建立父子 Module 依赖 4.1 删除不必要文件 4.2.修改 Parent 项目 packaging 4.3.修改子项目pom.xml 信息 4.4. 补充说明 5. 项目继承关系…

ios15及以上webview、Safari使用Websocket断连,1006无清晰错误码

文章目录 问题表现&#xff1a;定位疑似原因&#xff1a;解决方式&#xff1a;定位问题思路过程记录&#xff1a;1、对比前端代码运行环境问题2、写纯请求前端代码连接&#xff0c;确认是否接口部署服务问题&#xff1b;3、IOS连接是否有对TSL安全协议版本有要求&#xff08;使…

【Java入门合集】第六章异常处理

博主&#xff1a;命运之光 专栏&#xff1a;JAVA入门 学习目标 掌握异常的概念&#xff0c;Java中的常见异常类&#xff1b; 掌握Java中如何捕获和处理异常&#xff1b; 掌握自定义异常类及其使用&#xff1b; 目录 异常概述 异常体系 常见的异常 Java的异常处理机制 方式…

The Certificate Of Harbor On Rancher(2.5.16)

序言 针对Rancher2.5.16版本的情况. 问题是: 当我们创建RancherServcie后,RancherService会在容器内通构建k3s环境,然后k3s去我们的私有Harbor拉取镜像. 问题就在我们虽然在虚拟机上给docker设置了可以不通过Https的方式拉取Hrabor的镜像,但是RancherService中的k3s也会去拉…

Android---bitmap优化

目录 Bitmap 占用内存大小计算 Bitmap | Drawable | InputStream | Byte[] 之间进行转换 Bitmap 相关方法 BitmapFactory 工厂类 Bitmap 占用内存大小计算 Bitmap 作为位图&#xff0c;需要读入一张图片中每一个像素点的数据&#xff0c;其主要占用内存的地方也正是这些像…

【axios 使用】下载文件流 以及 获取响应头header content-disposition

获取数据都知道&#xff0c;使用的是 axios,主要是设置 responseType: blob 由于项目使用的是 apipost &#xff0c;所以在使用的时候还要设置 isReturnNativeResponse -是否返回本机响应标头&#xff0c;一般可能设置 responseType: blob 就可以了 export function getDow…

本地使用IIS快速搭建一个属于自己的网站,并发布公网访问「无需购买云服务器」

文章目录 1.前言2.Windows网页设置2.1 Windows IIS功能设置2.2 IIS网页访问测试 3. Cpolar内网穿透3.1 下载安装Cpolar内网穿透3.2 Cpolar云端设置3.3 Cpolar本地设置 4.公网访问测试5.结语 1.前言 在网上各种教程和介绍中&#xff0c;搭建网页都会借助各种软件的帮助&#xf…

java+iClientOpenlayers实现城市内涝积涝模拟内涝模拟演进

程序实现效果图 洪水演进过程 一、应用背景 城市内涝和积涝是一个严重的问题&#xff0c;特别是在气候变化的背景下。为了更好地了解这个问题&#xff0c;模拟城市内涝和积涝是非常重要的。这个过程可以帮助我们预测哪些区域可能会受到影响&#xff0c;以及在发生内涝和积涝时…

人人都应该知道的CPU缓存运行效率

提到CPU性能&#xff0c;大部分同学想到的都是CPU利用率&#xff0c;这个指标确实应该首先被关注。但是除了利用率之外&#xff0c;还有很容易被人忽视的指标&#xff0c;就是指令的运行效率。如果运行效率不高&#xff0c;那CPU利用率再忙也都是瞎忙&#xff0c;产出并不高。 …