redis防重复提交

package org.mid.config.aspect;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 功能描述: 避免重复提交
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NoRepeatSubmit {

    /**
     * 指定时间内不可重复提交,单位毫秒
     *
     * @return
     */
    long timeout() default 3000;

    /**
     * 数据主键ID的属性名, 如果多个属性作为唯一标识用英文逗号分隔
     */
    String idColumns() default "id";
}

package org.mid.config.aspect;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.cango.cloud.redis.utils.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.cango.mid.carserviceweb.config.jwt.JwtUtil;
import org.cango.mid.carserviceweb.utils.ResponseDataUtil;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Date;

/**
 * 平常开发项目可能会遇到这些情况
 * 1.用于用户误操作,多次点击表单提交按钮
 * 2.由于网速等原因造成页面卡顿,用户重复刷新提交页面
 * 3.恶业用户如利用postman等工具重复恶意提交表单
 * 这些情况都会造成表单的重复提交,造成数据重复,如何防止表单重复提交,汇总了以下4种方案
 * <p>
 * 功能描述:aop 自定义切入点(spring-boot-starter-aop)
 * <p>
 * 其他方案:
 * 1.通过javascript屏蔽提交按钮(不推荐 )
 * 2.数据库增加唯一键约束 (简单粗暴)
 * 3.利用session防止表单重复提交-拦截器(推荐):服务返回表单页面时,会生成一个token,当表单提交时,token失效;token为空或者失效则表单提交失败(发送token,验证token)
 * 4.使用apo自定义切入实现
 * <p>
 * 方案4 实现原理:
 * 1.自定义重复提交标记(noRepeatSubmit)
 * 2.对于防止重复提交的Controller里的方法加上注解
 * 3.新增Aspect切入点,为noRepeatSubmit加入切入点
 * 4.每次提交表单时,aspect都会保存当前key到redis(设置过期时间)
 * 5.重复提交时aspect会判断当前redis是否有该key,若有则拦截
 * <p>
 * aspectj面向切面的框架,提供的一些功能实现aop代理非常方便
 *
 */
@Slf4j
@Aspect
@Component
public class RepeatSubmitAspect {

    private final String PRE_KEY = "web-CarService";

    /**
     * 切面类中定义增强
     *
     * @param noRepeatSubmit
     */
    @Around("@annotation(noRepeatSubmit)")
    public Object around(ProceedingJoinPoint point, NoRepeatSubmit noRepeatSubmit) throws Throwable {
        // 获取超时时间
        long timeOut = noRepeatSubmit.timeout();
        // 数据主键ID的属性名
        String idColumns = noRepeatSubmit.idColumns();
        // 组装key:用户唯一标识+操作类和方法
        String key = getRedisKey(point, idColumns);

        // value为空,则加入缓存,并设置过期过期时间
        if (RedisUtil.exists(key)) {
            return ResponseDataUtil.failure("请勿重复提交");
        } else {
            RedisUtil.set(key, String.valueOf(new Date().getTime()), timeOut);

            log.info("设置redis key:{}, 超时时间:{}ms", key, timeOut);
        }

        //执行Object
        Object object = point.proceed();

        return object;
    }

//    @After("@annotation(noRepeatSubmit)")
//    public void releaseResource(JoinPoint point, NoRepeatSubmit noRepeatSubmit) {
//        // 组装key:用户唯一标识+操作类和方法
//        String key = getRedisKey(point);
//        System.out.println("@After:deleteObject:" + key);
//        redisTemplate.delete(key);
//    }

    /**
     * 获取当前登录用户
     */
    private Integer getUserId() {
        Subject subject = SecurityUtils.getSubject();
        Integer userId = JwtUtil.getUserId(String.valueOf(subject.getPrincipal()));
        if (userId == null) {
            userId = 0;
        }
        return userId;
    }


    /**
     * @param point
     * @param idColumns 数据主键ID的属性名
     * @return
     */
    private String getRedisKey(JoinPoint point, String idColumns) {
        // 获取注解
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();

        // 获取类,方法
        String className = method.getDeclaringClass().getName();
        String methodName = method.getName();
        String fullMethod = className + "." + methodName;

        String params = Arrays.toString(point.getArgs());
        log.info("请求路径:{}, 参数:{}", fullMethod, params);

        // 获取数据ID
        JSONObject obj = JSONArray.parseArray(params).getJSONObject(0);
        String[] idColumnsArr = idColumns.split(",");
        String dataId = "";
        for (String col : idColumnsArr) {
            dataId = String.valueOf(obj.get(col)) + "_";
        }
        // 组装key:用户唯一标识+操作类和方法
        final String key = PRE_KEY + "_" + className + "_" + methodName + "_" + getUserId() + "_" + dataId;
        return key;
    }

}

 @PostMapping("/test")
 @NoRepeatSubmit(idColumns = "code")
    public ResultModel addOrder(@RequestBody OrderReq req) {
}
posted @   亲爱的阿道君  阅读(521)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示