Java使用@Idempotent注解处理幂等问题,防止二次点击
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Java使⽤@Idempotent注解处理幂等问题,防⽌⼆次点击Java使⽤⾃定义注解@Idempotent处理幂等问题,防⽌⼆次点击
幂等实现原理就是利⽤AOP⾯向切⾯编程,在执⾏业务逻辑之前插⼊⼀个⽅法,⽣成⼀个token,存⼊redis并插⼊到response中返回给前台,然后前台再拿着这个token发起请求,经过判断,只执⾏第⼀次请求,多余点击的请求都拦截下来.
创建⾃定义注解@Idempotent
package mon.annotation;
import ng.annotation.*;
//注解信息会被添加到Java⽂档中
@Documented
//注解的⽣命周期,表⽰注解会被保留到什么阶段,可以选择编译阶段、类加载阶段,或运⾏阶段
@Retention(RetentionPolicy.RUNTIME)
//注解作⽤的位置,ElementType.METHOD表⽰该注解仅能作⽤于⽅法上
@Target(ElementType.METHOD)
public @interface Idempotent {
}
创建⾃定义注解@IdempotentToken
package mon.annotation;
import ng.annotation.*;
//注解信息会被添加到Java⽂档中
@Documented
//注解的⽣命周期,表⽰注解会被保留到什么阶段,可以选择编译阶段、类加载阶段,或运⾏阶段
@Retention(RetentionPolicy.RUNTIME)
//注解作⽤的位置,ElementType.METHOD表⽰该注解仅能作⽤于⽅法上
@Target(ElementType.METHOD)
public @interface IdempotentToken {
}
@Idempotent注解的配置类IdempotentInterceptor
package org.jeecg.config.idempotent;
import cn.hutool.core.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
import ng3.StringUtils;
import mon.annotation.Idempotent;
import mon.annotation.IdempotentToken;
import mon.util.RedisUtil;
import ponent;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import ng.reflect.Method;
import java.util.UUID;
/**
* @author zbw
*/
@Slf4j
@Component
public class IdempotentInterceptor implements HandlerInterceptor {
private static final String VERSION_NAME = "version";
private static final String TOKEN_NAME = "idempotent_token";
private static final String IDEMPOTENT_TOKEN_PREFIX = "idempotent:token:";
/*private RedisTemplate<String, Object> redisTemplate;*/
private RedisUtil redisUtil;
public IdempotentInterceptor(RedisUtil redisUtil){
this.redisUtil = redisUtil;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
IdempotentToken idempotentTokenAnnotation = method.getAnnotation(IdempotentToken.class);
if(idempotentTokenAnnotation!=null){
//重新更新token
String token = UUID.randomUUID().toString().replaceAll("-","");
response.addHeader(TOKEN_NAME,token);
//解决后端传递token前端⽆法获取问题
response.addHeader("Access-Control-Expose-Headers",TOKEN_NAME);
redisUtil.set(IDEMPOTENT_TOKEN_PREFIX+token,token);
}
Idempotent idempotentAnnotation = method.getAnnotation(Idempotent.class);
if(idempotentAnnotation!=null){
checkIdempotent(request);
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
private void checkIdempotent(HttpServletRequest request) {
//⾸先到request中去拿TOKEN_NAME
String token = request.getHeader(TOKEN_NAME);
if(ObjectUtil.isEmpty(token)){
token = "";
}
if (StringUtils.isBlank(token)) {// header中不存在token
token = request.getParameter(TOKEN_NAME);
if (StringUtils.isBlank(token)) {// parameter中也不存在token
throw new IllegalArgumentException("幂等token丢失,请勿重复提交");
}
}
if (!redisUtil.hasKey(IDEMPOTENT_TOKEN_PREFIX+token)) {
throw new IllegalArgumentException("请勿重复提交");
}
boolean bool = redisUtil.delete(IDEMPOTENT_TOKEN_PREFIX+token);
if(!bool){
throw new IllegalArgumentException("没有删除对应的token");
}
}
}
这个注解配置是基于redis的,在这⾥redis的配置略过,本章只是讲解如何简单的使⽤
后端的话,需要在该页⾯的list查询上加上@IdempotentToken注解,原理是涉及到查询成功后会刷新⼀下页⾯,重新获取token @Idempotent注解配置完成就可以直接加在controller的对应⽅法上,如图:
前端的⼀些配置(这⾥我⽤的是ant design vue):
user.js⾥⾯加⼊
idempotentToken:''
SET_IDEMPOTENT_TOKEN:(state,token)=>{
state.idempotentToken = token
},
getter.js⾥⾯加⼊
idempotentToken: state => er.idempotentToken,
request.js⾥⾯修改
// request interceptor
e(config => {
const token = Vue.ls.get(ACCESS_TOKEN)
if (token) {
config.headers[ 'X-Access-Token' ] = token // 让每个请求携带⾃定义 token 请根据实际情况⾃⾏修改 }
const idempotent_token = Vue.ls.get(IDEMPOTENT_TOKEN)
if(idempotent_token){
config.headers['idempotent_token'] = idempotent_token
}
//update-begin-author:taoyan date:2020707 for:多租户
let tenantid = Vue.ls.get(TENANT_ID)
if (!tenantid) {
tenantid = 0;
}
config.headers[ 'tenant_id' ] = tenantid
//update-end-author:taoyan date:2020707 for:多租户
if(config.method=='get'){
if(config.url.indexOf("sys/dict/getDictItems")<0){
config.params = {
_t: Date.parse(new Date())/1000,
...config.params
}
}
}
return config
},(error) => {
return Promise.reject(error)
})
// response interceptor
e((response) => {
let idempotent_token = response.headers[IDEMPOTENT_TOKEN]
if(idempotent_token){
Vue.ls.set(IDEMPOTENT_TOKEN,idempotent_token,7 * 24 * 60 * 60 * 1000)
mit('SET_IDEMPOTENT_TOKEN', idempotent_token)
}
return response.data
}, err)
const installer = {
vm: {},
install (Vue, router = {}) {
e(VueAxios, router, service)
}
mutation-type.js加⼊定义的常量
export const IDEMPOTENT_TOKEN='idempotent_token'
这样算是完成了,再次出现⼆次请求时直接会被拦截。