业务场景:
当前业务有个年份管理界面,比如把2020年此年份关闭 ,那么2020年此年份的所有数据都无法再进行相关操作(增删改),但是可以查询。
解决方案:
1.后端写个接口,前端调用判断下
问题:如果界面多,按钮多,前端工作量也是挺大的
2.后端写个拦截器,实现接口拦截,如果查出此年份已关闭,后端给出提示
问题:比较完美的解决了问题,但是所有请求都会进拦截器(此问题也好解决,加路径过滤,给需要的方法加特殊标识区分出来,但是由于业务开发已经基本完成,前后端联调也基本完成。再去改动接口路径不太合适。暂时没想出其他方案)
此处采用方案二:
1.最开始采用注解+拦截器
问题:
报错:Required request body is missing
原因:
由于 request中getReader()和getInputStream()只能调用一次。
所以在Controller里面方法上@ResponseBody会再次调用一次getInputStream(),此时就报错了
解决方案:
把请求保存,构建可重复读取inputStream的request。于是就用到了过滤器
2.注解+过滤器+拦截器
一、创建过滤器
1.增加request封装类,保存流
package com.cloud.common.adapter;
import io.micrometer.core.instrument.util.StringUtils;
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;
/**
* @author GS
* @Description 由于 request中getReader()和getInputStream()只能调用一次 导致在Controller @ResponseBody的时候获取不到 null 或 Stream closed
* 在项目中,可能会出现需要针对接口参数进行校验等问题
* 构建可重复读取inputStream的request.
* @date 2022/6/6 8:51
*/
public class RequestWrapper extends HttpServletRequestWrapper {
// 将流保存下来
private byte[] requestBody;
public RequestWrapper(HttpServletRequest request) throws IOException {
super(request);
requestBody = readBytes(request.getReader(), "utf-8");
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream basic = new ByteArrayInputStream(requestBody);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return basic.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
/**
* 通过BufferedReader和字符编码集转换成byte数组
*
* @param br
* @param encoding
* @return
* @throws IOException
*/
private byte[] readBytes(BufferedReader br, String encoding) throws IOException {
String str = null, retStr = "";
while ((str = br.readLine()) != null) {
retStr += str;
}
if (StringUtils.isNotBlank(retStr)) {
return retStr.getBytes(Charset.forName(encoding));
}
return null;
}
}
2.创建过滤器
package com.cloud.common.adapter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* 过滤器
*
* @author GS
* @date 2022/6/6 9:08
*/
public class RequestFilter implements Filter {
// @Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
ServletRequest requestWrapper = null;
if (servletRequest instanceof HttpServletRequest) {
requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest);
}
if (requestWrapper == null) {
filterChain.doFilter(servletRequest, servletResponse);
} else {
filterChain.doFilter(requestWrapper, servletResponse);
}
}
@Override
public void destroy() {
}
}
3.将过滤器注入spring容器
package com.cloud.common.adapter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
/**
* 将过滤器注入spring容器中
*
* @author GS
* @date 2022/6/6 10:54
*/
@Configuration
public class FilterConfig {
@Bean
Filter bodyFilter() {
return new RequestFilter();
}
@Bean
public FilterRegistrationBean<RequestFilter> filters() {
FilterRegistrationBean<RequestFilter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter((RequestFilter) bodyFilter());
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.setName("requestFilter");
//filterRegistrationBean.setOrder();多个filter的时候order的数值越小 则优先级越高
return filterRegistrationBean;
}
}
二、创建注解
package com.cloud.common.adapter;
import java.lang.annotation.*;
/**
* 年份是否关闭注解
*
* @author GS
* @date 2022/5/16 10:15
*/
@Documented
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface YearAnnotation {
}
三、创建拦截器
1.创建拦截器
package com.cloud.common.adapter;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.cloud.budget.service.BgYearSetService;
import com.cloud.common.core.util.ResponseResult;
import io.micrometer.core.instrument.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StreamUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
/**
* 请求拦截器
*
* @author GS
* @date 2022/5/16 10:16
*/
@Slf4j
public class RequestInterceptor extends HandlerInterceptorAdapter {
// 引入年份service
@Autowired
private yearService yearService;
/**
* 进入拦截的方法前触发
*
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
//获取当前方法上的指定注解
YearAnnotation yearAnnotation = method.getAnnotation(YearAnnotation.class);
//判断当前注解是否存在
if (yearAnnotation != null) {
// 获取请求方式
String requestMethod = request.getMethod();
// 获取请求参数
String paramsStr = "";
String year = "";
if ("GET".equals(requestMethod)) {
//Get方式请求参数
paramsStr = request.getQueryString();
year = paramsStr;
} else {
///获取post、put、delete请求参数
paramsStr = this.getPostParm(request);
// 这里应该有个方法判断是object 还是 list,使用的json解析方法也不一样
JSONObject jsonObject = JSONUtil.parseObj(paramsStr);
year = jsonObject.getStr("year");
}
if (year != null) {
boolean sealing = yearService.isSealing(year);
if (sealing) {
returnJson(response, JSONUtil.toJsonStr(ResponseResult.failed("此年份已关闭无法操作相关数据")));
return false;
}
}
}
return true;
}
/**
* 离开拦截的方法后触发
*
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
/**
* 返回
*
* @param response
* @param json
* @throws Exception
*/
private void returnJson(HttpServletResponse response, String json) throws Exception {
PrintWriter writer = null;
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html; charset=utf-8");
try {
writer = response.getWriter();
writer.print(json);
} catch (IOException e) {
throw e;
} finally {
if (writer != null) {
writer.close();
}
}
}
private String getPostParm(HttpServletRequest request) throws Exception{
try {
RequestWrapper readerWrapper = new RequestWrapper(request);
return getBodyParams(readerWrapper.getInputStream(), request.getCharacterEncoding());
} catch (Exception e) {
throw e;
}
}
/**
* 获取POST、PUT、DELETE请求中Body参数
*
* @param
* @return 字符串
*/
private String getBodyParams(ServletInputStream inputStream, String charset) throws Exception {
try {
String body = StreamUtils.copyToString(inputStream, Charset.forName(charset));
if (StringUtils.isEmpty(body)) {
return "";
}
return body;
} catch (Exception e) {
throw e;
}
}
}
2.注入拦截器
package com.cloud.common.adapter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 将拦截去注入spring容器中
*
* @author GS
* @date 2022/5/16 10:19
*/
@Configuration
public class InterceptorRegister implements WebMvcConfigurer {
@Bean
public RequestInterceptor yearSealingInterceptor() {
return new RequestInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(yearSealingInterceptor());
}
}
四、使用
@YearAnnotation
@PutMapping
@ApiOperation(value = "保存")
public Result update(@RequestBody PreDTO dto) {
preService.updateDeptBudget(dto);
return Result.ok();
}
注:@YearAnnotation 把自定义的注解加到Controller层的对应方法上,就能实现拦截了
参考引用:
Required request body is missing_hai330的博客-CSDN博客
Spring Boot HandlerInterceptor拦截器 :Required request body is missing OR Stream closed-蒲公英云