拦截器、过滤器、参数读取坑记录
目的:做个统一网关接口、app请求要跳到H5的接口复用
主要问题:
1.H5的登录信息获取 是通过Session 来获取 ,app则是mid(用户id)来获取,信息来源不同
2.如何区分终端来源是H5还是app
解决思路一:
写一个公用方法,在每一个controller中去调用,判断参数,如果是app的则通过mid获取,H5走session
解决思路二:
拦截器完成,在preHandle 中统一拦截配置中需要跳转到H5的请求url,统一取登录信息,放入到缓存或者request中,controller只需要从request中或者缓存中获取登录信息不需要再去判断来源
解决如下:
不管是思路一还是思路二,都会遇到同一个问题,如何取区分app和H5的终端参数,如何取?
1.公用参数里有system(ios、Android)通过这个来区分
2.由于app所有的请求都是post且参数都在body里面,请求头类型为application/json
用request.getParameter(xxx)只能拿到url后面以?xxx=xxx这种拼接格式 或者 请求头类型为 application/x-www-form-urlencoded 这种的。 所以只能用流来解析app传过来的参数。 现有的controller为什么不是流解析,是因为 我们controller里有@RequestBody 来标识绑定body参数,所以我们可以用标识后面的map或者是对象来取。
3.流解析弊端,通俗点就是只能解析一遍,也就是说拦截器中解析一遍拿到systeme参数判断是app还是H5之后,后续controller就没法取到参数值了。原因是因为流解析之后是要关闭的,无法再往下传。所以引入filter 把我们的request重写之后再往下传。
代码:
1.拦截器 CheckCmbMemberInterceptor 主要拦截配置中需要跳转的url,不同的终端不同获取登录对象,统一放到request中。
2.过滤器RequestFilter 主要保存request 信息 用于后续controller操作。后续如果有敏感词过滤,黑白名单,接口开关控制等等, 都可以通过这个filter来实现!
拦截器代码
springmvc的xml
<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**" /> <mvc:exclude-mapping path="/error/*.json" /> <bean class="com.xxx.xxx.xxx.interceptor.CheckCmbMemberInterceptor"> <property name="excludeUrlPattern"> <list> <value>/user/myWallet</value> </list> </property> </bean> </mvc:interceptor> </mvc:interceptors>
对应的拦截器
CheckCmbMemberInterceptor
package com.xxx.xxx.web.interceptor; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.OutputStream; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.AntPathMatcher; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import com.alibaba.dubbo.common.utils.IOUtils; import com.google.gson.Gson; import com.xxx.xxx.model.CmbMember; import com.xxx.xxx.service.core.consumer.QrpayMemberService; import com.xxx.xxx.utils.BaseResult; import com.xxx.xxx.utils.ErrorCodeEnum; import com.xxx.xxx.utils.LogPrintUtil; import com.xxx.xxx.web.interceptor.req.BaseReq; import com.xxx.xxx.web.interceptor.req.CustomReq; /** * 登入检查的拦截器。 区分 CmbMember 来源 是H5还是app * */ public class CheckCmbMemberInterceptor implements HandlerInterceptor { private List<String> excludeUrlPattern; private AntPathMatcher urlPatternMatcher = new AntPathMatcher(); private Gson gson = new Gson(); private static final String SYSTEM_IOS = "ios"; private static final String SYSTEM_ANDROID = "android"; @Autowired private QrpayMemberService qrpayMemberService; // @Autowired // private JedisApi jedisApi; public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 如果没在配置中,直接返回 for (int i = 0; excludeUrlPattern != null && i < excludeUrlPattern.size(); i++) { if (!urlPatternMatcher.match(excludeUrlPattern.get(i), request.getServletPath())) { return true; } } // 获取参数判断终端返回 BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream())); String reqBody = IOUtils.read(reader); BaseReq req = gson.fromJson(reqBody, BaseReq.class); LogPrintUtil.printFreeLog("reqId", "CheckCmbMemberInterceptor", "preHandle", "统一拦截器公共req参数", req); if (req != null && (SYSTEM_IOS.equalsIgnoreCase(req.getSystem()) || SYSTEM_ANDROID.equalsIgnoreCase(req.getSystem()))) { return appHandle(request, response, handler, reqBody); } else { return h5Handle(request, response, handler); } } private boolean appHandle(HttpServletRequest request, HttpServletResponse response, Object handler, String reqBody) throws Exception { // 获取用户信息,放入 CustomReq req = gson.fromJson(reqBody, CustomReq.class); if (req.getMid() == null) { writeError(request, response, handler, BaseResult.buildFailBaseResult(ErrorCodeEnum.ERROR_402.getCode())); return false; } CmbMember cmbMember = qrpayMemberService.getMemberByMid(Long.valueOf(req.getMid())); if (cmbMember == null) { writeError(request, response, handler, BaseResult.buildFailBaseResult(ErrorCodeEnum.ERROR_450.getCode())); return false; } request.setAttribute("member", cmbMember); return true; } private void writeError(HttpServletRequest request, HttpServletResponse response, Object handler, @SuppressWarnings("rawtypes") BaseResult br) throws Exception { OutputStream output = null; response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); output = response.getOutputStream(); byte[] rspJson = new Gson().toJson(br, br.getClass()).getBytes("UTF-8"); output.write(rspJson); output.flush(); } private boolean h5Handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (request.getSession() == null) { // 由于H5返回的格式各异,不做统一拦截保持原有逻辑 在controller中作返回错误处理 LogPrintUtil.printFreeLog("reqId", "CheckCmbMemberInterceptor", "H5Handle", "统一拦截器H5", "request.getSession()为空"); return true; } Object member = request.getSession().getAttribute("member"); request.setAttribute("member", member); return true; } public void setExcludeUrlPattern(List<String> excludeUrlPattern) { this.excludeUrlPattern = excludeUrlPattern; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }
接口controller 调用
@RequestMapping(value = "/myWallet") public String myWallet(HttpServletRequest request) throws Exception { //查询用户资金信息 //Object member = request.getSession().getAttribute("member"); Object member = request.getAttribute("member"); if(member == null){ logger.info("当前操作用户session失效,直接跳转登录页面"); return "/user/login"; } CmbMember cmbMember = (CmbMember)member; CmbFundAct cmbFundAct = this.qrpayMemberCommonService.getMemberFund(cmbMember.getMid()); request.setAttribute("cmbFundAct",cmbFundAct); return "user/myWallet"; }
filter代码
web.xml声明
<!-- 过滤request请求的Filter --> <filter> <filter-name>requestFilter</filter-name> <filter-class>com.xxx.xxx.web.filter.RequestFilter</filter-class> </filter> <filter-mapping> <filter-name>requestFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
对应的
RequestFilter
package com.xxx.xxx.web.filter; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import com.xxx.xxx.web.interceptor.util.BodyReaderHttpServletRequestWrapper; /** * 过滤器把请求流保存起来 接着传递到controller * */ public class RequestFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) request; // 防止流读取一次后就没有了, 所以需要将流继续写出去 ServletRequest requestWrapper = new BodyReaderHttpServletRequestWrapper(httpServletRequest); chain.doFilter(requestWrapper, response); } @Override public void destroy() { } }
过滤器中重写的
BodyReaderHttpServletRequestWrappe
package com.xxx.xxx.web.interceptor.util; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.Charset; import java.util.Enumeration; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import com.alibaba.dubbo.common.utils.IOUtils; /*** * 构造一个 BodyReaderHttpServletRequestWrapper * 重写 HttpServletRequest * 解决 IO读取body参数,后续request为空的情况 * */ public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper { private final byte[] body; public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException { super(request); BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream(), Charset.forName("UTF-8"))); String reqBody = IOUtils.read(reader); body = reqBody.getBytes(Charset.forName("UTF-8")); } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream bais = new ByteArrayInputStream(body); return new ServletInputStream() { @Override public int read() throws IOException { return bais.read(); } @Override public boolean isFinished() { // TODO Auto-generated method stub return false; } @Override public boolean isReady() { // TODO Auto-generated method stub return false; } @Override public void setReadListener(ReadListener readListener) { // TODO Auto-generated method stub } }; } @Override public String getHeader(String name) { return super.getHeader(name); } @Override public Enumeration<String> getHeaderNames() { return super.getHeaderNames(); } @Override public Enumeration<String> getHeaders(String name) { return super.getHeaders(name); } }
相关推荐
我们可以利用拦截器来实现对HTTP请求的过滤,包括修改请求参数、日志记录、权限验证等。 要创建一个自定义的拦截器,你需要继承`HandlerInterceptorAdapter`类,并重写其中的三个方法: 1. `preHandle...
注意,由于日志拦截器可能会在多个请求之间共享,因此在生产环境中,为了性能考虑,我们应该避免在拦截器中直接读取响应体,而是应该只记录基本信息,例如状态码和头部信息。 要在OkHttp的Client中添加这个拦截器,...
1. **记录失败请求**:拦截器需要有能力存储失败的请求,包括请求方法(GET、POST等)、URL、请求头和请求体等信息。 2. **重试策略**:定义何时重试请求。通常,我们可以基于HTTP状态码、网络错误类型(如连接超时...
27. **Parameter FilterInterceptor**:参数过滤拦截器,从参数列表中删除不必要的参数。 28. **ProfilingInterceptor**:性能剖析拦截器,可以通过参数激活性能分析,默认的拦截器栈设计适用于大多数应用程序。 ...
2. **标准过滤器链**: 请求被一系列的标准过滤器处理,这些过滤器包括但不限于`ActionContextCleanUp`(清理当前线程的ActionContext等)和`FilterDispatcher`(通过`ActionMapper`来确定需要调用哪个Action)。...
在Struts2中,拦截器是实现业务逻辑增强的一种方式,它们在Action执行前后执行,可以实现日志记录、权限验证、性能监控等功能。要添加拦截器,你需要在struts.xml配置文件中定义拦截器栈,并关联到特定的Action或者...
Flume 拦截器允许我们在数据流入通道之前对其进行预处理,比如过滤、修改或添加元数据等操作。了解如何开发拦截器对于优化数据流和满足特定业务需求非常有用。 首先,我们需要理解 Java 在 Flume 中的角色。Java 是...
拦截器可以看作是一种特殊的过滤器(Filter),但与传统的Servlet过滤器相比,它更加轻量级,并且能够更好地与Struts2框架集成。 #### 二、拦截器的工作原理 1. **加载配置**:当应用程序启动时,Struts2会读取...
9. **安全考虑**: 短信拦截器有潜在的安全风险,因为它可以读取所有短信,包括验证码和敏感信息。因此,只有在充分理解其工作原理和风险后,才能在生产环境中使用。 10. **测试与调试**: 在实际设备上进行测试非常...
在Java Web开发中,过滤器(Filter)是一种用于拦截请求和响应的重要机制,可以实现对用户请求的预处理和后处理,以及对响应的处理。本文将深入解析“JAVA过滤器标准代码”,探讨其核心功能、实现原理以及应用场景。...
拦截器是Struts2框架中的一个重要组件,用于在执行Action前后进行一系列处理,如验证用户输入、日志记录、事务管理等。在struts.xml文件中,可以定义全局或特定Action的拦截器栈,通过`<interceptors>`和`...
4. **处理结果**:解析完成后,拦截器可以对结果进行进一步处理,如校验、过滤或转换,确保数据的质量和安全性。 这种结合使用的方式具有以下优势: - **可扩展性**:通过添加或移除拦截器,可以轻松地扩展解析器...
- `init(FilterConfig config)`:初始化过滤器,读取配置参数。 - `doFilter(ServletRequest request, ServletResponse response, FilterChain chain)`:核心逻辑,执行过滤操作。 - `destroy()`:释放资源,清理...
1. **初始化阶段**:当Web应用启动时,容器会读取`web.xml`文件中的配置信息来创建并初始化过滤器对象。 2. **请求处理阶段**:每当一个请求到达时,如果该请求匹配了某个过滤器,则该过滤器的`doFilter()`方法将被...
与过滤器相比,拦截器提供了更细粒度的控制,可以在多个阶段进行干预。 ### 1. 概念理解 - **执行顺序图**:拦截器的执行顺序通常表现为一个链式调用的过程,preHandler()方法从上到下依次执行,postHandler()和...
2. **过滤器配置**:在Web应用的`web.xml`配置文件中,可以定义过滤器的类名、过滤器的映射路径以及过滤器的初始化参数。 3. **过滤器生命周期**:包括`init()`、`doFilter()`和`destroy()`方法,`init()`在过滤器...
这个过程涉及到几个关键步骤,包括登录处理、Cookie的创建与设置、过滤器的配置以及在请求处理之前读取Cookie中的信息来恢复上下文。 首先,当用户成功登录后,服务器端会创建一个包含用户身份信息的Cookie。这个...
过滤器可以拦截用户请求,并对请求进行预处理(如设置编码、登录验证等),也可以在服务器响应后进行后处理(如压缩响应数据、添加额外的头部信息等)。通过使用过滤器,可以简化应用开发,并将一些通用功能(如登录...