# 自定义接口幂等性注解 @Idempotent (基于redis缓存)
# 自定义注解
/**
* 幂等或防重复提交
*
* @author quansheng.zhang
* @since 2022/8/16 15:16
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
/**
* 幂等的key,不设置则取所有参数toString
* spel表达式,支持多项拼接
*/
String[] keys() default {};
/**
* keys的分隔符
*/
String split() default "-";
/**
* 锁过期时间
*/
int timeout() default 5000;
/**
* 锁过期时间单位
*/
TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
/**
* 锁的位置,不设置则取URI
*/
String location() default "";
/**
* 提醒信息
*/
String message() default "操作过于频繁,请稍后重试!";
/**
* 执行完成后是否释放key
*/
boolean delKey() default false;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 定义切面
import com.zhengcheng.cache.expression.KeyResolver;
import com.zhengcheng.cache.idempotent.annotation.Idempotent;
import com.zhengcheng.common.exception.IdempotentException;
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.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* IdempotentAspect
*
* @author quansheng.zhang
* @since 2022/8/16 15:25
*/
@Slf4j
@Aspect
public class IdempotentAspect {
private static final String REPEAT_LOCK_PREFIX = "zc:idempotent:";
@Resource
private RedissonClient redissonClient;
@Resource
private KeyResolver keyResolver;
@Pointcut("@annotation(com.zhengcheng.cache.idempotent.annotation.Idempotent)")
public void pointCut() {
}
/**
* around
*/
@Around("pointCut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
Object[] args = joinPoint.getArgs();
if (!method.isAnnotationPresent(Idempotent.class)) {
return joinPoint.proceed(args);
}
Idempotent idempotent = method.getAnnotation(Idempotent.class);
String lockKey = getLockKey(idempotent, joinPoint);
RLock lock = redissonClient.getLock(lockKey);
if (lock == null || !lock.tryLock(0, idempotent.timeout(), idempotent.timeUnit())) {
log.error("handle present repeat submission tryLock failed, lockKey: {}", lockKey);
throw new IdempotentException(idempotent.message());
}
try {
return joinPoint.proceed(args);
} finally {
if (idempotent.delKey() && lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
private String getLockKey(Idempotent idempotent, ProceedingJoinPoint joinPoint) {
String suffix;
if ((idempotent.keys() == null || idempotent.keys().length == 0) && (joinPoint.getArgs() != null && joinPoint.getArgs().length > 0)) {
suffix = Arrays.asList(joinPoint.getArgs()).toString();
} else {
suffix = keyResolver.resolver(idempotent.keys(), idempotent.split(), joinPoint);
}
String location;
if (!StringUtils.hasText(idempotent.location())) {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
assert requestAttributes != null;
HttpServletRequest request = requestAttributes.getRequest();
location = request.getRequestURI();
} else {
location = idempotent.location();
}
return REPEAT_LOCK_PREFIX + location + ":" + suffix;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# 自定义异常类
/**
* 幂等性异常
*
* @author : quansheng.zhang
* @date : 2019/9/23 0:50
*/
public class IdempotentException extends RuntimeException {
private static final long serialVersionUID = 6610083281801529147L;
public IdempotentException(String message) {
super(message);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13