Verificación de envío repetido + Los datos de flujo HttpServletRequest no se pueden leer repetidamente

fondo funcional

El tercero quiere obtener nuestros datos de interfaz. Realizamos una verificación de autenticación unificada en la solicitud e interceptamos envíos repetidos. Todos estos deben obtener los parámetros de la solicitud actual y realizar la verificación para evitar envíos repetidos.

lograr

① Escriba una clase de anotación personalizada

import java.lang.annotation.*;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface RedisLock {
    int expire() default 5;
}

Luego agregue la anotación @RedisLock al método que necesitamos para verificar el envío repetido
inserte la descripción de la imagen aquí
②Escriba un interceptor personalizado y escriba la lógica comercial

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
public class RepeatSubmitInterceptor extends HandlerInterceptorAdapter {

    private static final Logger LOGGER = LoggerFactory.getLogger(RepeatSubmitInterceptor.class);

    @Value("${spring.profiles.active}")
    private String springProfilesActive;
    @Value("${spring.application.name}")
    private String springApplicationName;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            RedisLock redisLock = method.getAnnotation(RedisLock.class);
            if (redisLock != null) {
                //设置缓存时间
                Integer expire = redisLock.expire();
                if (expire < 0) {
                    expire = 5;
                }
                LOGGER.info("进入重复提交效验");
                //就行重复效验
                if (isRepeatSubmit(request, expire)) {
                    ServletUtils.writeResponse(response, ResultEnum.CODE_6__REPETITION_OPERATION);
                    return false;
                }
            }
        }
        return true;
    }

    private Boolean isRepeatSubmit(HttpServletRequest request, Integer expire) throws IOException {
//        String currParams = getBodyString(request);
//        if (StringUtils.isBlank(currParams)) {
//            currParams = JSON.toJSONString(request.getParameterMap());
//        }
//        //参数加密
//        String md5Params = MD5Utils.getMD5(currParams);
        //设置Key值
        //同一个人,5秒内不能重复保存同一个接口
        LoginUserBo userBo = UserUtils.getUserFromSession();
        String key = "repeatSubmitLock:" + springApplicationName + ":" + springProfilesActive + ":" + userBo.getOrgNum() + ":" + userBo.getName() + ":" + request.getRequestURI();
        LOGGER.info(key);
        //加入分布式事务锁
        boolean exist = JedisUtils.tryGetDistributedLock(key, request.getRequestURI(), expire);
        if (!exist) {
            return true;
        }
        return false;
    }

    public static String getBodyString(ServletRequest request) {
        //暂时不加参数效验,未解决request流只读一次
        StringBuilder sb = new StringBuilder();
        BufferedReader reader = null;
        try (InputStream 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) {
            LOGGER.warn("getBodyString出现问题!");
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    LOGGER.error(ExceptionUtils.getMessage(e));
                }
            }
        }
        return sb.toString();
    }
}

El código anterior solo implementa a la misma persona, la misma interfaz no se puede guardar repetidamente en 5 segundos y el problema de leer solo el flujo de solicitud en getBodyString no se ha resuelto.

Los datos de flujo de HttpServletRequest no se pueden leer repetidamente

Una solución madura y común es interceptar cualquier solicitud a través de un interceptor, siempre que los parámetros de la solicitud actual se obtengan en el interceptor. Sin embargo, cuando solo recibe la solicitud en el interceptor y usa métodos como request.getParameter(), solo puede obtener los datos del formulario y los parámetros de la barra de direcciones, pero no los datos del encabezado de la solicitud. Al usar request.getInputStream(), puede obtener los parámetros. Sin embargo, en el proceso comercial de la interfaz específica, si utiliza métodos como request.getParameter(), no se pueden obtener los parámetros entrantes.
inserte la descripción de la imagen aquí
Encontraremos que después de usar el método request.getInputStream() en el interceptor para obtener los parámetros, y luego ir a nuestra interfaz de llamada real, no obtendremos los parámetros, diciendo que la transmisión está cerrada, porque la transmisión solo se puede leer una vez.

solución

Vuelva a escribir la clase contenedora HttpServletRequestWrapper, use el filtro para reemplazar HttpServletRequest en el subproceso con el envuelto antes de cualquier solicitud y escriba los datos de flujo en el caché al mismo tiempo que se llama al método getInputStream. Si desea obtener parámetros más tarde, simplemente lea los datos almacenados en caché directamente. De esta forma, el contenido de la Solicitud se puede leer varias veces.
①Encapsular solicitud de clase personalizada ContentCachingRequestWrapper

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

/**
 *
 * 重写 HttpServletRequestWrapper
 *
 * @Author: didi
 * @Date 2022-09-21
 */
public class ContentCachingRequestWrapper extends HttpServletRequestWrapper {
    private final byte[] body;

    public ContentCachingRequestWrapper(HttpServletRequest request) {
        super(request);
        StringBuilder sb = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8))){
            String line = "";
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        body = sb.toString().getBytes(StandardCharsets.UTF_8);
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {

        final ByteArrayInputStream inputStream = 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() throws IOException {
                return inputStream.read();
            }
        };
    }

    public byte[] getBody() {
        return body;
    }
    /**
     * 获取请求Body
     *
     * @return String
     */
    public String getBodyString() {
        return new String(body);
    }
}

②Filtro personalizado

import com.github.pagehelper.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


import javax.servlet.*;
import javax.servlet.FilterConfig;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * @Author didi
 * @Create 2022/9/21 9:04
 */
public class ReplaceStreamFilter  implements Filter {
    private static final Logger logger = LoggerFactory.getLogger(ReplaceStreamFilter.class);

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        logger.info("StreamFilter初始化...");
    }


    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        String contentType = request.getContentType();
        if(!StringUtil.isEmpty(contentType) && contentType.contains("multipart/form-data")) {
            chain.doFilter(request, response);
            return;
        }
        if(request instanceof HttpServletRequest) {
             request = new ContentCachingRequestWrapper((HttpServletRequest) request);
        }
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        logger.info("StreamFilter销毁...");
    }
}

③Agregar configuración de filtro

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;

/**
 * @Author didi
 * @Description 过滤器配置类
 * @Create 2022/9/21 9:06
 */
@Configuration
public class FilterContextConfig {
    /**
     * 注册过滤器
     *
     * @return FilterRegistrationBean
     */
    @Bean
    public FilterRegistrationBean someFilterRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(replaceStreamFilter());
        registration.addUrlPatterns("/*");
        registration.setName("streamFilter");
        return registration;
    }

    /**
     * 实例化StreamFilter
     *
     * @return Filter
     */
    @Bean(name = "replaceStreamFilter")
    public Filter replaceStreamFilter() {
        return new ReplaceStreamFilter();
    }
}

El método de implementación específico del flujo de solicitud encapsulado

ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
String currParams = requestWrapper.getBodyString();

Interceptor personalizado completo

public class RepeatSubmitInterceptor extends HandlerInterceptorAdapter {

    private static final Logger LOGGER = LoggerFactory.getLogger(RepeatSubmitInterceptor.class);

    @Value("${spring.profiles.active}")
    private String springProfilesActive;
    @Value("${spring.application.name}")
    private String springApplicationName;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            RedisLock redisLock = method.getAnnotation(RedisLock.class);
            if (redisLock != null) {
                //设置缓存时间
                Integer expire = redisLock.expire();
                if (expire < 0) {
                    expire = 5;
                }
                LOGGER.info("进入重复提交效验");
                //就行重复效验
                if (isRepeatSubmit(request, expire)) {
                    ServletUtils.writeResponse(response, ResultEnum.CODE_6__REPETITION_OPERATION);
                    return false;
                }
            }
        }
        return true;
    }

    /**
     *5秒内判断重复提交 同一个人同一个参数同一个参数(地址栏+请求体,不包含文件流请求体)为重复提交进行拦截
     * @param request 当前请求
     * @param expire redis
     * @return
     */
    private Boolean isRepeatSubmit(HttpServletRequest request, Integer expire)  {
        /**
         *  TODO ContentCachingRequestWrapper被new两次,重复新建,按理来说整个request以及被替换,无需在new
         *  在没有其他框架封装request时可以进行强转(比如zuul,shiro,security)
         */
        String currParams="";
        String contentType = request.getContentType();
        Map<String, String[]> parameterMap = request.getParameterMap();
        if (!parameterMap.isEmpty()) {
            currParams = JSON.toJSONString(parameterMap);
        }
        // 如果上传文件不能对request进行包装,提升流已经关闭
        if(StringUtil.isNotEmpty(contentType) && !contentType.contains("multipart/form-data")) {
            ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
            String bodyString = requestWrapper.getBodyString();
            currParams = StringUtils.isEmpty(currParams) ? bodyString : currParams + bodyString;
        }
        LOGGER.info("requestParamJson --> {}", currParams);
        //参数加密
        String md5Params = MD5Utils.getMD5(currParams);
        //设置Key值
        //同一个人,5秒内不能重复保存同一个接口
        LoginUserBo userBo = UserUtils.getUserFromSession();
        String key = "repeatSubmitLock:" + springApplicationName + ":" + springProfilesActive + ":" + userBo.getOrgNum() + ":" + userBo.getName() + ":" + request.getRequestURI()+":"+md5Params;
        LOGGER.info(key);
        //加入分布式事务锁
        boolean exist = JedisUtils.tryGetDistributedLock(key, request.getRequestURI(), expire);
        if (!exist) {
            return true;
        }
        return false;
    }
}

Supongo que te gusta

Origin blog.csdn.net/weixin_43832604/article/details/128784080
Recomendado
Clasificación