需求描述
通过自定义的拦截器对部分接口的返回添加缓存,请求接口和参数作为redisKey
假设自定义 MyHandlerInterceptor 拦截器对部分接口进行拦截, 我们需要实现HandlerInterceptor 接口并重写 其中的部分方法如下:
@Component
public class MyHandlerInterceptor implements HandlerInterceptor {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String eid = request.getParameter("eid");
// 获取对应参数 的缓存
String redisKey = getRedisKey(request);
String s = stringRedisTemplate.opsForValue().get(redisKey);
if(StringUtils.isNotBlank(s)){
ResponseData responseData = JSONObject.parseObject(s, ResponseData.class);
// 直接返回前端
result(response,responseData);
return false;
}
return HandlerInterceptor.super.preHandle(request, response, handler);
}
//在afterCompletion 方法中 获取到 方法执行后的响应体,判断如果执行成功则 生成对应key 和 对应结果,存入redis 中
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
String eid = request.getParameter("eid");
String responseBody = ContentCachingWrapperFilter.getResponseBody(response);
ResponseData responseData = JSONObject.parseObject(responseBody, ResponseData.class);
if(responseData.getCode() == 1){
String redisKey = getRedisKey(request);
stringRedisTemplate.opsForValue().set(redisKey,JSONObject.toJSONString(responseData), 30, TimeUnit.MINUTES);
}
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
public String getRedisKey(HttpServletRequest request){
Map<String, String[]> parameterMap = request.getParameterMap();
List<String> resultList = new ArrayList<>();
parameterMap.forEach((s, strings) -> {
for (String string : strings) {
if(StringUtils.isNotBlank(string)){
resultList.add(string);
}
}
});
// 参数排序
List<String> collect = resultList.stream().sorted().collect(Collectors.toList());
String join = StringUtils.join(collect, "_");
return request.getRequestURI()+"_"+ join;
}
// 直接返回响应体给前端
public static void result(HttpServletResponse response, ResponseData result) {
try {
response.setContentType("text/json");
response.setCharacterEncoding("UTF-8");
PrintWriter write = response.getWriter();
String s = JSONUtil.toJsonStr(result);
write.write(s);
write.flush();
write.close();
} catch ( IOException e ) {
e.printStackTrace();
}
}
}
以上代码关键在于如何在 afterCompletion 方法中获取到相应参数
String responseBody = ContentCachingWrapperFilter.getResponseBody(response);
借助以下工具类
package com.boshiyun.application.intercept;
import org.apache.commons.io.IOUtils;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.WebUtils;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
/**
* 缓存请求体和响应体过滤器
*
* <p>
* 由于 requestBody 和 responseBody 分别对应的是 InputStream 和 OutputStream,由于流的特性,读取完之后就无法再被使用了。
* 所以,需要额外缓存一次流信息。
* </p>
*
* @author Charles7c
* @since 2022/9/22 16:33
*/
@Component
public class ContentCachingWrapperFilter extends OncePerRequestFilter implements Ordered {
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 10;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// 包装流,可重复读取
if (!(request instanceof ContentCachingRequestWrapper)) {
request = new ContentCachingRequestWrapper(request);
}
if (!(response instanceof ContentCachingResponseWrapper)) {
response = new ContentCachingResponseWrapper(response);
}
filterChain.doFilter(request, response);
updateResponse(response);
}
/**
* 更新响应(不操作这一步,会导致接口响应空白)
*
* @param response 响应对象
* @throws IOException /
*/
private void updateResponse(HttpServletResponse response) throws IOException {
ContentCachingResponseWrapper responseWrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
Objects.requireNonNull(responseWrapper).copyBodyToResponse();
}
/**
* 获取请求体
*
* @param request 请求对象
* @return 请求体
*/
public static String getRequestBody(HttpServletRequest request) throws IOException {
String requestBody = "";
ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
if (wrapper != null) {
requestBody = IOUtils.toString(wrapper.getContentAsByteArray(), StandardCharsets.UTF_8.toString());
}
return requestBody;
}
/**
* 获取响应体
*
* @param response 响应对象
* @return 响应体
*/
public static String getResponseBody(HttpServletResponse response) throws IOException {
String responseBody = "";
ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
if (wrapper != null) {
responseBody = IOUtils.toString(wrapper.getContentAsByteArray(), StandardCharsets.UTF_8.toString());
}
return responseBody;
}
}
requestBody 和 responseBody 分别对应的是 InputStream 和 OutputStream,由于流的特性,读取完之后就无法再被使用了。以上的逻辑就是起到 流的重复使用