一、前言
有時(shí)在項(xiàng)目開(kāi)發(fā)中某些接口邏輯比較復(fù)雜,響應(yīng)時(shí)間長(zhǎng),那么可能導(dǎo)致重復(fù)提交問(wèn)題。
二、如何解決
1.先定義一個(gè)防重復(fù)提交的注解。
import java.lang.annotation.*;
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit {
/**
* 防重復(fù)操作限時(shí)標(biāo)記數(shù)值(存儲(chǔ)redis限時(shí)標(biāo)記數(shù)值)
*/
String value() default "value" ;
/**
* 防重復(fù)操作過(guò)期時(shí)間(借助redis實(shí)現(xiàn)限時(shí)控制)
*/
int expireSeconds() default 10;
}
2.編寫(xiě)防重復(fù)操作的AOP
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Objects;
@Slf4j
@Component
@Aspect
@Order(0)
public class NoRepeatSubmitAspect {
private static final String TOKENAuthorization = "Authorization";
private static final String TOKENUSERNAME = "api-userName";
private static final String PREVENT_DUPLICATION_PREFIX = "PREVENT_DUPLICATION_PREFIX:";
@Autowired
private RedisService redisService;
@Pointcut("@annotation(com.dp.aop.annotation.RepeatSubmit)")
public void preventDuplication() {}
@Around("preventDuplication()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
if (Objects.isNull(request)) {
return joinPoint.proceed();
}
//獲取執(zhí)行方法
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
//獲取防重復(fù)提交注解
RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
//獲取token以及方法標(biāo)記,生成redisKey
String header = request.getHeader(TOKENAuthorization);
String token = header == null ? "" : header;
String requestHeader = request.getHeader(TOKENUSERNAME);
String headerToken = requestHeader == null ? "" : requestHeader;
token = token + headerToken;
String url = request.getRequestURI();
// 通過(guò)前綴 + url + token + 函數(shù)參數(shù)簽名 來(lái)生成redis上的 key
String redisKey = PREVENT_DUPLICATION_PREFIX
.concat(url)
.concat(token)
.concat(getMethodSign(method, joinPoint.getArgs()));
RedisLock redisLock = null;
try {
try {
redisLock = redisService.tryLock(redisKey, annotation.expireSeconds());
} catch (Exception e) {
log.error("tryLock error ", e);
throw new BizException(CommonMsgConstants.NoRepeatSubmitMsg);
}
return joinPoint.proceed();
} catch (Throwable throwable) {
log.error("throwable trace is ", throwable);
throw new RuntimeException(throwable);
} finally {
if (Objects.nonNull(redisLock)) {
redisLock.unlock();
}
}
}
/**
* 生成方法標(biāo)記:采用數(shù)字簽名算法SHA1對(duì)方法簽名字符串加簽
*
* @param method
* @param args
* @return
*/
private String getMethodSign(Method method, Object... args) {
StringBuilder sb = new StringBuilder(method.toString());
for (Object arg : args) {
sb.append(toString(arg));
}
return DigestUtil.sha1Hex(sb.toString());
}
private String toString(Object arg) {
if (Objects.isNull(arg)) {
return "null";
}
if (arg instanceof Number) {
return arg.toString();
}
return JSONObject.toJSONString(arg);
}
}
3.接下來(lái)定義redisService類(lèi)文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-669062.html
@Component
public class RedisService {
public RedisLock tryLock(String lockKey, int expireTime) {
String lockValue = UUID.randomUUID().toString();
Boolean hasLock = (Boolean)this.redisTemplate.execute((connection) -> {
Object nativeConnection = connection.getNativeConnection();
String status = null;
if (nativeConnection instanceof Jedis) {
Jedis jedis = (Jedis)nativeConnection;
status = jedis.set(lockKey, lockValue, "nx", "ex", expireTime);
} else {
JedisCluster jedisx = (JedisCluster)nativeConnection;
status = jedisx.set(lockKey, lockValue, "nx", "ex", (long)expireTime);
}
return "OK".equals(status);
});
if (hasLock) {
return new RedisService.RedisLockInner(this.redisTemplate, lockKey, lockValue);
} else {
throw new RuntimeException("獲取鎖失敗,lockKey:" + lockKey);
}
}
private class RedisLockInner implements RedisLock {
private RedisTemplate redisTemplate;
private String key;
private String expectedValue;
protected RedisLockInner(RedisTemplate redisTemplate, String key, String expectedValue) {
this.redisTemplate = redisTemplate;
this.key = key;
this.expectedValue = expectedValue;
}
public Object unlock() {
final List<String> keys = new ArrayList();
keys.add(this.key);
final List<String> values = new ArrayList();
values.add(this.expectedValue);
Object result = this.redisTemplate.execute(new RedisCallback<Long>() {
public Long doInRedis(RedisConnection connection) throws DataAccessException {
Object nativeConnection = connection.getNativeConnection();
return nativeConnection instanceof JedisCluster ? (Long)((JedisCluster)nativeConnection).eval("if redis.call('get',KEYS[1])==ARGV[1]\n then\n return redis.call('del',KEYS[1])\n else\n return 0\n end", keys, values) : (Long)((Jedis)nativeConnection).eval("if redis.call('get',KEYS[1])==ARGV[1]\n then\n return redis.call('del',KEYS[1])\n else\n return 0\n end", keys, values);
}
});
return result;
}
public void close() throws Exception {
this.unlock();
}
}
}
4.最后在Controller接口加上注解就行了。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-669062.html
到了這里,關(guān)于springboot aop實(shí)現(xiàn)接口防重復(fù)操作的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!