其實這個問題一直存在,只是你作爲開發人員有沒有考慮到的問題。
所謂的冪等性,我的理解就是客戶端的重複提交請求,而後端只處理一次。
有很多博文都提供瞭解決方案,例如:https://www.cnblogs.com/panxuejun/p/8599968.html
在這裏,談談我的方案。
開發環境:springmvc4.3.18.RELEASE, redis,spring-data-redis 1.8.13.RELEASE。
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
</dependencies>
要去除客戶端的重複提交請求,就要進行攔截。那麼,我們只需要在handler之前進行攔截即可。定義自己的攔截器:
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.annotation.Resource;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Enumeration;
import java.util.concurrent.TimeUnit;
public class RequestInterceptor implements HandlerInterceptor {
@Resource
private RedisTemplate<String, String> redisTemplate;
@Override
public boolean preHandle(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response, Object handler) throws Exception {
final Enumeration parameterNames = request.getParameterNames();
StringBuilder stringBuilder = new StringBuilder();
while (parameterNames.hasMoreElements()) {
String key = (String) parameterNames.nextElement();
String parameter = request.getParameter(key);
stringBuilder.append(key).append("=").append(parameter).append("&");
}
String key = "request:" + md5(stringBuilder.toString());
if (redisTemplate.opsForValue().setIfAbsent(key, key)) {
redisTemplate.expire(key, 1, TimeUnit.MINUTES);
System.out.println("第一次提交");
return true;
}
System.out.println("第二次提交");
return false;
}
@Override
public void postHandle(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
private static String md5(String str) {
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] s = md5.digest(str.getBytes());
String ss= "";
String result = "";
for (int i = 0; i < s.length; i++) {
ss = Integer.toHexString(s[i] & 0xff);
if (ss.length() == 1) {
result += "0" + ss;
} else {
result += ss;
}
}
return result;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
System.out.println(md5("123"));
}
}
代碼解讀:
1)我們只需要前置攔截即可,如果重複了,後續的都不去處理了。
2)根據什麼攔截呢?根據客戶端提交請求的參數進行攔截,如果相同的參數提交了多次,那麼只處理第一次,後面提交的直接返回。
3)使用redis的機制進行處理:首先第一次請求,計算出請求參數的md5值,並存儲起來,設置有效期爲1分鐘(即1分鐘內的相同請求均拒絕處理)。爲什麼要算md5值?也沒什麼,就是不要在redis裏明文顯示而已。
在springmvc.xml文件中添加:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.xwszt.springmvcdemo.RequestInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
其它的配置文件內容:
redis.database=0
redis.maxTotal=100
redis.maxIdle=30
redis.minIdle=5
redis.timeout=1000
redis.host.name=127.0.0.1
redis.host.port=6379
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 加載配置文件 -->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<array>
<value>classpath:config.properties</value>
</array>
</property>
</bean>
<context:component-scan base-package="com.xwszt.springmvcdemo"/>
<!-- jedis鏈接池 -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="${redis.maxTotal}"/>
<property name="maxIdle" value="${redis.maxIdle}"/>
<property name="minIdle" value="${redis.minIdle}"/>
<property name="testOnBorrow" value="true"/>
</bean>
<!--jedis客戶端鏈接工廠-->
<bean id="redisConnFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="usePool" value="true"/>
<property name="database" value="${redis.database}"/>
<property name="poolConfig" ref="jedisPoolConfig"/>
<property name="hostName" value="${redis.host.name}"/>
<property name="port" value="${redis.host.port}"/>
<!--<property name="password" value="${redis.password}"/>-->
<property name="timeout" value="${redis.timeout}"/>
</bean>
<!--redisTemplate-->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="redisConnFactory"/>
<property name="keySerializer" ref="stringRedisSerializer"/>
<property name="valueSerializer" ref="jackson2JsonRedisSerializer"/>
<property name="hashKeySerializer" ref="stringRedisSerializer"/>
<property name="hashValueSerializer" ref="jackson2JsonRedisSerializer"/>
</bean>
<bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
<bean id="jackson2JsonRedisSerializer" class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
<bean id="jdkSerializationRedisSerializer" class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>
<bean id="redisMessageListener" class="com.xwszt.springmvcdemo.msg.MsgListener">
<property name="redisTemplate" ref="redisTemplate"/>
</bean>
</beans>
歡迎來噴!