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

news/2024/5/12 22:56:10/文章来源:https://blog.csdn.net/ShanLanF/article/details/130579395

文章目录

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

一、前言

最近做的一个公司项目,因为客户需要对特殊字符做搜索,但是前端的请求参数无法传递到后端,所以前端对所有列表请求的请求参数做了统一的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 {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic 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);}}@Overridepublic 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*/@Overridepublic Enumeration<String> getParameterNames() {return new Vector(params.keySet()).elements();}/*** 根据params,修改请求体* @param name* @return*/@Overridepublic String getParameter(String name) {String[] values = params.get(name);if (values == null || values.length == 0) {return null;}return values[0];}/*** 根据params,修改请求体* @param name* @return*/@Overridepublic 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.luyixian.cn/news_show_298076.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

光耦继电器和普通继电器的区别概述

光耦继电器和普通继电器都是电气传感器元件中的一种&#xff0c;其都能够将电能转化成机械能&#xff0c;并实现各种开关控制和保护控制。但光耦继电器与普通继电器最大的区别在于其输入电路与输出电路之间通过光电转换器件&#xff0c;而不是实现物理接触。本文将从结构、工作…

Spring MVC——Rest风格

REST&#xff08;Representational State Transfer&#xff09; 当我们想表示一个网络资源的时候&#xff0c;可以使用两种方式: 我们分别用查询id为1的用户信息与保存用户信息举例传统风格资源描述形式 http://localhost/user/getById?id1http://localhost/user/saveUser RES…

记一次azkaban调度异常处理

一、背景 预发布环境使用的数据库性能比较低&#xff0c;根据业务测试的需求&#xff0c;需要将数据库更换成 稳定高性能的数据库。更换业务数据库后azkaban定时任务失败 二、数据库服务信息 说明&#xff1a;该部分使用代号来代替&#xff0c;非真实信息 该数据库存储了azka…

linux彻底卸载mysql步骤

第一步&#xff0c;先查看是否安装了mysql mysql -u root -p 如果提示bash: mysql: command not found...则没有安装过mysql 如果提示需要输入密码&#xff0c;那就证明安装了mysql 第二步&#xff0c;查看mysql运行状态并关闭 先查看下mysql的运行状态&#xff08;如果已经…

电脑缺少msvcp140.dll怎么办,缺少msvcp140一键修复方法

电脑缺少msvcp140.dll怎么办&#xff1f;这个问题相信不少小伙伴都遇到过&#xff0c;msvcp140.dll文件是很多软件跟游戏运行必须用到的文件&#xff0c;如果丢失或者损坏&#xff0c;很多软件都会无法打开运行。其实知道知道方法&#xff0c;修复起来其实也不会很难&#xff0…

OAK相机如何将 YOLO NAS 模型转换成blob格式?

编辑&#xff1a;OAK中国 首发&#xff1a;oakchina.cn 喜欢的话&#xff0c;请多多&#x1f44d;⭐️✍ 内容可能会不定期更新&#xff0c;官网内容都是最新的&#xff0c;请查看首发地址链接。 ▌前言 Hello&#xff0c;大家好&#xff0c;这里是OAK中国&#xff0c;我是助手…

k8s pv pvc的使用

k8s pv pvc的使用 安装nfs服务器 yum install rpcbind nfs-utils -y systemctl enable rpcbind systemctl enable nfs systemctl start rpcbind systemctl start nfsmkdir -p /root/data/sc-data [rootmaster sc-data]# cat /etc/exports /root/data/sc-data 192.168.1.0/24(…

运营商三要素验证原理,这篇文章就够了!

引言 运营商三要素验证 API 是一种基于手机号码、身份证号码和姓名等三种信息的验证服务&#xff0c;主要用于验证用户身份信息的真实性和一致性&#xff0c;以及查询手机号码所属的运营商信息。 运营商三要素 API 的验证原理 1. 身份验证的原理 身份信息验证是运营商三要素…

系统安全分析与设计

目录 第五章、系统安全分析与设计1、信息系统安全属性2、对称加密技术与非对称加密技术3、信息摘要4、数字签名5、数字信封与PGP6、网络安全6.1、各个网络层次的安全保障6.2、网络威胁与攻击6.3、防火墙 第五章、系统安全分析与设计 1、信息系统安全属性 安全属性 保密性&…

JavaScript实现求1-100之间不能被3整除的数之和,求100以内偶数的和的两个程序代码

以下为实现求1-100之间不能被3整除数之和求100以内偶数的和的两个程序代码和运行截图 目录 前言 一、实现输入两个数比较两个数的大小 1.1 运行流程及思想 1.2 代码段 1.3 JavaScript语句代码 1.4 运行截图 二、求100以内偶数的和 2.1 运行流程及思想 2.2 代码段 2.3…

北峰通信,用专业打造“全方位、立体化”应急通信保障体系

最近热映的电影《惊天救援》里&#xff0c;杜江饰演的消防员韩凯&#xff0c;在一次化工园区发生爆炸后&#xff0c;他作为消防救援站里的通信员&#xff0c;第一时间奔赴重灾区&#xff0c;及时将第一现场的情况传到了后方指挥部。 众所周知&#xff0c;通讯系统是生命线系统的…

685页40万字某省市场监管智慧应用一体化项目(word可编辑)

1.2.3.1 数字XX公共能力建设现状 1.2.3.1.1 数字XX通用基础应用平台现状 通用基础应用平台提供具有共性特征的跨部门、跨层级业务应用&#xff0c;与本项目有关的平台包括某省网上办事大厅、某省政务服务 APP 统一平台&#xff08;X政通 APP&#xff09;、某省公共信用信息平…

rk3568 修改开机logo

rk3568 修改开机显示logo Android 显示 logo 的作用是为了标识应用程序或设备的品牌和身份。在应用程序中&#xff0c;logo 可以帮助用户快速识别应用程序&#xff0c;并与其他应用程序区分开来。在设备中&#xff0c;logo 可以帮助用户识别设备的品牌和型号&#xff0c;以及与…

python操作集合

# 集合 l{1,2,1} print(l) sset(range(5)) print(s)# 判断in 或 not in print(5 not in l) # 集合元素新增操作 l.add(4) l.update(1,3,6) print(l) l.update((1,3,5)) l.update([4,4,6]) # 删除集合元素 l.remove(2) l.discard(2) # 无目的的删除 自己不带参数 l.pop() l.cl…

AutoCV第七课:ML基础

目录 ML基础前言1. 复习sqrt函数2. 线性回归预测房价2.1 问题分析2.2 代码实现2.3 总结 个人总结 ML基础 前言 手写AI推出的全新保姆级从零手写自动驾驶CV课程&#xff0c;链接。记录下个人学习笔记&#xff0c;仅供自己参考。 本次课程主要学习复习 sqrt 函数和线性回归预测房…

【C++】-const对象及成员函数之类和对象中篇完结(中)

&#x1f496;作者&#xff1a;小树苗渴望变成参天大树 ❤️‍&#x1fa79;作者宣言&#xff1a;认真写好每一篇博客 &#x1f4a8;作者gitee:gitee &#x1f49e;作者专栏&#xff1a;C语言,数据结构初阶,Linux,C 文章目录 前言一、案例的引入二、const对象和成员函数三、取地…

【2023 · CANN训练营第一季】应用开发深入讲解——第三章应用调试

学习资源 日志参考文档 应用开发FAQ 日志主要用于记录系统的运行过程及异常信息&#xff0c;帮助快速定位系统运行过程中出现的问题以及开发过程中的程序调试问题。 日志分为如下两大类&#xff1a; 系统类日志&#xff1a;系统运行产生的日志。主要包括&#xff1a; Contro…

【跟着陈七一起学C语言】今天总结:函数、数组、指针之间的关系

友情链接&#xff1a;专栏地址 知识总结顺序参考C Primer Plus&#xff08;第六版&#xff09;和谭浩强老师的C程序设计&#xff08;第五版&#xff09;等&#xff0c;内容以书中为标准&#xff0c;同时参考其它各类书籍以及优质文章&#xff0c;以至减少知识点上的错误&#x…

数智无限|东土科技科东软件5月活动预告

这个5月&#xff0c;东土科技&科东软件将携带自主研发的国产操作系统Intewell、智能控制通用工具软件MaVIEW、边缘通用控制器NewPre、基于TSN技术的智能化工业网络硬件、数字工厂智能产线一站式解决方案&#xff0c;以及面向智能工厂的离散控制、流程控制、运动控制、机器人…

第十四届蓝桥杯大赛软件赛省赛(Java 大学A组)

蓝桥杯 2023年省赛真题 Java 大学A组 试题 A: 特殊日期  试题 B: 与或异或 把填空挂上跟大伙对对答案&#xff0c;先把C/C B组的做了。 试题 A: 特殊日期 本题总分&#xff1a;5 分 【问题描述】 记一个日期为 y y \small yy yy 年 m m \small mm mm 月 d d \small dd dd 日…