|
|
@@ -0,0 +1,122 @@
|
|
|
+package com.fs.app.queue.footprint;
|
|
|
+
|
|
|
+import com.fs.common.constant.LiveKeysConstant;
|
|
|
+import com.fs.common.core.redis.RedisCache;
|
|
|
+import com.fs.hisStore.domain.FsStoreProductRelationScrm;
|
|
|
+import com.fs.hisStore.service.IFsStoreProductRelationScrmService;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import javax.annotation.PostConstruct;
|
|
|
+import javax.annotation.PreDestroy;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.List;
|
|
|
+import java.util.UUID;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 商品足迹 Redis 队列:接口入队,单线程慢消费,原子锁保障处理后删除。
|
|
|
+ */
|
|
|
+@Service
|
|
|
+public class FootprintQueueService {
|
|
|
+
|
|
|
+ private static final Logger log = LoggerFactory.getLogger(FootprintQueueService.class);
|
|
|
+
|
|
|
+ private static final long EMPTY_QUEUE_SLEEP_MS = 1000L;
|
|
|
+ private static final long CONSUME_INTERVAL_MS = 200L;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private RedisCache redisCache;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private IFsStoreProductRelationScrmService productRelationService;
|
|
|
+
|
|
|
+ private volatile boolean running = true;
|
|
|
+ private Thread consumerThread;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将足迹写入任务放入 Redis 队列
|
|
|
+ */
|
|
|
+ public void enqueue(Long userId, Long productId) {
|
|
|
+ if (userId == null || productId == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ FootprintQueueItem item = new FootprintQueueItem(userId, productId, UUID.randomUUID().toString());
|
|
|
+ redisCache.setVoice(LiveKeysConstant.FOOTPRINT_QUEUE_KEY, item);
|
|
|
+ }
|
|
|
+
|
|
|
+ @PostConstruct
|
|
|
+ public void startConsumer() {
|
|
|
+ consumerThread = new Thread(this::consumeLoop, "footprint-queue-consumer");
|
|
|
+ consumerThread.setDaemon(true);
|
|
|
+ consumerThread.start();
|
|
|
+ log.info("商品足迹队列消费者线程已启动");
|
|
|
+ }
|
|
|
+
|
|
|
+ @PreDestroy
|
|
|
+ public void stopConsumer() {
|
|
|
+ running = false;
|
|
|
+ if (consumerThread != null) {
|
|
|
+ consumerThread.interrupt();
|
|
|
+ }
|
|
|
+ log.info("商品足迹队列消费者线程已停止");
|
|
|
+ }
|
|
|
+
|
|
|
+ private void consumeLoop() {
|
|
|
+ while (running) {
|
|
|
+ try {
|
|
|
+ FootprintQueueItem item = redisCache.popVoiceKey(LiveKeysConstant.FOOTPRINT_QUEUE_KEY);
|
|
|
+ if (item == null) {
|
|
|
+ Thread.sleep(EMPTY_QUEUE_SLEEP_MS);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ String lockKey = LiveKeysConstant.FOOTPRINT_PROCESSING_KEY + item.getTaskId();
|
|
|
+ redisCache.setCacheObject(lockKey, item,
|
|
|
+ LiveKeysConstant.FOOTPRINT_PROCESSING_LOCK_EXPIRE, TimeUnit.SECONDS);
|
|
|
+
|
|
|
+ try {
|
|
|
+ saveFootprint(item.getUserId(), item.getProductId());
|
|
|
+ redisCache.deleteObject(lockKey);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("消费商品足迹失败, userId={}, productId={}, taskId={}",
|
|
|
+ item.getUserId(), item.getProductId(), item.getTaskId(), e);
|
|
|
+ redisCache.deleteObject(lockKey);
|
|
|
+ redisCache.setVoice(LiveKeysConstant.FOOTPRINT_QUEUE_KEY, item);
|
|
|
+ }
|
|
|
+
|
|
|
+ Thread.sleep(CONSUME_INTERVAL_MS);
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ break;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("商品足迹队列消费异常", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void saveFootprint(Long userId, Long productId) {
|
|
|
+ FsStoreProductRelationScrm query = new FsStoreProductRelationScrm();
|
|
|
+ query.setIsDel(0);
|
|
|
+ query.setUserId(userId);
|
|
|
+ query.setProductId(productId);
|
|
|
+ query.setType("foot");
|
|
|
+ List<FsStoreProductRelationScrm> relations = productRelationService.selectFsStoreProductRelationList(query);
|
|
|
+ if (relations != null && !relations.isEmpty()) {
|
|
|
+ FsStoreProductRelationScrm relation = relations.get(0);
|
|
|
+ relation.setUpdateTime(new Date());
|
|
|
+ productRelationService.updateFsStoreProductRelation(relation);
|
|
|
+ } else {
|
|
|
+ FsStoreProductRelationScrm relation = new FsStoreProductRelationScrm();
|
|
|
+ relation.setUserId(userId);
|
|
|
+ relation.setIsDel(0);
|
|
|
+ relation.setProductId(productId);
|
|
|
+ relation.setType("foot");
|
|
|
+ relation.setCreateTime(new Date());
|
|
|
+ relation.setUpdateTime(new Date());
|
|
|
+ productRelationService.insertFsStoreProductRelation(relation);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|