|
@@ -2,7 +2,11 @@ package com.qmth.exam.reserve.mq;
|
|
|
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
|
|
|
+import com.qmth.boot.core.concurrent.service.ConcurrentService;
|
|
|
|
+import com.qmth.boot.core.exception.StatusException;
|
|
|
|
+import com.qmth.exam.reserve.bean.Constants;
|
|
import com.qmth.exam.reserve.bean.apply.ApplyRecordCacheBean;
|
|
import com.qmth.exam.reserve.bean.apply.ApplyRecordCacheBean;
|
|
|
|
+import com.qmth.exam.reserve.cache.CacheConstants;
|
|
import com.qmth.exam.reserve.cache.impl.ApplyTaskCacheService;
|
|
import com.qmth.exam.reserve.cache.impl.ApplyTaskCacheService;
|
|
import com.qmth.exam.reserve.entity.StudentApplyEntity;
|
|
import com.qmth.exam.reserve.entity.StudentApplyEntity;
|
|
import com.qmth.exam.reserve.enums.EventType;
|
|
import com.qmth.exam.reserve.enums.EventType;
|
|
@@ -14,6 +18,7 @@ import org.apache.rocketmq.spring.annotation.ConsumeMode;
|
|
import org.apache.rocketmq.spring.annotation.MessageModel;
|
|
import org.apache.rocketmq.spring.annotation.MessageModel;
|
|
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
|
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
|
import org.apache.rocketmq.spring.core.RocketMQListener;
|
|
import org.apache.rocketmq.spring.core.RocketMQListener;
|
|
|
|
+import org.redisson.api.RLock;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
import org.slf4j.LoggerFactory;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
@@ -40,6 +45,9 @@ public class ExamReserveMQConsumer implements RocketMQListener<MessageExt> {
|
|
@Autowired
|
|
@Autowired
|
|
private ApplyTaskCacheService applyTaskCacheService;
|
|
private ApplyTaskCacheService applyTaskCacheService;
|
|
|
|
|
|
|
|
+ @Autowired
|
|
|
|
+ private ConcurrentService concurrentService;
|
|
|
|
+
|
|
@Override
|
|
@Override
|
|
public void onMessage(MessageExt message) {
|
|
public void onMessage(MessageExt message) {
|
|
ApplyRecordCacheBean queueBean = JsonHelper.toObj(message.getBody(), ApplyRecordCacheBean.class);
|
|
ApplyRecordCacheBean queueBean = JsonHelper.toObj(message.getBody(), ApplyRecordCacheBean.class);
|
|
@@ -48,34 +56,55 @@ public class ExamReserveMQConsumer implements RocketMQListener<MessageExt> {
|
|
throw new RuntimeException("MQ消息转换失败!");
|
|
throw new RuntimeException("MQ消息转换失败!");
|
|
}
|
|
}
|
|
|
|
|
|
- ApplyRecordCacheBean cacheBean = applyTaskCacheService.getStudentApplyRecord(queueBean.getStudentId(),
|
|
|
|
- queueBean.getExamSiteId(), queueBean.getTimePeriodId());
|
|
|
|
|
|
+ // 考生预约操作锁
|
|
|
|
+ String studentApplyLockKey = String.format(CacheConstants.LOCK_STUDENT_APPLY, queueBean.getStudentId());
|
|
|
|
+ RLock studentApplyLock = (RLock) concurrentService.getLock(studentApplyLockKey);
|
|
|
|
+ try {
|
|
|
|
+ if (!studentApplyLock.tryLock()) {
|
|
|
|
+ log.warn("获取锁失败,同一个考生不允许同时操作预约!lockKey:{}", studentApplyLockKey);
|
|
|
|
+ throw new StatusException(Constants.SYSTEM_BUSY);
|
|
|
|
+ }
|
|
|
|
+ log.info("获取锁成功!lockKey:{}", studentApplyLockKey);
|
|
|
|
|
|
- final ApplyRecordCacheBean bean;
|
|
|
|
- if (cacheBean == null) {
|
|
|
|
- log.warn("【考生预约队列】MQ队列元素对应的缓存记录不存在!bizId:{} msgId:{}", queueBean.getBizId(), message.getMsgId());
|
|
|
|
|
|
+ ApplyRecordCacheBean cacheBean = applyTaskCacheService.getStudentApplyRecord(queueBean.getStudentId(),
|
|
|
|
+ queueBean.getExamSiteId(), queueBean.getTimePeriodId());
|
|
|
|
|
|
- // 若缓存数据不存在,则采用队列数据持久化
|
|
|
|
- bean = queueBean;
|
|
|
|
- // 更新缓存
|
|
|
|
- applyTaskCacheService.saveStudentApplyRecord(bean);
|
|
|
|
- } else {
|
|
|
|
- if (cacheBean.getBizId() != null && cacheBean.getBizId() >= queueBean.getBizId()) {
|
|
|
|
- log.info("【考生预约队列】MQ消息消费!bizId:{} cacheBizId:{} msgId:{}", queueBean.getBizId(),
|
|
|
|
- cacheBean.getBizId(), message.getMsgId());
|
|
|
|
|
|
+ final ApplyRecordCacheBean bean;
|
|
|
|
+ if (cacheBean == null) {
|
|
|
|
+ log.warn("【考生预约队列】MQ队列元素对应的缓存记录不存在!bizId:{} msgId:{}", queueBean.getBizId(), message.getMsgId());
|
|
|
|
|
|
- // 默认采用最新预约缓存数据持久化(极端情况下队列消息顺序异常)
|
|
|
|
- bean = cacheBean;
|
|
|
|
|
|
+ // 若缓存数据不存在,则采用队列数据持久化
|
|
|
|
+ bean = queueBean;
|
|
|
|
+ // 更新缓存
|
|
|
|
+ applyTaskCacheService.saveStudentApplyRecord(bean);
|
|
} else {
|
|
} else {
|
|
- log.warn("【考生预约队列】缓存中的队列业务流水号无效!bizId:{} cacheBizId:{} msgId:{}", queueBean.getBizId(),
|
|
|
|
- cacheBean.getBizId(), message.getMsgId());
|
|
|
|
|
|
+ if (cacheBean.getBizId() != null && cacheBean.getBizId() >= queueBean.getBizId()) {
|
|
|
|
+ log.info("【考生预约队列】MQ消息消费!bizId:{} cacheBizId:{} msgId:{}", queueBean.getBizId(),
|
|
|
|
+ cacheBean.getBizId(), message.getMsgId());
|
|
|
|
+
|
|
|
|
+ // 默认采用最新预约缓存数据持久化(极端情况下队列消息顺序异常)
|
|
|
|
+ bean = cacheBean;
|
|
|
|
+ } else {
|
|
|
|
+ log.warn("【考生预约队列】缓存中的队列业务流水号无效!bizId:{} cacheBizId:{} msgId:{}", queueBean.getBizId(),
|
|
|
|
+ cacheBean.getBizId(), message.getMsgId());
|
|
|
|
+
|
|
|
|
+ // 其它情况,采用队列数据持久化
|
|
|
|
+ bean = queueBean;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
- // 其它情况,采用队列数据持久化
|
|
|
|
- bean = queueBean;
|
|
|
|
|
|
+ this.saveOrUpdate(bean);
|
|
|
|
+ } finally {
|
|
|
|
+ try {
|
|
|
|
+ // 解锁前检查当前线程是否持有该锁
|
|
|
|
+ if (studentApplyLock.isLocked() && studentApplyLock.isHeldByCurrentThread()) {
|
|
|
|
+ studentApplyLock.unlock();
|
|
|
|
+ log.info("解锁成功!lockKey:{}", studentApplyLockKey);
|
|
|
|
+ }
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
+ log.warn("解锁失败!lockKey:{} err:{}", studentApplyLockKey, e.getMessage());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
-
|
|
|
|
- this.saveOrUpdate(bean);
|
|
|
|
}
|
|
}
|
|
|
|
|
|
public void saveOrUpdate(ApplyRecordCacheBean bean) {
|
|
public void saveOrUpdate(ApplyRecordCacheBean bean) {
|