单体项目前后台解决表单重复提交的幂等性问题
一、前台
1. main.js添加全局参数
import Vue from ‘vue’ // 防止重复提交指令 const preventReClick = Vue.directive(‘preventReClick’, { inserted (el, binding) { el.addEventListener(‘click’, () => { if (!el.disabled) { el.disabled = true setTimeout(() => { el.disabled = false }, binding.value || 3000) } }) } }) export { preventReClick } Vue.use(preventReClick);
2. 在el-button标签中使用v-preventReClick
<el-button v-preventReClick @click="save()">保存</el-button>
二、后台
1. 依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>21.0</version> </dependency>
2. 自定义锁注解
import java.lang.annotation.*; /** * 自定义 锁的注解 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface LocalLock { String key() default ""; }
3. 配置类
import com.community.common.model.ErrorCode; import com.community.manage.model.Public.BoardMessage; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; 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.springframework.context.annotation.Configuration; import org.springframework.util.StringUtils; import java.lang.reflect.Method; import java.util.concurrent.TimeUnit; /** * 基于本地缓存 */ @Aspect @Configuration public class LockMethodInterceptor { private static final Cache<String, Object> CACHES = CacheBuilder.newBuilder() // 最大缓存 100 个 .maximumSize(1000) // 设置写缓存后 5 秒钟过期 .expireAfterWrite(20, TimeUnit.SECONDS) .build(); @Around("execution(public * *(..)) && @annotation(LocalLock)") public Object interceptor(ProceedingJoinPoint pjp){ MethodSignature signature = (MethodSignature) pjp.getSignature(); Method method = signature.getMethod(); LocalLock localLock = method.getAnnotation(LocalLock.class); //通过反射拿到注解对象 String key = getKey(localLock.key(), pjp.getArgs()); // 控制台设置了 key=arg[0},所以只匹配了第一个参数 if (!StringUtils.isEmpty(key)) { if (CACHES.getIfPresent(key) != null) { return new BoardMessage(ErrorCode.getFail(),"请勿重复请求"); } // 如果是第一次请求,就将 key 当前对象压入缓存中 CACHES.put(key, key); } try { return pjp.proceed(); } catch (Throwable throwable) { throw new RuntimeException("服务器异常"); } finally { CACHES.invalidate(key); } } /** * key 的生成策略,如果想灵活可以写成接口与实现类的方式 * * @param keyExpress 表达式 * @param args 参数 * @return 生成的key */ private String getKey(String keyExpress, Object[] args) { for (int i = 0; i < args.length; i++) { keyExpress = keyExpress.replace("arg[" + i + "]", args[i].toString()); } return keyExpress; } }
4. 在controller方法上用@LocalLock
@PostMapping @LocalLock(key="arg[0]") public BoardMessage saveDepartment(VueDepartment vueDepartment) { }
上一篇:
通过多线程提高代码的执行效率例子