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
    评论
RedisTemplate 是 Spring Data Redis 提供的一个模板类,用于简化对 Redis 数据库的操作。切面编程(Aspect-Oriented Programming, AOP)是一种编程范式,它允许开发者将关注点(如日志、事务管理等)从业务逻辑中分离出来。 要在 RedisTemplate 中利用切面编程实现接口重复提交,通常我们会结合 Spring AOP 和 @ReactiveMethodEventListener 注解。首先,创建一个切面类,比如 `RepeatSubmitAspect`: ```java import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; @Aspect @Component public class RepeatSubmitAspect { private final StringRedisTemplate redisTemplate; public RepeatSubmitAspect(StringRedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; } @Before("execution(* com.example.yourpackage.YourService.*(..))") public void preventDuplicateSubmission(Object target, Method method, Object[] args) { // 使用目标接口名和方法名作为唯一标识,检查是否存在已提交记录 String key = "your-interface:" + target.getClass().getSimpleName() + "#" + method.getName(); Boolean isAlreadySubmitted = redisTemplate.hasKey(key); if (isAlreadySubmitted) { throw new DuplicateSubmitException("提交已经存在,不允许重复提交"); } // 如果不存在,存储一个新的标记 redisTemplate.opsForValue().set(key, "true", /* 设置过期时间 */); } } ``` 在这个例子中,当 `YourService` 的某个接口方法被调用时,这个切面会检查是否已有相同的请求记录。如果有,就抛出异常防止重复提交。如果没有,先存入一个标记并设置过期时间。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

kung900519

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

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

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

打赏作者

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

抵扣说明:

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

余额充值