美文网首页
JAVA中阶培训(一)-策略模式与Redis拓展

JAVA中阶培训(一)-策略模式与Redis拓展

作者: 一击必中 | 来源:发表于2021-05-11 20:35 被阅读0次

    一、前情提要

    本次培训是在SpringBoot标准化培训的基础上,进行的相关拓展。未参加SpringBoot标准化培训的请自行翻阅之前文档,培训的目的是帮助java开发成员“厌倦”了乏味的增删改查的工作后,渴望对更加复杂或优美的java技术进一步获知,进而提升自己的java水平、拓展自己的知识面。

    以下所有功能示例,均来自生产项目

    二、功能实例

    1、java策略模式

    概念:定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换。

    策略模式把对象本身和运算规则区分开来,因此我们整个模式也分为三个部分。
    环境类(Context):用来操作策略的上下文环境,也就是我们游客。
    抽象策略类(Strategy):策略的抽象,出行方式的抽象
    具体策略类(ConcreteStrategy):具体的策略实现,每一种出行方式的具体实现。

    实际例子(物联网智能设备处理)

    项目结构
    image.png
    1、自定义注释@interface

    参考链接:自定义注释@interface的用法理解_zhangbeizhen18的博客-CSDN博客

    /**
     * describe:事件处理策略
     *
     * @author tangn
     * @date 2019/04/24
     */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface TuyaMessageEvent {
        /**
         * 事件类型
         *
         * @return 事件类型
         */
        String value();
    }
    
    2、策略转发接口
    /**
     * @author tangn
     * @desc 处理转发接口
     * @date 2019/04/24
     */
    public interface EventNotifyStrategy {
    
        /**
         * 处理前准备通知参数
         *
         * @param pulsarMessage 通知消息
         * @return 处理结果
         */
        boolean preEventHandle(PulsarMessage pulsarMessage);
    
        /**
         * 处理后处理通知结果
         *
         * @param pulsarMessage 通知消息
         * @throws Exception 异常
         */
        void afterEventHandle(PulsarMessage pulsarMessage) throws Exception;
        
    }
    
    3、事件处理策略判断类
    
    /**
     * @author tangn
     * @desc 事件处理策略判断类
     * @date 2019/04/24
     */
    public class EventNotifyStrategyFactory {
    
        /**
         * 私有化构造函数,单例开始
         */
        private EventNotifyStrategyFactory() {
    
        }
    
        private static class Builder {
            private static final EventNotifyStrategyFactory EVENT_NOTIFY_STRATEGY_FACTORY = new EventNotifyStrategyFactory();
        }
    
        @SuppressWarnings("unused")
        public static EventNotifyStrategyFactory getInstance() {
            return Builder.EVENT_NOTIFY_STRATEGY_FACTORY;
        }
    
        /**
         * 单例结束
         */
        private static final String PAY_STRATEGY_IMPLEMENTATION_PACKAGE = "com.decentchina.cronjob.pulsar.strategy.event";
        private static final Map<String, Class> STRATEGY_MAP = new HashMap<>();
    
        // 获取所有事件类型策略
        static {
            Reflections reflections = new Reflections(PAY_STRATEGY_IMPLEMENTATION_PACKAGE);
            Set<Class<?>> classSet = reflections.getTypesAnnotatedWith(TuyaMessageEvent.class);
            classSet.forEach(aClass -> {
                TuyaMessageEvent payAnnotation = aClass.getAnnotation(TuyaMessageEvent.class);
                STRATEGY_MAP.put(payAnnotation.value(), aClass);
            });
        }
    
        /**
         * 根据支付策略类型获取支付策略bean
         *
         * @param type class
         * @return 实体
         */
        public static EventNotifyStrategy getStrategy(String type) {
            // 反射获取支付策略实现类clazz
            Class clazz = STRATEGY_MAP.get(type);
            if (StringUtils.isEmpty(clazz)) {
                return null;
            }
            // 通过applicationContext获取bean
            return (EventNotifyStrategy) BeansUtil.getBean(clazz);
        }
    }
    
    4、提供事件处理服务的抽象类
    /**
     * @author tangn
     * @desc 提供事件处理服务的抽象类
     * @date 2019/04/24
     */
    @Slf4j
    @Service
    public abstract class AbstractEventNotifyService implements EventNotifyStrategy {
    
        protected String eventType;
    
        /**
         * 无需处理的事件
         */
        private static final String NONE_EVENT = "none";
    
        /**
         * 各类事件处理
         *
         * @param eventType     事件类型
         * @param pulsarMessage 通知消息
         * @throws Exception 异常
         * @desc 涂鸦pulsar的消费订阅模式是失效备源模式,消息只会在某一个客户端被消费,消费如果失败,就会被堆积,失效时间为2小时。为防止
         * 异常模式下大量消息堆积导致异常或者断连,所有消费消息都必须被正确消费,无用消息走NONE_EVENT时间,所有异常模式不能使用return
         */
        public void eventHandle(String eventType, PulsarMessage pulsarMessage) throws Exception {
            this.eventType = eventType;
            boolean checkResult = preEventHandle(pulsarMessage);
            if (checkResult) {
                log.info("[设备状态变更事件]成功 eventType[{}]", eventType);
            } else {
                log.info("[设备状态变更事件]失败 eventType[{}]", eventType);
                this.eventType = NONE_EVENT;
                pulsarMessage.setEvent(NONE_EVENT);
            }
            afterEventHandle(pulsarMessage);
            log.info("[设备状态变更事件][{}]事件类型通知信息[{}] 处理完成", pulsarMessage.getEvent(), pulsarMessage);
        }
    }
    
    
    5、事件通知实现类
    /**
     * @author tangn
     * @desc: 事件通知实现类
     * @date 2019/04/24
     */
    @Slf4j
    @Service
    public class EventNotifyServiceImpl extends AbstractEventNotifyService {
    
        @Override
        public boolean preEventHandle(PulsarMessage pulsarMessage) {
            EventNotifyStrategy eventNotifyStrategy = EventNotifyStrategyFactory.getStrategy(this.eventType);
            if (eventNotifyStrategy == null) {
                log.info("没有[{}]类型的事件\r\n", this.eventType);
                return false;
            }
            return eventNotifyStrategy.preEventHandle(pulsarMessage);
        }
    
        @Override
        @SuppressWarnings("ConstantConditions")
        public void afterEventHandle(PulsarMessage pushMessage) throws Exception {
            EventNotifyStrategy payStrategy = EventNotifyStrategyFactory.getStrategy(this.eventType);
            payStrategy.afterEventHandle(pushMessage);
        }
    }
    
    6、具体策略功能实现
    
    /**
     * @author tangn
     * @date 2021/2/19 10:07
     * 设备在线
     */
    @Slf4j
    @Service
    @TuyaMessageEvent("online")
    public class OnlineEvent implements EventNotifyStrategy {
        @Resource
        private StoreDeviceDao storeDeviceDao;
        @Resource
        private DeviceDao deviceDao;
        @Resource
        private OffOnlineAlarmService offOnlineAlarmService;
    
        /**
         * 预处理时间
         *
         * @param pulsarMessage 通知消息
         * @return boolean
         */
        @Override
        public boolean preEventHandle(PulsarMessage pulsarMessage) {
            return true;
        }
    
        @Override
        public void afterEventHandle(PulsarMessage pulsarMessage) throws Exception {
            // 获取设备信息
            StoreDevices storeDevice = storeDeviceDao.queryStoreDeviceInfoByDeviceUid(pulsarMessage.getDevId());
            if (Objects.isNull(storeDevice)) {
                log.warn("设备在线,查询不到该设备[{}]", pulsarMessage.getDevId());
            } else {
                // 检测设备是否在线
                if (CommonStatusEnum.OFF.equals(storeDevice.getOnline())) {
                    // 更新在线状态
                    deviceDao.updateDeviceOnlineState(storeDevice.getId(), CommonStatusEnum.ON);
                    //上线提醒
                    offOnlineAlarmService.onlineAlarm(storeDevice.getStoreCode(), pulsarMessage.getDevId(), LocalDateTime.now());
                }
            }
        }
    }
    
    7、策略调用
       abstractEventNotifyService.eventHandle("online", pulsarMessage);
    

    2、Redis巧妙使用,别光会用string(会员系统抢券)

    /**
     * @author zhongzq
     * @date 2019-12-18 10:37
     */
    @Service
    public class CouponCenterServiceImpl implements CouponCenterService {
        @Resource
        private CouponCenterDao couponCenterDao;
        @Resource
        private UserService userService;
        @Resource(name = "redisTemplateObject")
        private RedisTemplate<String, Object> redisTemplate;
        @Resource
        private OwnCouponDao ownCouponDao;
    
        /**
         * 领券中心领券
         *
         * @param user             会员
         * @param shopCouponCenter 领券中心券
         * @return : com.orangeconvenient.common.entity.MessageBean
         */
        @Transactional(rollbackFor = Exception.class)
        @Override
        public MessageBean<ShopCouponCenterVO> receive(User user, ShopCouponCenterVO shopCouponCenter) {
            if (!ShopCouponCenterActivityStatusEnum.PROCESSING.equals(shopCouponCenter.getCenterCouponStatus())) {
                return new MessageBean<>(ErrorCodeEnum.NO, "此优惠券已抢光");
            }
            LocalDateTime endTime = null;
            if (ShopCouponCenterTypeEnum.TIME_LIMIT.equals(shopCouponCenter.getCenterType())) {
                ShopCouponCenterActivity shopCouponCenterActivity = Optional.ofNullable(couponCenterDao.getActivityById(shopCouponCenter.getActivityId()))
                        .orElseThrow(() -> new ErrorCodeException(ErrorCodeEnum.NONE, "该活动已结束"));
                if (ShopCouponCenterActivityStatusEnum.OVER.equals(shopCouponCenterActivity.getActivityStatus()) ||
                        !LocalDateTime.now().isBefore(shopCouponCenterActivity.getEndTime())) {
                    return new MessageBean<>(ErrorCodeEnum.NONE, "该活动已结束");
                }
                endTime = shopCouponCenterActivity.getEndTime();
            }
            boolean isTimeRange = CouponEffectiveTypeEnum.DATE_TYPE_FIX_TIME_RANGE.equals(shopCouponCenter.getTimeType());
            LocalDateTime couponBeginTime = isTimeRange ?
                    shopCouponCenter.getBeginTime() : LocalDateTimeUtil.begin(LocalDate.now().plusDays(shopCouponCenter.getFixedBeginTerm()));
            LocalDateTime couponEndTime = isTimeRange ?
                    shopCouponCenter.getEndTime() : couponBeginTime.toLocalDate().plusDays(shopCouponCenter.getFixedBeginTerm() > 0 ? shopCouponCenter.getFixedTerm() - 1 : shopCouponCenter.getFixedTerm()).atTime(23, 59, 59);
            if (isTimeRange && !LocalDateTime.now().isBefore(couponEndTime)) {
                return new MessageBean<>(ErrorCodeEnum.NO, "此优惠券已抢光");
            }
            RedisAtomicLong surplusQuantity = getSurplusQuantity(shopCouponCenter, endTime);
            if (surplusQuantity.get() <= 0L) {
                return new MessageBean<>(ErrorCodeEnum.NO, "此优惠券已抢光");
            }
            String totalNumRedisKey = Constant.ORANGE_VIP_COUPON_CENTER_RECEIVE_PREFIX + shopCouponCenter.getId();
            if (Objects.nonNull(shopCouponCenter.getPerTotalLimit())) {
                if (!Objects.equals(Boolean.TRUE, redisTemplate.opsForHash().hasKey(totalNumRedisKey, user.getId())) &&
                        redisTemplate.opsForHash().putIfAbsent(totalNumRedisKey, user.getId(), couponCenterDao.getUserTotalNum(user.getId().longValue(), shopCouponCenter.getId())) &&
                        ShopCouponCenterTypeEnum.TIME_LIMIT.equals(shopCouponCenter.getCenterType())) {
                    redisTemplate.expire(totalNumRedisKey, LocalDateTimeUtil.goneSeconds(LocalDateTime.now(), endTime), TimeUnit.SECONDS);
                }
                if ((int) redisTemplate.opsForHash().get(totalNumRedisKey, user.getId()) >= shopCouponCenter.getPerTotalLimit()) {
                    return new MessageBean<>(ErrorCodeEnum.NO, "您已达到领取次数,无法领取");
                }
            }
            doSendCouponCenter(user, shopCouponCenter, couponBeginTime, couponEndTime, surplusQuantity, endTime, totalNumRedisKey);
            return new MessageBean<>(ErrorCodeEnum.OK, "领取成功");
        }
    
        /**
         * 发放领券中心优惠券
         *
         * @param user             会员
         * @param shopCouponCenter 领券中心优惠券
         * @param couponBeginTime  优惠券有效期开始时间
         * @param couponEndTime    优惠券有效期结束时间
         * @param surplusQuantity  redis剩余数量
         * @param endTime          限时抢券结束时间
         * @param totalNumRedisKey 总领取数量redisKey
         */
        private void doSendCouponCenter(User user, ShopCouponCenterVO shopCouponCenter, LocalDateTime couponBeginTime, LocalDateTime couponEndTime, RedisAtomicLong surplusQuantity, LocalDateTime endTime, String totalNumRedisKey) {
            try {
                long surplusNum = surplusQuantity.decrementAndGet();
                if (surplusNum < 0L) {
                    throw new ErrorCodeException(ErrorCodeEnum.NO, "此优惠券已抢光");
                }
                if (!Objects.equals(Boolean.TRUE, redisTemplate.opsForHash().hasKey(totalNumRedisKey, user.getId())) &&
                        redisTemplate.opsForHash().putIfAbsent(totalNumRedisKey, user.getId(), couponCenterDao.getUserTotalNum(user.getId().longValue(), shopCouponCenter.getId())) &&
                        ShopCouponCenterTypeEnum.TIME_LIMIT.equals(shopCouponCenter.getCenterType())) {
                    redisTemplate.expire(totalNumRedisKey, LocalDateTimeUtil.goneSeconds(LocalDateTime.now(), endTime), TimeUnit.SECONDS);
                }
                if ((int) redisTemplate.opsForHash().get(totalNumRedisKey, user.getId()) >= shopCouponCenter.getPerTotalLimit()) {
                    throw new ErrorCodeException(ErrorCodeEnum.NO, "您已达到领取次数,无法领取");
                }
                Long increment = redisTemplate.opsForHash().increment(totalNumRedisKey, user.getId(), 1L);
                if (increment > shopCouponCenter.getPerTotalLimit()) {
                    redisTemplate.opsForHash().increment(totalNumRedisKey, user.getId(), -1L);
                    throw new ErrorCodeException(ErrorCodeEnum.NO, "您已达到领取次数,无法领取");
                }
                List<UserOwnCoupon> userOwnCoupons = Collections.singletonList(UserOwnCoupon.builder().ownCouponId(shopCouponCenter.getCouponId()).userId(user.getId().longValue()).tradeNo(null).useStatus(CouponUseStatusEnum.NO_USED)
                        .couponNo(CheckUtil.fillZero(shopCouponCenter.getCouponId(), 5) + CheckUtil.fillZero(user.getId().longValue(), 5) + System.nanoTime())
                        .startValidateTime(couponBeginTime).endValidateTime(couponEndTime)
                        .couponSource(OwnCouponSourceEnum.COUPON_CENTER).couponSourceId(shopCouponCenter.getId()).build());
                try {
                    ownCouponDao.insertUserOwnCoupons(userOwnCoupons.size(), userOwnCoupons);
                    // 领券达到总数量,关闭券
                    if (couponCenterDao.ifCenterCouponNumMax(shopCouponCenter) >= shopCouponCenter.getPreIssueQuantity()
                            &&
                            couponCenterDao.close(shopCouponCenter, ShopCouponCenterActivityStatusEnum.OVER) == 1) {
                        redisTemplate.delete(Arrays.asList(Constant.ORANGE_VIP_COUPON_CENTER_PREFIX + shopCouponCenter.getId(), totalNumRedisKey));
                    }
                } catch (Exception e) {
                    if (Objects.nonNull(shopCouponCenter.getPerTotalLimit())) {
                        redisTemplate.opsForHash().increment(totalNumRedisKey, user.getId(), -1L);
                    }
                    throw e;
                }
            } catch (Exception e) {
                surplusQuantity.incrementAndGet();
                throw e;
            }
        }
    
        /**
         * 获取redis中的领券中心优惠券剩余数量
         *
         * @param shopCouponCenter 领券中心优惠券
         * @param endTime          限时抢券结束时间
         * @return : org.springframework.data.redis.support.atomic.RedisAtomicLong
         */
        @SuppressWarnings("ConstantConditions")
        private RedisAtomicLong getSurplusQuantity(ShopCouponCenter shopCouponCenter, LocalDateTime endTime) {
            RedisAtomicLong surplusQuantity;
            String surplusQuantityRedisKey = Constant.ORANGE_VIP_COUPON_CENTER_PREFIX + shopCouponCenter.getId();
            if (!Objects.equals(Boolean.TRUE, redisTemplate.hasKey(surplusQuantityRedisKey))) {
                surplusQuantity = new RedisAtomicLong(surplusQuantityRedisKey, redisTemplate.getConnectionFactory());
                if (surplusQuantity.compareAndSet(0, shopCouponCenter.getPreIssueQuantity() - couponCenterDao.getNowTotalNum(shopCouponCenter.getId())) &&
                        ShopCouponCenterTypeEnum.TIME_LIMIT.equals(shopCouponCenter.getCenterType())) {
                    surplusQuantity.expireAt(LocalDateTimeUtil.localDateTime2Date(endTime));
                }
            } else {
                surplusQuantity = new RedisAtomicLong(surplusQuantityRedisKey, redisTemplate.getConnectionFactory());
            }
            return surplusQuantity;
        }
    }
    
    

    讲解点:


    1、RedisAtomicLong
    知识延伸1:
    (使用RedisAtomicLong优化性能_饭团小哥哥iop的博客-CSDN博客_redisatomiclong
    知识延伸2:
    AtomicLong用法_weixin_39967234的博客-CSDN博客

    • 这是一个spring-data-redis包中提供的,能够对数据中的Long类型进行原子性操做的类,用来保证在并发情况下,数据不会被超卖。
    • AtomicLong 只能在一个应用中使用
    • RedisAtomicLong 可以在所有与Redis有连接的应用中使用
    // 取值
     surplusQuantity = new RedisAtomicLong(surplusQuantityRedisKey, redisTemplate.getConnectionFactory());
    // 加值
     long surplusNum = surplusQuantity.incrementAndGet();
    // 减值
     long surplusNum = surplusQuantity.decrementAndGet();
    // 更值
    public final boolean compareAndSet(int expect, int update);
    // 解释(凭自身能力理解)
    value的值为expect的值,第二是把value的值更新为
    update,这两步是原子操作,在没有多线程锁的情况下,借助cpu锁保证数据安全。
    原因:在RedisAtomicLong内部有一个 private volatile int value; 
    volatile保证变量的线程间可见性,compareAndSet方法实际上是做了两部操作,第一是比较
    

    2.redis过期时间使用

    // RedisAutomicLong 过期
     surplusQuantity.expireAt(LocalDateTimeUtil.localDateTime2Date(endTime));
    // hash过期
     redisTemplate.expire(totalNumRedisKey, LocalDateTimeUtil.goneSeconds(LocalDateTime.now(), endTime), TimeUnit.SECONDS);
    

    3.redisTemplate.opsForHash()
    redisTemplate.opsForHash()_小泽-CSDN博客

    • Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。
    • Redis 中每个 hash 可以存储 2^32 - 1 键值对(40多亿)。
    // 放值
    redisTemplate.opsForHash().putIfAbsent(totalNumRedisKey, user.getId(), couponCenterDao.getUserTotalNum(user.getId().longValue(), shopCouponCenter.getId()))
    // 取值
    redisTemplate.opsForHash().get(totalNumRedisKey, user.getId())
    // 增值
    Long increment = redisTemplate.opsForHash().increment(totalNumRedisKey, user.getId(), 1L);
    // 减值
    redisTemplate.opsForHash().increment(totalNumRedisKey, user.getId(), -1L);
    

    4.redisTemplate.delete

    // 删除多个key
    redisTemplate.delete(Arrays.asList(Constant.ORANGE_VIP_COUPON_CENTER_PREFIX + shopCouponCenter.getId(), totalNumRedisKey));
    

    3、Redis分布式锁(千云系统防重点击)

      /**
     * 防重点击限制
     *
     * @author 韩涛
     * @date 2020年03月28日 10时33分
     */
    @Documented
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface RepeatClickLimit {
    
        /**
         * 操作超时时间
         *
         * @return 超时时间
         */
        long operateTimeOut() default 5;
    
        /**
         * 操作超时时间单位
         *
         * @return 时间单位
         */
        TimeUnit timeUnit() default TimeUnit.SECONDS;
    
    }
    
    /**
     * 防重点击切面
     *
     * @author 韩涛
     * @date 2019年12月3日14:20:07
     */
    @Slf4j
    @Aspect
    @Component
    public class RepeatClickLimitAspect {
        @Resource
        private RedisTemplate<String, Object> redisTemplate;
        /**
         * 防重点击RedisKey前缀
         */
        private static final String REPEAT_CLICK_LIMIT_REDIS_KEY_PREFIX = "FasRepeatClickLimit_";
    
        /**
         * 防重点击实现方法
         *
         * @param point            连接点
         * @param repeatClickLimit 防重点击注解
         * @return 方法执行结果
         * @throws Throwable 方法执行异常
         */
        @Around("@annotation(repeatClickLimit)")
        public Object doDistributedLock(ProceedingJoinPoint point, RepeatClickLimit repeatClickLimit) throws Throwable {
            HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
            String storeCodeLimit = request.getHeader("storeCodeLimit");
            // 获取执行方法的类名
            String className = point.getTarget().getClass().getName();
            // 获取执行方法的方法名
            String methodName = point.getSignature().getName();
            // RedisKey=前缀+类名+方法名+管理员ID
            String redisKey = REPEAT_CLICK_LIMIT_REDIS_KEY_PREFIX + className + methodName + storeCodeLimit;
            // 使用Redis分布式锁,超时时间为注解自定义时间
            redisTemplate.setEnableTransactionSupport(true);
            redisTemplate.multi();
            redisTemplate.opsForValue().setIfAbsent(redisKey, "");
            redisTemplate.expire(redisKey, repeatClickLimit.operateTimeOut(), repeatClickLimit.timeUnit());
            List<Object> result = redisTemplate.exec();
            log.info("请求:[{}]", !(Boolean) result.get(0));
            if (!(Boolean) result.get(0)) {
                log.info("返回");
                // 获取签名
                Signature signature = point.getSignature();
                // 若不是方法签名直接抛异常
                if (!(signature instanceof MethodSignature)) {
                    throw new ErrorCodeException(ErrorCodeEnum.ERROR, "操作频繁,请稍后重试");
                }
                MethodSignature methodSignature = (MethodSignature) signature;
                // 根据方法名及参数类型获取方法
                Method currentMethod = point.getTarget().getClass().getMethod(methodSignature.getName(),
                        methodSignature.getParameterTypes());
                // 获取返回值类型
                Class<?> returnType = currentMethod.getReturnType();
                // 返回值类型为SimpleMessage或MessageBean时,直接返回,其他抛出异常
                if (SimpleMessage.class.equals(returnType)) {
                    return new SimpleMessage(ErrorCodeEnum.NO, "操作频繁,请稍后重试");
                }
                if (MessageBean.class.equals(returnType)) {
                    return MessageBean.builder().errorCode(ErrorCodeEnum.OK.getCode()).errorMsg("操作频繁,请稍后重试").build();
                }
                throw new ErrorCodeException(ErrorCodeEnum.ERROR, "操作频繁,请稍后重试");
            }
            try {
                log.info("方法执行");
                //执行目标方法
                return point.proceed();
            } finally {
                log.info("删除");
                // 删除RedisKey
                redisTemplate.delete(redisKey);
            }
        }
    
    }
    
    

    分布式锁文件

    /**
     * REDIS锁
     *
     * @author 陈豆豆
     * @date 2020-03-16
     */
    @Slf4j
    @Component
    public class RedisLockService {
        @Autowired
        private RedisTemplate<String, String> redisTemplate;
    
        /***
         * 加锁
         * @param key
         * @param value 当前时间+超时时间(超时时间最好设置在10秒以上,保证在不同的项目获取到的时间误差在控制范围内)
         * @return 锁住返回true
         */
        public boolean lock(String key, String value) {
            try {
                //setNX 返回boolean
                Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(key, value);
                if (aBoolean) {
                    return true;
                }
                //如果锁超时 ***
                String currentValue = redisTemplate.opsForValue().get(key);
                if (!StringUtils.isEmpty(currentValue) && Long.parseLong(currentValue) < System.currentTimeMillis()) {
                    //获取上一个锁的时间
                    String oldvalue = redisTemplate.opsForValue().getAndSet(key, value);
                    if (!StringUtils.isEmpty(oldvalue) && oldvalue.equals(currentValue)) {
                        return true;
                    }
                }
            } catch (Exception e) {
                log.error("加锁发生异常[{}]", e.getLocalizedMessage(), e);
            }
            return false;
        }
    
        /***
         * 解锁
         * @param key
         * @param value
         * @return
         */
        public void unlock(String key, String value) {
            try {
                String currentValue = redisTemplate.opsForValue().get(key);
                if (StringUtils.isBlank(currentValue)) {
                    return;
                }
                if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {
                    redisTemplate.delete(key);
                }
            } catch (Exception e) {
                log.error("解锁异常[{}]", e.getLocalizedMessage(), e);
            }
        }
    }
    

    使用方法

    /**
     * 门店现金收银对账
     *
     * @author guojw
     * @date 2020/09/24
     */
    @Slf4j
    @AllArgsConstructor
    public class CashReconciliationThread implements Runnable {
        private CashReconciliationService cashReconciliationService;
        private RedisLockService redisLockService;
    
        @Override
        public void run() {
            long currentTime = Instant.now().plus(15, MINUTES).toEpochMilli();
            boolean lock = redisLockService.lock(TaskNameEnum.CASH_PAYMENT.getStr(), String.valueOf(currentTime));
            //防止重复开启
            if (!lock) {
                return;
            }
            try {
                cashReconciliationService.cashPaymentReconciliation();
            } catch (Exception e) {
                log.error("现金收银对账线程异常[{}]", e.getLocalizedMessage(), e);
            } finally {
                redisLockService.unlock(TaskNameEnum.CASH_PAYMENT.getStr(), String.valueOf(currentTime));
            }
        }
    }
    
    

    知识延伸

    超卖了100瓶飞天茅台!!酿成一个重大事故!Redis分布式锁使用不当 (qq.com)

    相关文章

      网友评论

          本文标题:JAVA中阶培训(一)-策略模式与Redis拓展

          本文链接:https://www.haomeiwen.com/subject/bejndltx.html