增加了幂等控制
This commit is contained in:
@ -0,0 +1,38 @@
|
||||
package com.chinaunicom.mall.ebtp.common.idempotent.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author ITyunqing
|
||||
*/
|
||||
@Inherited
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(value = RetentionPolicy.RUNTIME)
|
||||
public @interface Idempotent {
|
||||
|
||||
/**
|
||||
* 有效期 默认:1 有效期要大于程序执行时间,否则请求还是可能会进来
|
||||
* @return expireTime
|
||||
*/
|
||||
int expireTime() default 1;
|
||||
|
||||
/**
|
||||
* 时间单位 默认:s
|
||||
* @return TimeUnit
|
||||
*/
|
||||
TimeUnit timeUnit() default TimeUnit.SECONDS;
|
||||
|
||||
/**
|
||||
* 提示信息,可自定义
|
||||
* @return String
|
||||
*/
|
||||
String info() default "重复请求,请稍后重试";
|
||||
|
||||
/**
|
||||
* 是否在业务完成后删除key true:删除 false:不删除
|
||||
* @return boolean
|
||||
*/
|
||||
boolean delKey() default false;
|
||||
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
package com.chinaunicom.mall.ebtp.common.idempotent.aspect;
|
||||
|
||||
import com.chinaunicom.mall.ebtp.common.exception.common.CommonExceptionEnum;
|
||||
import com.chinaunicom.mall.ebtp.common.idempotent.annotation.Idempotent;
|
||||
import com.chinaunicom.mall.ebtp.common.util.JsonUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.annotation.*;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
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.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* The Idempotent Aspect
|
||||
*
|
||||
* @author ITyunqing
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@Aspect
|
||||
public class IdempotentAspect {
|
||||
|
||||
private static final ThreadLocal<Map<String, Object>> THREAD_CACHE = ThreadLocal.withInitial(HashMap::new);
|
||||
|
||||
private static final String RMAPCACHE_KEY = "idempotent";
|
||||
|
||||
private static final String KEY = "key";
|
||||
|
||||
private static final String DELKEY = "delKey";
|
||||
|
||||
@Autowired
|
||||
@Qualifier("redisTempletToken")
|
||||
private RedisTemplate<String, Object> redisTempletToken;
|
||||
|
||||
@Pointcut("@annotation(com.chinaunicom.mall.ebtp.common.idempotent.annotation.Idempotent)")
|
||||
public void pointCut() {
|
||||
}
|
||||
|
||||
@Before("pointCut()")
|
||||
public void beforePointCut(JoinPoint joinPoint) {
|
||||
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
|
||||
.getRequestAttributes();
|
||||
assert requestAttributes != null;
|
||||
HttpServletRequest request = requestAttributes.getRequest();
|
||||
|
||||
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
||||
Method method = signature.getMethod();
|
||||
if (!method.isAnnotationPresent(Idempotent.class)) {
|
||||
return;
|
||||
}
|
||||
Idempotent idempotent = method.getAnnotation(Idempotent.class);
|
||||
|
||||
|
||||
/*key : url + 参数列表作为区分*/
|
||||
String url = request.getRequestURL().toString();
|
||||
String argString = JsonUtils.objectToJson(Arrays.asList(joinPoint.getArgs()));
|
||||
assert argString != null;
|
||||
String redisKey = String.valueOf(argString.hashCode());
|
||||
String key = url.concat(":").concat(redisKey);
|
||||
|
||||
long expireTime = idempotent.expireTime();
|
||||
String info = idempotent.info();
|
||||
TimeUnit timeUnit = idempotent.timeUnit();
|
||||
boolean delKey = idempotent.delKey();
|
||||
|
||||
// do not need check null
|
||||
Map<Object, Object> rMapCache = redisTempletToken.opsForHash().entries(RMAPCACHE_KEY);
|
||||
String value = LocalDateTime.now().toString().replace("T", " ");
|
||||
if (null != rMapCache.get(key)) {
|
||||
// had stored
|
||||
CommonExceptionEnum.FRAME_EXCEPTION_COMMON_DATA_OTHER_ERROR.customValidName("[idempotent]:" + info,true);
|
||||
}
|
||||
synchronized (this) {
|
||||
boolean submitAble = redisTempletToken.opsForValue().setIfAbsent(key, value,expireTime,timeUnit);
|
||||
if (!submitAble) {
|
||||
CommonExceptionEnum.FRAME_EXCEPTION_COMMON_DATA_OTHER_ERROR.customValidName("[idempotent]:" + info,true);
|
||||
}
|
||||
else {
|
||||
log.info("[idempotent]:has stored key={},value={},expireTime={}{},now={}", key, value, expireTime,
|
||||
timeUnit, LocalDateTime.now().toString());
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Object> map = THREAD_CACHE.get();
|
||||
map.put(KEY, key);
|
||||
map.put(DELKEY, delKey);
|
||||
}
|
||||
|
||||
@After("pointCut()")
|
||||
public void afterPointCut(JoinPoint joinPoint) {
|
||||
Map<String, Object> map = THREAD_CACHE.get();
|
||||
if (CollectionUtils.isEmpty(map)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<Object, Object> mapCache = redisTempletToken.opsForHash().entries(RMAPCACHE_KEY);
|
||||
if (mapCache.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
String key = map.get(KEY).toString();
|
||||
boolean delKey = (boolean) map.get(DELKEY);
|
||||
|
||||
if (delKey) {
|
||||
mapCache.remove(key);
|
||||
log.info("[idempotent]:has removed key={}", key);
|
||||
}
|
||||
THREAD_CACHE.remove();
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user