关于web项目的幂等性

其实这个问题一直存在,只是你作为开发人员有没有考虑到的问题。

所谓的幂等性,我的理解就是客户端的重复提交请求,而后端只处理一次。

有很多博文都提供了解决方案,例如: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>

 

欢迎来喷!

 

转载于:https://my.oschina.net/OHC1U9jZt/blog/1929103

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值