后台服务
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

162 lines
5.8 KiB

  1. package com.iformall.aop;
  2. import com.alibaba.fastjson.JSONObject;
  3. import com.iformall.annotation.RedisCache;
  4. import com.iformall.common.ErrorCode;
  5. import com.iformall.domain.po.WxCUser;
  6. import com.iformall.domain.po.base.BaseCUserEntity;
  7. import com.iformall.exception.MallinkException;
  8. import com.iformall.service.CUserTokenService;
  9. import com.iformall.utils.HashUtil;
  10. import com.iformall.utils.RedisLock;
  11. import lombok.extern.slf4j.Slf4j;
  12. import org.apache.commons.lang3.StringUtils;
  13. import org.aspectj.lang.ProceedingJoinPoint;
  14. import org.aspectj.lang.annotation.Around;
  15. import org.aspectj.lang.annotation.Aspect;
  16. import org.aspectj.lang.annotation.Pointcut;
  17. import org.aspectj.lang.reflect.MethodSignature;
  18. import org.springframework.beans.factory.annotation.Autowired;
  19. import org.springframework.data.redis.core.StringRedisTemplate;
  20. import org.springframework.data.redis.core.ValueOperations;
  21. import org.springframework.stereotype.Component;
  22. import org.springframework.web.context.request.RequestContextHolder;
  23. import org.springframework.web.context.request.ServletRequestAttributes;
  24. import javax.servlet.http.HttpServletRequest;
  25. import java.lang.reflect.Method;
  26. import java.util.Arrays;
  27. import java.util.Objects;
  28. @Slf4j
  29. @Aspect
  30. @Component
  31. public class RedisCacheAspect {
  32. /**
  33. * 参数分隔符 param1|param2|param3
  34. **/
  35. private static final String DELIMITER_PARAMS = "|";
  36. /**
  37. * key 分隔符
  38. */
  39. private static final String DELIMITER_KEY = ":";
  40. @Autowired
  41. private StringRedisTemplate stringRedisTemplate;
  42. @Autowired
  43. RedisLock redisLock;
  44. @Autowired
  45. private CUserTokenService cUserTokenService;
  46. @Pointcut("@annotation(com.iformall.annotation.RedisCache)")
  47. public void cacheAspect() {
  48. }
  49. @Around("cacheAspect()")
  50. public Object round(ProceedingJoinPoint jp) throws Throwable {
  51. HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
  52. // 查询token信息
  53. //从header中获取token
  54. String token = request.getHeader("token");
  55. //如果header中不存在token,则从参数中获取token
  56. if (StringUtils.isBlank(token)) {
  57. token = request.getParameter("token");
  58. }
  59. if(StringUtils.isBlank(token) || "null".equals(token) || "undefined".equals(token)){
  60. token="";
  61. //throw new MallinkException(ErrorCode.NET_TOKEN_EMPTY.getCode(),"token为空["+token+"]");
  62. }
  63. log.info("token>>>>>>>>>>>>>>>"+token);
  64. String tenantId;
  65. BaseCUserEntity cUser = null ;
  66. if(StringUtils.isNotBlank(token)) {
  67. cUser = cUserTokenService.getByToken(token);
  68. }
  69. if (Objects.isNull(cUser) || cUser.getExpireTime().getTime() < System.currentTimeMillis()) {
  70. tenantId = "0";
  71. } else {
  72. tenantId = cUser.getTenantId();
  73. }
  74. // 请求参数
  75. Object[] args = jp.getArgs();
  76. // 接口 返回结果
  77. Object result;
  78. String redisKey;
  79. //得到注释的名称
  80. MethodSignature signature = (MethodSignature) jp.getSignature();
  81. //判断tag是否在用户权限中 如果存在加入参数查询
  82. Method method = signature.getMethod();
  83. RedisCache cache = method.getAnnotation(RedisCache.class) ;
  84. if (cache.autoKey()) {
  85. // 构建 redisKey => reqPath:tenantId:md5(param1_param2_param3)
  86. String requestUrl = request.getRequestURI();
  87. // 接口路径 reqPath => /api/xxx
  88. String reqPath = requestUrl.substring(requestUrl.indexOf("/api/"));
  89. if (cache.md5()) {
  90. String md5Before = StringUtils.join(args, DELIMITER_PARAMS);
  91. redisKey = StringUtils.join(reqPath, DELIMITER_KEY, tenantId, DELIMITER_KEY, HashUtil.md5(StringUtils.join(token, md5Before, DELIMITER_PARAMS)));
  92. } else {
  93. redisKey = StringUtils.join(reqPath, DELIMITER_KEY, tenantId, DELIMITER_KEY, StringUtils.join(token,args, DELIMITER_PARAMS));
  94. }
  95. } else {
  96. redisKey = cache.key();
  97. }
  98. // redisKey 不存在,获取接口数据并返回
  99. if (StringUtils.isBlank(redisKey)) {
  100. result = jp.proceed(args);
  101. } else {
  102. ValueOperations<String, String> valueOperations = stringRedisTemplate.opsForValue();
  103. String value = valueOperations.get(redisKey);
  104. if (value == null) {
  105. String lockKey = StringUtils.join(redisKey, ":", "lock");
  106. long time = System.currentTimeMillis() + 2000;
  107. String timeStr = String.valueOf(time);
  108. try {
  109. //分布式锁,保证一个线程读DB,其它线程排队
  110. if (redisLock.lock2(lockKey, timeStr)) {
  111. log.debug("CacheAspect 读库中, key:{}: " + lockKey);
  112. result = jp.proceed(args);
  113. String json_data = JSONObject.toJSONString(result);
  114. valueOperations.set(redisKey, json_data, cache.expireTime(), cache.dateUnit());
  115. log.debug("CacheAspect 缓存不存在,获取接口数据并放入缓存,key:{}, expire:{}", redisKey, cache.expireTime());
  116. } else {
  117. log.debug("CacheAspect 读库等待中, key:{}: " + lockKey);
  118. Thread.sleep(2000);
  119. value = valueOperations.get(redisKey);
  120. result = parseCache(jp, redisKey, value);
  121. }
  122. } catch (Throwable throwable) {
  123. log.error("CacheAspect proceed error , Illegal argument: {} in {}.{}()", Arrays.toString(jp.getArgs()),
  124. jp.getSignature().getDeclaringTypeName(), jp.getSignature().getName(), throwable);
  125. throw throwable;
  126. } finally {
  127. redisLock.unlock(lockKey, timeStr);
  128. }
  129. } else {
  130. result = parseCache(jp, redisKey, value);
  131. }
  132. // 计数器
  133. stringRedisTemplate.opsForHash().increment("hotapi", redisKey, 1);
  134. }
  135. return result;
  136. }
  137. private Object parseCache(ProceedingJoinPoint jp, String redisKey, String value) {
  138. if (StringUtils.isBlank(value)) {
  139. log.debug("CacheAspect 缓存不存在,解析缓存数据为空:{},key:{}", value, redisKey);
  140. return null;
  141. }
  142. Class<?> returnType = ((MethodSignature) jp.getSignature()).getReturnType();
  143. log.debug("CacheAspect 缓存存在,解析缓存并返回,key:{}", redisKey);
  144. return JSONObject.parseObject(value, returnType);
  145. }
  146. }