直接上代碼吧,懶得寫了
1、過濾器,難點:
service的調用,網上說可以Autowired,我試了半天不行,最后在init里去手動是可以了,而且觀察也只會初始化一次,一樣的。
body參數的獲取后,流會關閉。如果不考慮其它地方還要再取值,可用ContentCachingRequestWrapper。
如果在攔截器等地方還要再取的話,要用RequestWrapper,代碼在后面
package com.filter; import com.model.user.User; import com.service.sys.LogService; import com.util.StringUtils; import org.springframework.web.util.ContentCachingRequestWrapper; import org.springframework.web.util.ContentCachingResponseWrapper; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Date; public class LoginFilter implements Filter { //無法用AutoWired自動注入,改為在init里手動注入 private LogService logService; @Override public void init(FilterConfig filterConfig) { /* //試了半天DelegatingFilterProxy,一直不行,還是手動注入可以就算了,跟蹤發現只會調用一次,也一樣 if (logService == null) { ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(filterConfig.getServletContext()); LogService logService = context.getBean(LogService.class); this.logService = logService; } */ } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { //改在攔截器里處理,此處直接放過 // chain.doFilter(request, response); RequestWrapper requestWrapper = null; if (request instanceof HttpServletRequest) { requestWrapper = new RequestWrapper((HttpServletRequest) request); } if (requestWrapper == null) { chain.doFilter(request, response); } else { chain.doFilter(requestWrapper, response); } /* HttpServletRequest servletRequest = (HttpServletRequest) request; HttpServletResponse servletResponse = (HttpServletResponse) response; HttpSession session = servletRequest.getSession(); // 獲得用戶請求的URI String path = servletRequest.getRequestURI(); User user = (User) session.getAttribute("user"); // 登陸頁面無需過濾 if (path.contains("/login/")) { doFilter(servletRequest, servletResponse, chain, user); return; } // 判斷如果沒有取到賬號信息,就跳轉到登陸頁面 if (user == null) { // 跳轉到登陸頁面 servletResponse.sendRedirect(servletRequest.getContextPath() + "/login/index.do"); } else { // 已經登陸,繼續此次請求。如有需要,可以再驗證下賬號的操作權限,以防跳過前端界面,直接用工具后台發起的請求 doFilter(servletRequest, servletResponse, chain, user); } */ } void doFilter(HttpServletRequest servletRequest, HttpServletResponse servletResponse, FilterChain chain, User user) throws IOException, ServletException { String contentType = servletRequest.getContentType(); String method = servletRequest.getMethod(); String path = servletRequest.getServletPath(); if (contentType != null && contentType.startsWith("multipart/form-data") && "POST".equalsIgnoreCase(method)) { System.out.println("當前請求為文件上傳,不作請求日志收集"); } else { ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(servletRequest); ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(servletResponse); long start = new Date().getTime(); try { chain.doFilter(requestWrapper, responseWrapper); } finally { String requestBody = servletRequest.getQueryString(); if (StringUtils.isEmpty(requestBody)) { requestBody = new String(requestWrapper.getContentAsByteArray()); } responseWrapper.copyBodyToResponse(); } } } @Override public void destroy() { } }
2、RequestWrapper
網上最多的就是這個,但我試了半天,一直踩坑。主要有:
import javax.servlet.ReadListener; 這個找不到,后來發現是servlet版本太低,升高版本就有了
照着抄后,post提交的body仍然消失了(但沒報錯),表現在比如登錄,明明輸了賬號密碼,也能在Filter里接收到參數,但Controller就是為空,坑了幾晚
package com.filter; import com.util.HttpUtil; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; 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; public class RequestWrapper extends HttpServletRequestWrapper { private final byte[] body; public RequestWrapper(HttpServletRequest request) { super(request); body = HttpUtil.getBodyString(request).getBytes(Charset.forName("UTF-8")); } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } @Override public ServletInputStream getInputStream() { 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(); } }; } @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); } }
3、關鍵的就在這里,最后還是一篇貼子救了命
http://blog.sina.com.cn/s/blog_550048a30102x7pp.html
在大神的代碼下,終於可以了,淚流滿面!
其實之前兩種代碼風格都有見過,就是不知道要判斷一下,按contenttype來區分
public static String getBodyString(ServletRequest request) { String contenttype = request.getContentType(); if (contenttype != null && contenttype.contains("x-www-form-urlencoded")) { String bodystring = ""; Enumeration pars = request.getParameterNames(); while (pars.hasMoreElements()) { String n = (String) pars.nextElement(); bodystring += n + "=" + request.getParameter(n) + "&"; } bodystring = bodystring.endsWith("&") ? bodystring.substring(0, bodystring.length() - 1) : bodystring; return bodystring; } else if (contenttype != null && contenttype.contains("json")) { StringBuilder sb = new StringBuilder(); InputStream inputStream = null; BufferedReader reader = null; try { inputStream = request.getInputStream(); reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8"))); String line; while ((line = reader.readLine()) != null) { sb.append(line); } } catch (IOException e) { e.printStackTrace(); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (reader != null) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } return sb.toString(); } return ""; }
4、過濾器,如果前面沒處理好,這邊要么是獲取到的post是空,要么一獲取完post,就會導致Controller沒值,或是報什么流關閉之類的,也是折騰了幾晚
package com.filter; import com.model.sys.Log; import com.model.user.User; import com.service.sys.LogService; import com.util.HttpUtil; import com.util.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.NamedThreadLocal; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; public class AuthInterceptor extends HandlerInterceptorAdapter { @Autowired private LogService logService; private NamedThreadLocal<Long> startTimeThreadLocal = new NamedThreadLocal<>("StopWatch-StartTime"); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String path = request.getRequestURI(); HttpSession session = request.getSession(); User user = (User) session.getAttribute("user"); // 登陸頁面無需過濾 if (path.contains("/login/")) { return true; } // 判斷如果沒有取到賬號信息,就跳轉到登陸頁面 if (user == null) { // 跳轉到登陸頁面 response.sendRedirect(request.getContextPath() + "/login/index.do"); //response是整個頁面跳轉 //request.getRequestDispatcher("/login/index.do").forward(request, response); //request是內部重定向 return true; } else { startTimeThreadLocal.set(System.currentTimeMillis());//線程安全(該數據只有當前請求的線程可見) return true; } } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws IOException { Object startTimeObj = startTimeThreadLocal.get(); if (startTimeObj == null) return; long time = System.currentTimeMillis() - startTimeThreadLocal.get(); HandlerMethod methodHandler = (HandlerMethod) handler; HttpSession session = request.getSession(); String path = request.getServletPath(); User user = (User) session.getAttribute("user"); AuthAnnotation authAnnotation = methodHandler.getMethodAnnotation(AuthAnnotation.class); String description = authAnnotation != null ? authAnnotation.description() : null; String params = request.getQueryString(); if (StringUtils.isEmpty(params)) { params = HttpUtil.getBodyString(request); } Log log = new Log(); log.setCrudType("MVC"); log.setUserId(user.getId()); log.setUrl(path); log.setDescription(description); log.setRespTime(StringUtils.toInteger(time)); log.setParams(params); logService.insert(log); } }
5、本來在Filter里處理也是可以,但Filter處理不了控制器的屬性(或是我不會),最終換到攔截器里處理,過濾器本來直接放空,但發現不行,就是上面說的流只能讀一次的問題。現在過濾器就是中轉一下,業務的驗證邏輯還是放在攔截器里處理,就是為了這種控制器上面自定義的屬性
@AuthAnnotation(description = "業務流程", authType = AuthAnnotation.AuthType.LOGIN) @RequestMapping(value = "/wfFlow") public String wfFlow() { return "views/workflow/wfFlow"; }
package com.filter; import java.lang.annotation.*; @Documented //文檔生成時,該注解將被包含在javadoc中,可去掉 //@Target(ElementType.METHOD)//目標是方法 @Retention(RetentionPolicy.RUNTIME) //注解會在class中存在,運行時可通過反射獲取 @Inherited public @interface AuthAnnotation { String description(); enum AuthType { PUBLIC, LOGIN, EDIT } AuthType authType() default AuthType.LOGIN; }
終於大功告成了,在此過程中,熟悉了過濾器和攔截器的各種坑,也算是有收獲