接口幂等性解决方案

本文介绍了如何使用SpringAOP和Redis在Java应用中实现接口幂等性,通过`ForbidRepeatClickInterceptor`拦截器,防止用户在短时间内重复点击,确保请求的唯一性。关键点包括`setnx`操作和使用Redis作为缓存机制来控制请求执行频率。
摘要由CSDN通过智能技术生成

接口幂等性解决方案 - 知乎 (zhihu.com)

 Redis 中的 setnx 命令_redis setnx-CSDN博客

-------------------------------------------------------------------------------------------------------------------------------

下面直接上最基础的代码

后面可以在此基础上优化

 

@Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface ForbidRepeatClick { }
import com.alibaba.fastjson.JSONObject;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

@Component
@Aspect
@Order(1)
public class ForbidRepeatClickInterceptor {
    private static final Logger LOGGER = LoggerFactory.getLogger(ForbidRepeatClickInterceptor.class);
    @Autowired
    private RedisTemplate redisTemplate;

    @Pointcut("@annotation(ForbidRepeatClick)")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object forbidRepeatClick(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println(333);
        //1、根据入参方法名获取组装的redis的key值
        String redisKey = getRedisKey(pjp);
        LOGGER.info("方法的参数为:{}", redisKey);
        //情景是,点击一次,违约成本反欺诈就开始分析,按钮就不能点击了.然后完成分析后, 后端return给前端成功,前端跳转页面.
        //这样写是3秒内无法重复点击此提交按钮,
        //去掉过期时间,加上2.1的delete就是当这个方法走完后,按钮才能被点击
        if (redisTemplate.opsForValue().setIfAbsent(redisKey, "exist",10,TimeUnit.SECONDS)) {
            //假设 lockName 是你的锁的名称,expireTime 是你希望锁持续的时间(以分钟为单位)
            redisTemplate.expire(redisKey, 10, TimeUnit.SECONDS);

            Object res = null;

                System.out.println(444);
                //2、当前方法同一时间段无完全同参数调用,则继续往下执行
                res = pjp.proceed();
                System.out.println(555);

            //2.1 执行后将数据从redis删除 (建议设置过期时间,自动删除)
            redisTemplate.delete("aaa");
            System.out.println(666);
            return res;
        }
        //3、当前方法同一时间段具有相同参数执行,则不再执行,直接返回错误标识
        LOGGER.info("ForbidRepeatClickInterceptor->forbidRepeatClick->redisKey:exist");
        return "不要重复点击按钮";
    }

    /**
     * 获取存储的redis的key值
     *
     * @param pjp
     * @return
     */
    private String getRedisKey(ProceedingJoinPoint pjp) throws InstantiationException, IllegalAccessException {
        //1.获取参数值
        Object[] args = pjp.getArgs();
        //2.获取参数名称
        Signature signature = pjp.getSignature();
        if (signature instanceof MethodSignature) {
            MethodSignature methodSignature = (MethodSignature) signature;
            String[] parameterNames = methodSignature.getParameterNames();
        }
        //3.获取返回类型
        Signature signature1 = pjp.getSignature();
        if (signature1 instanceof MethodSignature) {
            MethodSignature methodSignature = (MethodSignature) signature;
            // 被切的方法
            Method method = methodSignature.getMethod();
            // 返回类型
            Class<?> methodReturnType = method.getReturnType();
            // 实例化
            Object o = methodReturnType.newInstance();
        }
        //4.全限定类名
        Signature signature2 = pjp.getSignature();
        String declaringTypeName = signature2.getDeclaringTypeName();
        //5.方法名
        Signature signature3 = pjp.getSignature();
        String name = signature3.getName();


        // 1、获取被代理的对象类型
        String className = pjp.getTarget().getClass().getName();

        // 2、获取当前代理的方法名
        String methodName = pjp.getSignature().getName();

        // 3、获取入参并转换成jason串   入参要有userId,或者公共方法获取userId
        String convertJson = convertArgsToJson(pjp.getArgs());
         //(此key值过长,建议用md5加密转换缩短长度)
        String redisKey = className + "->" + methodName + "->" + convertJson;

        return redisKey;
    }

    /**
     * 将传入的参数拼接成json类型的字符串
     *
     * @param args
     * @return
     */
    private String convertArgsToJson(Object[] args) {
        StringBuilder convertJson = new StringBuilder();
        for (Object object : args) {
            if (!(object instanceof HttpServletRequest)) {  // 此处判断不能舍去
                convertJson.append(JSONObject.toJSONString(object));
            }
        }
        return convertJson.toString();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值