|
|
@@ -0,0 +1,234 @@
|
|
|
+package com.haha.service.impl;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
+import com.haha.entity.DoorRecord;
|
|
|
+import com.haha.mapper.DoorRecordMapper;
|
|
|
+import com.haha.service.DoorRecordService;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.data.redis.core.StringRedisTemplate;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+
|
|
|
+import java.time.LocalDateTime;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 设备开关门记录服务实现类
|
|
|
+ *
|
|
|
+ * @author haha
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+@RequiredArgsConstructor
|
|
|
+public class DoorRecordServiceImpl extends ServiceImpl<DoorRecordMapper, DoorRecord> implements DoorRecordService {
|
|
|
+
|
|
|
+ private final StringRedisTemplate stringRedisTemplate;
|
|
|
+ private static final String DOOR_RECORD_REDIS_KEY = "haha:door:open:";
|
|
|
+ private static final long REDIS_EXPIRE_MINUTES = 30; // Redis 过期时间 30 分钟
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public DoorRecord saveDoorOpenRecord(String deviceId, Long userId, String activityId,
|
|
|
+ String doorIndex, String openType, String source) {
|
|
|
+ try {
|
|
|
+ LocalDateTime now = LocalDateTime.now();
|
|
|
+
|
|
|
+ // 1. 保存到数据库
|
|
|
+ DoorRecord record = new DoorRecord();
|
|
|
+ record.setActivityId(activityId);
|
|
|
+ record.setDeviceId(deviceId);
|
|
|
+ record.setUserId(userId);
|
|
|
+ record.setDoorIndex(doorIndex);
|
|
|
+ record.setOpenType(openType);
|
|
|
+ record.setDoorStatus("OPENED");
|
|
|
+ record.setNobuy(0); // 默认假设有消费,后续 AI 识别会更新
|
|
|
+ record.setOpenTime(now);
|
|
|
+ record.setSource(source);
|
|
|
+
|
|
|
+ boolean saved = this.save(record);
|
|
|
+ if (saved) {
|
|
|
+ log.info("保存开门记录到数据库 - activityId: {}, deviceId: {}, userId: {}",
|
|
|
+ activityId, deviceId, userId);
|
|
|
+ } else {
|
|
|
+ log.error("保存开门记录到数据库失败 - activityId: {}", activityId);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 同时保存到 Redis(用于快速查询和临时状态)
|
|
|
+ saveToRedis(activityId, deviceId, userId, doorIndex, openType, now, source);
|
|
|
+
|
|
|
+ return record;
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("保存开门记录失败 - activityId: {}", activityId, e);
|
|
|
+ throw new RuntimeException("保存开门记录失败", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public DoorRecord getByActivityId(String activityId) {
|
|
|
+ // 优先从数据库查询
|
|
|
+ return baseMapper.getByActivityId(activityId);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public boolean updateDoorClosed(String activityId) {
|
|
|
+ try {
|
|
|
+ DoorRecord record = getByActivityId(activityId);
|
|
|
+ if (record == null) {
|
|
|
+ log.warn("关门记录不存在 - activityId: {}", activityId);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ LocalDateTime closeTime = LocalDateTime.now();
|
|
|
+ int result = baseMapper.updateDoorStatus(activityId, "CLOSED", closeTime);
|
|
|
+
|
|
|
+ if (result > 0) {
|
|
|
+ log.info("更新门状态为已关门 - activityId: {}, 持续时长:{}秒",
|
|
|
+ activityId, record.getDuration());
|
|
|
+
|
|
|
+ // 同时更新 Redis
|
|
|
+ updateRedisDoorStatus(activityId, "CLOSED", closeTime);
|
|
|
+ return true;
|
|
|
+ } else {
|
|
|
+ log.error("更新门状态失败 - activityId: {}", activityId);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("更新门状态失败 - activityId: {}", activityId, e);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public boolean markNoConsume(String activityId) {
|
|
|
+ try {
|
|
|
+ int result = baseMapper.markNoConsume(activityId);
|
|
|
+ if (result > 0) {
|
|
|
+ log.info("标记无消费记录 - activityId: {}", activityId);
|
|
|
+
|
|
|
+ // 同时更新 Redis
|
|
|
+ updateRedisNobuy(activityId, 1);
|
|
|
+ return true;
|
|
|
+ } else {
|
|
|
+ log.error("标记无消费记录失败 - activityId: {}", activityId);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("标记无消费记录失败 - activityId: {}", activityId, e);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public boolean linkOrderId(String activityId, Long orderId) {
|
|
|
+ try {
|
|
|
+ int result = baseMapper.linkOrderId(activityId, orderId);
|
|
|
+ if (result > 0) {
|
|
|
+ log.info("关联订单 ID - activityId: {}, orderId: {}", activityId, orderId);
|
|
|
+
|
|
|
+ // 同时更新 Redis
|
|
|
+ updateRedisOrderId(activityId, orderId);
|
|
|
+ return true;
|
|
|
+ } else {
|
|
|
+ log.error("关联订单 ID 失败 - activityId: {}", activityId);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("关联订单 ID 失败 - activityId: {}", activityId, e);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<DoorRecord> getRecentRecords(Long userId, String deviceId, int limit) {
|
|
|
+ return baseMapper.getRecentRecords(userId, deviceId, limit);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int countByTimeRange(String deviceId, LocalDateTime startTime, LocalDateTime endTime) {
|
|
|
+ return baseMapper.countByTimeRange(deviceId, startTime, endTime);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void cleanupExpiredRedisRecords() {
|
|
|
+ // 这个方法可以定时调用,清理过期的 Redis 记录
|
|
|
+ // 由于数据库有唯一索引,不用担心数据丢失
|
|
|
+ log.info("清理过期 Redis 开门记录任务已执行");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public com.baomidou.mybatisplus.core.metadata.IPage<DoorRecord> getDeviceRecordsByPage(
|
|
|
+ String deviceId, int page, int pageSize) {
|
|
|
+ LambdaQueryWrapper<DoorRecord> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(DoorRecord::getDeviceId, deviceId)
|
|
|
+ .orderByDesc(DoorRecord::getOpenTime);
|
|
|
+
|
|
|
+ return this.page(new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(page, pageSize), wrapper);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== Redis 操作方法 ====================
|
|
|
+
|
|
|
+ private void saveToRedis(String activityId, String deviceId, Long userId,
|
|
|
+ String doorIndex, String openType, LocalDateTime openTime, String source) {
|
|
|
+ try {
|
|
|
+ String key = DOOR_RECORD_REDIS_KEY + activityId;
|
|
|
+ Map<String, String> record = new HashMap<>();
|
|
|
+ record.put("activityId", activityId);
|
|
|
+ record.put("deviceId", deviceId);
|
|
|
+ record.put("userId", String.valueOf(userId));
|
|
|
+ record.put("doorIndex", doorIndex != null ? doorIndex : "");
|
|
|
+ record.put("openType", openType);
|
|
|
+ record.put("openTime", String.valueOf(openTime.toEpochSecond(java.time.ZoneOffset.UTC)));
|
|
|
+ record.put("source", source != null ? source : "");
|
|
|
+ record.put("status", "OPENED");
|
|
|
+
|
|
|
+ stringRedisTemplate.opsForHash().putAll(key, record);
|
|
|
+ stringRedisTemplate.expire(key, REDIS_EXPIRE_MINUTES, TimeUnit.MINUTES);
|
|
|
+
|
|
|
+ log.debug("保存开门记录到 Redis: activityId={}", activityId);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("保存开门记录到 Redis 失败 - activityId: {}", activityId, e);
|
|
|
+ // Redis 失败不影响数据库操作
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void updateRedisDoorStatus(String activityId, String doorStatus, LocalDateTime closeTime) {
|
|
|
+ try {
|
|
|
+ String key = DOOR_RECORD_REDIS_KEY + activityId;
|
|
|
+ stringRedisTemplate.opsForHash().put(key, "doorStatus", doorStatus);
|
|
|
+ stringRedisTemplate.opsForHash().put(key, "closeTime",
|
|
|
+ String.valueOf(closeTime.toEpochSecond(java.time.ZoneOffset.UTC)));
|
|
|
+ log.debug("更新 Redis 门状态 - activityId: {}, status: {}", activityId, doorStatus);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("更新 Redis 门状态失败 - activityId: {}", activityId, e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void updateRedisNobuy(String activityId, int nobuy) {
|
|
|
+ try {
|
|
|
+ String key = DOOR_RECORD_REDIS_KEY + activityId;
|
|
|
+ stringRedisTemplate.opsForHash().put(key, "nobuy", String.valueOf(nobuy));
|
|
|
+ log.debug("更新 Redis 无消费标记 - activityId: {}", activityId);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("更新 Redis 无消费标记失败 - activityId: {}", activityId, e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void updateRedisOrderId(String activityId, Long orderId) {
|
|
|
+ try {
|
|
|
+ String key = DOOR_RECORD_REDIS_KEY + activityId;
|
|
|
+ stringRedisTemplate.opsForHash().put(key, "orderId", String.valueOf(orderId));
|
|
|
+ log.debug("更新 Redis 订单 ID - activityId: {}, orderId: {}", activityId, orderId);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("更新 Redis 订单 ID 失败 - activityId: {}", activityId, e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|