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

一、前情提要

本次培训是在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)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,036评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,046评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,411评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,622评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,661评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,521评论 1 304
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,288评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,200评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,644评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,837评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,953评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,673评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,281评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,889评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,011评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,119评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,901评论 2 355

推荐阅读更多精彩内容