Browse Source

新增redis 事务锁

yeshijie 4 years ago
parent
commit
47daa53fe5

+ 47 - 0
svr/svr-cloud-care/src/main/java/com/yihu/jw/care/aop/BaseAop.java

@ -0,0 +1,47 @@
package com.yihu.jw.care.aop;
import org.apache.commons.lang3.StringUtils;
import org.json.JSONObject;
import javax.servlet.http.HttpServletRequest;
/**
 * Created by yeshijie on 2021/4/23.
 */
public class BaseAop {
    public String write(int code, String msg) {
        try {
            JSONObject json = new JSONObject();
            json.put("status", code);
            json.put("msg", msg);
            return json.toString();
        } catch (Exception e) {
            return null;
        }
    }
    public String error(int code, String msg) {
        try {
            JSONObject json = new JSONObject();
            json.put("status", code);
            json.put("msg", msg);
            return json.toString();
        } catch (Exception e) {
            return null;
        }
    }
    public JSONObject getAgent(HttpServletRequest request) {
        try {
            String userAgent = request.getHeader("userAgent");
            if (StringUtils.isEmpty(userAgent)) {
                userAgent = request.getHeader("User-Agent");
            }
            System.out.println("userAgent:" + userAgent);
            return new JSONObject(userAgent);
        } catch (Exception e) {
            return null;
        }
    }
}

+ 1 - 35
svr/svr-cloud-care/src/main/java/com/yihu/jw/care/aop/ObserverRequiredAOP.java

@ -19,7 +19,7 @@ import java.io.PrintWriter;
 */
@Aspect
@Component
public class ObserverRequiredAOP {
public class ObserverRequiredAOP extends BaseAop{
    //Controller层切点路径
    @Pointcut("execution(* com.yihu.jw.care.endpoint..*.*(..))")
    public void controllerAspect() {
@ -52,38 +52,4 @@ public class ObserverRequiredAOP {
        return o;
    }
    public String write(int code, String msg) {
        try {
            JSONObject json = new JSONObject();
            json.put("status", code);
            json.put("msg", msg);
            return json.toString();
        } catch (Exception e) {
            return null;
        }
    }
    public String error(int code, String msg) {
        try {
            JSONObject json = new JSONObject();
            json.put("status", code);
            json.put("msg", msg);
            return json.toString();
        } catch (Exception e) {
            return null;
        }
    }
    public JSONObject getAgent(HttpServletRequest request) {
        try {
            String userAgent = request.getHeader("userAgent");
            if (StringUtils.isEmpty(userAgent)) {
                userAgent = request.getHeader("User-Agent");
            }
            System.out.println("userAgent:" + userAgent);
            return new JSONObject(userAgent);
        } catch (Exception e) {
            return null;
        }
    }
}

+ 45 - 0
svr/svr-cloud-care/src/main/java/com/yihu/jw/care/aop/RedisLock.java

@ -0,0 +1,45 @@
package com.yihu.jw.care.aop;
import com.yihu.jw.care.util.RedisConstant;
import java.lang.annotation.*;
/**
 * redis分布式锁注解
 *
 * Created by yeshijie on 2021/4/23.
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisLock {
    /**
     * 锁值
     *
     * @return {@link String}
     */
    String value() default "";
    /**
     * spel参数(#开头)
     *
     * @return {@link String}
     */
    String key() default "";
    /**
     * 上锁超时时间,单位秒
     *
     * @return long
     */
    long time() default RedisConstant.REDIS_LOCK_EXPIRE;
    /**
     * 提示
     *
     * @return {@link String}
     */
    String msg() default "请求频繁,请稍后再试";
}

+ 178 - 0
svr/svr-cloud-care/src/main/java/com/yihu/jw/care/aop/RedisLockAOP.java

@ -0,0 +1,178 @@
package com.yihu.jw.care.aop;
import com.google.common.base.Joiner;
import com.yihu.jw.care.exception.BusinessException;
import com.yihu.jw.care.service.common.RedisService;
import com.yihu.jw.care.util.RedisConstant;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelParseException;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
 * redis分布式锁AOP
 *
 * @author baichuan.wu
 * @version 1.0.0
 * @date 2019-12-26 14:16
 */
@Aspect
@Order(-1)
@Component
public class RedisLockAOP {
    private static final Logger LOGGER = LoggerFactory.getLogger(RedisLockAOP.class);
    @Autowired
    private RedisService redisService;
    private static final ExpressionParser PARSER = new SpelExpressionParser();
    private static final ParameterNameDiscoverer DISCOVERER = new LocalVariableTableParameterNameDiscoverer();
    private static final String UNDERLINE = "_";
    private static final String REDIS_LOCK = "REDIS_LOCK";
    /**
     * aop切入点
     *
     * @param redisLock
     */
    @Pointcut("@annotation(redisLock)")
    public void pointCut(RedisLock redisLock) {
    }
    /**
     * aop环绕
     *
     * @param joinPoint
     * @param redisLock
     * @return {@link Object}
     */
    @Around(value = "pointCut(redisLock)", argNames = "joinPoint,redisLock")
    public Object around(ProceedingJoinPoint joinPoint, RedisLock redisLock) throws Throwable {
        return aroundRedisLock(joinPoint, redisLock);
    }
    /**
     * 分布式锁
     *
     * @param joinPoint
     * @param redisLock
     * @return {@link Object}
     */
    public Object aroundRedisLock(ProceedingJoinPoint joinPoint, RedisLock redisLock) throws Throwable {
        String lockKey = null;
        Object result;
        boolean lock = false;
        try {
            lockKey = getLockKey(joinPoint, redisLock);
            lock = redisService.tryLock(lockKey, REDIS_LOCK, redisLock.time());
            if (!lock) {
                LOGGER.error("【LOCK】{}", redisLock.msg());
                throw new BusinessException(redisLock.msg());
            }
            result = joinPoint.proceed();
        } catch (SpelParseException e) {
            LOGGER.error("【LOCK】表达式解析异常", e);
            throw new BusinessException("【LOCK】请求异常,请稍后再试");
        } catch (SpelEvaluationException e) {
            LOGGER.error("【LOCK】表达式异常:{}", e.getMessage());
            throw new BusinessException("【LOCK】请求异常,请稍后再试");
        } catch (BusinessException e) {
            LOGGER.error("【LOCK】请求异常", e);
            throw new BusinessException(e.getMessage());
        } catch (Exception e) {
            LOGGER.error("【LOCK】请求异常", e);
            throw new BusinessException("请求异常,请稍后再试");
        } finally {
            if (lock) {
                redisService.releaseLock(lockKey);
            }
        }
        return result;
    }
    /**
     * 获取分布式锁key
     *
     * @param joinPoint
     * @param redisLock
     * @return {@link String}
     */
    public String getLockKey(ProceedingJoinPoint joinPoint, RedisLock redisLock) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        List<String> keyList = new ArrayList<>();
        keyList.add(RedisConstant.REDIS_LOCK_PREFIX);
        keyList.add(joinPoint.getTarget().getClass().getSimpleName().toUpperCase());
        // 如果配置key前缀则使用配置的,否则用方法名
        if (StringUtils.isNotBlank(redisLock.value())) {
            keyList.add(redisLock.value());
        } else {
            keyList.add(methodSignature.getName().toUpperCase());
        }
        // 解析spel表达式
        String key = getParseKey(redisLock.key(), methodSignature.getMethod(), joinPoint.getArgs());
        if (StringUtils.isNotBlank(key)) {
            keyList.add(key);
        }
        return Joiner.on(UNDERLINE).join(keyList);
    }
    /**
     * 获取缓存的key
     * key 定义在注解上,支持SPEL表达式
     *
     * @param key
     * @param method
     * @param args
     * @return {@link String}
     */
    public String getParseKey(String key, Method method, Object[] args) {
        if (StringUtils.isBlank(key)) {
            return getHexKey(args);
        }
        // 获取请求参数名
        String[] paramNames = DISCOVERER.getParameterNames(method);
        if (paramNames != null && paramNames.length > 0) {
            StandardEvaluationContext context = new StandardEvaluationContext();
            for (int i = 0; i < args.length; i++) {
                context.setVariable(paramNames[i], args[i]);
            }
            // 解析表达式
            return PARSER.parseExpression(key).getValue(context, String.class);
        } else {
            return getHexKey(args);
        }
    }
    /**
     * hex加密key
     *
     * @param args
     * @return {@link String}
     */
    public static String getHexKey(Object[] args) {
        if (args != null && args.length > 0) {
            return DigestUtils.md5Hex(Arrays.toString(args));
        } else {
            return null;
        }
    }
}

+ 1 - 23
svr/svr-cloud-care/src/main/java/com/yihu/jw/care/aop/ServicesAuthAOP.java

@ -7,7 +7,6 @@ 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.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
@ -23,7 +22,7 @@ import java.util.List;
 */
@Aspect
@Component
public class ServicesAuthAOP {
public class ServicesAuthAOP extends BaseAop{
    @Autowired
    private ServicePackageService servicePackageService;
@ -68,26 +67,5 @@ public class ServicesAuthAOP {
        return o;
    }
    public String write(int code, String msg) {
        try {
            JSONObject json = new JSONObject();
            json.put("status", code);
            json.put("msg", msg);
            return json.toString();
        } catch (Exception e) {
            return null;
        }
    }
    public String error(int code, String msg) {
        try {
            JSONObject json = new JSONObject();
            json.put("status", code);
            json.put("msg", msg);
            return json.toString();
        } catch (Exception e) {
            return null;
        }
    }
}

+ 125 - 0
svr/svr-cloud-care/src/main/java/com/yihu/jw/care/config/RedisConfig.java

@ -0,0 +1,125 @@
package com.yihu.jw.care.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yihu.jw.care.util.RedisConstant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.*;
/**
 * 缓存配置
 *
 * @author baichuan.wu
 * @version 1.0
 * @date 2019/4/8 15:17
 */
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
    @Autowired
    private RedisConnectionFactory factory;
    @Bean
    public CacheManager cacheManager(RedisTemplate redisTemplate) {
        RedisCacheManager rcm = new RedisCacheManager(redisTemplate);
        rcm.setDefaultExpiration(RedisConstant.REDIS_LOCK_EXPIRE_30);
        return rcm;
    }
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
        redisTemplate.setConnectionFactory(factory);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        /**hash也同样存对象数组*/
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
    /**
     * 对hash类型的数据操作
     *
     * @param redisTemplate
     * @return {@link HashOperations <String,String,Object>}
     */
    @Bean
    public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForHash();
    }
    /**
     * 对redis字符串类型数据操作
     *
     * @param redisTemplate
     * @return {@link ValueOperations <String, Object>}
     */
    @Bean
    public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForValue();
    }
    /**
     * 对链表类型的数据操作
     *
     * @param redisTemplate
     * @return {@link ListOperations <String, Object>}
     */
    @Bean
    public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForList();
    }
    /**
     * 对无序集合类型的数据操作
     *
     * @param redisTemplate
     * @return {@link SetOperations <String, Object>}
     */
    @Bean
    public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForSet();
    }
    /**
     * 对有序集合类型的数据操作
     *
     * @param redisTemplate
     * @return {@link ZSetOperations <String, Object>}
     */
    @Bean
    public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForZSet();
    }
    /**
     * string序列化
     *
     * @return {@link RedisSerializer <String>}
     */
    private RedisSerializer<String> keySerializer() {
        return new StringRedisSerializer();
    }
}

+ 21 - 0
svr/svr-cloud-care/src/main/java/com/yihu/jw/care/endpoint/patient/PatientEndpoint.java

@ -1,10 +1,12 @@
package com.yihu.jw.care.endpoint.patient;
import com.yihu.jw.care.aop.ObserverRequired;
import com.yihu.jw.care.aop.RedisLock;
import com.yihu.jw.care.aop.ServicesAuth;
import com.yihu.jw.care.aop.ServicesAuthAOP;
import com.yihu.jw.care.dao.label.WlyyPatientLabelDao;
import com.yihu.jw.care.service.patient.CarePatientService;
import com.yihu.jw.entity.base.patient.BasePatientDO;
import com.yihu.jw.restmodel.web.Envelop;
import com.yihu.jw.restmodel.web.PageEnvelop;
import com.yihu.jw.restmodel.web.endpoint.EnvelopRestEndpoint;
@ -32,6 +34,25 @@ public class PatientEndpoint extends EnvelopRestEndpoint {
    private WlyyPatientLabelDao patientLabelDao;
    @Autowired
    ServicesAuthAOP servicesAuthAOP;
    public int num = 20;
    @RequestMapping(value = "testRedisLock")
    @RedisLock(key = "#patientDO.id")
    public Envelop testRedisLock(BasePatientDO patientDO) {
        try {
            String s = Thread.currentThread().getName() + "=====================" + patientDO.getId();
            if (num > 0) {
                System.out.println(s + "排号成功,号码是:" + num);
                num--;
            } else {
                System.out.println(s + "排号失败,号码已经被抢光");
            }
            return success(patientDO);
        } catch (Exception e) {
            e.printStackTrace();
            return failed("获取失败",-1);
        }
    }
    @GetMapping(value = "testServicesAuth1")
    @ApiOperation(value = "测试居民服务项权限操作")

+ 29 - 0
svr/svr-cloud-care/src/main/java/com/yihu/jw/care/exception/BusinessException.java

@ -0,0 +1,29 @@
package com.yihu.jw.care.exception;
/**
 * 业务异常
 *
 * Created by yeshijie on 2021/4/23.
 */
public class BusinessException extends RuntimeException {
    public BusinessException() {
        super();
    }
    public BusinessException(String msg) {
        super(msg);
    }
    public BusinessException(String msg, Throwable cause) {
        super(msg, cause);
    }
    public BusinessException(Throwable cause) {
        super(cause);
    }
    protected BusinessException(String msg, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(msg, cause, enableSuppression, writableStackTrace);
    }
}

+ 109 - 0
svr/svr-cloud-care/src/main/java/com/yihu/jw/care/service/common/RedisService.java

@ -0,0 +1,109 @@
package com.yihu.jw.care.service.common;
import com.yihu.jw.care.util.RedisConstant;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
/**
 * redis接口实现
 *
 * @author baichuan.wu
 * @version 1.0.0
 * @date 2019-12-19 15:00
 */
@Service("redisService")
public class RedisService {
    private static final Logger log = LoggerFactory.getLogger(RedisService.class);
    @Autowired
    private RedisTemplate redisTemplate;
    private static final String LOCK_LUA = "if redis.call('setNx',KEYS[1],ARGV[1]) == 1 then redis.call('expire',KEYS[1],ARGV[2]) return 1 else return 0 end";
    private static final String UNLOCK_LUA = "if redis.call('get', KEYS[1]) == ARGV[1] then redis.call('del', KEYS[1]) end return 1 ";
    private static final Long SUCCESS = 1L;
    public boolean tryLock(String lockKey, String value) {
        try {
            RedisScript<Long> redisScript = new DefaultRedisScript<>(LOCK_LUA, Long.class);
            Object result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), value, RedisConstant.REDIS_LOCK_EXPIRE);
            return SUCCESS.equals(result);
        } catch (Exception e) {
            log.error("Get Lock Exception", e);
            return false;
        }
    }
    public boolean tryLock(String lockKey, String value, long expire) {
        try {
            RedisScript<Long> redisScript = new DefaultRedisScript<>(LOCK_LUA, Long.class);
            Object result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), value, expire);
            return SUCCESS.equals(result);
        } catch (Exception e) {
            log.error("Get Lock Exception", e);
            return false;
        }
    }
    public void releaseLock(String lockKey) {
        try {
            redisTemplate.delete(lockKey);
        } catch (Exception e) {
            log.error("Release Lock Exception", e);
        }
    }
    public boolean releaseLock(String lockKey, String value) {
        try {
            RedisScript<Long> redisScript = new DefaultRedisScript<>(UNLOCK_LUA, Long.class);
            Object result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), value);
            return SUCCESS.equals(result);
        } catch (Exception e) {
            log.error("Release Lock Exception", e);
            return false;
        }
    }
    public boolean expire(String key, long expire) {
        try {
            if (expire > 0) {
                return redisTemplate.expire(key, expire, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }
    public boolean hasKey(String key) {
        try {
            return StringUtils.isBlank(key) ? false : redisTemplate.hasKey(key);
        } catch (Exception e) {
            return false;
        }
    }
}

+ 22 - 0
svr/svr-cloud-care/src/main/java/com/yihu/jw/care/util/RedisConstant.java

@ -0,0 +1,22 @@
package com.yihu.jw.care.util;
/**
 * redis常量
 *
 * Created by yeshijie on 2021/4/23.
 */
public class RedisConstant {
    /**
     * redis锁
     */
    public static final String REDIS_LOCK_PREFIX = "REDIS_LOCK";
    /**
     * redis锁默认过期时间(s秒)
     */
    public static final long REDIS_LOCK_EXPIRE = 15L;
    /**
     * redis锁默认过期时间(s秒)
     */
    public static final long REDIS_LOCK_EXPIRE_30 = 30L;
}