RedisCacheAspect.java 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. package com.poyee.aspectj;
  2. import com.alibaba.fastjson.JSONObject;
  3. import com.alibaba.fastjson.parser.Feature;
  4. import com.poyee.annotation.RedisCache;
  5. import com.poyee.base.dto.BaseReq;
  6. import com.poyee.base.dto.Page;
  7. import com.poyee.base.dto.Result;
  8. import com.poyee.base.dto.UserInfo;
  9. import com.poyee.enums.CacheExpirePolicy;
  10. import com.poyee.util.DateUtils;
  11. import com.poyee.util.ServletUtils;
  12. import com.poyee.util.SpElUtils;
  13. import lombok.extern.slf4j.Slf4j;
  14. import org.apache.commons.lang3.StringUtils;
  15. import org.aspectj.lang.ProceedingJoinPoint;
  16. import org.aspectj.lang.annotation.Around;
  17. import org.aspectj.lang.annotation.Aspect;
  18. import org.aspectj.lang.reflect.MethodSignature;
  19. import org.springframework.core.DefaultParameterNameDiscoverer;
  20. import org.springframework.core.annotation.Order;
  21. import org.springframework.data.redis.core.RedisTemplate;
  22. import org.springframework.stereotype.Component;
  23. import java.lang.reflect.Field;
  24. import java.lang.reflect.Method;
  25. import java.util.HashMap;
  26. import java.util.List;
  27. import java.util.Map;
  28. import java.util.Objects;
  29. /**
  30. * Redis 缓存切面
  31. */
  32. @Slf4j
  33. @Aspect
  34. @Component
  35. @Order(1)
  36. public class RedisCacheAspect {
  37. private final RedisTemplate<String, Object> redisTemplate;
  38. public RedisCacheAspect(RedisTemplate<String, Object> redisTemplate) {
  39. this.redisTemplate = redisTemplate;
  40. }
  41. @Around("@annotation(redisCache)")
  42. public Object doAround(ProceedingJoinPoint joinPoint, RedisCache redisCache) throws Throwable {
  43. String keyExpression = redisCache.key();
  44. String keySource = redisCache.keySource();
  45. CacheExpirePolicy expirePolicy = redisCache.expirePolicy();
  46. long customExpireTime = redisCache.expireTime();
  47. if (expirePolicy == CacheExpirePolicy.CUSTOM) {
  48. expirePolicy.setExpireTime(customExpireTime);
  49. }
  50. // 构建上下文变量
  51. Map<String, Object> contextVars = new HashMap<>();
  52. //如果获取不到用户信息, 判断方法参数中 doCache 是否为 true,如果是则解析方法参数中数据 进行缓存, 否则直接返回结果
  53. Object[] args = joinPoint.getArgs();
  54. MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
  55. Method method = methodSignature.getMethod();
  56. DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
  57. String[] paramNames = nameDiscoverer.getParameterNames(method);
  58. // 检查方法参数中是否有 doCache 参数且为 true
  59. boolean doCache = false;
  60. Object arg = null;
  61. for (int i = 0; i < args.length; i++) {
  62. if (paramNames != null && i < paramNames.length) {
  63. arg = args[i];
  64. if(arg instanceof BaseReq && ((BaseReq) arg).isDoRedis()){
  65. doCache = true;
  66. }
  67. }
  68. }
  69. // 从指定 source 获取变量值(如 userContext)
  70. switch (keySource) {
  71. case "userContext":
  72. //根据
  73. List<String> strings = SpElUtils.extractLabels(keyExpression);
  74. String keyWord = strings.get(0);
  75. if(doCache && Objects.nonNull(arg)){
  76. Field declaredField = arg.getClass().getDeclaredField(keyWord);
  77. declaredField.setAccessible(true);
  78. Object valueObj = declaredField.get(arg);
  79. contextVars.put(keyWord, valueObj);
  80. } else {
  81. //获取当前用户信息
  82. UserInfo userInfo = ServletUtils.getUserInfo();
  83. try {
  84. Class<?> aClass = userInfo.getClass();
  85. Field declaredField = aClass.getDeclaredField(keyWord);
  86. declaredField.setAccessible(true);
  87. Object valueObj = declaredField.get(userInfo);
  88. contextVars.put(keyWord, valueObj);
  89. } catch (Exception e) {
  90. }
  91. }
  92. break;
  93. case "args":
  94. // Object[] args = joinPoint.getArgs();
  95. // MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
  96. // Method method = methodSignature.getMethod();
  97. // DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
  98. // String[] paramNames = nameDiscoverer.getParameterNames(method);
  99. for (int i = 0; i < args.length; i++) {
  100. if (paramNames != null && i < paramNames.length) {
  101. contextVars.put(paramNames[i], args[i]);
  102. }
  103. }
  104. break;
  105. default:
  106. // 自定义 source,可扩展
  107. break;
  108. }
  109. // 解析 key
  110. String finalKey = SpElUtils.parseKey(keyExpression, contextVars);
  111. // 查询缓存
  112. Object cachedValue = redisTemplate.opsForValue().get(finalKey);
  113. if (cachedValue != null) {
  114. //判断是否为JSONObject 进行转换
  115. if(cachedValue instanceof String){
  116. //获取过期时间
  117. long expireTime = redisTemplate.getExpire(finalKey);
  118. log.debug("缓存命中[剩余时间:{} s],返回结果:{}", expireTime,(cachedValue.toString().length() > 200 ? cachedValue.toString().substring(0, 200) +"..." : cachedValue.toString()));
  119. //解析为json 并 取得 type 区分是 page 还是result
  120. String type = "result";
  121. if(cachedValue.toString().startsWith("{")){
  122. JSONObject jsonObject = JSONObject.parseObject((String) cachedValue);
  123. type = jsonObject.getString("type");
  124. }
  125. if(type.equals("page")){
  126. Page page = JSONObject.parseObject((String) cachedValue, Page.class, Feature.OrderedField);
  127. //设置缓存过期时间
  128. page.setRedisCacheTimes(expireTime);
  129. //如果缓存key 为空 则设置 key
  130. if(StringUtils.isBlank(page.getRedisCacheKey())){
  131. page.setRedisCacheKey(finalKey);
  132. }
  133. //如果是分页 则判断 结果是否排序 排序字段和规则, 对结果重新排序
  134. // doCheckSortByPageOrderBy(page);
  135. return page;
  136. }else{
  137. Result result = JSONObject.parseObject((String) cachedValue, Result.class, Feature.OrderedField);
  138. //设置缓存过期时间
  139. result.setRedisCacheTimes(expireTime);
  140. //如果缓存key 为空 则设置 key
  141. if(StringUtils.isBlank(result.getRedisCacheKey())){
  142. result.setRedisCacheKey(finalKey);
  143. }
  144. return result;
  145. }
  146. }
  147. return cachedValue;
  148. }
  149. // 执行目标方法
  150. Object result = joinPoint.proceed();
  151. // 判断返回值是否有效(非空、非异常)
  152. if (result == null || result instanceof Boolean && !(Boolean) result) {
  153. return result;
  154. }
  155. //处理 返回 code
  156. if((result.getClass().getName().equals("com.poyee.base.dto.Result") || result.getClass().getName().equals("com.poyee.base.dto.Page") )
  157. && result.getClass().getDeclaredField("code") != null){
  158. try{
  159. Field codeField = result.getClass().getDeclaredField("code");
  160. codeField.setAccessible(true);
  161. Object codeObj = codeField.get(result);
  162. if(codeObj != null && Integer.parseInt(codeObj.toString()) != 200 && Integer.parseInt(codeObj.toString()) != 0 ){
  163. return result;
  164. }
  165. }catch (Exception e){
  166. //忽略异常
  167. }
  168. }
  169. // 设置缓存
  170. long expireSeconds = expirePolicy.getExpireSeconds();
  171. //设置 查询数据库操作时间
  172. try{
  173. Field codeField = result.getClass().getDeclaredField("lastOptTime");
  174. codeField.setAccessible(true);
  175. Object codeObj = codeField.get(result);
  176. //设置当前时间为最后一次操作时间
  177. if(codeObj == null){
  178. codeField.set(result, DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD_HH_MM_SS));
  179. }
  180. //设置缓存key
  181. codeField = result.getClass().getDeclaredField("redisCacheKey");
  182. codeField.setAccessible(true);
  183. codeField.set(result, finalKey);
  184. //设置缓存时间
  185. codeField = result.getClass().getDeclaredField("redisCacheTimes");
  186. codeField.setAccessible(true);
  187. codeField.set(result, expirePolicy.getExpireSeconds());
  188. }catch (Exception e){
  189. log.error("设置 查询数据库操作时间 异常",e);
  190. }
  191. String jsonString = JSONObject.toJSONString(result);
  192. if (expireSeconds > 0) {
  193. } else {
  194. // 默认设置缓存时间为1小时 (3600秒)
  195. expireSeconds = 3600;
  196. }
  197. redisTemplate.opsForValue().set(finalKey, jsonString, expireSeconds, java.util.concurrent.TimeUnit.SECONDS);
  198. //判断是否需要缓存key集合
  199. if(redisCache.logKey()
  200. && StringUtils.isNotBlank(redisCache.logKeyRule())
  201. && !Objects.equals(redisCache.logKeyRule(),"key")){
  202. String logKeyExpression = redisCache.logKeyRule();
  203. // 解析 key
  204. String finalLogKey = SpElUtils.parseKey(logKeyExpression, contextVars);
  205. // redis 缓存 设置 list add 值
  206. redisTemplate.opsForList()
  207. .leftPush(finalLogKey, finalKey);
  208. // 设置列表过期时间,与缓存过期时间一致
  209. redisTemplate.expire(finalLogKey, expireSeconds, java.util.concurrent.TimeUnit.SECONDS);
  210. }
  211. log.debug("缓存结果:{}", jsonString.length() > 200 ? jsonString.substring(0, 200) + "..." : jsonString);
  212. return result;
  213. }
  214. private void doCheckSortByPageOrderBy(Page page) {
  215. String orderByField = page.getOrderBy();
  216. if(StringUtils.isNotBlank(orderByField)) {
  217. List<?> rows = page.getRows();
  218. boolean isDesc = page.isDesc(); // 是否降序
  219. try {
  220. rows.sort((o1, o2) -> {
  221. if (o1 == null || o2 == null) return 0;
  222. // 使用反射获取 orderByField 的值
  223. Object v1 = getFieldValue(o1, orderByField);
  224. Object v2 = getFieldValue(o2, orderByField);
  225. if (v1 == null && v2 == null) return 0;
  226. if (v1 == null) return isDesc ? 1 : -1;
  227. if (v2 == null) return isDesc ? -1 : 1;
  228. // 按类型安全比较
  229. int result;
  230. if (v1 instanceof Comparable && v2.getClass()
  231. .isAssignableFrom(v1.getClass())) {
  232. result = ((Comparable) v1).compareTo(v2);
  233. } else {
  234. // 转为字符串比较(兜底)
  235. result = v1.toString()
  236. .compareTo(v2.toString());
  237. }
  238. return isDesc ? -result : result; // 反转符号实现降序
  239. });
  240. } catch (Exception e) {
  241. log.warn("缓存分页数据排序失败,orderByField={}", orderByField, e);
  242. }
  243. }
  244. }
  245. // 使用反射获取字段值
  246. private Object getFieldValue(Object obj, String fieldName) {
  247. if (obj == null || StringUtils.isBlank(fieldName)) return null;
  248. Class<?> clazz = obj.getClass();
  249. String[] fields = fieldName.split("\\."); // 支持嵌套属性 a.b.c
  250. try {
  251. for (String field : fields) {
  252. Field declaredField = findField(clazz, field);
  253. if (declaredField == null) return null;
  254. declaredField.setAccessible(true);
  255. obj = declaredField.get(obj);
  256. if (obj == null) break;
  257. clazz = obj.getClass();
  258. }
  259. return obj;
  260. } catch (Exception e) {
  261. log.debug("获取对象 {} 字段 {} 值失败", obj.getClass().getSimpleName(), fieldName, e);
  262. return null;
  263. }
  264. }
  265. // 支持父类字段查找
  266. private Field findField(Class<?> clazz, String name) {
  267. while (clazz != null) {
  268. try {
  269. return clazz.getDeclaredField(name);
  270. } catch (NoSuchFieldException e) {
  271. clazz = clazz.getSuperclass();
  272. }
  273. }
  274. return null;
  275. }
  276. }