package q.admin.api.annotation;
import java.lang.annotation.*;
/**
* 防止重新提交
*/
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit {
}
package w.admin.api.config;
import w.admin.api.interceptor.ApiInterceptor;
import w.admin.api.interceptor.RepeatSubmitInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Autowired
private RepeatSubmitInterceptor repeatSubmitInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(repeatSubmitInterceptor);
}
}
package q.admin.api.config;
import a.admin.api.interceptor.RepeatableFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Filter过滤器自定义配置
*/
@Configuration
public class FilterConfig{
@Bean
public FilterRegistrationBean someFilterRegistration(){
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new RepeatableFilter());
registration.addUrlPatterns("/*");
registration.setName("repeatableFilter");
registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
return registration;
}
}
package q.admin.api.interceptor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.MediaType;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* 重复提交过滤器-在拦截器执行之前先执行此过滤器,用于将request/response等对象封装到自定义的请求包装器中
*/
public class RepeatableFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
ServletRequest requestWrapper = null;
if (request instanceof HttpServletRequest && StringUtils.equalsAnyIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_JSON_UTF8_VALUE)) {
requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response);
}
if (null == requestWrapper) {
chain.doFilter(request, response);
} else {
chain.doFilter(requestWrapper, response);
}
}
@Override
public void destroy() {
}
}
入参处理
package q.admin.api.interceptor;
import q.admin.api.utils.HttpHelper;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletResponse;
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;
/**
* 构建可重复读取inputStream的request
*/
public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException {
super(request);
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
body = HttpHelper.getBodyString(request).getBytes("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() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
}
package q.admin.api.utils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
/**
* 通用http工具封装
*/
public class HttpHelper {
private static final Logger LOGGER = LoggerFactory.getLogger(HttpHelper.class);
public static String getBodyString(ServletRequest 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();
}
}
package ccff.admin.api.interceptor;
import ccff.admin.api.annotation.RepeatSubmit;
import com.alibaba.fastjson.JSON;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import q.web.model.R;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
/**
* 重复提交拦截器
*/
@Component
public abstract class RepeatSubmitInterceptor extends HandlerInterceptorAdapter {
//开始拦截
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
RepeatSubmit submitAnnotation = method.getAnnotation(RepeatSubmit.class);
if (submitAnnotation != null) {
//如果是重复提交,则进行拦截,拒绝请求
if (this.isRepeatSubmit(request)) {
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().print(JSON.toJSONString(R.fail(500, "不允许重复提交,请稍后重试!")));
return false;
}
}
return true;
} else {
return super.preHandle(request, response, handler);
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
super.afterCompletion(request, response, handler, ex);
}
//自定义方法逻辑-判定是否重复提交
public abstract boolean isRepeatSubmit(HttpServletRequest request);
}
package q.admin.api.interceptor;
import q.admin.api.dto.RepeatSubmitCacheDto;
import q.admin.api.utils.HttpHelper;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Component
@Slf4j
public class SameUrlDataRepeatInterceptor extends RepeatSubmitInterceptor{
//防重提交 key
public static final String REPEAT_SUBMIT_KEY = "Repeat_Submit:";
private static final int IntervalTime = 8;
//构建本地缓存,有效时间为8秒钟
private final Cache<String,String> cache= CacheBuilder.newBuilder().expireAfterWrite(IntervalTime, TimeUnit.SECONDS).build();
//真正实现“是否重复提交的逻辑”
@Override
public boolean isRepeatSubmit(HttpServletRequest request) {
String currParams= HttpHelper.getBodyString(request);
if (StringUtils.isBlank(currParams)){
currParams=new Gson().toJson(request.getParameterMap());
}
//获取请求地址,充当A1
String url=request.getRequestURI();
//充当B1
RepeatSubmitCacheDto currCacheData=new RepeatSubmitCacheDto(currParams,System.currentTimeMillis(),url);
//充当键A1
String cacheRepeatKey=REPEAT_SUBMIT_KEY+url;
String cacheValue=cache.getIfPresent(cacheRepeatKey);
log.info("缓存中的Key对应的值Value: {}",cacheValue);
//从缓存中查找A1对应的值,如果存在,说明当前请求不是第一次了.
if (StringUtils.isNotBlank(cacheValue)){
//充当B2
RepeatSubmitCacheDto preCacheData=new Gson().fromJson(cacheValue,RepeatSubmitCacheDto.class);
if (this.compareParams(currCacheData,preCacheData) && this.compareTime(currCacheData,preCacheData)){
return true;
}
}
//否则,就是第一次请求
Map<String, Object> cacheMap = new HashMap<>();
cacheMap.put(url, currCacheData);
cache.put(cacheRepeatKey,new Gson().toJson(currCacheData));
return false;
}
//比较参数
private boolean compareParams(RepeatSubmitCacheDto currCacheData, RepeatSubmitCacheDto preCacheData){
Boolean res=currCacheData.getRequestData().equals(preCacheData.getRequestData());
return res;
}
//判断两次间隔时间
private boolean compareTime(RepeatSubmitCacheDto currCacheData, RepeatSubmitCacheDto preCacheData){
Boolean res=( (currCacheData.getCurrTime() - preCacheData.getCurrTime()) < (IntervalTime * 1000) );
return res;
}
}