JAVA中階培訓(xùn)(一)-策略模式與Redis拓展

一、前情提要

本次培訓(xùn)是在SpringBoot標(biāo)準(zhǔn)化培訓(xùn)的基礎(chǔ)上史侣,進(jìn)行的相關(guān)拓展震庭。未參加SpringBoot標(biāo)準(zhǔn)化培訓(xùn)的請自行翻閱之前文檔,培訓(xùn)的目的是幫助java開發(fā)成員“厭倦”了乏味的增刪改查的工作后暇榴,渴望對更加復(fù)雜或優(yōu)美的java技術(shù)進(jìn)一步獲知,進(jìn)而提升自己的java水平嗡靡、拓展自己的知識面跺撼。

以下所有功能示例,均來自生產(chǎn)項(xiàng)目

二讨彼、功能實(shí)例

1歉井、java策略模式

概念:定義一系列的算法,把每一個(gè)算法封裝起來, 并且使它們可相互替換。

策略模式把對象本身和運(yùn)算規(guī)則區(qū)分開來哈误,因此我們整個(gè)模式也分為三個(gè)部分哩至。
環(huán)境類(Context):用來操作策略的上下文環(huán)境躏嚎,也就是我們游客。
抽象策略類(Strategy):策略的抽象菩貌,出行方式的抽象
具體策略類(ConcreteStrategy):具體的策略實(shí)現(xiàn)卢佣,每一種出行方式的具體實(shí)現(xiàn)。

實(shí)際例子(物聯(lián)網(wǎng)智能設(shè)備處理)

項(xiàng)目結(jié)構(gòu)
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虚茶、策略轉(zhuǎn)發(fā)接口
/**
 * @author tangn
 * @desc 處理轉(zhuǎn)發(fā)接口
 * @date 2019/04/24
 */
public interface EventNotifyStrategy {

    /**
     * 處理前準(zhǔn)備通知參數(shù)
     *
     * @param pulsarMessage 通知消息
     * @return 處理結(jié)果
     */
    boolean preEventHandle(PulsarMessage pulsarMessage);

    /**
     * 處理后處理通知結(jié)果
     *
     * @param pulsarMessage 通知消息
     * @throws Exception 異常
     */
    void afterEventHandle(PulsarMessage pulsarMessage) throws Exception;
    
}
3、事件處理策略判斷類

/**
 * @author tangn
 * @desc 事件處理策略判斷類
 * @date 2019/04/24
 */
public class EventNotifyStrategyFactory {

    /**
     * 私有化構(gòu)造函數(shù)仇参,單例開始
     */
    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;
    }

    /**
     * 單例結(jié)束
     */
    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);
        });
    }

    /**
     * 根據(jù)支付策略類型獲取支付策略bean
     *
     * @param type class
     * @return 實(shí)體
     */
    public static EventNotifyStrategy getStrategy(String type) {
        // 反射獲取支付策略實(shí)現(xiàn)類clazz
        Class clazz = STRATEGY_MAP.get(type);
        if (StringUtils.isEmpty(clazz)) {
            return null;
        }
        // 通過applicationContext獲取bean
        return (EventNotifyStrategy) BeansUtil.getBean(clazz);
    }
}
4嘹叫、提供事件處理服務(wù)的抽象類
/**
 * @author tangn
 * @desc 提供事件處理服務(wù)的抽象類
 * @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的消費(fèi)訂閱模式是失效備源模式,消息只會在某一個(gè)客戶端被消費(fèi)诈乒,消費(fèi)如果失敗罩扇,就會被堆積,失效時(shí)間為2小時(shí)怕磨。為防止
     * 異常模式下大量消息堆積導(dǎo)致異澄辜ⅲ或者斷連,所有消費(fèi)消息都必須被正確消費(fèi)肠鲫,無用消息走NONE_EVENT時(shí)間员帮,所有異常模式不能使用return
     */
    public void eventHandle(String eventType, PulsarMessage pulsarMessage) throws Exception {
        this.eventType = eventType;
        boolean checkResult = preEventHandle(pulsarMessage);
        if (checkResult) {
            log.info("[設(shè)備狀態(tài)變更事件]成功 eventType[{}]", eventType);
        } else {
            log.info("[設(shè)備狀態(tài)變更事件]失敗 eventType[{}]", eventType);
            this.eventType = NONE_EVENT;
            pulsarMessage.setEvent(NONE_EVENT);
        }
        afterEventHandle(pulsarMessage);
        log.info("[設(shè)備狀態(tài)變更事件][{}]事件類型通知信息[{}] 處理完成", pulsarMessage.getEvent(), pulsarMessage);
    }
}

5、事件通知實(shí)現(xiàn)類
/**
 * @author tangn
 * @desc: 事件通知實(shí)現(xiàn)類
 * @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导饲、具體策略功能實(shí)現(xiàn)

/**
 * @author tangn
 * @date 2021/2/19 10:07
 * 設(shè)備在線
 */
@Slf4j
@Service
@TuyaMessageEvent("online")
public class OnlineEvent implements EventNotifyStrategy {
    @Resource
    private StoreDeviceDao storeDeviceDao;
    @Resource
    private DeviceDao deviceDao;
    @Resource
    private OffOnlineAlarmService offOnlineAlarmService;

    /**
     * 預(yù)處理時(shí)間
     *
     * @param pulsarMessage 通知消息
     * @return boolean
     */
    @Override
    public boolean preEventHandle(PulsarMessage pulsarMessage) {
        return true;
    }

    @Override
    public void afterEventHandle(PulsarMessage pulsarMessage) throws Exception {
        // 獲取設(shè)備信息
        StoreDevices storeDevice = storeDeviceDao.queryStoreDeviceInfoByDeviceUid(pulsarMessage.getDevId());
        if (Objects.isNull(storeDevice)) {
            log.warn("設(shè)備在線集侯,查詢不到該設(shè)備[{}]", pulsarMessage.getDevId());
        } else {
            // 檢測設(shè)備是否在線
            if (CommonStatusEnum.OFF.equals(storeDevice.getOnline())) {
                // 更新在線狀態(tài)
                deviceDao.updateDeviceOnlineState(storeDevice.getId(), CommonStatusEnum.ON);
                //上線提醒
                offOnlineAlarmService.onlineAlarm(storeDevice.getStoreCode(), pulsarMessage.getDevId(), LocalDateTime.now());
            }
        }
    }
}
7、策略調(diào)用
   abstractEventNotifyService.eventHandle("online", pulsarMessage);

2帜消、Redis巧妙使用,別光會用string(會員系統(tǒng)搶券)

/**
 * @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;

    /**
     * 領(lǐng)券中心領(lǐng)券
     *
     * @param user             會員
     * @param shopCouponCenter 領(lǐng)券中心券
     * @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, "此優(yōu)惠券已搶光");
        }
        LocalDateTime endTime = null;
        if (ShopCouponCenterTypeEnum.TIME_LIMIT.equals(shopCouponCenter.getCenterType())) {
            ShopCouponCenterActivity shopCouponCenterActivity = Optional.ofNullable(couponCenterDao.getActivityById(shopCouponCenter.getActivityId()))
                    .orElseThrow(() -> new ErrorCodeException(ErrorCodeEnum.NONE, "該活動(dòng)已結(jié)束"));
            if (ShopCouponCenterActivityStatusEnum.OVER.equals(shopCouponCenterActivity.getActivityStatus()) ||
                    !LocalDateTime.now().isBefore(shopCouponCenterActivity.getEndTime())) {
                return new MessageBean<>(ErrorCodeEnum.NONE, "該活動(dòng)已結(jié)束");
            }
            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, "此優(yōu)惠券已搶光");
        }
        RedisAtomicLong surplusQuantity = getSurplusQuantity(shopCouponCenter, endTime);
        if (surplusQuantity.get() <= 0L) {
            return new MessageBean<>(ErrorCodeEnum.NO, "此優(yōu)惠券已搶光");
        }
        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, "您已達(dá)到領(lǐng)取次數(shù)浓体,無法領(lǐng)取");
            }
        }
        doSendCouponCenter(user, shopCouponCenter, couponBeginTime, couponEndTime, surplusQuantity, endTime, totalNumRedisKey);
        return new MessageBean<>(ErrorCodeEnum.OK, "領(lǐng)取成功");
    }

    /**
     * 發(fā)放領(lǐng)券中心優(yōu)惠券
     *
     * @param user             會員
     * @param shopCouponCenter 領(lǐng)券中心優(yōu)惠券
     * @param couponBeginTime  優(yōu)惠券有效期開始時(shí)間
     * @param couponEndTime    優(yōu)惠券有效期結(jié)束時(shí)間
     * @param surplusQuantity  redis剩余數(shù)量
     * @param endTime          限時(shí)搶券結(jié)束時(shí)間
     * @param totalNumRedisKey 總領(lǐng)取數(shù)量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, "此優(yōu)惠券已搶光");
            }
            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, "您已達(dá)到領(lǐng)取次數(shù)泡挺,無法領(lǐng)取");
            }
            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, "您已達(dá)到領(lǐng)取次數(shù),無法領(lǐng)取");
            }
            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);
                // 領(lǐng)券達(dá)到總數(shù)量命浴,關(guān)閉券
                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中的領(lǐng)券中心優(yōu)惠券剩余數(shù)量
     *
     * @param shopCouponCenter 領(lǐng)券中心優(yōu)惠券
     * @param endTime          限時(shí)搶券結(jié)束時(shí)間
     * @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;
    }
}

講解點(diǎn):


1娄猫、RedisAtomicLong
知識延伸1:
(使用RedisAtomicLong優(yōu)化性能_飯團(tuán)小哥哥iop的博客-CSDN博客_redisatomiclong
知識延伸2:
AtomicLong用法_weixin_39967234的博客-CSDN博客

  • 這是一個(gè)spring-data-redis包中提供的,能夠?qū)?shù)據(jù)中的Long類型進(jìn)行原子性操做的類生闲,用來保證在并發(fā)情況下媳溺,數(shù)據(jù)不會被超賣。
  • AtomicLong 只能在一個(gè)應(yīng)用中使用
  • RedisAtomicLong 可以在所有與Redis有連接的應(yīng)用中使用
// 取值
 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鎖保證數(shù)據(jù)安全蝎困。
原因:在RedisAtomicLong內(nèi)部有一個(gè) private volatile int value; 
volatile保證變量的線程間可見性录语,compareAndSet方法實(shí)際上是做了兩部操作,第一是比較

2.redis過期時(shí)間使用

// 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 是一個(gè)string類型的field和value的映射表禾乘,hash特別適合用于存儲對象澎埠。
  • Redis 中每個(gè) 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

// 刪除多個(gè)key
redisTemplate.delete(Arrays.asList(Constant.ORANGE_VIP_COUPON_CENTER_PREFIX + shopCouponCenter.getId(), totalNumRedisKey));

3始藕、Redis分布式鎖(千云系統(tǒng)防重點(diǎn)擊)

  /**
 * 防重點(diǎn)擊限制
 *
 * @author 韓濤
 * @date 2020年03月28日 10時(shí)33分
 */
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatClickLimit {

    /**
     * 操作超時(shí)時(shí)間
     *
     * @return 超時(shí)時(shí)間
     */
    long operateTimeOut() default 5;

    /**
     * 操作超時(shí)時(shí)間單位
     *
     * @return 時(shí)間單位
     */
    TimeUnit timeUnit() default TimeUnit.SECONDS;

}
/**
 * 防重點(diǎn)擊切面
 *
 * @author 韓濤
 * @date 2019年12月3日14:20:07
 */
@Slf4j
@Aspect
@Component
public class RepeatClickLimitAspect {
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    /**
     * 防重點(diǎn)擊RedisKey前綴
     */
    private static final String REPEAT_CLICK_LIMIT_REDIS_KEY_PREFIX = "FasRepeatClickLimit_";

    /**
     * 防重點(diǎn)擊實(shí)現(xiàn)方法
     *
     * @param point            連接點(diǎn)
     * @param repeatClickLimit 防重點(diǎn)擊注解
     * @return 方法執(zhí)行結(jié)果
     * @throws Throwable 方法執(zhí)行異常
     */
    @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");
        // 獲取執(zhí)行方法的類名
        String className = point.getTarget().getClass().getName();
        // 獲取執(zhí)行方法的方法名
        String methodName = point.getSignature().getName();
        // RedisKey=前綴+類名+方法名+管理員ID
        String redisKey = REPEAT_CLICK_LIMIT_REDIS_KEY_PREFIX + className + methodName + storeCodeLimit;
        // 使用Redis分布式鎖,超時(shí)時(shí)間為注解自定義時(shí)間
        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;
            // 根據(jù)方法名及參數(shù)類型獲取方法
            Method currentMethod = point.getTarget().getClass().getMethod(methodSignature.getName(),
                    methodSignature.getParameterTypes());
            // 獲取返回值類型
            Class<?> returnType = currentMethod.getReturnType();
            // 返回值類型為SimpleMessage或MessageBean時(shí),直接返回,其他拋出異常
            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("方法執(zhí)行");
            //執(zhí)行目標(biāo)方法
            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 當(dāng)前時(shí)間+超時(shí)時(shí)間(超時(shí)時(shí)間最好設(shè)置在10秒以上,保證在不同的項(xiàng)目獲取到的時(shí)間誤差在控制范圍內(nèi))
     * @return 鎖住返回true
     */
    public boolean lock(String key, String value) {
        try {
            //setNX 返回boolean
            Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(key, value);
            if (aBoolean) {
                return true;
            }
            //如果鎖超時(shí) ***
            String currentValue = redisTemplate.opsForValue().get(key);
            if (!StringUtils.isEmpty(currentValue) && Long.parseLong(currentValue) < System.currentTimeMillis()) {
                //獲取上一個(gè)鎖的時(shí)間
                String oldvalue = redisTemplate.opsForValue().getAndSet(key, value);
                if (!StringUtils.isEmpty(oldvalue) && oldvalue.equals(currentValue)) {
                    return true;
                }
            }
        } catch (Exception e) {
            log.error("加鎖發(fā)生異常[{}]", 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);
        }
    }
}

使用方法

/**
 * 門店現(xiàn)金收銀對賬
 *
 * @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));
        //防止重復(fù)開啟
        if (!lock) {
            return;
        }
        try {
            cashReconciliationService.cashPaymentReconciliation();
        } catch (Exception e) {
            log.error("現(xiàn)金收銀對賬線程異常[{}]", e.getLocalizedMessage(), e);
        } finally {
            redisLockService.unlock(TaskNameEnum.CASH_PAYMENT.getStr(), String.valueOf(currentTime));
        }
    }
}

知識延伸

超賣了100瓶飛天茅臺F盐取!釀成一個(gè)重大事故伍派!Redis分布式鎖使用不當(dāng) (qq.com)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末江耀,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子拙已,更是在濱河造成了極大的恐慌决记,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件倍踪,死亡現(xiàn)場離奇詭異系宫,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)建车,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進(jìn)店門扩借,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人缤至,你說我怎么就攤上這事潮罪。” “怎么了领斥?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵嫉到,是天一觀的道長。 經(jīng)常有香客問我月洛,道長何恶,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任嚼黔,我火速辦了婚禮细层,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘唬涧。我一直安慰自己疫赎,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布碎节。 她就那樣靜靜地躺著捧搞,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上实牡,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天陌僵,我揣著相機(jī)與錄音,去河邊找鬼创坞。 笑死碗短,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的题涨。 我是一名探鬼主播偎谁,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼纲堵,長吁一口氣:“原來是場噩夢啊……” “哼巡雨!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起席函,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤铐望,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后茂附,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體正蛙,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年营曼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了乒验。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,094評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蒂阱,死狀恐怖锻全,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情录煤,我是刑警寧澤鳄厌,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站妈踊,受9級特大地震影響部翘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜响委,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望窖梁。 院中可真熱鬧赘风,春花似錦、人聲如沸纵刘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至瞬捕,卻和暖如春鞍历,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背肪虎。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工劣砍, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人扇救。 一個(gè)月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓刑枝,卻偏偏與公主長得像,于是被迫代替她去往敵國和親迅腔。 傳聞我的和親對象是個(gè)殘疾皇子装畅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內(nèi)容