765 lines
23 KiB
Markdown
765 lines
23 KiB
Markdown
![]() |
---
|
|||
|
date: 2025-05-07
|
|||
|
category:
|
|||
|
- JAVA
|
|||
|
tag:
|
|||
|
- 表单
|
|||
|
- 重复提交
|
|||
|
- 防重
|
|||
|
---
|
|||
|
|
|||
|
防止表单和参数重复提交案例
|
|||
|
<!-- more -->
|
|||
|
# 防止表单和参数重复提交
|
|||
|
|
|||
|
## 1. 编写注解
|
|||
|
|
|||
|
```java
|
|||
|
/**
|
|||
|
* 防止表单重复提交
|
|||
|
*
|
|||
|
* @author 氓氓编程
|
|||
|
* @Date: 2021-06-08-16:35
|
|||
|
* @Inherited @interface 自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节
|
|||
|
* @Target 用于描述注解的使用范围(作用于方法上)
|
|||
|
* @Retention 被描述的注解在什么范围内有效 (在运行时有效,即运行时保留)
|
|||
|
*/
|
|||
|
@Inherited
|
|||
|
@Target(ElementType.METHOD)
|
|||
|
@Retention(RetentionPolicy.RUNTIME)
|
|||
|
@Documented
|
|||
|
public @interface RepeatSubmit {
|
|||
|
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
## 2. 自定义拦截器
|
|||
|
|
|||
|
```java
|
|||
|
/**
|
|||
|
* @author 茫茫编程
|
|||
|
* @Date: 2021-06-08-16:29
|
|||
|
*/
|
|||
|
@Slf4j
|
|||
|
@Component
|
|||
|
public abstract class RepeatSubmitInterceptor extends HandlerInterceptorAdapter {
|
|||
|
/**
|
|||
|
* @param request 是指经过spring封装的请求对象, 包含请求地址, 头, 参数, body(流)等信息.
|
|||
|
* @param response 是指经过spring封装的响应对象, 包含输入流, 响应body类型等信息.
|
|||
|
* @param handler 是指controller的@Controller注解下的"完整"方法名, 是包含exception等字段信息的.
|
|||
|
* @return 是否放行
|
|||
|
* @throws Exception 异常
|
|||
|
*/
|
|||
|
@Override
|
|||
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
|||
|
//instanceof是Java中的二元运算符,左边是对象,右边是类;当对象是右边类或子类所创建对象时,返回true;否则,返回false。
|
|||
|
if (handler instanceof HandlerMethod) {
|
|||
|
log.info("进来了");
|
|||
|
//把handler强转为HandlerMethod
|
|||
|
HandlerMethod handlerMethod = (HandlerMethod) handler;
|
|||
|
//获取当前请求的方法
|
|||
|
Method method = handlerMethod.getMethod();
|
|||
|
//获取当前去请求方法上是否有注解@RepeatSubmit
|
|||
|
RepeatSubmit repeatSubmit = method.getAnnotation(RepeatSubmit.class);
|
|||
|
//如果方法有@RepeatSubmit注解,进入if
|
|||
|
if (repeatSubmit != null) {
|
|||
|
//判断是否是重复提交,重复提交进入
|
|||
|
if (isRepeatSubmit(request, response)) {
|
|||
|
//返回消息实体类
|
|||
|
R message = R.error().message("不允许重复提交");
|
|||
|
//把消息响应给客户端
|
|||
|
ServletUtils.renderString(response, JSONUtil.toJsonStr(message));
|
|||
|
//拦截
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
//注解和不重复直接放行
|
|||
|
return true;
|
|||
|
} else {
|
|||
|
//如果handler不是HandlerMethod或子类放行
|
|||
|
return super.preHandle(request, response, handler);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 弗雷调用该方法会使用子类的实现
|
|||
|
* 验证是否重复提交由子类实现具体的防重复提交的规则
|
|||
|
*
|
|||
|
* @param request 请求
|
|||
|
* @return 是否是重复提交
|
|||
|
*/
|
|||
|
public abstract boolean isRepeatSubmit(HttpServletRequest request,HttpServletResponse response);
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
## 3. 自定义拦截器子类
|
|||
|
|
|||
|
```java
|
|||
|
/**
|
|||
|
* 判断请求url和数据是否和上一次相同,
|
|||
|
* 如果和上次相同,则是重复提交表单。 有效时间为10秒内。
|
|||
|
*
|
|||
|
* @author 氓氓编程
|
|||
|
* @Date: 2021-06-08-17:27
|
|||
|
*/
|
|||
|
@Slf4j
|
|||
|
@Component
|
|||
|
public class SameUrlDataInterceptor extends RepeatSubmitInterceptor {
|
|||
|
/**
|
|||
|
* 重复参数
|
|||
|
*/
|
|||
|
public final String REPEAT_PARAMS = "repeatParams";
|
|||
|
|
|||
|
/**
|
|||
|
* 重复时间
|
|||
|
*/
|
|||
|
public final String REPEAT_TIME = "repeatTime";
|
|||
|
|
|||
|
/**
|
|||
|
* 间隔时间单位秒
|
|||
|
* 两次相同参数的请求,如果间隔时间大于该参数,系统不会认定为重复提交的数据
|
|||
|
*/
|
|||
|
private int intervalTime = 120;
|
|||
|
|
|||
|
public SameUrlDataInterceptor(JwtUtil jwtUtil, RedisCache redisCache) {
|
|||
|
this.jwtUtil = jwtUtil;
|
|||
|
this.redisCache = redisCache;
|
|||
|
}
|
|||
|
|
|||
|
public void setIntervalTime(int intervalTime) {
|
|||
|
this.intervalTime = intervalTime;
|
|||
|
}
|
|||
|
|
|||
|
private final JwtUtil jwtUtil;
|
|||
|
|
|||
|
/**
|
|||
|
* 注入redis
|
|||
|
*/
|
|||
|
private final RedisCache redisCache;
|
|||
|
|
|||
|
/**
|
|||
|
* 重写父类判断是否重复的抽象方法
|
|||
|
*
|
|||
|
* @param request 请求
|
|||
|
* @return true=重复提交 false=未重复
|
|||
|
*/
|
|||
|
@SneakyThrows
|
|||
|
@SuppressWarnings("unchecked")
|
|||
|
@Override
|
|||
|
public boolean isRepeatSubmit(HttpServletRequest request, HttpServletResponse response) {
|
|||
|
//1.声明当前参数
|
|||
|
String nowParams = null;
|
|||
|
//判断请求是否为空
|
|||
|
if (request != null) {
|
|||
|
//把request 转为可重复获取流的RepeatedlyRequestWrapper
|
|||
|
RepeatedlyRequestWrapper repeatedlyRequest = new RepeatedlyRequestWrapper(request, response);
|
|||
|
//获取body参数
|
|||
|
nowParams = RepeatedlyRequestWrapper.getBodyString(repeatedlyRequest);
|
|||
|
}
|
|||
|
//如果请求体body参数为空,获取Parameter的参数
|
|||
|
if (StringUtils.isEmpty(nowParams)) {
|
|||
|
assert request != null;
|
|||
|
nowParams = JSONUtil.toJsonStr(request.getParameterMap());
|
|||
|
log.info("body=={}", nowParams);
|
|||
|
}
|
|||
|
//把数据存储起来
|
|||
|
Map<String, Object> nowMap = new HashMap<>(80);
|
|||
|
nowMap.put(REPEAT_PARAMS, nowParams);
|
|||
|
nowMap.put(REPEAT_TIME, System.currentTimeMillis());
|
|||
|
log.info("nowMap=={}", nowMap);
|
|||
|
|
|||
|
// 请求地址(作为存放cache的key值)
|
|||
|
String url = request.getRequestURI();
|
|||
|
|
|||
|
//唯一标识-获取请求头的token值
|
|||
|
String submitKey = request.getHeader(jwtUtil.getHeader());
|
|||
|
//如果token为空,使用请求地址作为key
|
|||
|
if (StringUtils.isEmpty(submitKey)) {
|
|||
|
submitKey = url;
|
|||
|
}
|
|||
|
//唯一标识(指定key+消息头)
|
|||
|
String cacheRepeatKey = "repeat_submit:" + submitKey;
|
|||
|
log.info("repeat_submit=={}", cacheRepeatKey);
|
|||
|
|
|||
|
//缓存中获取上次请求数据
|
|||
|
Object cacheObject = redisCache.getCacheObject(cacheRepeatKey);
|
|||
|
log.info("cacheObject=={}", cacheObject);
|
|||
|
//如果缓存中没有数据,则存放
|
|||
|
if (cacheObject == null) {
|
|||
|
Map<String, Object> cacheMap = new HashMap<>(109);
|
|||
|
cacheMap.put(url, nowMap);
|
|||
|
redisCache.setCacheObject(cacheRepeatKey, cacheMap, intervalTime, TimeUnit.SECONDS);
|
|||
|
} else {
|
|||
|
//强转为map
|
|||
|
Map<String, Object> preDataMap = (Map<String, Object>) cacheObject;
|
|||
|
//判断该map是否有url作为的键
|
|||
|
if (preDataMap.containsKey(url)) {
|
|||
|
//根据map中的键url 获取对应的参数
|
|||
|
Map<String, Object> preMap = (Map<String, Object>) preDataMap.get(url);
|
|||
|
return compareParams(nowMap, preMap) && compareTime(nowMap, preMap);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 比较两次请求参数是否相同
|
|||
|
*
|
|||
|
* @param nowMap 现在的数据
|
|||
|
* @param preMap 之前的数据
|
|||
|
* @return true=相同 false=不相同
|
|||
|
*/
|
|||
|
private boolean compareParams(Map<String, Object> nowMap, Map<String, Object> preMap) {
|
|||
|
String now = (String) nowMap.get(REPEAT_PARAMS);
|
|||
|
String pre = (String) preMap.get(REPEAT_PARAMS);
|
|||
|
return now.equals(pre);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 比较两次请求时间间隔
|
|||
|
*
|
|||
|
* @param nowMap 现在的数据
|
|||
|
* @param preMap 之前的数据
|
|||
|
* @return true=相同 false=不相同
|
|||
|
*/
|
|||
|
private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> preMap) {
|
|||
|
long now = (Long) nowMap.get(REPEAT_TIME);
|
|||
|
long pre = (Long) preMap.get(REPEAT_TIME);
|
|||
|
//如果两次间隔时间小于10秒
|
|||
|
return (now - pre) < this.intervalTime * 1000L;
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
## 4. 配置拦截器到web中
|
|||
|
|
|||
|
```java
|
|||
|
/**
|
|||
|
* @author 氓氓编程
|
|||
|
*/
|
|||
|
@Configuration
|
|||
|
public class CorsConfig implements WebMvcConfigurer {
|
|||
|
|
|||
|
private final RepeatSubmitInterceptor repeatSubmitInterceptor;
|
|||
|
//构造方法注入
|
|||
|
public CorsConfig(RepeatSubmitInterceptor repeatSubmitInterceptor) {
|
|||
|
this.repeatSubmitInterceptor = repeatSubmitInterceptor;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/**
|
|||
|
* 解决跨域
|
|||
|
* @return CorsFilter
|
|||
|
*/
|
|||
|
@Bean
|
|||
|
public CorsFilter corsFilter() {
|
|||
|
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
|||
|
CorsConfiguration configuration = new CorsConfiguration();
|
|||
|
// 设置访问源地址(允许那些地址访问服务器)
|
|||
|
configuration.addAllowedOrigin("*");
|
|||
|
// 设置访问源请求方法(方法)
|
|||
|
configuration.addAllowedMethod("*");
|
|||
|
// 设置访问源请求头(头部信息)
|
|||
|
configuration.addAllowedHeader("*");
|
|||
|
// 跨域需要暴露的请求头(因为跨域访问默认不能获取全部头部信息)
|
|||
|
configuration.addExposedHeader("token");
|
|||
|
// 注册配置
|
|||
|
source.registerCorsConfiguration("/**", configuration);
|
|||
|
return new CorsFilter(source);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 添加拦截器
|
|||
|
* @param registry registry
|
|||
|
*/
|
|||
|
@Override
|
|||
|
public void addInterceptors(InterceptorRegistry registry) {
|
|||
|
registry.addInterceptor(repeatSubmitInterceptor);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
```
|
|||
|
|
|||
|
## 5. redis
|
|||
|
|
|||
|
### 5.1 配置
|
|||
|
|
|||
|
```java
|
|||
|
**
|
|||
|
* @author 氓氓编程
|
|||
|
*
|
|||
|
*/
|
|||
|
@Configuration
|
|||
|
public class RedisCacheConfig extends CachingConfigurerSupport {
|
|||
|
@Bean
|
|||
|
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
|
|||
|
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
|
|||
|
redisTemplate.setConnectionFactory(redisConnectionFactory);
|
|||
|
//key序列化
|
|||
|
redisTemplate.setKeySerializer(new StringRedisSerializer());
|
|||
|
//value序列化
|
|||
|
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
|
|||
|
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
|
|||
|
redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer());
|
|||
|
redisTemplate.afterPropertiesSet();
|
|||
|
return redisTemplate;
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
> 以下是需要用到的工具类
|
|||
|
|
|||
|
### 5.2 redis 工具类
|
|||
|
|
|||
|
```java
|
|||
|
/**
|
|||
|
* Redis 工具类
|
|||
|
*
|
|||
|
* @author 氓氓编程
|
|||
|
* @Date: 2021-06-08-17:35
|
|||
|
*/
|
|||
|
@Component
|
|||
|
public class RedisCache {
|
|||
|
|
|||
|
private final RedisTemplate<Object, Object> redisTemplate;
|
|||
|
|
|||
|
|
|||
|
public RedisCache(RedisTemplate<Object, Object> redisTemplate) {
|
|||
|
this.redisTemplate = redisTemplate;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/**
|
|||
|
* 缓存基本对象,Integer、String、实体类等
|
|||
|
*
|
|||
|
* @param key 键
|
|||
|
* @param value 值
|
|||
|
*/
|
|||
|
public void setCacheObject(final String key, final Object value) {
|
|||
|
redisTemplate.opsForValue().set(key, value);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 带有效时间缓存基本对象,Integer、String、实体类等
|
|||
|
*
|
|||
|
* @param key 键
|
|||
|
* @param value 值
|
|||
|
* @param timeout 有效时间
|
|||
|
* @param timeUnit 有效时间单位
|
|||
|
*/
|
|||
|
public void setCacheObject(final String key, final Object value, final Integer timeout, final TimeUnit timeUnit) {
|
|||
|
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 给某个键设置有效时间
|
|||
|
*
|
|||
|
* @param key 需要设置有效时间的键
|
|||
|
* @param timeout 设置的时间 默认是秒
|
|||
|
* @return true=设置成功 false=设置失败
|
|||
|
*/
|
|||
|
public boolean expire(final String key, final long timeout) {
|
|||
|
return expire(key, timeout, TimeUnit.SECONDS);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/**
|
|||
|
* 给某个键设置有效时间
|
|||
|
*
|
|||
|
* @param key 需要设置有效时间的键
|
|||
|
* @param timeout 设置的时间
|
|||
|
* @param timeUnit 有效时间单位
|
|||
|
* @return true=设置成功 false=设置失败
|
|||
|
*/
|
|||
|
public boolean expire(final String key, final long timeout, final TimeUnit timeUnit) {
|
|||
|
Boolean isSuccess = redisTemplate.expire(key, timeout, timeUnit);
|
|||
|
if (isSuccess != null) {
|
|||
|
return isSuccess;
|
|||
|
}
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 根据键获取某个缓存的值
|
|||
|
*
|
|||
|
* @param key 键
|
|||
|
* @return Object
|
|||
|
*/
|
|||
|
public Object getCacheObject(final String key) {
|
|||
|
return redisTemplate.opsForValue().get(key);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/**
|
|||
|
* 根据建删除缓存
|
|||
|
*
|
|||
|
* @param key 键
|
|||
|
* @return true=成功 false=失败
|
|||
|
*/
|
|||
|
public boolean deleteObject(final String key) {
|
|||
|
Boolean isDelete = redisTemplate.delete(key);
|
|||
|
return isDelete != null && isDelete;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 根据键批量删除
|
|||
|
*
|
|||
|
* @param collection 装有键的集合
|
|||
|
* @return 删除成功的数量
|
|||
|
*/
|
|||
|
public long deleteObject(final Collection<Object> collection) {
|
|||
|
Long count = redisTemplate.delete(collection);
|
|||
|
return count == null ? 0 : count;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 缓存List集合数据
|
|||
|
*
|
|||
|
* @param key 键
|
|||
|
* @param dataList 待缓存的List数据
|
|||
|
* @return 缓存成功的数量
|
|||
|
*/
|
|||
|
public long setCacheList(final String key, final List<Object> dataList) {
|
|||
|
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
|
|||
|
return count == null ? 0 : count;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 获取List缓存数据
|
|||
|
*
|
|||
|
* @param key 键
|
|||
|
* @return 缓存键值对应的数据
|
|||
|
*/
|
|||
|
public List<Object> getCacheList(final String key) {
|
|||
|
return redisTemplate.opsForList().range(key, 0, -1);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 缓存Set
|
|||
|
*
|
|||
|
* @param key 键
|
|||
|
* @param dataSet 待缓存的Set数据
|
|||
|
* @return 缓存数据的对象
|
|||
|
*/
|
|||
|
public BoundSetOperations<Object, Object> setCacheSet(final String key, final Set<Object> dataSet) {
|
|||
|
BoundSetOperations<Object, Object> setOperations = redisTemplate.boundSetOps(key);
|
|||
|
Iterator<Object> it = dataSet.iterator();
|
|||
|
if (it.hasNext()) {
|
|||
|
setOperations.add(it.next());
|
|||
|
}
|
|||
|
return setOperations;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/**
|
|||
|
* 获取Set缓存数据
|
|||
|
*
|
|||
|
* @param key 键
|
|||
|
* @return 缓存键值对应的数据
|
|||
|
*/
|
|||
|
public Set<Object> getCacheSet(final String key) {
|
|||
|
return redisTemplate.opsForSet().members(key);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @param key 键
|
|||
|
* @param dataMap 缓存的map数据
|
|||
|
*/
|
|||
|
public void setCacheMap(final String key, final Map<String, Object> dataMap) {
|
|||
|
if (dataMap != null) {
|
|||
|
redisTemplate.opsForHash().putAll(key, dataMap);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/**
|
|||
|
* 获得缓存的Map
|
|||
|
*
|
|||
|
* @param key 键
|
|||
|
* @return 获得缓存的数据
|
|||
|
*/
|
|||
|
public Map<Object, Object> getCacheMap(final String key) {
|
|||
|
return redisTemplate.opsForHash().entries(key);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/**
|
|||
|
* 往Hash中存入数据
|
|||
|
*
|
|||
|
* @param key Redis键
|
|||
|
* @param hKey Hash键
|
|||
|
* @param value 值
|
|||
|
*/
|
|||
|
public void setCacheMapValue(final String key, final String hKey, final Object value) {
|
|||
|
redisTemplate.opsForHash().put(key, hKey, value);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 获取Hash中的数据
|
|||
|
*
|
|||
|
* @param key Redis键
|
|||
|
* @param hKey Hash键
|
|||
|
* @return Hash中的对象
|
|||
|
*/
|
|||
|
public Object getCacheMapValue(final String key, final String hKey) {
|
|||
|
return redisTemplate.opsForHash().get(key, hKey);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 获取多个Hash中的数据
|
|||
|
*
|
|||
|
* @param key Redis键
|
|||
|
* @param hKeys Hash键集合
|
|||
|
* @return Hash对象集合
|
|||
|
*/
|
|||
|
public List<Object> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {
|
|||
|
return redisTemplate.opsForHash().multiGet(key, hKeys);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 获得缓存的基本对象列表
|
|||
|
*
|
|||
|
* @param pattern 字符串前缀
|
|||
|
* @return 对象列表
|
|||
|
*/
|
|||
|
public Collection<Object> keys(final String pattern) {
|
|||
|
return redisTemplate.keys(pattern);
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
## 6. Servlet参数发送的工具类
|
|||
|
|
|||
|
```java
|
|||
|
/**
|
|||
|
* 客户端工具类
|
|||
|
*
|
|||
|
* @author 氓氓编程
|
|||
|
* @Date: 2021-06-08-16:50
|
|||
|
*/
|
|||
|
public class ServletUtils {
|
|||
|
/**
|
|||
|
* 获取String参数
|
|||
|
*/
|
|||
|
public static String getParameter(String name) {
|
|||
|
return getRequest().getParameter(name);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 获取String参数,如墨没有设置一个默认值
|
|||
|
*/
|
|||
|
public static String getParameter(String name, String defaultValue) {
|
|||
|
return Convert.toStr(getRequest().getParameter(name), defaultValue);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 获取session
|
|||
|
*/
|
|||
|
public static HttpSession getSession() {
|
|||
|
return getRequest().getSession();
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 获取Integer参数
|
|||
|
*/
|
|||
|
public static Integer getParameterToInt(String name) {
|
|||
|
return Convert.toInt(getRequest().getParameter(name));
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 获取response
|
|||
|
*/
|
|||
|
public static HttpServletResponse getResponse() {
|
|||
|
return getRequestAttributes().getResponse();
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 获取request
|
|||
|
*/
|
|||
|
public static HttpServletRequest getRequest() {
|
|||
|
return getRequestAttributes().getRequest();
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 获取到当前的HttpServletRequest
|
|||
|
*
|
|||
|
* @return ServletRequestAttributes
|
|||
|
*/
|
|||
|
public static ServletRequestAttributes getRequestAttributes() {
|
|||
|
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
|
|||
|
return (ServletRequestAttributes) attributes;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @param response 当前请求的响应
|
|||
|
* @param string 传输的文字
|
|||
|
*/
|
|||
|
public static void renderString(HttpServletResponse response, String string) {
|
|||
|
try {
|
|||
|
response.setStatus(200);
|
|||
|
response.setContentType("application/json");
|
|||
|
response.setCharacterEncoding("UTF-8");
|
|||
|
response.getWriter().print(string);
|
|||
|
} catch (IOException e) {
|
|||
|
e.printStackTrace();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
## 7. 可重复读取流的RepeatedlyRequestWrapper
|
|||
|
|
|||
|
```java
|
|||
|
/**
|
|||
|
* 构建可重复读取inputStream的请求request
|
|||
|
*
|
|||
|
* @author 氓氓编程
|
|||
|
* @Date: 2021-06-09-8:47
|
|||
|
*/
|
|||
|
@Slf4j
|
|||
|
public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper {
|
|||
|
|
|||
|
/**
|
|||
|
* 存放请求体中的数据
|
|||
|
*/
|
|||
|
private final byte[] body;
|
|||
|
|
|||
|
public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws UnsupportedEncodingException {
|
|||
|
super(request);
|
|||
|
request.setCharacterEncoding("UTF-8");
|
|||
|
response.setCharacterEncoding("UTF-8");
|
|||
|
this.body = getBodyString(request).getBytes(StandardCharsets.UTF_8);
|
|||
|
}
|
|||
|
|
|||
|
@Override
|
|||
|
public BufferedReader getReader() {
|
|||
|
return new BufferedReader(new InputStreamReader(getInputStream()));
|
|||
|
}
|
|||
|
|
|||
|
@Override
|
|||
|
public ServletInputStream getInputStream(){
|
|||
|
//1.创建一个字节数组流存放请求体
|
|||
|
final ByteArrayInputStream bodyInputStream = new ByteArrayInputStream(body);
|
|||
|
//2.返回获取的body中的数据流
|
|||
|
return new ServletInputStream() {
|
|||
|
@Override
|
|||
|
public int read(){
|
|||
|
return bodyInputStream.read();
|
|||
|
}
|
|||
|
|
|||
|
@Override
|
|||
|
public int available(){
|
|||
|
return body.length;
|
|||
|
}
|
|||
|
|
|||
|
@Override
|
|||
|
public boolean isFinished() {
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
@Override
|
|||
|
public boolean isReady() {
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
@Override
|
|||
|
public void setReadListener(ReadListener readListener) {
|
|||
|
|
|||
|
}
|
|||
|
};
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/**
|
|||
|
* 获取请求体
|
|||
|
*
|
|||
|
* @param request 请求
|
|||
|
* @return 字符串
|
|||
|
*/
|
|||
|
public static String getBodyString(ServletRequest request) {
|
|||
|
//1.创建一个StringBuilder
|
|||
|
StringBuilder sb = new StringBuilder();
|
|||
|
//2.声明一个读缓存的流
|
|||
|
BufferedReader reader = null;
|
|||
|
//3.获取请求中的流
|
|||
|
try (InputStream inputStream = request.getInputStream()) {
|
|||
|
//把请求中的流读取出来给reader
|
|||
|
reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
|
|||
|
//声明line存放每一行的数据
|
|||
|
String line;
|
|||
|
//一行一行的读取数据并赋值给line,line不为空
|
|||
|
while ((line = reader.readLine()) != null) {
|
|||
|
//追加写入
|
|||
|
sb.append(line);
|
|||
|
}
|
|||
|
} catch (IOException e) {
|
|||
|
log.warn("获取请求体中数据出现问题");
|
|||
|
} finally {
|
|||
|
//如果reader不为空,关闭流
|
|||
|
if (reader != null) {
|
|||
|
try {
|
|||
|
reader.close();
|
|||
|
} catch (IOException e) {
|
|||
|
log.error(ExceptionUtil.getMessage(e));
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
return sb.toString();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
```
|
|||
|
|
|||
|
## 8. 让可重复读的流生效
|
|||
|
|
|||
|
```java
|
|||
|
/**
|
|||
|
* 使用重写后的RepeatedlyRequestWrapper
|
|||
|
* <p>
|
|||
|
* Repeatable 过滤器
|
|||
|
*/
|
|||
|
public class RepeatableFilter implements Filter {
|
|||
|
/**
|
|||
|
* startsWithIgnoreCase 判断开始部分是否与二参数相同。不区分大小写
|
|||
|
*
|
|||
|
* @param request 请求
|
|||
|
* @param response 响应
|
|||
|
* @param chain 放行
|
|||
|
* @throws IOException io异常
|
|||
|
* @throws ServletException 异常
|
|||
|
*/
|
|||
|
@Override
|
|||
|
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
|||
|
//声明一个ServletRequest
|
|||
|
ServletRequest requestWrapper = null;
|
|||
|
//判断request是HttpServletRequest或子类并且request.getContentType()开头包含application/json
|
|||
|
if (request instanceof HttpServletRequest && StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) {
|
|||
|
//创建一个可重复获取流的RepeatedlyRequestWrapper赋值给ServletRequest
|
|||
|
requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response);
|
|||
|
}
|
|||
|
//为空,直接放行
|
|||
|
if (null == requestWrapper) {
|
|||
|
chain.doFilter(request, response);
|
|||
|
} else {
|
|||
|
//赋值完毕可重复读流继续向下传
|
|||
|
chain.doFilter(requestWrapper, response);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
```java
|
|||
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
|||
|
@Bean
|
|||
|
public FilterRegistrationBean someFilterRegistration() {
|
|||
|
FilterRegistrationBean registration = new FilterRegistrationBean();
|
|||
|
registration.setFilter(new RepeatableFilter());
|
|||
|
registration.addUrlPatterns("/*");
|
|||
|
registration.setName("repeatableFilter");
|
|||
|
registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
|
|||
|
return registration;
|
|||
|
}
|
|||
|
```
|
|||
|
|