一、前情提要
本次培訓(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)
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));
}
}
}
知識延伸