AOP实现防止接口重复提交

一:实现方法

1.自定义防重复提交的注解和切面

2.在需要验证的接口上增加注解(一般是创建、修改的接口)

3.以每次调用的 类名+方法名+请求数据 的MD5值作为key,value任意值都可以,缓存起来(redis或本地缓存或其他),并设置一个合适的缓存失效时间。

4.每次调用时根据key判断,缓存是否存在,存在则抛出异常或提示,不存在则执行业务逻辑。

二:防重复提交注解

package com.platform.cloudlottery.aop;

import java.lang.annotation.*;

/**
 * @author : chenkun
 * @version V1.0
 * @Project: cloudlottery-interface
 * @Package com.platform.cloudlottery.aop
 * @Description: TODO(这里用一句话描述这个类的作用)
 * @date Date : 2019年10月16日 11:26
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmitCheck {
    int keepSeconds() default 60;
}

三:切面

package com.platform.cloudlottery.aop;

import com.alibaba.fastjson.JSON;
import com.platform.cloudlottery.common.codec.Md5Utils;
import com.platform.cloudlottery.common.redis.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * @author : chenkun
 * @version V1.0
 * @Project: cloudlottery-interface
 * @Package com.platform.cloudlottery.aop
 * @Description: TODO(防止重复提交请求)
 * @date Date : 2019年10月16日 11:24
 */
@Aspect
@Component
@Slf4j
public class RepeatSubmitAspect {
    @Resource
    private RedisService redisService;

    @Pointcut("@annotation(RepeatSubmitCheck)")
    public void requestPointcut() {

    }

    @Before("requestPointcut() && @annotation(repeatSubmitCheck)")
    public void aroundCheck(JoinPoint joinPoint, RepeatSubmitCheck repeatSubmitCheck) {
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        String paramsJsonStr = JSON.toJSONString(args);

        String srcStr = className + "_" + methodName + "_" + paramsJsonStr;
        String signStr = Md5Utils.md5(srcStr);
        log.info("防止重复提交的请求 Redis Key:"+signStr);

        //判断缓存是否存在不存在则添加缓存
        if(!redisService.exists(signStr)){
            redisService.setex(signStr, repeatSubmitCheck.keepSeconds(), "1");
        } else {//重复请求
            log.info("重复提交的请求数据:"+srcStr);
            throw new RuntimeException("重复请求");
        }
    }
}

推荐使用Redis。

四:RedisService类

package com.platform.cloudlottery.common.redis;

import com.alibaba.fastjson.JSON;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;

import java.util.List;
import java.util.Map;

@AllArgsConstructor
public class RedisService {

    private RedisTemplate<String, Object> redisTemplate;

    public boolean set(final String key, final String value) {
        return redisTemplate.execute((RedisCallback<Boolean>) connection -> {
            RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
            return connection.set(serializer.serialize(key), serializer.serialize(value));
        });
    }

    public boolean setNx(final String key, final String value) {
        return redisTemplate.execute((RedisCallback<Boolean>) connection -> {
            RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
            return connection.setNX(serializer.serialize(key), serializer.serialize(value));
        });
    }

    public boolean setex(final String key, long expire, final String value) {
        return redisTemplate.execute((RedisCallback<Boolean>) connection -> {
            RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
            return connection.setEx(serializer.serialize(key), expire, serializer.serialize(value));
        });
    }

    public String get(final String key) {
        return redisTemplate.execute(new RedisCallback<String>() {
            @Override
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
                byte[] value = connection.get(serializer.serialize(key));
                return serializer.deserialize(value);
            }
        });
    }

    public String getSet(final String key, final String value){
        return redisTemplate.execute((RedisCallback<String>) connection -> {
            RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
            byte[] preValue = connection.getSet(serializer.serialize(key), serializer.serialize(value));
            return serializer.deserialize(preValue);
        });
    }

    public Long del(final String key) {
        return redisTemplate.execute((RedisCallback<Long>) connection -> {
            RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
            return connection.del(key.getBytes());
        });
    }


    public boolean expire(final String key, long expire) {
        return redisTemplate.execute((RedisCallback<Boolean>) connection -> connection.expire(key.getBytes(),expire));
    }

    public <T> boolean setList(String key, List<T> list) {
        String value = JSON.toJSONString(list);
        return set(key, value);
    }

    public <T> List<T> getList(String key, Class<T> clz) {
        String json = get(key);
        if (json != null) {
            List<T> list = JSON.parseArray(json, clz);
            return list;
        }
        return null;
    }

    public long lpush(final String key, Object obj) {
        final String value = JSON.toJSONString(obj);
        long result = redisTemplate.execute((RedisCallback<Long>) connection -> {
            RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
            long count = connection.lPush(serializer.serialize(key), serializer.serialize(value));
            return count;
        });
        return result;
    }

    public long rpush(final String key, Object obj) {
        final String value = JSON.toJSONString(obj);
        long result = redisTemplate.execute((RedisCallback<Long>) connection -> {
            RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
            long count = connection.rPush(serializer.serialize(key), serializer.serialize(value));
            return count;
        });
        return result;
    }

    public String lpop(final String key) {
        String result = redisTemplate.execute((RedisCallback<String>) connection -> {
            RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
            byte[] res = connection.lPop(serializer.serialize(key));
            return serializer.deserialize(res);
        });
        return result;
    }

    public boolean exists(String key) {
        return redisTemplate.execute((RedisCallback<Boolean>) connection -> {
            return connection.exists(key.getBytes());
        });
    }

    public Long incrBy(final String key, long value) {
        return redisTemplate.execute((RedisCallback<Long>) connection -> {
            return connection.incrBy(key.getBytes(), value);
        });
    }

    public Long incr(final String key) {
        return redisTemplate.execute((RedisCallback<Long>) connection -> {
            return connection.incr(key.getBytes());
        });
    }

    public Long decr(final String key) {
        return redisTemplate.execute((RedisCallback<Long>) connection -> {
            return connection.decr(key.getBytes());
        });
    }

    public Long decrBy(final String key, long value) {
        return redisTemplate.execute((RedisCallback<Long>) connection -> connection.decrBy(key.getBytes(), value));
    }

    /**
     * 分布式锁
     *
     * @param key
     * @param value
     * @return
     */
    public boolean lock(final String key, final String value) {
        //如何不存在key,则可以保存成功
        if (setNx(key, value)) {
            return true;
        }

        String currentValue = get(key);
        //如果锁过期
        if (!StringUtils.isEmpty(currentValue) && Long.parseLong(currentValue) < System.currentTimeMillis()) {//currentValue不为空且小于当前时间
            //获取上一个锁的时间value
            String oldValue = getSet(key, value);//对应getset,如果key存在

            //假设两个线程同时进来这里,因为key被占用了,而且锁过期了。获取的值currentValue=A(get取的旧的值肯定是一样的),两个线程的value都是B,key都是K.锁时间已经过期了。
            //而这里面的getAndSet一次只会一个执行,也就是一个执行之后,上一个的value已经变成了B。只有一个线程获取的上一个值会是A,另一个线程拿到的值是B。
            if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {
                //oldValue不为空且oldValue等于currentValue,也就是校验是不是上个对应的商品时间戳,也是防止并发
                return true;
            }
        }
        return false;
    }

    /**
     * 解锁
     *
     * @param key
     * @param value
     */
    public void unlock(String key, String value) {
        String currentValue = get(key);
        if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {
            del(key);//删除key
        }
    }

    public boolean hSet(String key, Map<String,Object> map, long time){
        redisTemplate.opsForHash().putAll(key,map);
        expire(key,time);
        return true;
    }

    public Long hIncr(String key, String item,long i){
        return redisTemplate.opsForHash().increment(key,item,i);
    }

    public boolean hHasKey(String key,String item){
        return redisTemplate.opsForHash().hasKey(key,item);
    }

    public Object hGet(String key,String item){
        return redisTemplate.opsForHash().get(key,item);
    }

    public boolean hSet(String key, String item, Object value){
        redisTemplate.opsForHash().put(key,item,value);
        return true;
    }

    public boolean hSet(String key, Map<String,Object> map){
        redisTemplate.opsForHash().putAll(key,map);
        return true;
    }

}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
分布式接口幂等性问题是指在分布式系统中,由于网络延迟、重试机制等原因,可能导致同一个请求被重复处理,从而产生重复的业务逻辑。为了解决这个问题,需要保证接口的幂等性。 保证接口的幂等性的方法有多种。一种常见的方法是使用唯一标识来标识每一次请求,比如订单id、支付流水号或者前端生成的唯一随机串。在每次请求之前,需要将唯一标识存放到数据库或者缓存中。后端服务在处理请求之前,需要先检查这个唯一标识是否存在,如果存在,则判定此次请求已经处理过,不需要进行重复处理。这样可以避免重复的业务逻辑。 在分布式场景中,由于负载均衡算法的原因,可能会导致同一个请求被多台机器处理。为了解决这个问题,可以使用分布式锁来保证只有一个机器能够处理该请求。另外,使用分布式事务也可以保证接口的幂等性。 此外,还可以通过拦截器(AOP)和注解的方式实现一个通用的解决方案,避免每次请求都写重复的代码。在设计系统时,幂等性是一个需要首要考虑的问题,特别是在涉及到金融交易等关键业务的系统中。 综上所述,保证分布式接口的幂等性可以通过使用唯一标识、分布式锁、分布式事务等方法来实现。这样可以避免重复的业务逻辑和数据不一致的问题。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* *2* [分布式环境下接口幂等性浅析](https://blog.csdn.net/ice24for/article/details/86084613)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [分布式开发(二)---接口幂等性(防止重复提交)](https://blog.csdn.net/icanlove/article/details/117652662)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kung900519

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值