• 基于SpringMVC架构对Request进行参数重写


    环境:SpringBoot 2.0 以上的版本

    假设目前有一种场景,我需要把所有请求都统一用一种方法来接收参数,同时还能加塞Request的参数。

    例如:不管请求的参数是否是JSON,所有的参数接收处不添加 @RequestBody 注解。

    LoginController.java

    1. @PostMapping("/login")
    2. public Result login(LoginDTO dto) {
    3. log.info("");
    4. return loginService.login(dto);
    5. }

    这里,我们想要做到上述的效果,需要在过滤器上针对Request对象进行处理。

    这里简单讲解下思路:

            首先,我是使用了SpringSecurity框架来维护系统的用户权限的,但用户登陆后的信息是统一存放Redis中。每次用户请求时如果Token校验通过,就会从Redis获取用户登陆的信息,加塞进Security的会话中。

    针对用户校验及权限这一块,如果不需要的,可以删掉,从48-60行。

    主要是通过继承了HttpServletRequestWrapper类解决重写Reqeust参数的问题。

    因为Request内置的params参数是final声明,所以我们无法通过直接更改该Map,所以重新定义了一个Map,然后是为了兼容JSON格式的数据,

    核心是构造方法 :

    初始化Map,然后根据对应的请求类型进行处理:

            1.如果是JSON或XML。我们就通过读取数据流的saveInputStreamData方法,然后解析到的数据加塞到Map中。

            2.如果是普通的表单类型数据,我们就直接加塞到Map中。

    关于saveInputStreamData方法:JSON请求只能通过流来获取其请求参数。而请求数据流只能读取一次。所以我们在读取了数据流后将读取到的数据保存下来,通过重写getInputStream方法来保证后续也能获取到请求数据流。

    然后额外添加了addAllParameters、addParameters方法,方便加塞参数。

    ParameterRequestWrapper方法:从Redis中解析出来的用户数据,我们加塞到请求参数中。

    1. package com.mrlv.rua.auth.filter;
    2. import cn.hutool.core.util.StrUtil;
    3. import cn.hutool.http.ContentType;
    4. import cn.hutool.json.JSONObject;
    5. import cn.hutool.json.JSONUtil;
    6. import com.auth0.jwt.exceptions.JWTVerificationException;
    7. import com.mrlv.rua.auth.consts.RedisPreConst;
    8. import com.mrlv.rua.auth.entity.LoginUser;
    9. import com.mrlv.rua.auth.utils.JwtUtil;
    10. import com.mrlv.rua.common.redis.utils.RedisUtil;
    11. import lombok.extern.slf4j.Slf4j;
    12. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    13. import org.springframework.security.core.context.SecurityContextHolder;
    14. import org.springframework.stereotype.Component;
    15. import org.springframework.web.filter.OncePerRequestFilter;
    16. import javax.servlet.*;
    17. import javax.servlet.http.HttpServletRequest;
    18. import javax.servlet.http.HttpServletRequestWrapper;
    19. import javax.servlet.http.HttpServletResponse;
    20. import java.io.BufferedReader;
    21. import java.io.ByteArrayInputStream;
    22. import java.io.IOException;
    23. import java.io.InputStreamReader;
    24. import java.nio.charset.StandardCharsets;
    25. import java.util.*;
    26. /**
    27. * @author lvshiyu
    28. * @description: 动态权限过滤器
    29. * @date 2022年07月05日 17:32
    30. */
    31. @Component
    32. @Slf4j
    33. public class AuthTokenFilter extends OncePerRequestFilter {
    34. /**
    35. * 过滤
    36. * @param request
    37. * @param response
    38. * @param filterChain
    39. */
    40. @Override
    41. protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    42. //获取请求头,判断是否已经登陆
    43. String token = request.getHeader(JwtUtil.HEADER_STRING);
    44. LoginUser userDetails = null;
    45. try {
    46. if (StrUtil.isNotBlank(token)) {
    47. String userId = JwtUtil.getUserId(token);
    48. String key = RedisPreConst.AUTH_ONLINE_USER + userId;
    49. if (RedisUtil.hHasKey(key, "PC")) {
    50. //这里使用Redis来存储用户登陆信息,同时通过SpringScurity来对用户登陆状态进行维护,如果不是使用SpringScurity的话,可以无视这一部分。
    51. userDetails = (LoginUser)RedisUtil.hget(key, "PC");
    52. UsernamePasswordAuthenticationToken user = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
    53. SecurityContextHolder.getContext().setAuthentication(user);
    54. }
    55. }
    56. request = new ParameterRequestWrapper(request, userDetails);
    57. } catch (JWTVerificationException e) {
    58. log.info("Token异常 error:{}", e.getMessage(), e);
    59. } catch (Exception e) {
    60. e.printStackTrace();
    61. }
    62. filterChain.doFilter(request, response);
    63. }
    64. /**
    65. * @author lvshiyu
    66. * @description: 重写 HttpServletRequestWrapper
    67. * @date 2022年8月24日 14:50
    68. */
    69. class ParameterRequestWrapper extends HttpServletRequestWrapper {
    70. /**
    71. * 请求参数
    72. */
    73. private Map params;
    74. /**
    75. * 用于保存读取body中数据
    76. */
    77. private byte[] body;
    78. /**
    79. * 用于保存读取body中数据
    80. */
    81. private String bodyMessage;
    82. /**
    83. * 自定义构造方法
    84. * @param request
    85. * @throws IOException
    86. */
    87. public ParameterRequestWrapper(HttpServletRequest request) throws IOException {
    88. super(request);
    89. //参数保存
    90. this.params = new HashMap<>();
    91. //初始化参数
    92. String contentType = request.getContentType().toLowerCase();
    93. //如果是application/json
    94. if (Objects.equals(ContentType.JSON.toString(), contentType)) {
    95. //解析数据流数据
    96. saveInputStreamData(request);
    97. JSONObject parameter = JSONUtil.parseObj(this.getBodyMessage());
    98. this.addAllParameters(parameter);
    99. } else if (Objects.equals(ContentType.XML.toString(), contentType)) {
    100. saveInputStreamData(request);
    101. JSONObject parameter = JSONUtil.parseFromXml(this.getBodyMessage()).getJSONObject("request");
    102. this.addAllParameters(parameter);
    103. } else {
    104. Enumeration headerNames = request.getParameterNames();
    105. while (headerNames.hasMoreElements()) {
    106. String key = headerNames.nextElement();
    107. this.addParameter(key, request.getParameter(key));
    108. }
    109. }
    110. }
    111. /**
    112. * 自定义构造方法
    113. * @param request
    114. * @throws IOException
    115. */
    116. public ParameterRequestWrapper(HttpServletRequest request, LoginUser user) throws IOException {
    117. this(request);
    118. if (user != null) {
    119. this.addParameter("userId", String.valueOf(user.getId()));
    120. this.addParameter("username", user.getUsername());
    121. this.addParameter("nickname", user.getNickname());
    122. }
    123. }
    124. /**
    125. * 覆盖(重写)父类的方法
    126. * @return
    127. * @throws IOException
    128. */
    129. @Override
    130. public BufferedReader getReader() throws IOException {
    131. return new BufferedReader(new InputStreamReader(getInputStream()));
    132. }
    133. /**
    134. * 覆盖(重写)父类的方法
    135. * @return
    136. * @throws IOException
    137. */
    138. @Override
    139. public ServletInputStream getInputStream() throws IOException {
    140. final ByteArrayInputStream inputStream = new ByteArrayInputStream(this.body);
    141. return new ServletInputStream() {
    142. @Override
    143. public boolean isFinished() {
    144. return false;
    145. }
    146. @Override
    147. public boolean isReady() {
    148. return false;
    149. }
    150. @Override
    151. public void setReadListener(ReadListener readListener) {
    152. }
    153. @Override
    154. public int read() throws IOException {
    155. return inputStream.read();
    156. }
    157. };
    158. }
    159. @Override
    160. public Enumeration getParameterNames() {
    161. return new Vector(params.keySet()).elements();
    162. }
    163. @Override
    164. public String getParameter(String name) {
    165. String[] values = this.params.get(name);
    166. if (values == null || values.length == 0) {
    167. return null;
    168. }
    169. return values[0];
    170. }
    171. @Override
    172. public String[] getParameterValues(String name) {
    173. String[] values = this.params.get(name);
    174. if (values == null || values.length == 0) {
    175. return null;
    176. }
    177. return values;
    178. }
    179. /**
    180. * 获取body中的数据
    181. * @return
    182. */
    183. public byte[] getBody() {
    184. return this.body;
    185. }
    186. /**
    187. * 把处理后的参数放到body里面
    188. * @param body
    189. */
    190. public void setBody(byte[] body) {
    191. this.body = body;
    192. }
    193. /**
    194. * 获取处理过的参数数据
    195. * @return
    196. */
    197. public String getBodyMessage() {
    198. return this.bodyMessage;
    199. }
    200. /**
    201. * 设置参数
    202. * @param otherParams
    203. */
    204. private void addAllParameters(Map otherParams) {
    205. for (Map.Entry entry : otherParams.entrySet()) {
    206. addParameter(entry.getKey(), entry.getValue());
    207. }
    208. }
    209. /**
    210. * 设置参数
    211. * @param name
    212. * @param value
    213. */
    214. private void addParameter(String name, Object value) {
    215. if (this.params == null) {
    216. this.params = new HashMap<>();
    217. }
    218. if (value != null) {
    219. if (value instanceof String[]) {
    220. this.params.put(name, (String[]) value);
    221. } else if (value instanceof String) {
    222. this.params.put(name, new String[]{(String) value});
    223. } else {
    224. this.params.put(name, new String[]{String.valueOf(value)});
    225. }
    226. }
    227. }
    228. /**
    229. * 保存请求的InputSteam的数据
    230. * @param request
    231. * @throws IOException
    232. */
    233. private void saveInputStreamData(HttpServletRequest request) throws IOException {
    234. int contentLength = request.getContentLength();
    235. ServletInputStream inputStream = request.getInputStream();
    236. this.body = new byte[contentLength];
    237. inputStream.read(this.body, 0, contentLength);
    238. this.bodyMessage = new String(this.body, StandardCharsets.UTF_8);
    239. }
    240. }
    241. }

    在调试途中遇到了一个问题:

    关于Request一旦使用了getInputStream后就无法通过getParameterMap获取参数的问题,通过源码我们可以发现,Request 中的 usingInputStream,标识是否使用过流来读取数据,如果使用过了,则会改为True。从而导致 getParameterMap 获取不到数据

    org.apache.catalina.connector.Request

    org.apache.catalina.connector.Request

    org.apache.catalina.connector.Request

    总结下来,一个过滤器就可以解决问题,如果能给诸位带来帮助,麻烦点个赞。有什么不理解的,欢迎留言。

  • 相关阅读:
    redis基础3——配置文件核心参数实测+RDB持久化、AOF持久化核心参数详解
    【Java】常用API——String类、Math类
    C#实现时钟控件
    mybatis04关联关系映射
    LeetCode只出现一次的数字
    第8章 使用注解的方式整合MyBatis 入门详解
    嵌入式5. ARM指令集详解
    【深度学习】实验1答案:Softmax实现手写数字识别
    Linux学习笔记-Docker安装配置及使用教程
    [SQL]视图和权限
  • 原文地址:https://blog.csdn.net/qq826303461/article/details/126520395