JAVA中阶培训(一)-策略模式与Redis拓展
2021-05-11 本文已影响0人
一击必中
一、前情提要
本次培训是在SpringBoot标准化培训的基础上,进行的相关拓展。未参加SpringBoot标准化培训的请自行翻阅之前文档,培训的目的是帮助java开发成员“厌倦”了乏味的增删改查的工作后,渴望对更加复杂或优美的java技术进一步获知,进而提升自己的java水平、拓展自己的知识面。
以下所有功能示例,均来自生产项目
二、功能实例
1、java策略模式
概念:定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换。
策略模式把对象本身和运算规则区分开来,因此我们整个模式也分为三个部分。
环境类(Context):用来操作策略的上下文环境,也就是我们游客。
抽象策略类(Strategy):策略的抽象,出行方式的抽象
具体策略类(ConcreteStrategy):具体的策略实现,每一种出行方式的具体实现。
实际例子(物联网智能设备处理)
项目结构
image.png1、自定义注释@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));
}
}
}
知识延伸