基于若依分离版防重复提交 进行适配开发
前提知识:
防重复提交功能 基于 request 完成防重复提交功能,但HttpServletRequest 一旦获取参数,请求中将不会存储参数信息,故需要开发可重复获取参数的请求对象。然后再springboot提供的拦截器中进行判断,实现防重复提交.
如果对原生的request进行包装,哪个时机进行包装?
这是需要引入过滤器,因过滤器的执行顺序在拦截器的前面,并且过滤器只能拿到请求对象和响应对象,所以在过滤器中可以很好的完成我们的需求
inputStream的request/**
* 构建可重复读取inputStream的request
*/
public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper {
/**
* UTF-8 字符集
*/
public static final String UTF8 = "UTF-8";
private final byte[] body;
public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException {
super(request);
request.setCharacterEncoding(UTF8);
response.setCharacterEncoding(UTF8);
body = HttpUtil.getBodyString(request).getBytes(UTF8);
}
@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 int available() throws IOException {
return body.length;
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
}
实现Filter接口
/**
* Repeatable 过滤器 实现可重复读参数请求
*/
public class RepeatableFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
ServletRequest requestWrapper = null;
if (request instanceof HttpServletRequest) {
requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response);
}
if (requestWrapper == null) {
chain.doFilter(request, response);
} else {
chain.doFilter(requestWrapper, response);
}
}
@Override
public void destroy() {
}
}
FilterConfig类/**
* @author zhangrl
* @description 过滤器相关配置类
* @date 2022/11/16 13:30
*/
@Configuration
public class FilterConfig {
/**
* 主要作用:实现重复读取参数的request
* @return FilterRegistrationBean
*/
@Bean
public FilterRegistrationBean filterRegistrationBean(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new RepeatableFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.setName("repeatableFilter");
registrationBean.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
return registrationBean;
}
}
/**
* 防重复提交注解
*/
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit {
/**
* 间隔时间(ms),小于此时间视为重复提交
*/
public int interval() default 5000;
/**
* 提示消息
*/
public String message() default "不允许重复提交,请稍候再试";
}
/**
* @author rikylinz
* @description 防重复提交
* @date 2022/11/15 14:35
*/
@Component
public abstract class RepeatSubmitInterceptor implements HandlerInterceptor {
@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 annotation = method.getAnnotation(RepeatSubmit.class);
if (annotation != null) {
if (this.isRepeatSubmit(request, annotation))
{
CommonResult commonResult = new CommonResult();
commonResult.setCode(503);
commonResult.setMessage(annotation.message());
this.result(HttpUtil.getResponse(), JSON.toJSONString(commonResult));
return false;
}
}
return true;
} else {
return true;
}
}
/**
* 验证是否重复提交由子类实现具体的防重复提交的规则,待实现
*
* @param request
* @return
* @throws Exception
*/
public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation);
/**
* 被拦截后返回给调用者的信息
* @param response 响应
* @param res 返回信息
*/
private void result(HttpServletResponse response, String res){
PrintWriter writer = null;
try {
response.setStatus(503);
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
response.setCharacterEncoding(Consts.UTF_8.toString());
writer = response.getWriter();
writer.write(res);
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
writer.flush();
writer.close();
}
}
}
/**
* @author rikylinz
* @description 判断请求url和数据是否和上一次相同,如果和上次相同,则是重复提交表单。 有效时间为5秒内。
* @date 2022/11/15 11:21
*/
@Slf4j
@Component
public class RepeatSubmitRule extends RepeatSubmitInterceptor {
public final String REPEAT_PARAMS = "repeatParams";
public final String REPEAT_TIME = "repeatTime";
/**
* 防重提交 redis key
*/
public static final String REPEAT_SUBMIT_KEY = "repeat_submit:";
@Autowired
private RedisUtil redisUtil;
@Override
public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation) {
UserPo user = ThreadLocalUtil.getUser();
String mobile = user.getMobile();
String nowParams = "";
if (request instanceof RepeatedlyRequestWrapper) {
RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request;
//获取body中的数据方法下面会有展示
nowParams = HttpUtil.getBodyString(repeatedlyRequest);
}else{
//获取body中的数据方法下面会有展示
nowParams = HttpUtil.getBodyString(request);
}
// body参数为空,获取Parameter的数据
if (StringUtils.isEmpty(nowParams))
{
nowParams = JSON.toJSONString(request.getParameterMap());
}
Map<String, Object> nowDataMap = new HashMap<>();
nowDataMap.put(REPEAT_PARAMS, nowParams);
nowDataMap.put(REPEAT_TIME, System.currentTimeMillis());
// 请求地址(作为存放cache的key值)
String url = request.getRequestURI();
// 唯一标识(指定key + url + 手机号)
String cacheRepeatKey = REPEAT_SUBMIT_KEY + url + mobile;
Map<String, Object> cacheMap = new HashMap<>();
cacheMap.put(url, nowDataMap);
//原子操作 添加 返回结果 true 添加成功(redis不存在) false 添加失败
Boolean isExist = null;
try {
isExist = redisUtil.setStringByAtomicity(cacheRepeatKey, JSON.toJSONString(cacheMap),
Integer.valueOf(annotation.interval()).longValue(), TimeUnit.MILLISECONDS);
} catch (Exception e) {
log.error("[校验防重复提交]原子性添加数据失败,原因:{}",e);
return false;
}
if (Boolean.FALSE.equals(isExist))
{
String sessionObj = null;
try {
sessionObj = redisUtil.getString(cacheRepeatKey);
} catch (Exception e) {
log.error("[校验防重复提交]redis获取数据失败,原因:{}",e);
return false;
}
JSONObject sessionMap = JSONObject.parseObject(sessionObj);
if (sessionMap.containsKey(url))
{
Map<String, Object> preDataMap = (Map<String, Object>) sessionMap.get(url);
if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap, annotation.interval()))
{
return true;
}
}
}
return false;
}
/**
* 判断参数是否相同
*/
private boolean compareParams(Map<String, Object> nowMap, Map<String, Object> preMap)
{
String nowParams = (String) nowMap.get(REPEAT_PARAMS);
String preParams = (String) preMap.get(REPEAT_PARAMS);
return nowParams.equals(preParams);
}
/**
* 判断两次间隔时间
*/
private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> preMap, int interval)
{
long time1 = (Long) nowMap.get(REPEAT_TIME);
long time2 = (Long) preMap.get(REPEAT_TIME);
if ((time1 - time2) < interval)
{
return true;
}
return false;
}
}
RedisUtil.java
/**
* string类型-获取value
* @param key key
* @return value
* @throws Exception
*/
public String getString(String key) throws Exception{
return stringRedisTemplate.opsForValue().get(key);
}
/**
* string类型-设置key-value 原子性操作
* @param key key
* @param value value
* @throws Exception
*/
public Boolean setStringByAtomicity(String key,String value,Long timeout,TimeUnit timeUnit) throws Exception{
return stringRedisTemplate.opsForValue().setIfAbsent(key, value, timeout, timeUnit);
}
HttpUtil.java
/**
* 获取body消息体中的参数列表
* @param request 请求对象
* @return
*/
public static String getBodyString(HttpServletRequest request)
{
StringBuilder sb = new StringBuilder();
BufferedReader reader = null;
try (InputStream inputStream = request.getInputStream())
{
reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.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();
}