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) {
}
不积跬步,无以至千里;不积小流,无以成江海。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具