diff --git a/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/idempotent/annotation/Idempotent.java b/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/idempotent/annotation/Idempotent.java new file mode 100644 index 0000000..cd0054a --- /dev/null +++ b/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/idempotent/annotation/Idempotent.java @@ -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; + +} diff --git a/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/idempotent/aspect/IdempotentAspect.java b/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/idempotent/aspect/IdempotentAspect.java new file mode 100644 index 0000000..f085801 --- /dev/null +++ b/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/idempotent/aspect/IdempotentAspect.java @@ -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> 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 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 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 map = THREAD_CACHE.get(); + map.put(KEY, key); + map.put(DELKEY, delKey); + } + + @After("pointCut()") + public void afterPointCut(JoinPoint joinPoint) { + Map map = THREAD_CACHE.get(); + if (CollectionUtils.isEmpty(map)) { + return; + } + + Map 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(); + } + +}