说明

  • 提供数据接口时,如果接口耗时较长,但数据一般情况下变化不大,可以使用自定义缓存,提高接口响应速度
  • 数据发生变化时,主动使缓存失效,保证接口返回的数据为最新数据
  • 本方案采用 注解+AOP 方案实现自定义缓存

封装

定义缓存注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package org.jeecg.modules.mock.aspect.anno;

import org.springframework.core.annotation.AliasFor;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomCache {
@AliasFor("key")
String value() default "";

@AliasFor("value")
String key() default "";

// 缓存下支持参数,参数名集合
String[] params() default {};

// 缓存有效期,默认 0 不过期。单位秒
int timeout() default 0;
}

定义缓存失效注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package org.jeecg.modules.mock.aspect.anno;

import org.springframework.core.annotation.AliasFor;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomCacheEvict {
@AliasFor("key")
String value() default "";

@AliasFor("value")
String key() default "";

// 缓存下支持参数,参数名集合
String[] params() default {};

// 支持多个缓存 key。表示使多个缓存 key 失效
// 使用时与 key/value 二选一
String[] keys() default {};
}

缓存 AOP

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package org.jeecg.modules.mock.aspect;

import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
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.jeecg.common.constant.CacheConstant;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.RedisUtil;
import org.jeecg.modules.basic.base.CustomDataConstant;
import org.jeecg.modules.mock.aspect.anno.CustomCache;
import org.springframework.beans.factory.annotation.Autowired;
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.Arrays;
import java.util.stream.Collectors;


/**
* 接口缓存
*
* @Author scott
* @email jeecgos@163.com
* @Date 2018年1月14日
*/
@Aspect
@Component
@Slf4j
public class CustomCacheAspect {
@Autowired
private RedisUtil redisUtil;

@Pointcut("@annotation(org.jeecg.modules.mock.aspect.anno.CustomCache)")
public void logPointCut() {

}

public static String getCacheKey(String key, String[] params, HttpServletRequest request) {
if(params.length == 0) {
return key;
}
String paramValues = Arrays.asList(params).stream().map(e -> {
String parameterValue = request.getParameter(e);
if(StrUtil.isEmpty(parameterValue) && CustomDataConstant.HTTP_PARAMETER_ORGCODE.equals(e)) {
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
return sysUser.getDataOrgCode();
} else {
return parameterValue;
}
}).filter(e->StrUtil.isNotBlank(e)).collect(Collectors.joining(""));

if(StrUtil.isNotBlank(paramValues)) {
return key + ":" + paramValues.hashCode();
} else {
return key;
}
}

@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
Object resultData = getDataFromCache(point);
if(resultData != null) {
return resultData;
}

long beginTime = System.currentTimeMillis();
//执行方法
Object result = point.proceed();
//执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
log.info("耗时:{} 毫秒", time);

//保存数据到缓存
saveDataInCache(point, result);

return result;
}

private Object getDataFromCache(ProceedingJoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();

CustomCache annotation = method.getAnnotation(CustomCache.class);
if(annotation != null){
String key = annotation.key();
String[] params = annotation.params();

HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
String cacheKey = getCacheKey(key, params, request);
return redisUtil.get(CacheConstant.CUSTOM_CACHE + ":" + cacheKey);
}
return null;
}

private void saveDataInCache(ProceedingJoinPoint joinPoint, Object obj) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();

CustomCache annotation = method.getAnnotation(CustomCache.class);
if(annotation != null){
String key = annotation.key();
String[] params = annotation.params();
int timeout = annotation.timeout();

HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
String cacheKey = getCacheKey(key, params, request);
if(timeout > 0) {
redisUtil.set(CacheConstant.CUSTOM_CACHE + ":" + cacheKey, obj, timeout);
} else {
redisUtil.set(CacheConstant.CUSTOM_CACHE + ":" + cacheKey, obj);
}
}
}
}

缓存失效 AOP

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
package org.jeecg.modules.mock.aspect;

import cn.hutool.core.util.StrUtil;
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.jeecg.common.constant.CacheConstant;
import org.jeecg.common.util.RedisUtil;
import org.jeecg.modules.mock.aspect.anno.CustomCacheEvict;
import org.jeecg.modules.mock.util.MockConstant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
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;


/**
* 接口缓存
*
* @Author scott
* @email jeecgos@163.com
* @Date 2018年1月14日
*/
@Aspect
@Component
@Slf4j
public class CustomCacheEvictAspect {
@Autowired
private RedisUtil redisUtil;
@Autowired
private RedisTemplate redisTemplate;

@Pointcut("@annotation(org.jeecg.modules.mock.aspect.anno.CustomCacheEvict)")
public void logPointCut() {

}

@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
//执行方法
Object result = point.proceed();

// 使缓存失效
evictCache(point);
return result;
}

private void evictCache(ProceedingJoinPoint point) {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();

CustomCacheEvict annotation = method.getAnnotation(CustomCacheEvict.class);
if(annotation != null){
String key = annotation.key();
String[] params = annotation.params();

if(StrUtil.isNotBlank(key)) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
String cacheKey = CustomCacheAspect.getCacheKey(key, params, request);
if(cacheKey.equals(key)) {
redisTemplate.delete(redisTemplate.keys(CacheConstant.CUSTOM_CACHE + ":" + cacheKey + "*"));
} else {
redisUtil.del(CacheConstant.CUSTOM_CACHE + ":" + cacheKey);
}
} else {
String[] keys = annotation.keys();
for (String cacheKey : keys) {
redisTemplate.delete(redisTemplate.keys(CacheConstant.CUSTOM_CACHE + ":" + cacheKey + "*"));
}
}
}
}
}

使用

缓存

1
2
3
4
5
6
7
public static final String API_CACHE_KEY_EVENT = "event";

@Override
@CustomCache(key = API_CACHE_KEY_EVENT + ":DepartEventSta", params = {"beginTime", "endTime", "orgCode"})
public Result<List<Map<String, Object>>> customData(BasicCustomDataConfig dataConfig, HttpServletRequest request) {

}

缓存失效

1
2
3
4
5
@CustomCacheEvict(key = API_CACHE_KEY_EVENT)
@PostMapping(value = "/createMockData")
public Result<?> createMockData(@RequestBody GeneratorParam generatorParam) {
return generatorEventMockData.execute(generatorParam);
}