| @@ -3,7 +3,9 @@ package com.iformall.aop; | |||||
| import com.alibaba.fastjson.JSONObject; | import com.alibaba.fastjson.JSONObject; | ||||
| import com.iformall.annotation.RedisCache; | import com.iformall.annotation.RedisCache; | ||||
| import com.iformall.common.ErrorCode; | |||||
| import com.iformall.domain.po.WxCUser; | import com.iformall.domain.po.WxCUser; | ||||
| import com.iformall.exception.MallinkException; | |||||
| import com.iformall.service.WxCUserTokenService; | import com.iformall.service.WxCUserTokenService; | ||||
| import com.iformall.utils.HashUtil; | import com.iformall.utils.HashUtil; | ||||
| import com.iformall.utils.RedisLock; | import com.iformall.utils.RedisLock; | ||||
| @@ -21,8 +23,8 @@ import org.springframework.stereotype.Component; | |||||
| import org.springframework.web.context.request.RequestContextHolder; | import org.springframework.web.context.request.RequestContextHolder; | ||||
| import org.springframework.web.context.request.ServletRequestAttributes; | import org.springframework.web.context.request.ServletRequestAttributes; | ||||
| import javax.annotation.Resource; | |||||
| import javax.servlet.http.HttpServletRequest; | import javax.servlet.http.HttpServletRequest; | ||||
| import java.lang.reflect.Method; | |||||
| import java.util.Arrays; | import java.util.Arrays; | ||||
| @Slf4j | @Slf4j | ||||
| @@ -54,7 +56,7 @@ public class RedisCacheAspect { | |||||
| } | } | ||||
| @Around("cacheAspect()") | @Around("cacheAspect()") | ||||
| public Object round(ProceedingJoinPoint jp, RedisCache cache) { | |||||
| public Object round(ProceedingJoinPoint jp) throws Throwable { | |||||
| HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); | HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); | ||||
| // 查询token信息 | // 查询token信息 | ||||
| //从header中获取token | //从header中获取token | ||||
| @@ -73,11 +75,15 @@ public class RedisCacheAspect { | |||||
| // 请求参数 | // 请求参数 | ||||
| Object[] args = jp.getArgs(); | Object[] args = jp.getArgs(); | ||||
| // 接口 返回结果 | // 接口 返回结果 | ||||
| Object result; | Object result; | ||||
| String redisKey; | String redisKey; | ||||
| //得到注释的名称 | |||||
| MethodSignature signature = (MethodSignature) jp.getSignature(); | |||||
| //判断tag是否在用户权限中 如果存在加入参数查询 | |||||
| Method method = signature.getMethod(); | |||||
| RedisCache cache = method.getAnnotation(RedisCache.class) ; | |||||
| if (cache.autoKey()) { | if (cache.autoKey()) { | ||||
| // 构建 redisKey => reqPath:tenantId:md5(param1_param2_param3) | // 构建 redisKey => reqPath:tenantId:md5(param1_param2_param3) | ||||
| String requestUrl = request.getRequestURI(); | String requestUrl = request.getRequestURI(); | ||||
| @@ -94,47 +100,54 @@ public class RedisCacheAspect { | |||||
| // redisKey 不存在,获取接口数据并返回 | // redisKey 不存在,获取接口数据并返回 | ||||
| if (StringUtils.isBlank(redisKey)) { | if (StringUtils.isBlank(redisKey)) { | ||||
| result = proceed(jp, args, null); | |||||
| result = jp.proceed(args); | |||||
| } else { | } else { | ||||
| ValueOperations<String, String> valueOperations = stringRedisTemplate.opsForValue(); | ValueOperations<String, String> valueOperations = stringRedisTemplate.opsForValue(); | ||||
| String value = valueOperations.get(redisKey); | String value = valueOperations.get(redisKey); | ||||
| if (value == null) { | if (value == null) { | ||||
| result = proceed(jp, args, StringUtils.join(redisKey, ":", "lock")); | |||||
| String json_data = JSONObject.toJSONString(result); | |||||
| valueOperations.set(redisKey, json_data, cache.expireTime(), cache.dateUnit()); | |||||
| log.debug("CacheAspect 缓存不存在,获取接口数据并放入缓存,key:{}, expire:{}", redisKey, cache.expireTime()); | |||||
| String lockKey = StringUtils.join(redisKey, ":", "lock"); | |||||
| long time = System.currentTimeMillis() + 2000; | |||||
| String timeStr = String.valueOf(time); | |||||
| try { | |||||
| //分布式锁,保证一个线程读DB,其它线程排队 | |||||
| if (redisLock.lock2(lockKey, timeStr)) { | |||||
| log.debug("CacheAspect 读库中, key:{}: " + lockKey); | |||||
| result = jp.proceed(args); | |||||
| String json_data = JSONObject.toJSONString(result); | |||||
| valueOperations.set(redisKey, json_data, cache.expireTime(), cache.dateUnit()); | |||||
| log.debug("CacheAspect 缓存不存在,获取接口数据并放入缓存,key:{}, expire:{}", redisKey, cache.expireTime()); | |||||
| } else { | |||||
| log.debug("CacheAspect 读库等待中, key:{}: " + lockKey); | |||||
| Thread.sleep(2000); | |||||
| value = valueOperations.get(redisKey); | |||||
| result = parseCache(jp, redisKey, value); | |||||
| } | |||||
| } catch (Throwable throwable) { | |||||
| log.error("CacheAspect proceed error , Illegal argument: {} in {}.{}()", Arrays.toString(jp.getArgs()), | |||||
| jp.getSignature().getDeclaringTypeName(), jp.getSignature().getName(), throwable); | |||||
| if (throwable instanceof MallinkException) { | |||||
| throw throwable; | |||||
| } | |||||
| throw new MallinkException(ErrorCode.SYS_SERVER_ERROR); | |||||
| } finally { | |||||
| redisLock.unlock(lockKey, timeStr); | |||||
| } | |||||
| } else { | } else { | ||||
| Class<?> returnType = ((MethodSignature) jp.getSignature()).getReturnType(); | |||||
| result = JSONObject.parseObject(value, returnType); | |||||
| log.debug("CacheAspect 缓存存在,解析缓存并返回,key:{}", redisKey); | |||||
| result = parseCache(jp, redisKey, value); | |||||
| } | } | ||||
| // 计数器 | |||||
| stringRedisTemplate.opsForHash().increment("hotapi", redisKey, 1); | |||||
| } | } | ||||
| return result; | return result; | ||||
| } | } | ||||
| private Object proceed(ProceedingJoinPoint jp, Object[] args, String lockKey) { | |||||
| // 超时1秒 | |||||
| long time = System.currentTimeMillis() + 1000; | |||||
| String timeStr = String.valueOf(time); | |||||
| try { | |||||
| //分布式锁,保证一个线程读DB,其它线程排队 | |||||
| if (StringUtils.isBlank(lockKey)) { | |||||
| //log.debug("CacheAspect 读库并返回,无锁"); | |||||
| return jp.proceed(args); | |||||
| } else if (redisLock.lock2(lockKey, timeStr)) { | |||||
| log.debug("CacheAspect 读库中, key:{}: " + lockKey); | |||||
| return jp.proceed(args); | |||||
| } else { | |||||
| // TODO 挂起,重试 | |||||
| log.debug("CacheAspect 排队中, key:{}: " + lockKey); | |||||
| } | |||||
| } catch (Throwable throwable) { | |||||
| log.error("CacheAspect proceed error , Illegal argument: {} in {}.{}()", Arrays.toString(jp.getArgs()), | |||||
| jp.getSignature().getDeclaringTypeName(), jp.getSignature().getName(), throwable); | |||||
| } finally { | |||||
| redisLock.unlock(lockKey, timeStr); | |||||
| private Object parseCache(ProceedingJoinPoint jp, String redisKey, String value) { | |||||
| if (StringUtils.isBlank(value)) { | |||||
| log.debug("CacheAspect 缓存存在,解析缓存数据为空:{},key:{}", value, redisKey); | |||||
| return null; | |||||
| } | } | ||||
| return null; | |||||
| Class<?> returnType = ((MethodSignature) jp.getSignature()).getReturnType(); | |||||
| log.debug("CacheAspect 缓存存在,解析缓存并返回,key:{}", redisKey); | |||||
| return JSONObject.parseObject(value, returnType); | |||||
| } | } | ||||
| } | |||||
| } | |||||
| @@ -3,7 +3,9 @@ package com.iformall.aop; | |||||
| import com.alibaba.fastjson.JSONObject; | import com.alibaba.fastjson.JSONObject; | ||||
| import com.iformall.annotation.RedisCache; | import com.iformall.annotation.RedisCache; | ||||
| import com.iformall.common.ErrorCode; | |||||
| import com.iformall.domain.po.WxCUser; | import com.iformall.domain.po.WxCUser; | ||||
| import com.iformall.exception.MallinkException; | |||||
| import com.iformall.service.WxCUserTokenService; | import com.iformall.service.WxCUserTokenService; | ||||
| import com.iformall.utils.HashUtil; | import com.iformall.utils.HashUtil; | ||||
| import com.iformall.utils.RedisLock; | import com.iformall.utils.RedisLock; | ||||
| @@ -22,6 +24,7 @@ import org.springframework.web.context.request.RequestContextHolder; | |||||
| import org.springframework.web.context.request.ServletRequestAttributes; | import org.springframework.web.context.request.ServletRequestAttributes; | ||||
| import javax.servlet.http.HttpServletRequest; | import javax.servlet.http.HttpServletRequest; | ||||
| import java.lang.reflect.Method; | |||||
| import java.util.Arrays; | import java.util.Arrays; | ||||
| @Slf4j | @Slf4j | ||||
| @@ -53,7 +56,7 @@ public class RedisCacheAspect { | |||||
| } | } | ||||
| @Around("cacheAspect()") | @Around("cacheAspect()") | ||||
| public Object round(ProceedingJoinPoint jp, RedisCache cache) { | |||||
| public Object round(ProceedingJoinPoint jp) throws Throwable { | |||||
| HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); | HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); | ||||
| // 查询token信息 | // 查询token信息 | ||||
| //从header中获取token | //从header中获取token | ||||
| @@ -72,11 +75,15 @@ public class RedisCacheAspect { | |||||
| // 请求参数 | // 请求参数 | ||||
| Object[] args = jp.getArgs(); | Object[] args = jp.getArgs(); | ||||
| // 接口 返回结果 | // 接口 返回结果 | ||||
| Object result; | Object result; | ||||
| String redisKey; | String redisKey; | ||||
| //得到注释的名称 | |||||
| MethodSignature signature = (MethodSignature) jp.getSignature(); | |||||
| //判断tag是否在用户权限中 如果存在加入参数查询 | |||||
| Method method = signature.getMethod(); | |||||
| RedisCache cache = method.getAnnotation(RedisCache.class) ; | |||||
| if (cache.autoKey()) { | if (cache.autoKey()) { | ||||
| // 构建 redisKey => reqPath:tenantId:md5(param1_param2_param3) | // 构建 redisKey => reqPath:tenantId:md5(param1_param2_param3) | ||||
| String requestUrl = request.getRequestURI(); | String requestUrl = request.getRequestURI(); | ||||
| @@ -93,47 +100,54 @@ public class RedisCacheAspect { | |||||
| // redisKey 不存在,获取接口数据并返回 | // redisKey 不存在,获取接口数据并返回 | ||||
| if (StringUtils.isBlank(redisKey)) { | if (StringUtils.isBlank(redisKey)) { | ||||
| result = proceed(jp, args, null); | |||||
| result = jp.proceed(args); | |||||
| } else { | } else { | ||||
| ValueOperations<String, String> valueOperations = stringRedisTemplate.opsForValue(); | ValueOperations<String, String> valueOperations = stringRedisTemplate.opsForValue(); | ||||
| String value = valueOperations.get(redisKey); | String value = valueOperations.get(redisKey); | ||||
| if (value == null) { | if (value == null) { | ||||
| result = proceed(jp, args, StringUtils.join(redisKey, ":", "lock")); | |||||
| String json_data = JSONObject.toJSONString(result); | |||||
| valueOperations.set(redisKey, json_data, cache.expireTime(), cache.dateUnit()); | |||||
| log.debug("CacheAspect 缓存不存在,获取接口数据并放入缓存,key:{}, expire:{}", redisKey, cache.expireTime()); | |||||
| String lockKey = StringUtils.join(redisKey, ":", "lock"); | |||||
| long time = System.currentTimeMillis() + 2000; | |||||
| String timeStr = String.valueOf(time); | |||||
| try { | |||||
| //分布式锁,保证一个线程读DB,其它线程排队 | |||||
| if (redisLock.lock2(lockKey, timeStr)) { | |||||
| log.debug("CacheAspect 读库中, key:{}: " + lockKey); | |||||
| result = jp.proceed(args); | |||||
| String json_data = JSONObject.toJSONString(result); | |||||
| valueOperations.set(redisKey, json_data, cache.expireTime(), cache.dateUnit()); | |||||
| log.debug("CacheAspect 缓存不存在,获取接口数据并放入缓存,key:{}, expire:{}", redisKey, cache.expireTime()); | |||||
| } else { | |||||
| log.debug("CacheAspect 读库等待中, key:{}: " + lockKey); | |||||
| Thread.sleep(2000); | |||||
| value = valueOperations.get(redisKey); | |||||
| result = parseCache(jp, redisKey, value); | |||||
| } | |||||
| } catch (Throwable throwable) { | |||||
| log.error("CacheAspect proceed error , Illegal argument: {} in {}.{}()", Arrays.toString(jp.getArgs()), | |||||
| jp.getSignature().getDeclaringTypeName(), jp.getSignature().getName(), throwable); | |||||
| if (throwable instanceof MallinkException) { | |||||
| throw throwable; | |||||
| } | |||||
| throw new MallinkException(ErrorCode.SYS_SERVER_ERROR); | |||||
| } finally { | |||||
| redisLock.unlock(lockKey, timeStr); | |||||
| } | |||||
| } else { | } else { | ||||
| Class<?> returnType = ((MethodSignature) jp.getSignature()).getReturnType(); | |||||
| result = JSONObject.parseObject(value, returnType); | |||||
| log.debug("CacheAspect 缓存存在,解析缓存并返回,key:{}", redisKey); | |||||
| result = parseCache(jp, redisKey, value); | |||||
| } | } | ||||
| // 计数器 | |||||
| stringRedisTemplate.opsForHash().increment("hotapi", redisKey, 1); | |||||
| } | } | ||||
| return result; | return result; | ||||
| } | } | ||||
| private Object proceed(ProceedingJoinPoint jp, Object[] args, String lockKey) { | |||||
| // 超时1秒 | |||||
| long time = System.currentTimeMillis() + 1000; | |||||
| String timeStr = String.valueOf(time); | |||||
| try { | |||||
| //分布式锁,保证一个线程读DB,其它线程排队 | |||||
| if (StringUtils.isBlank(lockKey)) { | |||||
| //log.debug("CacheAspect 读库并返回,无锁"); | |||||
| return jp.proceed(args); | |||||
| } else if (redisLock.lock2(lockKey, timeStr)) { | |||||
| log.debug("CacheAspect 读库中, key:{}: " + lockKey); | |||||
| return jp.proceed(args); | |||||
| } else { | |||||
| // TODO 挂起,重试 | |||||
| log.debug("CacheAspect 排队中, key:{}: " + lockKey); | |||||
| } | |||||
| } catch (Throwable throwable) { | |||||
| log.error("CacheAspect proceed error , Illegal argument: {} in {}.{}()", Arrays.toString(jp.getArgs()), | |||||
| jp.getSignature().getDeclaringTypeName(), jp.getSignature().getName(), throwable); | |||||
| } finally { | |||||
| redisLock.unlock(lockKey, timeStr); | |||||
| private Object parseCache(ProceedingJoinPoint jp, String redisKey, String value) { | |||||
| if (StringUtils.isBlank(value)) { | |||||
| log.debug("CacheAspect 缓存存在,解析缓存数据为空:{},key:{}", value, redisKey); | |||||
| return null; | |||||
| } | } | ||||
| return null; | |||||
| Class<?> returnType = ((MethodSignature) jp.getSignature()).getReturnType(); | |||||
| log.debug("CacheAspect 缓存存在,解析缓存并返回,key:{}", redisKey); | |||||
| return JSONObject.parseObject(value, returnType); | |||||
| } | } | ||||
| } | } | ||||
| @@ -7,8 +7,6 @@ import com.iformall.common.Result; | |||||
| import com.iformall.common.ResultData; | import com.iformall.common.ResultData; | ||||
| import com.iformall.domain.po.*; | import com.iformall.domain.po.*; | ||||
| import com.iformall.domain.po.base.TenantEntity; | import com.iformall.domain.po.base.TenantEntity; | ||||
| import com.iformall.domain.vo.WxMerchantVo; | |||||
| import com.iformall.enums.EnumMerchantStatus; | |||||
| import com.iformall.service.*; | import com.iformall.service.*; | ||||
| import io.swagger.annotations.Api; | import io.swagger.annotations.Api; | ||||
| import io.swagger.annotations.ApiImplicitParam; | import io.swagger.annotations.ApiImplicitParam; | ||||
| @@ -17,13 +15,9 @@ import org.slf4j.Logger; | |||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.beans.factory.annotation.Value; | import org.springframework.beans.factory.annotation.Value; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | |||||
| import org.springframework.web.bind.annotation.RequestMapping; | |||||
| import org.springframework.web.bind.annotation.RestController; | |||||
| import org.springframework.web.bind.annotation.*; | |||||
| import java.util.HashMap; | |||||
| import java.util.List; | |||||
| import java.util.Map; | |||||
| import java.util.*; | |||||
| @RestController | @RestController | ||||
| @RequestMapping("/api/mall") | @RequestMapping("/api/mall") | ||||
| @@ -46,6 +40,9 @@ public class WxMallController extends BaseController { | |||||
| @Autowired | @Autowired | ||||
| private WxLevelConfigService wxLevelConfigService; | private WxLevelConfigService wxLevelConfigService; | ||||
| @Autowired | |||||
| private RedisService redisService; | |||||
| @RedisCache | @RedisCache | ||||
| @AuthIgnore | @AuthIgnore | ||||
| @ApiOperation("商场图标") | @ApiOperation("商场图标") | ||||
| @@ -124,4 +121,16 @@ public class WxMallController extends BaseController { | |||||
| return new ResultData(version); | return new ResultData(version); | ||||
| } | } | ||||
| @ApiOperation("获取热点缓存接口对应redis key") | |||||
| @GetMapping("/hotApis") | |||||
| public ResultData hotApis() { | |||||
| return new ResultData(redisService.hotApi()); | |||||
| } | |||||
| @ApiOperation("删除指定key") | |||||
| @PostMapping("/delRedisKey") | |||||
| public ResultData delRedisKey(@RequestBody List<String> keys) { | |||||
| return new ResultData(redisService.delRedisKey(keys)); | |||||
| } | |||||
| } | } | ||||
| @@ -0,0 +1,22 @@ | |||||
| package com.iformall.service; | |||||
| import java.util.List; | |||||
| import java.util.Set; | |||||
| public interface RedisService { | |||||
| String KEY_HOT_API = "hotapi" ; | |||||
| /** | |||||
| * 获取热点缓存接口列表 | |||||
| * @return | |||||
| */ | |||||
| Set<Object> hotApi() ; | |||||
| /** | |||||
| * 清除指定key | |||||
| * @param keys | |||||
| * @return | |||||
| */ | |||||
| boolean delRedisKey(List<String> keys) ; | |||||
| } | |||||
| @@ -0,0 +1,45 @@ | |||||
| package com.iformall.service.impl; | |||||
| import com.iformall.service.RedisService; | |||||
| import lombok.extern.slf4j.Slf4j; | |||||
| import org.apache.commons.collections.CollectionUtils; | |||||
| import org.springframework.beans.factory.annotation.Autowired; | |||||
| import org.springframework.data.redis.core.StringRedisTemplate; | |||||
| import org.springframework.stereotype.Service; | |||||
| import java.util.List; | |||||
| import java.util.Set; | |||||
| @Slf4j | |||||
| @Service | |||||
| public class RedisServiceImpl implements RedisService { | |||||
| @Autowired | |||||
| private StringRedisTemplate stringRedisTemplate; | |||||
| @Override | |||||
| public Set<Object> hotApi() { | |||||
| return stringRedisTemplate.opsForHash().keys(KEY_HOT_API); | |||||
| } | |||||
| @Override | |||||
| public boolean delRedisKey(List<String> keys) { | |||||
| if (CollectionUtils.isEmpty(keys)) { | |||||
| return false; | |||||
| } | |||||
| boolean result; | |||||
| stringRedisTemplate.multi(); | |||||
| try { | |||||
| stringRedisTemplate.opsForValue().getOperations().delete(keys); | |||||
| stringRedisTemplate.opsForHash().delete(KEY_HOT_API, keys.toArray()); | |||||
| result = true; | |||||
| } catch (Exception e) { | |||||
| log.error("delRedisKey error", e); | |||||
| stringRedisTemplate.discard(); | |||||
| result = false; | |||||
| } finally { | |||||
| stringRedisTemplate.exec(); | |||||
| } | |||||
| return result; | |||||
| } | |||||
| } | |||||